چگونه یک سورس‌کد اصطلاحاً Legacy (قدیمی) را ریفکتور کنیم؟

چگونه یک سورس‌کد اصطلاحاً Legacy (قدیمی) را ریفکتور کنیم؟

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

اولین کسی باشید که به این سؤال پاسخ می‌دهید

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

تغییرات جدید را دیپلوی کنید
ممکن است تغییراتی که در سورس‌کد داده‌اید برای کاربر نهایی قابل‌مشاهده نباشند، اما با این حال باز هم آخرین تغییرات اِعمال‌شده را باید دیپلوی (منتشر) کنید زیرا قرار گرفتن برنامه در محیط واقعی و مشاهدهٔ عملکرد آن است که می‌تواند به شما بگوید تغییرات مؤثر بوده‌اند یا خیر (اگر در حال توسعهٔ‌ وب هستید، بین کاربر نهایی یا اصطلاحاً End User و سیستم قدیمی یک پروکسی قرار دهید که بدین ترتیب می‌توانید ریکوئست‌های منتهی به سیستم قدیمی و انتقال آن‌ها به سیستم جدید را کنترل نموده و بفهمید که کدام کدها اجرا می‌شوند و چه کسانی از آن‌ها بازدید می‌کنند. اگر پروکسی شما به‌قدر کافی هوشمندانه پیاده‌سازی شده باشد، احتمالاً‌ خواهید توانست بخشی از ترافیک را با استفاده از یک API مجزا به سیستم جدید هدایت کنید و تا زمانی که مطمئن شوید همه‌ چیز درست کار می‌کند، به همین روند ادامه دهید.)

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

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

منبع


رائفه خلیلی