” کد تولیدشده توسط AI همیشه آماده استفاده در پروژه واقعی نیست. در این مقاله بررسی میکنیم چرا کد AI نیاز به بررسی، تست و بازنویسی دارد. “
این روزها ساختن یک قابلیت جدید با کمک ابزارهای هوش مصنوعی خیلی راحتتر از قبل شده. کافیه توضیح بدی چی میخوای، چند لحظه صبر کنی و بعد با حجم زیادی از کد روبهرو بشی که در نگاه اول کاملاً منطقی به نظر میرسه.
یک فرم ساخته شده، دکمه کار میکنه، اطلاعات از API دریافت میشه، ظاهر صفحه هم خوبه و حتی ممکنه تستها هم با موفقیت اجرا بشن.
پس چرا هنوز خیلی وقتها باید این کد رو بازنویسی کنیم؟
جواب کوتاه اینه که «کدی که کار میکنه» با «کدی که برای یک پروژه واقعی مناسبه» یکی نیست.
هوش مصنوعی در تولید کد خیلی سریع و توانمنده، اما ساخت یک نرمافزار واقعی فقط نوشتن کد نیست. باید بدونیم کد جدید قراره کنار چه کدهایی قرار بگیره، در آینده چطور تغییر کنه، با چه شرایطی روبهرو بشه و چه هزینهای برای نگهداری داشته باشه.
توی این مقاله میخوام دقیقاً درباره همین فاصله صحبت کنم؛ فاصلهای که بین یک درخواست ساده از AI و رسیدن به کدی که واقعاً بشه با خیال راحت وارد پروژه کرد وجود داره.
فرض کن به یک ابزار هوش مصنوعی میگی:
برای من یک فرم ثبتنام با ایمیل و رمز عبور بساز.
AI میتونه خیلی سریع یک فرم کامل بسازه:
ورودی ایمیل
ورودی رمز عبور
اعتبارسنجی
دکمه ثبت
نمایش خطا
ارسال درخواست به سرور
همهچیز هم ممکنه درست کار کنه.
اما AI از کجا میدونه پروژه ما قبلاً چه ساختاری داشته؟
نمیدونه که شاید:
سیستم اعتبارسنجی مشخصی داریم
قبلاً یک کامپوننت ورودی ساختیم
روش مدیریت خطا در پروژه ما متفاوته
اطلاعات کاربر از قبل در جای دیگهای مدیریت میشه
پروژه برای درخواستهای API روش مشخصی داره
اسمگذاری فایلها و توابع قانون خاصی داره
این فرم قراره چند ماه بعد تبدیل به یک فرم چندمرحلهای بشه
و..
پس AI بیشتر چیزی رو حل میکنه که ما در درخواست خودمون بهش گفتیم؛ نه لزوماً تمام مسئلهای که درون پروژه وجود داره.
این موضوع یکی از مهمترین دلایلیه که باعث میشه کد تولیدشده توسط AI بعداً نیاز به بازبینی و بازنویسی داشته باشه.
یکی از اشتباهات رایج اینه که کد تولیدشده رو جدا از پروژه بررسی کنیم.
مثلاً AI یک کامپوننت React ساخته و وقتی اون رو بهتنهایی نگاه میکنی، کاملاً تمیز به نظر میرسه.
اما وقتی وارد پروژه میشه، متوجه میشی پروژه از قبل کامپوننت مشابهی داشته.
در نتیجه الان دو کامپوننت داریم که تقریباً یک کار انجام میدن:
Button
PrimaryButtonیا مثلاً یک روش جدید برای دریافت اطلاعات ساخته، در حالی که پروژه از قبل یک روش مشخص برای این کار داشته :)
اینجا مشکل لزوماً بد بودن کدی که AI نوشته نیست.
مشکل اینه که کد جدید با چیزی که از قبل داریم هماهنگ نیست.
برای این موضوع در مهندسی نرمافزار معمولاً از مفهوم Consistency یا «هماهنگی و یکدستی» صحبت میشه.
یک پروژه خوب فقط مجموعهای از کدهای خوب نیست؛ کدهای اون پروژه باید تا حد ممکن با هم یکدست باشن.
این اتفاق خیلی بیشتر از چیزی که فکر میکنیم رخ میده.
فرض کن پروژه ما قبلاً یک تابع برای نمایش قیمت داره:
formatPrice()حالا از AI میخوای یک صفحه فروشگاه بسازه.
AI ممکنه خودش یک تابع جدید ایجاد کنه:
formatProductPrice()و داخل اون دوباره منطق مربوط به قیمت رو بنویسه.
در ظاهر مشکلی وجود نداره.
اما الان یک منطق در دو نقطه مختلف پروژه وجود داره.
اگه بعداً قانون نمایش قیمت تغییر کنه، باید هر دو قسمت رو تغییر بدیم.
این همون چیزی هست که معمولاً با اصطلاح Code Duplication یا «تکرار کد» ازش یاد میشه.
تکرار کد همیشه فاجعه نیست و نباید برای حذف هر دو خط مشابه، یک سیستم پیچیده بسازیم. اما وقتی یک منطق مشخص در چند جای پروژه تکرار میشه، احتمال ناسازگاری و خطا بیشتر میشه.
بنابراین قبل از اینکه از AI بخوایم چیزی بسازه، باید بدونیم آیا چیزی شبیه به اون از قبل وجود داره یا نه.
این یکی از رفتارهایی هست که احتمالاً خیلی از ما بعد از کار با ابزارهای هوش مصنوعی دیدیم.
فرض کن میخوای یک فیلتر ساده برای محصولات بسازی.
به جای اینکه منطق مورد نیاز رو در یک یا دو فایل قرار بده، ممکنه AI چندین فایل مختلف بسازه:
hooks/
services/
utils/
adapters/
providers/
constants/
repositories/حالا برای یک قابلیت ساده باید بین چندین فایل رفتوآمد کنیم.
اینجا با مفهومی به نام Overengineering روبهرو میشیم.
Overengineering یعنی برای حل یک مسئله، راهحلی پیچیدهتر از چیزی که واقعاً نیاز داریم ایجاد کنیم.
پیچیدهتر بودن همیشه به معنی حرفهایتر بودن نیست.
گاهی بهترین راهحل همون راهحل سادهایه که مسئله رو درست حل میکنه و برای تغییرات آینده هم فضای کافی داره.
این نکته مخصوصاً در پروژههایی که با AI ساخته میشن مهمه؛ چون تولید چندین فایل و چندین لایه برای AI هزینه زیادی نداره، اما نگهداری اونها برای انسان هزینه داره.
طرف دیگه ماجرا هم وجود داره.
گاهی AI برای اینکه سریع یک قابلیت رو پیاده کنه، همهچیز رو در یک فایل قرار میده.
مثلاً یک کامپوننت ممکنه همزمان این کارها رو انجام بده:
نمایش رابط کاربری
دریافت اطلاعات
ارسال درخواست
اعتبارسنجی
مدیریت خطا
تغییر وضعیت
محاسبات مربوط به کسبوکار
در یک پروژه کوچک شاید این موضوع قابل قبول باشه.
اما با بزرگ شدن پروژه، تغییر دادن چنین فایلی سختتر میشه.
اینجا معمولاً درباره Separation of Concerns صحبت میکنیم؛ یعنی «جدا کردن مسئولیتها».
خیلی ساده:
هر بخش از برنامه بهتره تا حد ممکن مسئول یک نوع کار مشخص باشه.
البته این به معنی این نیست که برای هر کار یک فایل جدید بسازیم. هدف اینه که مرزهای منطقی پروژه رو حفظ کنیم، نه اینکه پروژه رو با دهها فایل کوچیک پر کنیم.
فرض کن از AI میخوای یک فرم پرداخت بسازه.
در حالت عادی همهچیز خیلی ساده است، کاربر اطلاعات رو وارد میکنه و روی پرداخت کلیک میکنه و بعدش درخواست ارسال میشه و در نهایت پرداخت موفق یا ناموفق میشه.
اما در دنیای واقعی همیشه این اتفاق نمیافته.
ممکنه:
سرور پاسخ نده یا پاسخ سرور خیلی دیر برسه
اطلاعات ناقص باشه
سرور پاسخ متفاوتی نسبت به چیزی که انتظار داشتیم برگردونه
این حالتها معمولاً Edge Case نامیده میشن؛ یعنی شرایط خاص و کمتر معمولی که ممکنه در مسیر اجرای برنامه اتفاق بیفتن.
نرمافزاری که فقط در حالت عادی درست کار میکنه، هنوز برای استفاده واقعی آماده نیست.
در واقع بخش بزرگی از کار یک توسعهدهنده اینه که به سؤالهایی فکر کنه که در نگاه اول به ذهن نمیرسن:
اگه این اتفاق افتاد چی؟
این قسمت خیلی مهمه.
فرض کن برای یک فروشگاه آنلاین میخوای قابلیت لغو سفارش ایجاد کنی.
به AI میگی:
امکان لغو سفارش رو اضافه کن.
AI میتونه خیلی خوب کدنویسیشو انجام بده.
اما یک سؤال مهم وجود داره:
چه زمانی سفارش قابل لغو هست؟
مثلاً شاید:
قبل از ارسال قابل لغو باشه
بعد از ارسال قابل لغو نباشه
در صورت پرداخت خاص، شرایط متفاوتی داشته باشه
مبلغ بعد از لغو باید به روش خاصی برگرده
موجودی محصول باید دوباره افزایش پیدا کنه
و..
اینها دیگه مسئله مربوط به React یا Laravel یا TypeScript نیستن.
اینها Business Logic یا «منطق کسبوکار» هستن.
هوش مصنوعی میتونه implementation یا نحوه پیادهسازی رو پیشنهاد بده، اما باید قوانین واقعی سیستم رو بهش بدیم و خودمون هم بررسی کنیم که این قوانین درست پیاده شدن.
یک کد کاملاً تمیز میتونه یک قانون کاملاً اشتباه رو اجرا کنه.
و این از یک کد کثیف خطرناکتره، چون ممکنه مدت بیشتری متوجه اشتباه نشیم :)
وقتی یک قابلیت رو الان میسازیم، معمولاً فقط به امروز فکر میکنیم.
اما نرمافزار قراره تغییر کنه.
مثلاً امروز فقط دو نوع کاربر داریم:
User
AdminAI هم بر اساس همین اطلاعات سیستم رو میسازه.
شش ماه بعد تصمیم میگیریم نقشهای بیشتری داشته باشیم:
User
Editor
Manager
Admin
Super Adminحالا بعضی از تصمیمهای سادهای که قبلاً گرفته شده ممکنه تغییر دادن سیستم رو سخت کنه.
اینجا با مفهومی به نام Technical Debt یا «بدهی فنی» روبهرو میشیم.
بدهی فنی یعنی تصمیم یا راهحلی که امروز کار ما رو سریعتر میکنه، اما ممکنه در آینده هزینه بیشتری برای تغییر و نگهداری ایجاد کنه.
البته بدهی فنی همیشه بد نیست.
گاهی آگاهانه تصمیم میگیریم امروز یک راهحل سادهتر بسازیم تا سریعتر محصول رو منتشر کنیم.
مشکل زمانی ایجاد میشه که این بدهی رو نبینیم یا نفهمیم که چه هزینهای ایجاد کرده.
یکی از چیزهایی که گاهی در کد تولیدشده توسط AI دیده میشه، کدی هست که کامپیوتر بهراحتی میفهمتش ولی انسان برای فهمیدنش باید چند دقیقه وقت بذاره.
مثلاً یک تابع طولانی با چند شرط تو در تو:
if (...) {
if (...) {
if (...) {
...
}
}
}ممکنه این کد کاملاً درست کار کنه.
اما وقتی سه ماه بعد خودمون یا یک توسعهدهنده دیگه بخواد تغییرش بده، باید اول بفهمه دقیقاً چه اتفاقی درون اون تابع میافته.
اینجا مفهوم Readability یا «خوانایی کد» اهمیت پیدا میکنه.
کد خوب فقط کدی نیست که اجرا بشه.
کد خوب کدی هم هست که انسان بتونه بفهمه، بررسی کنه و با اطمینان تغییرش بده.
فرض کن به AI میگی:
این صفحه رو به API وصل کن.
AI ممکنه فرض کنه API همیشه این شکل پاسخ میده:
{
"data": []
}ولی API واقعی ممکنه این شکلی باشه:
{
"products": []
}یا اصلاً در صورت خطا ساختار دیگهای داشته باشه.
این موضوع به Assumption یا «فرض» برمیگرده.
هر جا اطلاعات کافی نداشته باشه، AI مجبور میشه بر اساس چیزهایی که میدونه یک فرض ایجاد کنه.
مشکل از جایی شروع میشه که ما اون فرض رو بررسی نمیکنیم و مستقیماً کد رو وارد پروژه میکنیم.
بنابراین یکی از کارهای مهم هنگام استفاده از AI اینه که از خودمون بپرسیم:
این قسمت رو بر اساس اطلاعات واقعی ساخته یا چیزی رو خودش فرض کرده؟
فرض کن یک صفحه مدیریت داریم و فقط برای مدیرها دکمه حذف کاربر رو نمایش میدیم.
AI ممکنه چیزی شبیه این بسازه:
if (user.role === "admin") {
return <DeleteButton />
}ظاهر کار درست به نظر میرسه.
اما این امنیت واقعی نیست.
چون کاربر میتونه مستقیماً درخواست حذف رو به سرور ارسال کنه.
سرور باید خودش بررسی کنه که آیا این کاربر اجازه حذف کاربر دیگه رو داره یا نه.
این موضوع با مفهوم Authorization یا «مجوز دسترسی» شناخته میشه.
پس وقتی AI کدی برای بخشهای حساس تولید میکنه، نباید فقط بپرسیم:
آیا این کد کار میکنه؟
باید بپرسیم:
آیا این کد اجازه انجام کارها رو هم درست کنترل میکنه؟
گاهی کد تولیدشده توسط AI کاملاً درست کار میکنه، اما منابع بیشتری از چیزی که لازم داریم مصرف میکنه.
مثلاً:
درخواستهای تکراری به سرور ارسال میکنه.
یک کامپوننت رو بیدلیل چند بار دوباره اجرا میکنه.
فایلهای غیرضروری وارد پروژه میکنه.
تصاویر رو بدون بهینهسازی نمایش میده.
مقدار زیادی JavaScript به مرورگر میفرسته.
اینها در ابتدا ممکنه اصلاً دیده نشن.
روی سیستم توسعهدهنده همهچیز سریع و روانه.
اما وقتی تعداد کاربران زیاد میشه یا کاربر با موبایل و اینترنت ضعیف وارد سایت میشه، مشکل خودش رو نشون میده.
اینجا مفهومی به نام Performance یا «کارایی» مطرح میشه.
کارایی یعنی نرمافزار فقط جواب درست نده؛ بلکه تا حد ممکن با منابع مناسب و در زمان مناسب این کار رو انجام بده.
هوش مصنوعی میتونه خیلی سریع برای کد ما تست بنویسه.
اما یک مشکل وجود داره:
ممکنه تستها دقیقاً همون چیزی رو بررسی کنن که خود کد انجام میده، نه چیزی که کاربر واقعاً انتظار داره.
مثلاً به جای اینکه بررسی کنیم:
وقتی کاربر فرم رو ارسال میکنه، آیا سفارش درست ایجاد میشه؟
تست فقط بررسی کنه که:
فلان تابع اجرا شد.
این دو موضوع یکی نیستن.
اینجا مفهومی به نام Behavior یا «رفتار» اهمیت پیدا میکنه.
تست خوب در بسیاری از موارد باید بررسی کنه که سیستم از دید کاربر یا از دید یک قابلیت واقعی، رفتار درستی داره؛ نه اینکه فقط چند خط از implementation یا نحوه پیادهسازی اجرا شده باشن.
شاید مهمترین نکته کل مقاله همین باشه.
گاهی AI چند صد خط کد تولید میکنه و ما فقط میگیم:
خب، اجرا شد و مشکلی هم نداره.
و مستقیم میذاریمش داخل پروژه.
اما اگه خودمون نتونیم توضیح بدیم این کد چه کاری انجام میده، در واقع کنترل اون بخش از پروژه رو به دست AI سپردیم.
استفاده درست از AI این نیست که هر چیزی تولید کرد قبول کنیم.
باید بتونیم کد رو بخونیم و حداقل درباره بخشهای مهمش جواب این سؤالها رو بدونیم:
این قسمت چه کاری انجام میده؟
چرا اینجا قرار گرفته؟
اطلاعات از کجا میاد؟
چه زمانی تغییر میکنه؟
در صورت خطا چه اتفاقی میافته؟
چه فرضهایی پشت این کد وجود داره؟
اگه فردا این نیاز تغییر کنه، چه چیزی باید تغییر کنه؟
اگه جواب این سؤالها رو نمیدونیم، هنوز مرحله بررسی تموم نشده.
اینجا به نظرم باید یک تفاوت مهم رو در ذهنمون ایجاد کنیم.
نباید فرآیند رو اینطور ببینیم:
Prompt
↓
Code
↓
Doneفرآیند واقعی بیشتر شبیه اینه:
Prompt
↓
Code Generation
↓
Understanding
↓
Review
↓
Testing
↓
Refactoring
↓
ProductionRefactoring یا بازنویسی کد یعنی ساختار کد رو بهتر کنیم، بدون اینکه رفتار اصلی اون رو تغییر بدیم.
مثلاً ممکنه بعد از بررسی متوجه بشیم:
یک تابع بیش از حد طولانیه.
دو بخش کد تکراری هستن.
یک مسئولیت باید از یک کامپوننت جدا بشه.
اسم یک تابع مفهوم واقعی اون رو منتقل نمیکنه.
مدیریت خطا ناقصه.
اینجاست که کد AI تبدیل میشه به کدی که واقعاً متعلق به پروژهست.
فرض کن از AI میخوای:
یک صفحه لیست محصولات با جستجو، فیلتر و صفحهبندی بساز.
AI ممکنه خیلی سریع چیزی تحویلت بده که در نگاه اول عالی باشه.
اما قبل از وارد کردنش به پروژه، میتونی این سؤالها رو بررسی کنی:
آیا کامپوننتهای مشابهی از قبل داریم؟
آیا تمام وضعیتها در یک جا مدیریت شدن یا بیدلیل بین چند بخش پخش شدن؟
اگه کاربر خیلی سریع عبارت جستجو رو تغییر بده، چه اتفاقی میافته؟
اگه درخواست قبلی دیرتر از درخواست جدید جواب بده، آیا ممکنه نتیجه قدیمی روی صفحه نمایش داده بشه؟
وقتی هیچ محصولی وجود نداره چی؟
وقتی درخواست با خطا مواجه میشه چی؟
وقتی هنوز اطلاعات در حال دریافت شدنه چی؟
آیا فیلترها و صفحه فعلی باید در URL قرار بگیرن؟
اگه بعداً مرتبسازی و فیلترهای بیشتری اضافه کنیم، این ساختار هنوز قابل استفاده هست؟
آیا این کد از الگوها و روشهای موجود پروژه پیروی میکنه؟
این سؤالها شاید در Prompt اولیه وجود نداشته باشن.
اما دقیقاً همین سؤالها هستن که یک کد معمولی رو به کدی نزدیک میکنن که بشه در یک محصول واقعی استفاده کرد.
به نظرم نه.
اتفاقاً برعکس.
AI میتونه بخش بزرگی از زمان مربوط به تولید کد رو کاهش بده.
مشکل استفاده از AI نیست؛ مشکل استفاده بدون بررسی از اونه.
AI میتونه برای کارهایی مثل این فوقالعاده باشه:
ساخت نسخه اولیه یک قابلیت
تبدیل یک ایده به نمونه قابل اجرا
نوشتن کدهای تکراری
پیدا کردن چند راهحل مختلف
نوشتن تست اولیه
پیدا کردن خطا
توضیح دادن کدهای پیچیده
بازنویسی بخشهای قدیمی
پیشنهاد ساختار
اما در نهایت باید یک نفر تصمیم بگیره:
آیا این راهحل واقعاً برای این پروژه مناسب هست یا نه؟
و اون تصمیم هنوز به درک ما از پروژه، محصول و مسئله وابسته است.
به نظرم بهتره به جای اینکه AI رو جایگزین برنامهنویس ببینیم، اون رو یک ابزار خیلی سریع برای تولید و بررسی ایدهها در نظر بگیریم.
Prompt نقطه شروعه، نه نقطه پایان.
وقتی AI برای ما کد تولید میکنه، در واقع یک پیشنهاد برای پیادهسازی مسئله داریم.
بعد باید اون پیشنهاد رو بررسی کنیم، با ساختار پروژه هماهنگ کنیم، حالتهای مختلفش رو تست کنیم، مشکلاتش رو برطرف کنیم و در صورت نیاز ساختارش رو تغییر بدیم.
به همین دلیله که فاصله زیادی بین این دو جمله وجود داره:
«AI این قابلیت رو ساخت.»
و:
«این قابلیت آماده استفاده در محصوله.»
اولی ممکنه چند دقیقه زمان ببره.
دومی میتونه نیاز به ساعتها بررسی، تست و بازنویسی داشته باشه.
و این لزوماً ضعف AI نیست.
چون ساخت نرمافزار واقعی فقط تولید کد نیست.
مسئله اصلی، تصمیم گرفتن درباره کدی هست که باید تولید بشه، جایی که باید قرار بگیره و شرایطی که باید در اون درست کار کنه.
هرچقدر AI در تولید کد بهتر بشه، این بخش از کار کماهمیتتر نمیشه؛ اتفاقاً مهمتر میشه.
چون وقتی تولید کد ساده و سریع میشه، ارزش اصلی بیشتر به سمت درست انتخاب کردن، درست بررسی کردن و درست ساختن میره.
پس دفعه بعد که AI چند صد خط کد تحویلت داد و گفت:
Done!
شاید بهتر باشه قبل از اینکه کد رو وارد پروژه کنی، یه بار با دقت مرورش کنی.
اول ببین واقعاً Done شده یا فقط Generated شده..
اگه نکته یا نظری دارید، توی نظرات برام بنویسید.
فعلا تا بعد 👋
دیدگاه و یا پرسش خود را برای ما ارسال کنید.
هنوز دیدگاه یا پرسشی ایجاد نشده است :/
تجربهها، دیدگاهها و نکات الهامبخشی که با شما به اشتراک میگذاریم.