تطوير مدفوع بالمواصفات مع LLMs
spec النموذج فيه يشتغل بدون rework. البنية، الـ anti-patterns، والـ diff الفعلي.
جاري تحميل المقال…
spec النموذج فيه يشتغل بدون rework. البنية، الـ anti-patterns، والـ diff الفعلي.
جاري تحميل المقال…
حين ينتج النموذج كوداً سيّئاً، المشكلة شبه دائماً أعلى من النموذج. الـ prompt «أعطني دالّة تفعل X» يلمح إلى spec يجب على النموذج تخمينه. spec سيّئ ينتج كوداً سيّئاً. spec جيّد ينتج كوداً يحتاج تحريراً لا إعادة كتابة. هذه بنية spec شحنّا منه، والأنماط المضادّة التي أزلناها للوصول إلى هناك.
أربعة أقسام، بهذا الترتيب، كلّ مرّة:
## الهدف
جملة واحدة. «ماذا» بالإضافة إلى، حيث مفيد، «لماذا».
## المدخلات
- النوع، الاسم، ما يمثّله كلّ
- القيود (المدى، قابليّة الـ null، الصيغة)
- الحالات الحدّيّة التي تهمّ (فارغ، كبير، unicode، …)
## المخرجات
- النوع، كيف يبدو النجاح
- ماذا يعيد / يرمي الفشل
- آثار جانبيّة، إن وجدت
## أمثلة
ثلاثة على الأقلّ:
١. مسار سعيد: مدخل نموذجيّ، مخرج متوقّع
٢. حالة حدّيّة: قيد عند حدّه
٣. فشل: مدخل يجب أن ينتج خطأ
## خارج النطاق
ماذا هذا ليس له. سطران أو ثلاثة.قسم «خارج النطاق» هو السلاح السرّيّ. النموذج متفائل؛ سيُضيف بمساعدة ميزات لم تطلبها. قسم يقول صراحة «لا تتعامل مع pagination، ذلك يعيش في مكان آخر» يمنع حوالي نصف زحف النطاق غير المرغوب.
هذا spec أنتج كوداً مروّعاً، وإعادة الكتابة التي أنتجت شيئاً شحنّاه.
اكتب دالّة تستورد CSV لجهات الاتّصال. يجب أن تتحقّق من البريد وتتخطّى المكرّرات. تأكّد أنّها تتّسع.
أنتج هذا كوداً:
+.void فلم يكن لدى المنادي فكرة عن عدد المستوردين.كلّ من هذه كان تخميناً اضطرّ النموذج لاتّخاذه. كلّ تخمين كان خاطئاً.
## الهدف
استورد جهات الاتّصال من CSV stream إلى قاعدة بيانات Drift المحلّيّة، بشكل idempotent.
## المدخلات
- `Stream<List<int>> csv` — محتويات الملفّ، مُرمَّز UTF-8
- `String tenantId` — المستأجر النشط
- `int? batchSize` — حجم chunk للإدراجات، افتراضيّاً ٥٠٠
## المخرجات
- `ImportResult { imported: int, skipped: int, errors: List<RowError> }`
- يرمي `ImportFailed` فقط على خطأ قاتل (header تالف).
## أمثلة
١. ١٠٠٠ صفّ صحيح، بدون مكرّرات → imported=١٠٠٠، skipped=٠، errors=[]
٢. ١٠٠٠ صفّ، ٥٠ موجودون أصلاً بالبريد → imported=٩٥٠، skipped=٥٠، errors=[]
٣. ١٠٠٠ صفّ، ١٠ ببريد مشوّه → imported=٩٩٠، skipped=٠، errors=[١٠ RowErrors مع رقم الصفّ + السبب]
## خارج النطاق
- لا إرسال إشعارات. المنادي يفعل ذلك.
- لا تقارير تقدّم. المنادي يُغلّف بإشارة تقدّمه الخاصّة.
- لا حذف لجهات اتّصال «قديمة». الاستيراد يُدرج فقط.
- لا اكتشاف صيغة CSV تلقائيّ. UTF-8 مع فاصلة، سلاسل بين علامتَي اقتباس.التمريرة الأولى ضدّ هذا الـ spec كانت ٩٠٪ صحيحة. حرّرنا الـ ١٠٪
المتبقّية (في الغالب شكل RowError، الذي اخترعه النموذج بشكل معقول
لكن ليس بالضبط لنمطنا الموجود)، كتبنا اختبارات ضدّ الأمثلة الثلاثة،
شحنّا.
توقّفنا عن قول «النموذج يكتب كوداً سيّئاً» حالما اعترفنا بأنّ الشكوى الحقيقيّة كانت «النموذج يكتب الكود الذي لم ندرك أنّنا حدّدناه بشكل سيّئ».
خمسة أشياء كنّا نضعها في specs أضرّت بالإخراج:
«استخدم أفضل الممارسات في معالجة الأخطاء». هذا لا يعني شيئاً. النموذج
يأخذ متوسّط آلاف الاتّفاقيّات وينتج شيئاً عامّاً. استبدل بـ: «على خطأ
parse، ارمِ ParseError مع رقم الصفّ والسبب. استمرّ في معالجة الصفوف
المتبقّية.»
هذه الكلمات تطلق إفراط هندسة. النموذج يضيف caching، معالجة batch، تزامن async، كلّها قد تكون خاطئة لسياقك. استبدل بالقيد الفعليّ: «يجب أن يتعامل مع ١٠٠ ألف صفّ في أقلّ من ٣٠ ثانية على جهاز الاختبار.»
«اكتب دالّة تفعل X. وأيضاً تعامل مع الحالة حين Y. وأيضاً دعها Z». سلسلة «وأيضاً» تُقرأ من النموذج كسلسلة تبعيّة؛ النموذج يخترع تشابكاً بنيويّاً لم يكن مطلوباً. استبدل بمتطلّبات منفصلة، مسمّاة.
«استخدم hash map». «استخدم streams». تلميحات من مهندس متعب لم ينتهِ من التفكير. النموذج سيتبعها. إن لم تؤمن بالتلميح كاملاً، لا تُضمّنه. دع النموذج يقترح؛ أنت تحرّر.
هذه ميزة تنتج أكثر التمريرات الأولى إيلاماً. النموذج يخترع معاملات نوع وتجريدات لا تحتاجها. إن احتجت العمومية فعلاً، حدّد حالة الاستخدام الثانية. وإلّا قل «لجهات الاتّصال فقط، لا حاجة للتعميم.»
ثلاثة أماكن، بترتيب الأثر:
التصميم المفتوح («كيف يجب أن نُهيكل هذه الوحدة»)، التصحيح («ما الخطأ هنا»)، وإعادة الهيكلة («نظّف هذا») كلّها تحتاج أشكالاً مختلفة. الـ specs لـ «ابنِ هذا الشيء»، لا «حسّن هذا الشيء». prompts مختلفة.
الجزء التالي من هذه السلسلة عن حزم الـ eval لأدوات غير حتميّة، وهي أقرب شيء لاختبار وحدة للنموذج نفسه. صفحة السلسلة تتابع البقيّة.