«محلی کار میکند» (انگلیسی «Works on my machine») — عبارت کلاسیک توسعهدهندهای که نمیتواند باگ را در محیط محلی خود بازتولید کند، در حالی که باگ به طور پایدار در سایر اعضای تیم یا در محیط تولید ظاهر میشود. این وضعیت به دلیل تفاوت در پیکربندی، نسخه وابستگیها، سیستم عامل یا دادهها بین ماشین توسعهدهنده و محیطی که باگ در آن بازتولید میشود، ایجاد میگردد. طبق نظرسنجی Stack Overflow 2023، 58٪ از توسعهدهندگان حداقل ماهی یک بار این عبارت را میگویند و 31٪ هفتگی. بررسی میکنیم که چرا کد در همه جا یکسان کار نمیکند و چگونه محیط را استاندارد کنیم.
نکات اصلی
«محلی کار میکند» — عبارتی که توسعهدهنده زمانی به کار میبرد که همکار یا تستر باگی را گزارش میدهد اما روی ماشین توسعهدهنده آن باگ بازتولید نمیشود. از ظاهر اینطور به نظر میرسد که مشکل انکار میشود، اما از نظر فنی وضعیت واقعی است: کد واقعاً میتواند در یک محیط کار کند و در محیط دیگر از کار بیفتد. تفاوت در یک بیت از پیکربندی — و رفتار برنامه به طور اساسی تغییر میکند.
این عبارت به یک میم در جامعه فناوری اطلاعات تبدیل شده است، زیرا هم درست است و هم بیفایده. از دیدگاه توسعهدهنده — کد واقعاً روی ماشین او کار میکند. از دیدگاه تیم — مشکل وجود دارد و باید حل شود، نه اینکه بهانهتراشی کرد. طنز موقعیت این است که توسعهدهنده حقیقت را میگوید، اما این حقیقت به رفع باگ کمک نمیکند. این میم به قدری محبوب است که هزاران پست در Reddit، XKCD و کنفرانسهای DevOps به آن اختصاص یافته است.
از نظر فرآیندی، عبارت «محلی کار میکند» نشانگر مشکلاتی در بازتولیدپذیری محیط است. اگر دو توسعهدهنده نمیتوانند نتیجه یکسانی را روی کد یکسان دریافت کنند — یعنی فرآیند راهاندازی محیط استاندارد نشده است. روش DevOps میگوید: محیط باید با یک دستور از مخزن بدون اقدامات دستی قابل بازتولید باشد.
محیط محلی توسعهدهنده تقریباً همیشه با محیط تولید متفاوت است. توسعهدهنده از macOS یا Windows استفاده میکند در حالی که سرور روی Linux کار میکند. سیستمعاملهای مختلف دارای سیستمهای فایل، کدگذاریها، زمانبندی نخها و فراخوانیهای سیستمی متفاوت هستند. حتی اگر هر دو محیط Linux باشند — نسخه هسته، glibc، OpenSSL ممکن است متفاوت باشند.
دلیل دوم — مجموعه نرمافزارهای نصب شده. روی ماشین توسعهدهنده ممکن است نسخه سراسری Node.js 20 نصب شده باشد، در حالی که در پیکربندی CI/CD نسخه 18 مشخص شده است. یا توسعهدهنده به صورت محلی از PostgreSQL 16 استفاده میکند و در محیط تولید — PostgreSQL 14. تفاوت در نسخههای فرعی اغلب قابل توجه نیست، اما بهروزرسانیهای اصلی میتوانند رفتار کوئریهای SQL را تغییر دهند. طبق گزارش npm Inc.، 67٪ از باگهای مربوط به وابستگیها ناشی از تفاوت در نسخههای patch هستند.
دلیل سوم — شرایط شبکه. روی ماشین محلی تأخیر، محدودیت پهنای باند و مشکلات DNS وجود ندارد. در محیط تولید هر درخواست به API خارجی ممکن است به جای ۵ میلیثانیه ۵۰۰ میلیثانیه طول بکشد. تایماوتها، منطق تلاش مجدد، شرایط رقابتی — همه این مشکلات فقط تحت بار واقعی و در شرایط شبکه واقعی ظاهر میشوند. شبیهسازی شبکه از طریق ابزارهایی مانند Toxiproxy به شناسایی چنین مشکلاتی قبل از استقرار کمک میکند.
دلیل اول — عدم وجود داده. توسعهدهنده با فیکسچرهای آزمایشی کار میکند، در حالی که در محیط تولید میلیونها رکورد با مقادیر غیرمنتظره وجود دارد. NULL در فیلدی که توسعهدهنده آن را اجباری میدانست، کاراکتر Unicode در نام، رشته بیش از حد طولانی — همه اینها میتوانند باعث باگهایی شوند که در پایگاه داده محلی با دادههای مصنوعی قابل بازتولید نیستند.
دلیل دوم — پرچمهای مختلف کامپایل و ساخت. ساخت انتشار (Release/Distribution) ممکن است با ساخت دیباگ (Debug) متفاوت باشد. بهینهسازیهای کامپایلر، حذف لاگهای دیباگ، درونخطیسازی توابع — همه اینها ممکن است باگها را پنهان یا برعکس آشکار کنند. مثال معمول: در ساخت دیباگ یک assert کار میکند که در ساخت انتشار به دلیل ترتیب متفاوت مقداردهی متغیرها از کار میافتد.
دلیل سوم — کش محلی و فایلهای موقت. توسعهدهنده ممکن است متوجه باگ نشود، زیرا اسکریپتهای قدیمی در مرورگر کش شدهاند، دادههای منسوخ در Redis ذخیره شدهاند و فایلهای موقت از اجراهای قبلی در سیستم فایل باقی ماندهاند. اجرای تمیز (حالت ناشناس، پاک کردن کش، نصب تازه) اغلب باگی را بازتولید میکند که «خود به خود» ظاهر نمیشد.
دلیل چهارم — تضاد وابستگیهای سراسری و محلی. ابزارهایی مانند Ruby gems، Python pip، Node.js npm ممکن است بستههای نصب شده سراسری داشته باشند که به کارکرد کد به صورت محلی «کمک» میکنند اما در محیط تولید وجود ندارند. استفاده از محیطهای مجازی (virtualenv، venv، nvm) پروژه را از نصبهای سراسری جدا کرده و محیط را قابل تکرار میسازد.
عبارت «محلی کار میکند» اعتماد در تیم را از بین میبرد. اگر توسعهدهنده به طور منظم نتواند باگها را بازتولید کند، همکاران شروع به تردید در صلاحیت یا دقت آزمایش او میکنند. با گذشت زمان این امر به مدیریت خرد منجر میشود: هر تغییر نیاز به تأیید توسط یک توسعهدهنده دوم دارد که توسعه را کند میکند. طبق Google Project Aristotle، امنیت روانی در تیم به طور مستقیم بر بهرهوری تأثیر میگذارد و اختلافات مداوم درباره محیط یکی از عوامل کاهش آن است.
مشکل دوم — کند شدن بررسی کد. اگر توسعهدهنده نتواند باگ را به صورت محلی بازتولید کند، ممکن است درخواست کشش همکار را با عبارت «نزد من کار میکند — یعنی مشکل نزد توست» رد کند. این امر باعث ایجاد تعارض و تأخیر در تحویل ویژگیها میشود. استانداردسازی محیط این تعارض را برطرف میکند: اگر هر دو توسعهدهنده در یک ظرف Docker کار کنند، سؤال «نزد کی کار میکند» بیمعنی میشود.
مشکل سوم — از دست رفتن باگها در ردیاب. باگهایی که «توسط توسعهدهنده بازتولید نمیشوند» اغلب با یادداشت «قابل بازتولید نیست» بسته میشوند. یک ماه بعد، باگ در محیط تولید ظاهر میشود و رفع آن ۱۰ برابر هزینه بیشتری دارد. قاعده: اگر باگ حداقل در یک نفر بازتولید میشود — صرف نظر از اینکه نزد توسعهدهنده کار میکند یا نه، وجود دارد.
اولین و مؤثرترین روش — Docker. کل پروژه باید بدون اقدامات اضافی از طریق docker-compose up اجرا شود. پایگاه داده، کش، صف پیام، وبسرور — همه چیز در کانتینرها راهاندازی میشود. توسعهدهنده فقط Docker و Git را نصب میکند. بقیه — داخل کانتینرها. این تضمین میکند که همه اعضای تیم صرف نظر از سیستم عامل، محیط یکسانی دارند.
روش دوم — مدیران نسخه. اگر Docker ممکن نیست (محدودیتهای مجوز، زیرساخت قدیمی)، از nvm (Node.js)، rbenv (Ruby)، pyenv (Python)، sdkman (Java) استفاده کنید. مدیران نسخه امکان تغییر نسخه زبانها و ابزارها را در چارچوب پروژه فراهم میکنند. فایلهای .nvmrc، .ruby-version، .python-version باید در مخزن باشند و توسط CI/CD بررسی شوند.
روش سوم — Vagrant برای ماشینهای مجازی. Vagrant یک ماشین مجازی با سیستم عامل و پیکربندی مشخص روی VirtualBox یا VMware راهاندازی میکند. داخل VM تمام وابستگیها از طریق اسکریپتهای provisioning (shell، Ansible، Puppet) نصب میشوند. Vagrant سنگینتر از Docker است اما ایزولاسیون کامل در سطح سیستم عامل را فراهم میکند — برای پروژههایی که به نسخه خاصی از هسته Linux وابسته هستند مفید است.
روش چهارم — makefile و اسکریپتهای bootstrap. حتی یک Makefile ساده با اهداف install، test، build، clean میتواند اقدامات روتین را استاندارد کند. دستور make install باید تمام وابستگیها را نصب کند، پایگاه داده را راهاندازی کرده و دادههای آزمایشی ایجاد کند. نقطه ورود واحد برای همه توسعهدهندگان خطاهای دستی در راهاندازی محیط را حذف میکند.
ابزار اصلی — فایلهای قفل وابستگیها. package-lock.json (npm)، yarn.lock (Yarn)، Podfile.lock (CocoaPods)، pubspec.lock (Flutter) نسخه دقیق هر بسته را تثبیت میکنند. بدون فایل قفل، دو توسعهدهنده که وابستگیها را در زمانهای مختلف نصب کردهاند، ممکن است نسخههای فرعی متفاوتی دریافت کنند. فایل قفل باید در مخزن باشد و به صورت دستی ویرایش نشود.
ابزار دوم — .env.example در مخزن. فایل الگوی متغیرهای محیطی با توضیحات. توسعهدهنده آن را در .env کپی کرده و مقادیر خود را پر میکند. خط لوله CI/CD بررسی میکند که همه متغیرهای اجباری تنظیم شدهاند. طبق GitLab 2023، تیمهایی که از .env.example استفاده میکنند تعداد حوادث مربوط به متغیرهای محیطی را ۴۰٪ کاهش میدهند.
ابزار سوم — قلابهای پیش از commit. بررسی خودکاری که قبل از هر commit اجرا میشود: linter، فرمتکننده، بررسی نوع، تستها. اگر قلابها در همه توسعهدهندگان یکسان پیکربندی شده باشند، خطاهای قالببندی یا نوع که «روی ماشین محلی رد شدهاند» به محیط تولید نمیرسند. Husky برای JavaScript و pre-commit برای Python راهحلهای محبوبی هستند.
ابزار چهارم — خط لوله CI/CD که تستها را در محیط تمیز اجرا میکند. اگر تستها در CI قبول شوند اما به صورت محلی نه — مشکل در پیکربندی محیط محلی است. اگر تستها در CI رد شوند — درخواست کشش ادغام نمیشود. این قانون سخت از ورود باگهای «محلی کارکننده» به شاخه اصلی جلوگیری میکند.
سؤالات متداول
این یک واکنش دفاعی است: توسعهدهنده زمان زیادی را صرف اشکالزدایی میکند و شنیدن اینکه کد کار نمیکند از نظر روانی دردناک است. این عبارت برای «تغییر وضعیت» و شروع جستجوی علت بدون احساس گناه زمان میدهد.
بخواهید باگ را در محیط تمیز (نصب تمیز، حالت ناشناس) بازتولید کند. اگر بازتولید نشد — نسخه وابستگیها و متغیرهای محیطی را مقایسه کنید. اگر کمکی نکرد — یک محیط Docker مشابه با محیط تولید راهاندازی کنید.
Docker یک ظرف ایزوله با پیکربندی ثابت فراهم میکند که روی هر سیستم عاملی یکسان کار میکند. همه توسعهدهندگان از یک Dockerfile استفاده میکنند، بنابراین محیط یکسان است. اگر باگ در ظرف بازتولید نشود — یعنی مشکل واقعاً در کد است، نه در سیستم.
فایل قفل هش و نسخه دقیق تمام وابستگیهای گذرا را تثبیت میکند. حتی اگر نسخه جدیدی از وابستگی در مخزن بستهها منتشر شود، نصب بر اساس فایل قفل تضمین میکند که هر توسعهدهنده همان مجموعه بستهها را مانند دیگران دریافت کند.
Vagrant با VirtualBox در صورتی توجیه دارد که پروژه به ماژولهای خاص هسته سیستم عامل وابسته باشد یا ایزولاسیون کامل در سطح هسته نیاز داشته باشد. برای ۹۰٪ پروژهها Docker سبکتر، سریعتر و راحتتر است. انتخاب بستگی به عمق تعامل پروژه با سیستم عامل دارد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید