چگونه یک Legacy Codebase (سورس‌کد قدیمی) را ریفکتور کنیم؟

چگونه یک Legacy Codebase (سورس‌کد قدیمی) را ریفکتور کنیم؟

اگر شما هم یک برنامه‌نویس،‌ دولوپر یا مدیر پروژه باشید احتمالاً در طول عمر حرفه‌ای خود حداقل یک‌بار این وضعیت را تجربه کرده‌اید که مجموعهٔ حجیمی از هزاران خط کد را از برنامه‌نویسان پیشین شرکت به ارث ببرید که هرچند در زمان خود کدهای خوبی بوده‌اند اما اکنون به طرز ناامید‌کننده‌ای با اهداف فعلی شرکت در تضاد هستند! البته اگر خوش‌شانس باشید ممکن است برنامه‌نویسان قبلی شرکت، مستنداتی را نیز همراه با این مجموعه برای شما به ارث گذاشته باشند. به‌هرحال، اکنون شما مانده‌اید و مجموعه‌ای از کدهای قدیمی -و احتمالاً به‌هم‌ریخته- و چشم امید مدیران ارشد شرکت که به شما دوخته شده تا ببینند با این آشفته بازاری که به ارث برده‌اید، می‌خواهید چه کنید و چگونه می‌خواهید شرکت را از این دردسر نجات دهید؟ 

گرچه ریفکتورینگ سورس‌کدهای قدیمی کاری بس دشوار است، اما گاهی‌اوقات چاره‌ای جز این کار نیست و به همین منظور، در ادامه چند گام عملی را مطرح نموده‌ایم که در این راستا می‌تواند به شما کمک کند.

بک‌آپ بگیرید
قبل از این‌که کار بر روی کدها را آغاز کنید،‌ از همه چیز بک‌آپ بگیرید زیرا اگر این کار را نکنید و اتفاقی برای سورس‌کد بیفتد، بعدها ممکن است مجبور شوید روزی چند بار به این سؤال تکراری پاسخ دهید که «چرا بک‌آپ نگرفتی؟»

پس قبل از این‌که مجبور به عذرخواهی‌ شوید، محکم‌کاری نموده و چیزی را از قلم نیندازید و از همه چیز بک‌آپ بگیرید؛ سپس این بک‌آپ را به جای امنی انتقال داده و هرگز به آن دست نزنید مگر فقط در شرایط Read-Only.

سورس‌کد را فریز کنید
نخستین مرحلهٔ‌ بهبود و ارتقاء سورس‌کد این است که ساختار کلی آن‌را کاملاً درک کنید؛ بنابراین تا زمانی که بتوانید به یک درک کلی نسبت به سورس‌کد قدیمی برسید، تا حد امکان از ایجاد تغییر در ساختار سورس‌کد خودداری نموده و به‌اصطلاح آن‌را Freeze کنید.

اگر با برخوردن به هر مشکلی، فوراً شروع به تغییر ساختار سورس‌کد نمایید،‌ پس از نوشتن کدهای جدید دیگر این امکان را نخواهید داشت که کد جدید را با کد قدیمی مقایسه کنید و اگر نتوانید کدهای جدید و قدیمی را با هم مقایسه کنید، چه‌طور می‌توانید مطمئن باشید که مشکلی که در کدهای قدیمی وجود داشت برطرف شده است؟ بنابراین سعی کنید فعلاً ساختار کد قدیمی را دستکاری نکنید و آن‌را دست‌نخورده نگاه دارید.

برای کدهای موجود Test بنویسید
قبل از این‌که تغییری در سورس‌کد ایجاد کنید، تا جایی که می‌توانید دست به نوشتن تست‌های End-to-End و Integration بزنید؛ از این تست‌ها استفاده نموده و قطعه‌به‌قطعه و همچنین کل کدهای قدیمی را مورد بررسی قرار دهید تا مطمئن شوید که این کدها همان‌طور که شما فکر می‌کنید کار می‌کنند و همان خروجی مورد انتظار شما را ایجاد می‌کنند (البته منتظر نتایج عجیب‌وغریب هم باشید!)

این تست‌ها ۲ کاربرد مهم خواهند داشت: اول این‌که اگر کدی را درست درک نکرده باشید، بااستفاده از این تست‌ها در همان مراحل اولیه متوجه اشتباه خود خواهید شد. کاربرد دوم آن‌ها در زمان نوشتن کدهای جدید و جایگزین نمودن با کدهای قدیمی است؛ در این مورد، تست‌ها به صورت گاردریل عمل نموده و محدودهٔ کار را برای شما مشخص می‌کنند.

اگر از CI استفاده می‌کنید،‌ تست‌های نوشته‌ شدهٔ خود را به‌صورت خودکار درآورید تا بعد از اعمال هر تغییر جدید بتوانید مجموعهٔ‌ تست‌های خود را اجرا نموده و نتیجه را مشاهده کنید (CI یا Continuous Integration،‌ روشی در مهندسی نر‌م‌افزار است که در طی آن بلافاصله پس از افزودن کد جدید به کدبیس،‌ تست‌ها به‌صورت خودکار انجام شده و نتیجهٔ آن در معرض دید دولوپر قرار می‌گیرد.)

در آن واحد فقط روی یک موضوع تمرکز کنید
مراقب باشید تا دچار وسوسهٔ رفع هم‌زمان چند مشکل نشوید؛ پرداختن هم‌زمان به چند مشکل کمکی به شما نخواهد کرد و فقط کار را پیچیده‌تر خواهد نمود. مثلاً اگر در آن واحد هم باگ‌ها را برطرف نموده و هم قابلیت‌های جدیدی را به برنامه اضافه کنید، نمی‌توانید بفهمید که تغییر حاصل شده در عملکرد برنامه به‌خاطر باگ رفع شده است یا به خاطر قابلیت جدیدی که به آن افزوده‌اید؟

درواقع، شما می‌خواستید در زمان صرفه‌جویی شود اما حالا باید بخشی از زمان خود را صرف پیدا نمودن پاسخ این معما کنید. بنابراین به قول معروف، سری که درد نمی‌کند را دستمال نبندید و در هر بازهٔ زمانی تنها به رفع یک مشکل خاص بپردازید.

تغییر پلتفرم
اگر قصد دارید اپلیکیشن خود را به پلتفرم -مثلاً فریمورک- دیگری انتقال دهید،‌ حتماً دقت داشته باشید که به جز پلتفرم،‌ چیز دیگری را تغییر ندهید و همه‌چیز را دقیقاً مثل آنچه که در پلتفرم قبلی بود به پلتفرم جدید منتقل کنید (البته می‌توانید تست‌ها و مستندات بیشتری را به کد خود بیفزایید اما به جز این -درحین عوض نمودن پلتفرم- اعمال هیچ تغییر دیگری در سورس کد مجاز نیست!)

تغییر در معماری سورس‌کد
مرحلهٔ بعدی تغییر در معماری سورس‌کد است؛ در این مرحله آزاد هستید تا تغییرات دلخواه خود را در سطوح بالاتر ساختار نرم‌افزار اعمال کنید. مثلاً اگر سورس کد قدیمی خیلی یکپارچه و به‌هم پیوسته است،‌ اکنون می‌توانید آن‌را ماژولار کرده و همچنین می‌توانید فانکشن‌های بزرگی که مثلاً صدها خط کد دارند را به چند فانکشن کوچک‌تر تبدیل کنید (البته دقت داشته باشید که در حین اعمال این تغییرات،‌ نام وریبل‌ها و همچنین دیتا استراکچر‌ها را نباید تغییر دهید.)

مسألهٔ دیگری که حتماً باید به آن توجه داشت این است که اگر تغییرات سطح بالا و سطح پایین را هم‌زمان انجام می‌دهید،‌ حداقل سعی کنید این تغییرات را به یک فایل و یا -در بدترین حالت- به یک Subsystem محدود کنید تا دامنهٔ‌ تأثیر تغییرات اعمالی تا حد امکان محدود شود که در غیر این صورت، بعداً باید زمان و انرژی زیادی را صرف دیباگ نمودن کدهای خود کنید (البته اگر بتوانید این‌کار را انجام دهید!)

ریفکتورینگ سطح پایین
تا این مرحله از کار دیگر باید به درک بسیار خوبی از ماژول‌ها و این‌که هرکدام چه کاری انجام می‌دهند دست پیدا کرده باشید؛ حال آمادهٔ شروع بخش اصلی کار هستید و بخش اصلی کار چیزی نیست جز ریفکتور نمودن کدها به منظور ارتقاء‌ Maintainability و افزودن قابلیت‌های جدید به سورس‌کد.

این مرحله احتمالاً‌ وقت‌گیرترین مرحلهٔ‌ کار است؛ در این مرحله، همچنان که به جلو پیش می‌روید باید کار خود را مستند نمایید. بنابراین تا هنگامی که مطمئن نشده‌اید که ماژولی را کاملاً درک نموده و به قدر کافی آن‌را مستند نموده‌اید،‌ تغییری در آن ایجاد نکنید.

هرگز خود را محدود نکنید و اگر فکر می‌کنید تغییر نام وریبل‌ها و فانکشن‌ها به‌وضوح و ثبات ساختار سورس‌کد کمک می‌کند،‌ حتماً در این مرحله این‌کار را انجام دهید و تست‌هایی را نیز بنویسید تا بتوانید عملکرد سورس‌کد را پس از اعمال تغییرات بسنجید و در صورت نیاز، مجدد دست به تغییر سورس‌کد بزنید.

باگ‌ها را برطرف کنید
اکنون زمان اعمال تغییرات عینی و قابل‌مشاهده برای کاربر نهایی فرا رسیده است؛ حجم بزرگی از باگ‌هایی که طی سال‌های متمادی روی هم انبار شده‌اند حالا آماده‌اند تا یک جنگ واقعی را برای شما رقم بزنند.

روند معمول رفع باگ‌ها این است که اول باید مشکل را شناسایی کنید؛ برای این منظور، باید تستی بنویسید که عملکرد کد مورد نظر را مورد بررسی قرار دهد، سپس مشکل را برطرف کنید. در این مرحله، CI و تست‌های به‌اصطلاح End-to-End که قبلاً نوشته‌اید می‌توانند شما را از اشتباه کردن در اثر درک ناکافی و برخی شرایط محیطی حفظ نمایند.

ارتقاء دیتابیس
بعد از این‌که همه‌ٔ این کارها را انجام دادید، اگر هنوز هم فکر می‌کنید به چیزی که برنامه‌ریزی نموده بودید نرسیده‌اید و دیتابیس قدیمی شما نیاز به تغییرات بیشتری دارد، می‌توانید با تکیه بر شناخت و درکی که تا این لحظه از دیتابیس به‌دست آورده‌اید و با روشی مسئولانه و کنترل‌شده تغییرات مورد نظر خود را در دیتابیس اعمال کنید و یا آن‌را با مدل جدیدی جایگزین نمایید. برای این منظور، می‌توانید کل دیتابیس جدید را تست کنید و به این صورت مطمئن شوید که بدون مشکل و دردسر به دیتابیس جدید مهاجرت نموده‌اید. 

اگر طبق نقشه پیش رفته باشید، اکنون باید آمادهٔ پیاده‌سازی قابلیت‌های جدید باشید.

به‌دنبال بازآفرینی بیگ‌بنگ نباشید
منظور ما از بازآفرینی بیگ‌بنگ، انجام پروژه‌ای است که شکست آن تقریباً حتمی و تضمین شده است! فرض کنید شما سورس‌کد قدیمی را به یک‌باره طوری تغییر می‌دهید که گویا قدم به دنیای جدید و ناشناخته‌ای گذاشته‌اید و حتی نمی‌دانید در این دنیای جدید چه چیزی باید ایجاد شود.

روزها یکی پس از دیگری می‌گذرند و شما حل تمام مشکلات را به آخرین روز قبل از لانچ سیستم جدید موکول می‌کنید و این دقیقاً همان روزی است که شکست اندوه‌بار شما رقم خواهد خورد. در این روز، ناگهان متوجه خواهید شد که فرضیات منطقی شما خیلی هم درست از آب درنیامده و گویا کسانی که سیستم قبلی را طراحی و تنظیم کرده‌ بودند آنقدرها هم که شما فکر می‌کردید نادان و بی‌تدبیر نبوده‌اند!

پس اگر واقعاً تصمیم خود را گرفته‌اید که شرکت را به نابودی بکشانید -و البته خود را نیز به یک متخصص بدنام تبدیل نمایید- پروژهٔ بازآفرینی بیگ‌بنگ خود را شروع کنید؛ اما اگر می‌خواهید عاقلانه عمل کنید، باید بدانید که این مورد حتی در حد یک گزینهٔ‌ روی میز هم نمی‌تواند مطرح باشد.

کدهای جدید را به‌تدریج جایگزین کدهای قدیمی کنید
برای باز کردن گره این کلاف سردرگم، سریع‌ترین و امن‌ترین راه این است که بخش‌به‌بخش کدها را جداگانه بررسی و درک نموده و سپس سعی کنید به‌تدریج و تا حد ممکن با همان ابزارهای مورد استفاده در کد قدیمی به رفع مشکلات هر بخش بپردازید.

رفع تدریجی مشکلات بخش‌های مختلف سورس‌کد قدیمی به شما کمک می‌کند تا همچنان که تغییرات بیشتری ایجاد می‌کنید،‌ رفته‌رفته درک ذهنی گسترده‌تری نیز پیدا کنید و درنتیجه پس از اعمال کل تغییرات،‌ احساس نکنید که وارد یک دنیای جدید و ناشناخته شده‌اید.

تغییرات جدید را منتشر کنید
ممکن است تغییراتی که در سورس‌کد داده‌اید برای کاربر نهایی قابل‌مشاهده نباشند اما با این حال، باز هم آخرین تغییرات اعمال‌شده را باید منتشر کنید زیرا قرار گرفتن در محیط واقعی برنامه و مشاهدهٔ عملکرد آن است که می‌تواند به شما بگوید تغییرات مؤثر بوده یا نه.

از پروکسی‌ها بهره ببرید
اگر در حال توسعهٔ‌ وب هستید، بین کاربر نهایی (End User) و سیستم قدیمی یک پروکسی قرار دهید؛ به این ترتیب می‌توانید ریکوئست‌های منتهی به سیستم قدیمی و انتقال آن‌ها به سیستم جدید را کنترل نموده و بفهمید که کدام کدها اجرا می‌شود و چه کسانی از آن‌ها بازدید می‌کنند.

اگر پروکسی شما به‌قدر کافی هوشمندانه پیاده‌سازی شده باشد، احتمالاً‌ خواهید توانست بخشی از ترافیک را با استفاده از یک URL مجزا به سیستم جدید هدایت کنید و تا زمانی که مطمئن شوید همه‌ چیز درست کار می‌کند به همین روند ادامه دهید.

روی‌هم‌رفته، انجام همهٔ این کارها به زمان زیادی نیاز دارد!
خب بستگی دارد چه‌طور به موضوع نگاه کنید؛ درست است که پیروی از این مراحل ممکن است وقت زیادی را بگیرد، اما با گذراندن هر مرحله، درک و دانش شما از کل سیستم،‌ مشکلات و نحوهٔ‌ عملکرد آن بیشتر و‌ بیشتر می‌شود. بنابراین بهتر است به‌خاطر صرفه‌جویی در زمان،‌ امنیت کار را به‌خطر نیندازید و با یک نقشهٔ‌ گام‌به‌گام و حساب‌شده پیش بروید تا احتمال بروز مشکلات بزرگ و غیرقابل جبران کمتر شود و به‌صورت امن و بی‌خطر به هدف تعیین شده دست پیدا کنید.

آیا شما تجربه‌ای در زمینهٔ ریفکتورینگ کدهای قدیمی دارید؟ از نظر شما چه موارد دیگری وجود دارد که می‌تواند در انجام این کار به دولوپرها کمک کند؟ دیدگاه‌ها و تجربیات خود را با ما و سایر کاربران سکان آکادمی به اشتراک بگذارید.

منبع


رائفه خلیلی