آنتروپی در فرانتاند واقعی است. یک کدبیس یکشبه فرو نمیریزد، بلکه انباشته میشود. یک روز سهشنبه، یک کتابخانه برای فرمتبندی تاریخ اضافه میکنید. شش ماه بعد، شخص دیگری کتابخانه دیگری را اضافه میکند چون اولی را پیدا نکرده است. Polyfillها برای مرورگرهایی که دیگر از آنها پشتیبانی نمیکنید، روی هم انباشته میشوند. ابزارهای Build روی هم لایه میسازند. در نهایت، پوشه node_modules به یک کشوی پر از آشغالهای دیجیتال تبدیل میشود که در آن نمیتوان چیزی را بدون ترس دور انداخت. دیگر آپدیت نمیکنید. سپس دیگر نگاه هم نمیکنید. اینجاست که هر تغییر کوچک به یک قمار تبدیل میشود.
وقتی سعی داشتم نسخه Material UI را در یک پروژه قدیمی ارتقا دهم، به این بنبست خوردم. فایل package.json را باز کردم و به سختی نیمی از ورودیها را شناختم. دهها کتابخانه آنجا بودند؛ برخی سالها قدیمی بودند و برخی دیگر چنان مبهم بودند که مجبور شدم از git blame استفاده کنم تا بفهمم چه کسی آنها را اضافه کرده و چرا. دستور نصب نسخه جدید Material UI را اجرا کردم و ترمینال با هشدارهای peer dependency پر شد. پکیجی که میخواستم آپدیت کنم مشکلی نداشت، اما اکوسیستم اطراف آن مشکل داشت. فهمیدم که در حال انجام یک ارتقا نیستم، بلکه در حال کاوش در یک ویرانه هستم.
چرا این آشفتگی هزینهای بیش از غرور دارد
نادیده گرفتن وابستگیها یک مسئله ظاهری نیست؛ بلکه مشکلات واقعی و پرهزینهای ایجاد میکند.
خطرات امنیتی تهدیدی آشکار هستند. پکیجهای رها شده حاوی آسیبپذیریهای افشا شدهای هستند که اسکنرها هر هفته آنها را علامتگذاری میکنند. بدتر از آن، کتابخانههایی که مستقیماً نصب کردهاید ممکن است مشکلی نداشته باشند، اما وابستگیهای غیرمستقیم (transitive) که آنها کشیدهاند، مشکلدار باشند. شما بدون اینکه بدانید، بدهی فنی شخص دیگری را به ارث میبرید.
هزینه با فاصله افزایش مییابد. هرچه بیشتر صبر کنید، شکاف بین نسخهها بزرگتر میشود. پرش از یک نسخه major در React، یک کار است؛ اما پرش از سه نسخه، یک پروژه مهاجرت است که میتواند هفتهها از وقت شما را بگیرد. شما دیگر اصلاحات باگ، بهبودهای عملکرد و سازگاری با ابزارهای مدرن را دریافت نمیکنید. در نهایت تیم مجبور میشود بر اساس محدودیتهایی کدنویسی کند که دیگر وجود ندارند.
کتابخانهها میمیرند. پکیجی که نگهدارنده فعالی ندارد، به طور پیشفرض به یک fork اختصاصی برای شما تبدیل میشود. وقتی خراب میشود، شما همان کسی هستید که نیمهشب در حال خواندن کد منبع minified آن است. جامعه به سمت راهکارهای بهتر حرکت کرده است، اما تیم شما درگیر نگهداری از یک شبح شده است.
سرعت توسعه سقوط میکند. توسعهدهندگان جدید روزهای اول خود را صرف یادگیری APIهای خاص و منحصربهفرد برای ابزارهایی میکنند که توسط استانداردهای وب یا جایگزینهای اصلی جایگزین شدهاند. به جای عرضه ویژگیهای جدید، مهندسان ارشد شما به مورخان تبدیل میشوند و توضیح میدهند که چرا این پروژه هنوز از یک task runner مربوط به سال ۲۰۱۵ استفاده میکند.
پیش از تغییر حتی یک نسخه، حسابرسی کنید
بدترین اشتباه این است که یک آپدیت کلی انجام دهید و امیدوار باشید تستها پاس شوند. با یک حسابرسی شروع کنید. فایل package.json را بردارید و هر ورودی را بازخواست کنید.
چهار سوال بپرسید:
- این چه مشکلی را حل میکند؟
- دقیقاً کجا از آن استفاده میکنیم؟
- آیا هنوز ضروری است؟
- آیا اکنون جایگزین بهتری وجود دارد؟
شما با موارد تکراری مواجه خواهید شد. شاید moment و date-fns هر دو در لیست باشند چون دو توسعهدهنده در زمانهای مختلف یک مشکل مشابه را حل کردهاند. شاید یک polyfill برای Internet Explorer هنوز در پروژه باشد، در حالی که تحلیلهای شما نشان میدهد هیچ ترافیکی از مرورگرهای قدیمی وجود ندارد. شاید یک wrapper سفارشی دور fetch را بتوان حذف کرد، زیرا مرورگرهای مدرن موارد خاص (edge cases) را به صورت بومی مدیریت میکنند.
گاهی اوقات جایگزینی بهتر از آپدیت کردن است. کلنجار رفتن با یک کتابخانه نمودار رها شده برای عبور از سه سال تغییرات مخرب (breaking changes)، میتواند بیشتر از جایگزین کردن یک گزینه پایدار و بازسازی چند کامپوننت زمان ببرد. مایل باشید که از حجم پروژه کم کنید.
لایه پنهان: وابستگیهای غیرمستقیم و Semver
وابستگیهای مستقیم تنها بخش قابل مشاهده کوه یخ هستند. بخش اصلی در زیر سطح، در وابستگیهای غیرمستقیم (transitive dependencies) قرار دارد؛ یعنی پکیجهایی که پکیجهای شما به آنها نیاز دارند. شما آنها را انتخاب نکردهاید، اما آنها در فرآیند build شما اجرا میشوند. آنها حجم bundle شما را زیاد میکنند، سطح حمله را گسترش میدهند و گاهی اوقات به گونهای با هم تداخل پیدا میکنند که خطاهای مبهم در زمان build ایجاد میکنند.
شما باید نسخهبندی معنایی (semantic versioning) را بر اساس آنچه واقعاً معنا میدهد بخوانید، نه آنچه امیدوارید معنا بدهد.
- آپدیتهای Major: اینها مهاجرت هستند. تا زمانی که خلاف آن ثابت نشده، با آنها مانند تغییرات مخرب (breaking changes) برخورد کنید. تغییرات (changelog) را بخوانید، زمان اختصاص دهید و به طور کامل تست کنید.
- آپدیتهای Minor: اینها ویژگیهای جدید اضافه میکنند. همچنین میتوانند رفتار برنامه را به شکلی ظریف تغییر دهند. تصور نکنید که این آپدیتها بدون هزینه هستند.
- آپدیتهای Patch: اینها باگها را رفع میکنند. معمولاً ایمن هستند، اما اگر کد شما به آن باگ وابسته باشد، یا اگر پچ تغییری در بخشی از کد ایجاد کند که شما قبلاً با روش
monkey-patchingآن را دستکاری کرده بودید، ممکن است برنامه از کار بیفتد.
دانستن این قوانین به شما کمک میکند تا قبل از دست زدن به هر چیزی، ریسکها را دستهبندی کنید.
از ابزارهای خود مانند یک استادکار استفاده کنید
اگر از Yarn استفاده میکنید، چندین دستور داخلی، حدس و گمان را به یک فرآیند تبدیل میکنند.
ابتدا yarn outdated را اجرا کنید. این دستور تصویری از آنچه تغییر کرده است به شما میدهد...
