سبز شدن تستها پایان کار نیست. ایجنت ممکن است با حذف abstraction، تکرار، mock فراگیر یا hard-code کردن پاسخ، امروز CI را سبز و فردا تغییر بعدی را پرهزینه کند. این درس Refactor را با Oracle ثابت، معیار maintainability و Evidence Pack مستقل کامل میکند.
## اهداف یادگیری
میتوانی refactor کمخطر زیر تست سبز انجام دهی، coupling و duplication را با شواهد بررسی کنی، Reward Hacking را از بهبود واقعی جدا کنی و بستهٔ شواهد را به verifier تازه بسپاری.
## جایگاه در چرخه
این درس مرحلهٔ `Refactor → Verify → Integrate` برای Sliceهای TDD فصل ۶ است. قرارداد فصل ۵ همچنان مرجع است؛ refactor مجاز نیست status، idempotency scope، audit uniqueness، tenant isolation یا feature-flag behavior را تغییر دهد.
## Refactor و maintainability
Refactor رفتار مشاهدهشده را ثابت نگه میدارد و ساختار را بهتر میکند: نامگذاری دقیق، تابعهای با مسئولیت محدود، حذف duplication واقعی و مرز dependency روشن. «تمیزتر به نظر رسیدن» معیار کافی نیست. برای booking-platform میتوانی پیچیدگی branch، تعداد mockها، coupling به repository و خوانایی error map را قبل و بعد ثبت کنی؛ اینها proxy هستند، نه حقیقت کامل maintainability.
مثال بد
ایجنت برای سبز شدن race test، repository را در service mock میکند و شرط conflict را در test fixture hard-code میکند. همهٔ unitها سبز میشوند، اما integration دیگر database را نمیبیند. این Reward Hacking است: metric نزدیکتر (تست سبز) جای هدف واقعی (رفتار امن و قابلادغام) را گرفته است.
مثال اصلاحشده
اول تستها و Oracleها immutable میمانند. سپس `resolveRescheduleConflict` از handler جدا میشود، error mapping در یک map صریح قرار میگیرد و integration همچنان transaction واقعی را اجرا میکند. بعد از هر گام targeted test و در پایان full suite اجرا میشود. اگر نامی بهتر شد، contract response و telemetry unchanged ثبت میشوند.
## Evidence Pack
بستهٔ شواهد حداقل این موارد را دارد:
1. scope، authority و non-goal؛ 2. hash revision و Diff خلاصه با فایلهای حساس؛ 3. فرمانها، نسخهٔ runtime و خروجی targeted/full test؛ 4. Red واقعی، Green و regression/race evidence؛ 5. mutation result و mutantهای باز با rationale؛ 6. browser trace یا screenshot در صورت تغییر UI؛ 7. static/security/contract check؛ 8. reviewer مستقل، تصمیمهای باز و rollback؛ 9. محدودیت شواهد: چه چیزی unit است و چه چیزی production را ثابت نمیکند.
Evidence Pack گزارش ادعاست، نه انباشت log. خروجی سبز بدون فرمان و محیط قابل بازتولید ارزش کمی دارد.
## Verifier تازه
Verifier نباید همان Context و فرضهای implementer را کورکورانه ادامه دهد. یک جلسهٔ تازه باید از Diff، Spec و تست شروع کند، تست را مستقل اجرا کند، test weakening scan انجام دهد و ادعاهای Evidence Pack را با artifact تطبیق دهد. اگر verifier فقط به خلاصهٔ Agent اعتماد کند، استقلال نمایشی است.
سناریوی booking-platform
پس از fix double-booking، ایجنت service را به چند تابع تقسیم میکند. verifier باید بررسی کند unique/exclusion constraint، transaction boundary و mapping `409 SLOT_CONFLICT` همچنان وجود دارند. برای reschedule replay، باید count mutation/audit و fingerprint را ببیند. برای UI، Playwright trace باید از tenant درست و feature flag درست آمده باشد. هیچ refactorی نباید flag false را به route قابلدسترسی تبدیل کند.
## آزمایشگاه گامبهگام
**ورودی:** patch سبز از l22/l23، Spec v1.0، test commands و Evidence template.
۱. baseline را بگیر:
git diff --stat
pnpm exec vitest run apps/api/src/modules/appointments۲. فقط یک refactor structural انجام بده؛ مثلاً error mapping را استخراج کن. بعد targeted test را اجرا کن و Diff test را ثابت نگه دار.
۳. سپس full verification:
pnpm exec vitest run
pnpm exec playwright test --reporter=line
git diff --check۴. اسکن ضد تضعیف تست انجام بده:
rg -n "skip|todo|only|toBeDefined|toBeTruthy|mock.*repository" apps packagesاین فرمان false positive دارد؛ هر match باید دستی بررسی شود. سپس Evidence Pack را با hash، محیط، خروجی و محدودیتها بساز.
۵. یک verifier تازه، بدون خلاصهٔ implementer، Diff و Evidence را بررسی کند و یکی از `ACCEPT`, `CHANGES_REQUESTED`, `BLOCKED` را با دلیل بدهد.
**خروجی مورد انتظار:** تستها سبز، Diff کوچک و توضیحپذیر، Oracle بدون تضعیف، mutationهای حیاتی کشته، شواهد قابل تکرار و یک verdict مستقل.
**Recovery:** اگر refactor test را شکست، rollback ساختاری کن و behavior را دستنخورده نگه دار. اگر full suite در محیط متفاوت شکست، نسخه و fixture را ثبت و ادعای «سبز کامل» را پس بگیر. اگر verifier match مشکوک یافت، test را حذف نکن؛ owner و تصمیم را به `CHANGES_REQUESTED` تبدیل کن.
## تشخیص refactor واقعی از سبزسازی مصنوعی
پیش از refactor، رفتار را به چند دستهٔ قابل مشاهده تقسیم کن: پاسخ API، state پایدار، side effectها، telemetry مهم و مرزهای امنیتی. سپس برای هر دسته یک Oracle و یک تست مشخص کن. این کار جلوی refactorهایی را میگیرد که response را حفظ میکنند اما audit، version یا tenant check را تغییر میدهند. اگر رفتار داخلی عمداً بخشی از contract نیست، آن را در فهرست non-goal بنویس تا implementer مجبور نباشد جزئیات خصوصی را بیدلیل ثابت نگه دارد.
Refactor امن معمولاً یک گام کوچک دارد: استخراج تابع pure، تغییر نام یک مفهوم مبهم، جداسازی adapter از policy یا جایگزینی شرطهای تکراری با یک helper که همان ورودی و خروجی را حفظ میکند. بعد از هر گام، targeted test و `git diff --check` اجرا میشود. اگر یک تغییر همزمان ساختار، schema و assertion را عوض کند، تشخیص علت شکست دشوار میشود و دیگر نمیتوان گفت behavior ثابت مانده است. حجم کم بهتنهایی معیار نیست؛ مرز معنایی تغییر باید کوچک باشد.
proxyهای maintainability و محدودیت آنها
پیچیدگی cyclomatic، طول تابع، تعداد dependencyها، duplication و تعداد mockها میتوانند برای مقایسهٔ قبل و بعد مفید باشند، اما هیچکدام maintainability را مستقیم اندازه نمیگیرند. کاهش LOC ممکن است با انتقال منطق به helper مبهم یا افزایش coupling به global حاصل شود. کاهش mockها هم همیشه خوب نیست؛ گاهی mock دقیق مرز بیرونی را روشن میکند و حذف آن تست را به شبکهٔ واقعی و flaky وابسته میسازد. بنابراین هر metric باید همراه با تفسیر و نمونهٔ کد review شود.
یک refactor ارزشمند برای booking-platform باید ownership را روشنتر کند: policy تصمیم eligibility را بگیرد، repository persistence را انجام دهد و handler mapping HTTP را. در عین حال، policy نباید به request object، tenant ادعاشده از body یا clock واقعی وابسته شود. اگر dependency injection جدیدی اضافه میکنی، composition root و تست integration را هم بررسی کن. abstraction زمانی مفید است که تغییر بعدی را ارزانتر یا خطای قابلپیشبینی را آشکارتر کند؛ صرفاً بیشتر شدن interface نشانهٔ طراحی بهتر نیست.
## Evidence Pack بهعنوان قرارداد بازتولید
Evidence Pack را طوری بنویس که verifier بدون حضور implementer بتواند کار را تکرار کند. برای هر فرمان، working directory، نسخهٔ Node و package manager، environment variableهای غیرحساس، seed، زمان اجرا و exit code را ثبت کن. secret واقعی را هرگز داخل Pack نگذار؛ نام متغیر و روش استفاده از fake credential کافی است. اگر تست به database ephemeral یا browser خاصی نیاز دارد، setup و cleanup را نیز بنویس. یک log بدون context ممکن است قابلخواندن باشد اما قابلبازتولید نیست.
Pack باید بین observation و interpretation فرق بگذارد. observation میتواند این باشد که `42 tests passed`؛ interpretation باید محدود بماند: «policy و integration fixture در این محیط سبز شدند». جملهٔ «رفتار production تضمین شد» از این خروجی نتیجه نمیشود. برای هر limitation، owner و next step تعیین کن؛ مثلاً race در database واقعی هنوز با load test سنجیده نشده است. این دقت به verifier کمک میکند بهجای رد کل patch، فقط ادعای بیشازحد را محدود کند.
نسخهٔ Pack را به hash revision و فهرست فایلهای حساس وصل کن. اگر implementation بعداً عوض شد، Pack قدیمی نباید بهعنوان evidence جدید ارائه شود. برای artifactهای بزرگ مانند trace یا mutation report، مسیر، checksum و خلاصهٔ قابلخواندن ثبت کن. screenshot بدون viewport، font و browser version ارزش کمی دارد. همینطور خروجی coverage بدون configuration و exclusionها قابل مقایسه نیست.
## verifier تازه و بررسی ضدتضعیف
verifier تازه باید ابتدا با Diff و Spec شروع کند، نه با خلاصهٔ Agent. او باید بپرسد آیا test حذف، ضعیف یا جابهجا شده است؛ آیا `toBeDefined` جای assertion معنایی نشسته؛ آیا mock رفتار اصلی را از قبل برمیگرداند؛ و آیا integration به unit تبدیل شده است. جستوجوی واژههایی مثل `skip`، `only` و `todo` کمک اولیه است، اما false positive دارد و تصمیم نهایی باید از خواندن context بیاید.
برای refactorهای concurrency، verifier باید transaction boundary، unique یا exclusion constraint و ترتیب lock را دوباره بخواند. تست سبز با یک repository in-memory نمیتواند نبود double-booking در database واقعی را ثابت کند. برای idempotency، fingerprint و scope کلید را بررسی کن؛ پاسخ یکسان بدون count mutation کافی نیست. برای feature flag، هر دو حالت true و false و مسیر دسترسی را اجرا کن؛ refactor نباید flag خاموش را عملاً روشن کند. این بررسیها زمان میگیرند، اما دقیقاً همان بخشهایی هستند که abstraction ممکن است پنهان کند.
اگر verifier به محیط دسترسی ندارد، verdict باید `BLOCKED` باشد، نه `ACCEPT`. اگر یک Oracle ناقص است اما تغییر کوچک و روشن لازم دارد، `CHANGES_REQUESTED` بده و درخواست را به artifact مشخص وصل کن. تنها زمانی `ACCEPT` مناسب است که scope، تست، diff، evidence و limitation با هم سازگار باشند. مستقل بودن verifier به معنی مخالفت همیشگی نیست؛ به معنی داشتن اختیار برای توقف و دلیل قابلردگیری است.
## سناریوی refactor چندمرحلهای
فرض کن handler reschedule هم eligibility، هم transaction و هم error mapping را در یک تابع ۱۸۰ خطی انجام میدهد. گام اول فقط mapping خطا را به یک جدول صریح منتقل میکند؛ Unit و Contract اجرا میشوند. گام دوم policy pure را استخراج میکند؛ boundary testهای زمانی اجرا میشوند. گام سوم transaction callback را از handler جدا میکند؛ Integration race و audit اجرا میشوند. در هر مرحله test diff ثابت میماند و Pack به همان revision اشاره میکند.
اگر گام سوم fail شد، rollback فقط همان extraction است، نه بازنویسی assertionها. اگر full suite بهدلیل محیط browser fail شد ولی targeted backend سبز است، این دو observation جدا ثبت میشوند و verdict کلی `BLOCKED` باقی میماند. اگر failure نشان داد که policy به timezone محلی وابسته بوده، آن را پنهان نکن؛ owner تصمیم و قرارداد timezone باید به `DECIDE` منتقل شود. این روند refactor را از یک عملیات زیبایی کد به فرایند کاهش ریسک تبدیل میکند.
## خطاهای رایج و نشانهها
- refactor بزرگ در کنار feature؛ علت failure قابل تفکیک نیست.
- تغییر نام error یا status برای سادهکردن code؛ contract consumer میشکند.
- حذف integration برای سرعت؛ evidence از protected write به unit تقلیل مییابد.
- Evidence Pack فقط «همهچیز سبز بود» است؛ command، hash یا limitation ندارد.
- verifier تازه همان prompt و همان خروجی Agent را کپی میکند.
## چکلیست Verification
- [ ] refactor زیر تست سبز و در گامهای کوچک انجام شده است.
- [ ] contract، tenant، idempotency، race و audit unchanged یا مستند شدهاند.
- [ ] targeted و full suite، static check و diff check اجرا شدهاند.
- [ ] Evidence Pack فرمان، محیط، خروجی و limitation دارد.
- [ ] verifier تازه verdict مستقل و قابلردگیری داده است.
## تمرین مستقل
Evidence Pack ناقصی طراحی کن که در آن تستها سبزند اما integration حذف شده است. سه درخواست تغییر verifier را بنویس.
**Rubric پاسخ:** باید بازگرداندن integration race/transaction، اجرای mutation یا assertion audit، ثبت command/hash و توضیح اینکه unit سبز production correctness را ثابت نمیکند را بخواهد.
## سطح ادعا و تصمیم پذیرش
«تستها سبزند» فقط یک مشاهده است. پذیرش باید به دامنهٔ همان مشاهده محدود بماند: Unit policy میگوید ورودیهای نمونه چنین خروجی میدهند؛ Integration میگوید transaction و audit در fixture واقعی چنین رفتار میکنند؛ Playwright میگوید مسیر browser قابل استفاده است. هیچکدام بهتنهایی availability، ظرفیت production یا امنیت کامل را تضمین نمیکند. Evidence Pack باید این مرزها را صریح بنویسد تا verifier تازه مجبور نباشد از سکوت، ادعای بزرگتر بسازد.
در مقایسهٔ ابزارها، Cursor یا Claude Code برای تغییر محلی، Copilot برای workflowهای GitHub و Codex برای surfaceهای local/Cloud ممکن است راحتی متفاوتی داشته باشند؛ این تفاوت، مسئولیت review و rollback را حذف نمیکند. نام ابزار، مدل و مقدار token در Definition of Done نیست. معیار باید Diff قابل فهم، test Oracle، زمان تا verdict و outcome کاربر باشد. اگر verifier با محیط یا نسخهٔ متفاوت اجرا کرد، اختلاف را در Pack نگه دار و بهجای پنهان کردن آن، status را `CHANGES_REQUESTED` یا `BLOCKED` کن.
## جمعبندی
Refactor یعنی بهبود ساختار با حفظ Oracle، نه سبزکردن سریع CI. Evidence Pack ادعا را به فرمان و artifact وصل میکند و verifier تازه مانع میشود تولیدکنندهٔ تغییر داور نهایی آن باشد.
## منابع مستقیم
- [Martin Fowler — Refactoring](https://refactoring.com/) — منبع نویسنده برای تغییر ساختار با حفظ رفتار.
- [Martin Fowler — Technical Debt](https://martinfowler.com/bliki/TechnicalDebt.html) — استعاره و محدودیت آن برای maintainability.
- [Playwright — Trace viewer](https://playwright.dev/docs/trace-viewer) — مستند رسمی evidence مرورگر.
- [NIST SSDF](https://csrc.nist.gov/pubs/sp/800/218/final) — چارچوب رسمی فعالیتهای توسعهٔ امن؛ جایگزین review محلی نیست.
- [Anthropic — Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) — گزارش میدانی دربارهٔ ارزیابی و حلقهها، نه معیار قطعی کیفیت.
به زبان ساده
Refactor باید ساختار را بهتر کند بیآنکه Oracle و قرارداد را تغییر دهد. Evidence Pack نیز باید فرمان، revision، محیط، محدودیت و verdict verifier تازه را داشته باشد، نه فقط جملهٔ «تستها سبز بودند».
مثال واقعی
برای fix فرضی double-booking، verifier مستقل وجود constraint، transaction boundary، `409 SLOT_CONFLICT` و شمارش یک audit را از Diff و تست بازسازی میکند.