نکات کلیدی
- تلاشهای نوسازی را روی مفهومسازی نرمافزار متمرکز کنید، نه تولید کد، چون مفهومسازی گلوگاه چرخه عمر توسعه است.
- از ابزارهای هوش مصنوعی برای بازیابی طراحی مفهومی نرمافزار قدیمی استفاده کنید تا زحمت طراحی طولانیِ اولیه کاهش یابد.
- بیشتر ابزارهای تجاری هوش مصنوعی روی «پیچیدگیهای تصادفی» مرحله توسعه متمرکز هستند، جایی که تولید کد تا حد زیادی کالاییسازی شده است.
- از تحلیل ایستا برای شناسایی سیستماتیک زمینهٔ کد و پایگاه داده استفاده کنید که میتواند بهطور مؤثر توسط مدلهای زبانی بزرگ به کار گرفته شود.
- از قابلیتهای خلاصهسازی مدلهای زبانی بزرگ برای پیشنویسکردن نیازمندیهای کسبوکار برای کدهای قدیمی استفاده کنید.
در No Silver Bullet، فرد بروکس استدلال میکند که دستیابی به افزایش بهرهوری به اندازه یک مرتبه بزرگی (order of magnitude) در توسعه نرمافزار فقط زمانی رخ میدهد که پیچیدگی ذاتی مهندسی نرمافزار حل شود. از دید بروکس، پیچیدگی ذاتی مهندسی نرمافزار همان مفهومسازی «قطعات بههمقفلشده» نرمافزار است. این در تضاد با کار نسبتاً پیشپاافتادهٔ نمایش مفهوم انتخابشده در قالب یک پیادهسازی است.
امروزه ابزارهای پرکاربرد توسعه نرمافزارِ مجهز به هوش مصنوعی، مانند Copilot، aider و cline، وقتی یک توصیف زبان طبیعی از یک مفهوم به آنها داده شود، بهراحتی «نمایشها» را تولید میکنند. اما اگر بروکس درست گفته باشد، این ابزارها فقط پیچیدگی تصادفی مهندسی نرمافزار را هدف میگیرند، نه پیچیدگی ذاتیِ «مشخصکردن، طراحیکردن و آزمودن [ساختار مفهومی]».
در این مقاله، تجربهها و بینشهای خود را درباره اینکه چگونه مدلهای زبانی بزرگ (LLMها) به ما کمک کردند ساختارهای مفهومی پشت نرمافزار را آشکار کنیم و بهبود دهیم، به اشتراک میگذاریم. بحث میکنیم که این رویکردها چطور پیچیدگی ذاتی مهندسی نرمافزار را هدف قرار میدهند و احتمال موفقیت در پروژههای بزرگ و پیچیده نوسازی نرمافزار را افزایش میدهند.
نوسازی سامانههای قدیمی و مسئلهٔ بازیابی مفهوم
هیچجا دشواری مفهومسازی درست نرمافزار به اندازه سامانههای قدیمی (legacy) آشکار نیست. طبق تعریف، سامانههای قدیمی با وجود منسوخ بودن همچنان استفاده میشوند؛ یعنی از نظر کسبوکار حیاتیاند اما برای اکثر مهندسان کمتر آشنا هستند.
این سامانهها بهطور ویژه مستعد «انحراف مفهومی» بین کسبوکار و نرمافزار هستند.
وقتی تغییر نرمافزار دشوار میشود، کسبوکارها ممکن است تصمیم بگیرند انحراف مفهومی را تحمل کنند یا از طریق عملیات خود آن را جبران کنند. زمانی که دشواری اصلاح نرمافزار به اندازه کافی ریسک کسبوکاری ایجاد کند، یک تلاش برای نوسازی سامانه قدیمی آغاز میشود.
تلاشهای نوسازی سامانههای قدیمی، مسئلهٔ بازیابی مفهوم را بهخوبی نشان میدهند. در این شرایط، بازیابی مفهومِ زیربنایی یک سامانه نرمافزاری، مرحلهٔ گلوگاهی و پرزحمتِ هر تغییر است.
بدون آن، کسبوکار با ریسک شکست نوسازی مواجه میشود یا مشتریانی را از دست میدهد که به قابلیتهایی متکی هستند که ناشناختهاند یا بهاندازه کافی در نظر گرفته نشدهاند.

یک شرکت تجارت الکترونیک را در نظر بگیرید که در ابتدا فقط به مشتریان خردهفروشی میفروشد. بعداً شرکت یک واحد کسبوکار مستقل برای مشتریان عمدهفروشی ایجاد میکند که قواعد تخفیف و صورتحساب خودش را دارد. انحراف مفهومی میتواند زمانی رخ دهد که نرمافزار واحد عمدهفروشی جدید بهطور مستقیم به پیادهسازی واحد خردهفروشی وابسته باشد. قابلیتهای واحد عمدهفروشی جدید بر اساس قواعد کسبوکار خردهفروشی پیادهسازی میشوند و در نتیجه مفهوم «استقلال» بین دو واحد کسبوکار مدلسازی نمیشود.
نوسازی برشهای عمودی (vertical slices) از نرمافزار پیچیده پس از انحراف مفهومی قابلتوجه دشوار است، زیرا مفاهیم معمولاً گسترده، پرظرافت و پنهان هستند. خوشبختانه، تکنیکهایی که از مدلهای زبانی بزرگ استفاده میکنند کمک قابلتوجهی به بازیابی مفهوم میدهند و شتابدهندهٔ جدیدی برای تلاشهای نوسازی فراهم میکنند.
مداخلات هوش مصنوعی برای نوسازی نرمافزار
هدف هر تلاش توسعه نرمافزار، کاهش زمان چرخه برای استقرار کد در حالی است که نرخ خطا در حد قابل تحمل باقی بماند. ما تلاش کردیم مداخلات هوش مصنوعی را در چند نقطه از فرایند نوسازی توسعه دهیم تا زمان چرخه را کاهش دهیم، حتی برای سامانههای پیچیده. در اینجا عمدتاً روی مداخلات مرحله طراحی تمرکز میکنیم؛ بخشی که تا حد زیادی توسط ابزارهای تجاری هوش مصنوعی پوشش داده نشده است.
استفاده از همه مداخلات بهصورت یکپارچه و کاملاً خودکار، راهی برای نمونهسازی سریع و کاوش راهحلها فراهم میکند، مثل گونههای مختلف معماری سامانه. یک تلاش جدی نوسازی به کارشناسان انسانی نیاز دارد تا خروجیهای تولیدشده توسط هوش مصنوعی را تطبیق دهند و راستیآزمایی کنند، اما ما دریافتیم که کاهش زحمت با استفاده از AI، ریسک کار را کم میکند و تلاش را نظاممندتر میسازد.

مداخلات مرحله طراحی
هدف مرحله طراحی در نوسازی نرمافزار این است که به اندازه کافی رویکرد را اعتبارسنجی کنیم تا بتوانیم برنامهریزی و توسعه را آغاز کنیم، در حالی که مقدار بازکاری ناشی از اطلاعات از دسترفته را کمینه میکنیم. بهطور سنتی، زمان قابلتوجهی در مرحله طراحی صرف بررسی کد منبع سامانه قدیمی، تولید معماری هدف و جمعآوری نیازمندیهای کسبوکار میشود. این فعالیتها زمانبر هستند، بهشدت به هم وابستهاند و معمولاً گلوگاه نوسازی محسوب میشوند.
هنگام بررسی اینکه چگونه از LLMها برای بازیابی مفهوم استفاده کنیم، با سه چالش روبهرو شدیم تا بتوانیم بهطور مؤثر به تیمهای نوسازی سامانههای قدیمی خدمترسانی کنیم: چه زمینهای لازم است و چگونه باید به دست آید، چگونه زمینه را طوری سازماندهی کنیم که هم انسانها و هم LLMها بتوانند از آن استفاده کنند، و چگونه از بهبود تکرارشوندهٔ اسناد نیازمندی پشتیبانی کنیم. راهحلهایی که در ادامه توصیف میکنیم در یک پایپلاین کنار هم قرار میگیرند و میتوانند این چالشها را پوشش دهند.
ردیابی کد، زمینهٔ مرتبط برای LLMها را شناسایی میکند
ردیابی (Trace) چیست؟
برای طراحی ابزارهایی جهت جمعآوری زمینهٔ کد مفید برای کارهای نوسازی، مشاهده کردیم مهندسان چگونه از ادیتورهای مدرن کد و ابزارهای تحلیل ایستا برای کاوش کدبیسهای ناآشنا استفاده میکنند. مهندسان از جستوجوی کلیدواژه، regex و قراردادهای نامگذاری درخت سورس استفاده میکنند تا خودشان را حول نقطه ورود (entrypoint) مناسب برای یک کار مشخص در کدبیس جهتدهی کنند. ابزارهای رسم نمودار نرمافزار به بصریسازی ساختار نرمافزار کمک میکنند و قابلیتهایی مثل go-to-definition و find-all-references در ادیتور به خواننده کمک میکند بین بخشهای مرتبط کد حرکت کند.
معمولاً شناسایی entrypoint برای معماران و مهندسان کمزحمت بود، چون در درک ساختار سطح بالای نرمافزار تمرین دارند. اما دنبالکردن ارجاعها در کدبیس برای ارزیابی قابلیتها و ساختار، کار پرزحمتی بود. به همین دلیل، تمرکزمان را روی ساخت روشی گذاشتیم که کد را ردیابی کند تا بتوان آن را به LLM داد.
یک trace عبور سیستماتیک از درخت نحو انتزاعی (AST) را توصیف میکند که نتیجهاش یک ساختار درختی است و زمینهٔ کد مرتبط با یک کار نوسازی را تعریف میکند. برای انجام trace، یک نقطه شروع و یک معیار توقف انتخاب میکنیم که راهبرد بازنویسی را بازتاب دهد. برای مثال، راهبرد بازنویسی برای یک برنامهٔ فرممحور میتواند این باشد که لایه فرم و پایگاه داده را «فریز» کنیم اما تمام منطق کسبوکار بین آنها را بازنویسی کنیم. یک trace ارزشمند از یک کلاس فرم شروع میشود و در گرههایی که به پایگاه داده دسترسی دارند پایان مییابد، و با یک عمق ارجاع از پیش تعریفشده محدود میشود تا حجم کد برگشتی کنترل شود.
ما ابزارهای مختلفی را برای پارس کردن کد و انجام trace ارزیابی کردیم. زنجیرهابزارهای کامپایلر APIهای مخصوص زبان ارائه میدهند، در حالی که ابزارهایی مثل tree-sitter و ANTLR میتوانند طیف وسیعی از زبانهای برنامهنویسی را با یک API یکسان پارس کنند. برای کار ما، بر پایه API کامپایلر Roslyn ساختیم چون اطلاعات نوع (type) دقیقی برای برنامههای VB و C#.NET برمیگرداند. پیمایشگر درخت نحو ما جزئیاتی مثل نوع نماد فعلی، کد منبع آن و روابطش با نمادهای دیگر را ذخیره میکرد.
جمعآوری زمینهٔ کد
ما روی فرمت زمینهٔ کدی که به LLM میدادیم آزمایش کردیم. AST شامل جزئیات مربوط به هر نماد در کد است، که میتواند دادههای بسیار ریزدانهای مثل مقدار یک دستور انتساب را هم شامل شود. بر اساس پژوهشهای قبلی درباره خلاصهسازی کد توسط LLMها، انتظار داشتیم LLMها اگر کد منبع خام را دریافت کنند، خلاصههای بهتری تولید کنند.
بنابراین، زمینهٔ کد با فرمت markdown را با ASTهای خام مقایسه کردیم. در زمینهٔ کد markdown، از تیترهای H3 برای برچسبگذاری نام هر متد یا کلاس استفاده کردیم. در مقابل، فرمت AST ما شبیه یک ساختار درختی تو در تو با کاراکترهای ASCII بود. دریافتیم خلاصههای LLM وقتی از فرمت markdown استفاده میکردیم کاملتر بودند. ASTها عموماً پاسخهای فنیتری تولید میکردند که اغلب بیش از حد جزئی بود و به انسجام کلی لطمه میزد.
جمعآوری زمینهٔ پایگاه داده
هنگام پیمایش درخت نحو، وابستگیهای پایگاه داده را هم با پارس کردن سورس برای نحو SQL با استفاده از ANTLR دنبال میکردیم. اگر SQL پیدا میشد، نام جدولها و stored procedureها را استخراج میکردیم و روی گره ذخیره میکردیم. پس از trace، نام جدولها و stored procedureهای جمعآوریشده را با نامهایی که از یک schema dump از پایگاه دادهٔ برنامه به دست آمده بود مقایسه کردیم. این کار به ما اجازه داد یک فایل زمینهٔ پایگاه داده با فرمت markdown تولید کنیم که بخشی از schema را که کد ردیابیشده لمس میکند توصیف میکند. مشابه زمینهٔ کد، هر جدول یا stored procedure یک برچسب H3 دریافت میکرد و سپس schema یا SQL مرتبط میآمد.
مزایای ردیابی
ماهیت سیستماتیک trace در شناسایی زمینهٔ کد و پایگاه داده، نسبت به پریدن دستی بین ارجاعها و خلاصهکردن کد، افزایش بهرهوری ایجاد کرد. معمارانی که با آنها کار کردیم یک هفته یا بیشتر را صرف پریدن بین کدها میکردند، در چرخهها گیر میافتادند و در ساختن دانش از کدبیس مشکل داشتند. در مقایسه، trace میتواند در چند دقیقه انجام شود و فرایند سیستماتیک و قانونمحور، راهی ساختاریافته و تکرارپذیر برای تحلیل کد فراهم میکند.
مفید کردن زمینه برای انسانها و LLMها
بصریسازی یک trace
معماران و مهندسان روشهایی میخواستند تا زمینهٔ کد و پایگاه دادهٔ trace را بصری کنند تا معماری و طراحی را بهتر بفهمند. این کار را با ساخت قابلیت export برای traceها انجام دادیم. این قابلیت زمینهٔ کد و پایگاه داده را به نمودارهای کلاس، توالی و رابطهٔ موجودیت (ER) با فرمت PlantUML سریالیزه میکند. ما تلاش کردیم با پرامپت دادن به LLM، PlantUML را مستقیم از زمینهٔ کد و پایگاه داده تولید کنیم. اما نتایج قابل اتکا نبود. حتی با زمینههای متوسط ۵۰ هزار توکنی، LLMها جزئیات را از دست میدادند و در رعایت نحو PlantUML ثبات نداشتند.
همچنین دریافتیم خود markup مربوط به PlantUML بهعنوان زمینهٔ LLM هم مفید است. markup نمودار کلاس، LLMها را در روابط ساختاری بین نمادهای کد «زمینگیر» میکرد. بهجای اینکه از LLM بخواهیم رابطه بین دو تکه کد را حدس بزند، ارجاعهای صریح در PlantUML قابلیت اطمینان پاسخها را افزایش میداد. به همین شکل، نمودار ER بخشی از لایه ذخیرهسازی را که برنامه به آن وابسته است خلاصه میکرد و این به پاسخهای فنیتر منجر میشد. در نتیجه، markup نمودار کلاس و ER را در زمینهٔ کد و پایگاه دادهای که برای LLMها ارسال میکردیم قرار دادیم.
بازیابی نیازمندیهای کسبوکار
برای رسیدگی به مسئلهٔ مرکزی بازیابی مفهوم، به LLMها پرامپت دادیم تا با استفاده از زمینهٔ کد و پایگاه دادهٔ جمعآوریشده، یک سند نیازمندیهای کسبوکار (BRD) تولید کنند. با پیروی از بهترینعملهای مهندسی پرامپت، یک پرامپت واحد طراحی کردیم که برای کاربران فنی و غیرفنی مفید باشد. خروجیها شامل یک نمای کلی عمومی، نیازمندیهای عملکردی، توصیف رابط کاربری و ارجاعها بودند. اینها در جدول زیر خلاصه شدهاند.
| مؤلفهٔ خروجی | ارتباط/کاربرد |
|---|---|
| نمای کلی عمومی | خلاصهای از هدف کد و ارتباط آن با برنامه بزرگتر، که شامل یک توضیح کوتاه زمینهای درباره هدف برنامه بود |
| نیازمندیهای عملکردی | توصیف قواعد کسبوکار، محاسبات و نحوه رسیدگی به دادهها |
| رابط کاربری | توصیف عناصر رابط کاربری درگیر و جایگاه آنها در مسیر کاربر |
| ارجاعها | فهرستی از کلاسها، جدولها و stored procedureهای درگیر |
وقتی با مهندسانی که مرتباً روی کارهای نوسازی نرمافزار کار میکنند صحبت کردیم، فهمیدیم هر تلاش نوسازی احتمالاً به خروجیهای سفارشی نیاز دارد. برای مثال، بعضی نوسازیها نیازمند تکرار عملکردی دقیق از سامانه مبدأ به سامانه مقصد هستند. شاید هدف، کنارگذاشتن زیرساخت فرسوده یا زبانها و فریمورکهای خارج از پشتیبانی باشد. در این موارد، استخراج تست کیسهای عملکردی ارزش ویژهای دارد. نوسازیهای دیگر شامل فراهمسازی برای قابلیتهای جدید یا اصلاح رفتار هستند. در این موارد، BRD ممکن است برای فهم مسیرهای قابل انجام به سمت وضعیت مطلوب استفاده شود، بنابراین تست کیسهای عملکردی کمتر ارزشمند خواهند بود.
از آنجا که اندازهٔ زمینهٔ ما (اغلب بیش از ۱۵۰ هزار توکن) از محدودیت زمینهٔ بسیاری از مدلهای frontier بزرگتر بود، یک راهبرد پرامپتدهی تکرارشونده طراحی کردیم. ابتدا تعداد توکنهای زمینه را تخمین زدیم. سپس زمینه را به قطعههای قابل مدیریت تقسیم کردیم، معمولاً حدود ۵۰ هزار توکن، و بعد بهصورت تکرارشونده برای خلاصهسازی هر قطعه پرامپت دادیم. برای تخمین توکنها از tiktoken استفاده کردیم. با این حال، یک قاعدهٔ سرانگشتی ساده مثل اینکه ۱ توکن برابر با ۴ کاراکتر باشد هم برای این هدف مؤثر است.
یک نکته کلیدی این بود که مطمئن شویم قطعهبندی ما نمونههای کد یا زمینهٔ پایگاه داده را نصف نمیکند. برای جلوگیری از این کار، به تشخیص جداکنندههای markdown در فایلهای زمینهٔ کد تکیه کردیم.
بعد از اینکه همه قطعهها پردازش شدند، خروجیها را با یک پرامپت سنتز اختصاصی به یک پاسخ واحد تبدیل کردیم. این پرامپت شامل استدلال زنجیرهتفکر، یک چکلیست برای بخشهای لازم، و یک درخواست برای بررسی کامل بودن هر بخش بود. اگر یک پرامپت تکمرحلهای با زمینهٔ شما ممکن است، توصیه میکنیم از همان رویکرد شروع کنید. با پیشرفت مدلهای long-context (مثل Gemini Flash و مدلهای Lllama 4) و تواناییهای استدلالی، ممکن است پرامپتدهی تکرارشونده در نهایت غیرضروری شود.
گفتوگوی کد با هوش مصنوعی امکان پرسش عمیقتر را فراهم میکند
بازخورد گرفتیم که یک BRD اولیه معمولاً پرسشهای بعدی ایجاد میکند. اغلب پاسخ آن پرسشهای بعدی، افزودنیها یا بهروزرسانیهای ارزشمندی برای BRD اولیه هستند. ما برای این کارها با تولید تقویتشده با بازیابی (RAG) آزمایش کردیم. اما RAGها میتوانند به تنظیمات قابلتوجهی نیاز داشته باشند تا ارتباط جستوجو بالا بماند، بنابراین چون کاربران ما الگوهای پرسش متفاوتی داشتند، راهحلهایی را ترجیح دادیم که به RAG نیاز نداشته باشند. برای مثال، مهندسان و معماران نگران ارجاعهای کد بودند، در حالی که تحلیلگران کسبوکار و مدیران محصول به معنای مفهومی علاقه داشتند. بهینهسازی برای هر دو گروه آنقدر دشوار بود که روی چند کار مشخصِ پیگیری که کاربران درخواست کرده بودند تمرکز کردیم.
کار ۱: ترکیب دو BRD
برای ترکیب BRDهای دو trace، همان تکنیک پرامپتدهی تکرارشوندهٔ توضیحدادهشده را به کار گرفتیم تا پنجرههای زمینهٔ بزرگ را مدیریت کنیم. معمولاً کاربران میخواستند یک BRD موجود با زمینهٔ جدیدتر بهروزرسانی شود، پس از مرحله سنتز برای بهروزرسانی BRD اصلی با جزئیات trace جدیدتر استفاده کردیم. هزینه کلی این فرایند میتواند بالا باشد. هزینه آن بهصورت خطی با مقدار زمینهٔ کد و پایگاه دادهای که trace بیرون میکشد افزایش مییابد، بهعلاوه هزینهٔ مرحله سنتز. با این حال، توجه LLM را روی هر قطعه متمرکز میکند و جزئیات دو trace را بهتر حفظ میکند.
کار ۲: پیدا کردن زمینهٔ مرتبط دیگر
برای پیدا کردن کد منبع مرتبطی که ممکن است بیرون از trace اولیه باشد، آزمایش کردیم که کل کد مخزن را تا سطح متد قطعهبندی کنیم و با CodeBERT embedding بسازیم و آنها را در یک پایگاه داده برداری ذخیره کنیم تا برای بازیابی استفاده شود. این کار یک RAG ساده ساخت که کاربران میتوانستند برای پیدا کردن قطعه کدهای مرتبط با یک فرایند یا رفتار مورد نظر جستوجو کنند. برگرداندن نتایج برتر به کاربر کمک میکرد entrypointهای کد پیشنهاد شود، اما این پیادهسازی چند ایراد داشت.
اول اینکه نتایج بدون زمینهٔ اطراف برگردانده میشدند و تشخیص ارتباطشان برای کاربران سخت بود. دوم اینکه ارتباط کلی جستوجو پایین بود. دریافتیم این قابلیت به اندازه کافی قابل اتکا نیست که یک RAG کاملاً خودکار پیادهسازی کنیم که در آن قطعه کدها بازیابی شوند، تقویت شوند و از طرف کاربر به LLM ارسال شوند.
کار ۳: پرسشهای هدفمند روی trace فعلی
برای پاسخ به پرسشهای هدفمند درباره کدِ داخل یک trace، دو رویکرد را آزمایش کردیم: پرسشهای تکرارشونده روی کل زمینه (مثل کار ۱) و ایندکسکردن کد در یک پایگاه داده برداری (مثل کار ۲). رویکرد پرسشهای تکرارشونده پاسخهای جامعتری میداد اما از بازیابی برداری پرهزینهتر بود. پیشبینی میکنیم پیشرفت در مدلهای long-context عملی بودن ارسال زمینههای بزرگ در یک پرامپت واحد را بیشتر خواهد کرد.
نتیجهگیری
ترکیب رویکردهای سختگیرانه و سیستماتیک مثل تحلیل ایستا با خلاصهسازی هوش مصنوعی، امکان رویکردهای جدید و سفارشی برای نوسازی را فراهم میکند. توانایی خودکارسازی و بررسی خروجیهای تحلیل ایستا به کارشناسان انسانی کمک میکند با اطمینان کار کنند، در حالی که خلاصهسازی LLM بهطور چشمگیری زحمت آمادهسازی مستندات طولانی را کاهش میدهد. این مزیتها همکارانی با پیشزمینههای متفاوت را وارد فرایند نوسازی نرمافزار میکند. فقط وقتی تیمهای نرمافزار مفاهیم پشت نرمافزار قدیمی را بفهمند، میتوانند آن را بهطور مؤثر بازنویسی کنند، چه با ابزارهای هوش مصنوعی و چه بدون آن.
