دیباگ حدسزدن محل خطا نیست؛ فرایند کاهش عدمقطعیت است. ابتدا failure را در شرایط ثبتشده بازتولید میکنیم، سپس از runtime evidence برای ساخت فرضیههای رقیب استفاده میکنیم، آنها را رتبه میدهیم، با instrumentation کمینه جدا میکنیم، کوچکترین fix علتمحور را مینویسیم، regression میگیریم و ابزارگذاری موقت را حذف میکنیم. این نظم جلوی patch زنجیرهای و نسبتدادن هر خطا به آخرین diff را میگیرد.
## اهداف یادگیری
پس از این درس میتوانی failure را reproduce کنی، baseline و محیط را ثبت کنی، hypothesisهای رقیب را با آزمون متمایزکننده رتبهبندی کنی، log/trace امن اضافه کنی، علت را isolate کنی، minimal fix و regression بسازی و instrumentation را با evidence حذف یا رسمی کنی.
## جایگاه در چرخهٔ سرتاسری
در چرخهٔ `Observe → Verify` هستیم. فصل ۵ قرارداد reschedule را ثابت کرده و فصل ۶ از test oracle محافظت میکند. بنابراین bug زیر با این invariants سنجیده میشود: زمان ورودی در timezone کلینیک تفسیر میشود، در UTC ذخیره میشود، appointment و actor در tenant درستاند، idempotency replay همان response durable را میدهد و deadline بسته `409 RESCHEDULE_WINDOW_CLOSED` بدون mutation است. خطای timezone نباید با تغییر policy یا حذف assertion «حل» شود.
## مفهوم عمیق: از علامت تا علت
**Reproduce** یعنی ورودی، revision، zone، ساعت سیستم، runtime، tzdb و دادهٔ حداقلی را ثبت کنیم و failure را دستکم دو بار ببینیم. اگر فقط production رخ میدهد، نمونهٔ privacy-safe از event و correlation id جمع کن؛ ادعای «گاهی» reproduction نیست. **Runtime evidence** از کدخوانی قویتر است: log ساختاریافته، trace span، metric، database state و screenshot/response واقعی میگویند چه اتفاقی افتاده است.
پس از reproduction، یک Debug Record بساز:
Symptom: چه چیزی و برای کدام actor دیده شد؟
Scope: چه zone، revision، tenant و requestی؟
Expected/Actual: contract چه میگوید و runtime چه کرد؟
Evidence: log/trace/state با timestamp و correlation id.
Hypotheses: H1/H2/H3، احتمال، اثر، آزمون متمایزکننده.
Instrumentation: فیلدها، مدت، privacy و محل حذف.
Fix: کوچکترین تغییر علتمحور.
Regression: تست جدید و suiteهای مرتبط.
Exit: instrumentation حذف/مصوب، evidence پیوست.مثال بد
if (start.getHours() === 2) start.setHours(3);این patch «ساعت دو بد است» را hard-code میکند، zone و قانون همان تاریخ را نمیبیند، در منطقههای دیگر یا تغییرات tzdb میشکند و علت را اثبات نمیکند. اگر log میگوید یک ساعت جابهجا شده، تغییر مستقیم ساعت فقط علامت را پنهان میکند.
مثال خوب: hypothesis قبل از fix
برای appointment در `America/New_York` نزدیک transition:
1. H1: سرویس local time را با offset فعلی parse میکند، نه offset تاریخ appointment. آزمون: همان local input را با اجرای نزدیک و دور از transition مقایسه کن. 2. H2: رشتهٔ بدون zone در runtime به timezone ماشین تبدیل میشود. آزمون: همان fixture در `TZ=UTC` و `TZ=America/New_York` اجرا شود؛ اگر نتیجه تغییر کرد، parse غیرصریح محتمل است. 3. H3: repository زمان UTC را دوباره به local تبدیل و سپس دوباره UTC میکند. آزمون: مقدار قبل از write و مقدار queryشده مقایسه شود؛ یک conversion اضافه باید دیده شود.
اینها رتبهبندی ادعای قطعیت نیستند. H2 شاید ارزانترین و متمایزکنندهترین آزمون باشد؛ H1 بیشترین اثر domain را دارد. با نتیجهٔ هر آزمون رتبهها بهروزرسانی میشوند.
## سناریوی booking-platform: timezone و DST
فرض کن بیمار در timezone کلینیک وقت را از `2026-11-01 01:30` به `02:30` جابهجا کرده است؛ تاریخ نمونه باید با نسخهٔ ثبتشدهٔ tzdb در fixture یا config تست اجرا شود، زیرا قوانین zone نسخهپذیرند. قبل از fix، log امن چنین است:
{"event":"reschedule.time.convert","correlationId":"c-482",
"zone":"America/New_York","localStart":"2026-11-01T01:30",
"localEnd":"2026-11-01T02:30","offsetBefore":"-04:00",
"offsetAfter":"-05:00","instant":"2026-11-01T07:30:00Z",
"tzdb":"recorded-version"}در log نباید نام بیمار، raw token، `tenantId` قابلشناسایی یا idempotency key خام بیاید. اگر expected instant بهجای `07:30Z` `06:30Z` است، ابتدا ambiguity زمان تکرارشونده و قرارداد انتخاب fold را از Spec بررسی کن؛ policy تازه نساز. پس از fix، log باید نشان دهد local input با zone همان appointment به instant مورد انتظار تبدیل شد، offset برای همان تاریخ انتخاب شد و repository همان instant را نوشت. «بعد از fix درست شد» بدون این before/after evidence ضعیف است. مقدارهایی مانند `TZDB_VERSION=2026a` در این درس نام fixture/config ثبتشدهاند، نه متغیر استانداردی که runtime الزاماً مصرف کند؛ فقط اگر harness پروژه صریحاً آن را میخواند، بهعنوان config اجرایی به برنامه بده.
## Instrumentation و isolate
Instrumentation باید کوچک، reversible و time-boxed باشد. یک event دقیق در مرز parse، یک event پیش از write و یک assertion روی database state معمولاً از صدها log پراکنده بهتر است. correlation id را از request بگیر، اما PII را redact کن. سطح log را در محیط آزمایشی مشخص کن و retention را تعیین کن. اگر trace sampling است، نمونهٔ failure را حفظ کن؛ اگر log asynchronous است، flush را در fixture صریح کن.
برای isolate، متغیرها را یکییکی ثابت کن: zone، tzdb، runtime، database، clock و concurrency. اول pure conversion را با fixture جدا کن، سپس integration route، و در پایان browser/E2E. تغییر همزمان parser، repository و UI علت را پنهان میکند. اگر failure فقط با دو request رخ میدهد، barrier تست فصل ۵ را به sleep تبدیل نکن؛ overlap واقعی را ایجاد و نتیجهٔ persistent را بسنج.
## حداقل fix، regression و حذف instrumentation
Minimal fix فقط کوچکترین diff نیست؛ کوچکترین تغییری است که hypothesis تأییدشده را اصلاح میکند و contract را نگه میدارد. اگر H2 درست بود، parser ورودی بدون zone را در boundary رد یا صریح میکنیم؛ ساعت را دستی جابهجا نمیکنیم. اگر H3 درست بود، مالکیت conversion را بین service و repository روشن میکنیم؛ هر لایه دوباره تبدیل نمیکند.
Regression باید failure قبلی را دقیقاً بازتولید کند و invariantهای نزدیک را هم بپوشاند: local time پیش و پس از DST، zone بدون DST، زمان غیرمبهم، deadline ۲۴ ساعت، idempotency replay و no mutation در خطای eligibility. ابتدا تست جدید Red را روی revision قدیمی ببین، سپس fix را اجرا کن و targeted integration، contract و full suite مرتبط را بگیر. اگر تست فقط خروجی UTC را میسنجد، database state، audit و version را از قلم نینداز.
در پایان instrumentation موقت را حذف کن و تست کن که behavior بدون آن نیز سبز است. اگر event برای عملیات production ارزش دائمی دارد، آن را با schema، privacy، sampling، owner و alert به telemetry رسمی تبدیل کن؛ «فعلاً بماند» تصمیم معماری نیست. raw input و credential هیچوقت بهعنوان debug evidence قابل قبول نیستند.
## آزمایشگاه گامبهگام
**ورودی:** failure report reschedule، fixture timezone، revision قبل از fix و database محلی ephemeral.
۱. محیط را ثابت و ثبت کن:
TZ=UTC pnpm exec vitest run apps/api/src/modules/appointments/reschedule.timezone.int.test.tsپیش از اجرا، مقدار واقعی fixture/config مانند `tzdbFixtureVersion=2026a` را در Evidence ثبت کن؛ `TZ=UTC` متغیر استاندارد محیطی برای timezone فرایند است، اما `tzdbFixtureVersion` فقط metadata است مگر اینکه test harness پروژه آن را صریحاً مصرف کند. **Expected:** failure با correlation id، expected/actual instant و baseline revision دیده شود. ۲. همان fixture را دو بار و سپس با `TZ=America/New_York` اجرا کن. ۳. Debug Record و H1 تا H3 را بنویس؛ برای هر کدام یک آزمون متمایزکننده اضافه کن. ۴. instrumentation privacy-safe را در parse و write فعال کن و log قبل/بعد را ذخیره کن. ۵. H تأییدشده را isolate و minimal fix کن؛ contract فصل ۵، status و audit را تغییر نده. ۶. regressionهای transition و non-DST، replay و no-mutation را اجرا کن.
**خروجی مورد انتظار:** قبل از fix، failure قابلتکرار و log تبدیل ثبت شده؛ بعد از fix، instant درست، یک mutation/audit معتبر، و suite مرتبط سبز است. **Recovery:** اگر failure ناپایدار شد، fixture و clock را deterministic کن؛ اگر zone policy مبهم بود `DECIDE` به owner بده؛ اگر instrumentation PII چاپ کرد، فوراً log را متوقف و داده را پاکسازی کن؛ اگر fix بیش از parser/service boundary را تغییر داد، به Plan برگرد.
## خطاهای رایج و نشانهها
- patch پیش از reproduction؛ فقط آخرین کد تغییرکرده متهم است.
- فرضیهٔ واحد؛ هیچ آزمون متمایزکنندهای وجود ندارد.
- log متن آزاد و پر از PII؛ correlation و schema ندارد.
- instrumentation بیانتها؛ owner، مدت و حذف ندارد.
- تغییر timeout برای حذف symptom.
- تست timezone فقط روی ماشین توسعهدهنده.
- fix بزرگ همزمان در parser، repository و UI.
- regression فقط unit؛ state، audit و contract بررسی نمیشوند.
## Verification
- [ ] failure روی revision و محیط ثبتشده reproducible است.
- [ ] expected/actual و evidence قبل از fix پیوست شدهاند.
- [ ] دستکم سه hypothesis رتبهدار و آزمون متمایزکننده دارند.
- [ ] log قبل/بعد timezone شامل zone، offset، instant و نسخهٔ tzdb است و PII ندارد.
- [ ] minimal fix علت را اصلاح میکند و قرارداد فصل ۵ را حفظ میکند.
- [ ] regression transition، non-DST، replay و no-mutation اجرا شده است.
- [ ] instrumentation حذف یا با schema و owner رسمی شده است.
## تمرین مستقل
گزارش دهید که در `Europe/Berlin` یک reschedule یک ساعت جلو افتاده، اما فقط در یک container رخ میدهد. Debug Record کامل و ترتیب آزمایشها را بنویس.
**پاسخ نمونه و Rubric:** پاسخ باید image/runtime، `TZ`، tzdb، parser رشتهٔ بدون zone و double conversion را از هم جدا کند؛ ابتدا reproduction با fixture ثابت، سپس Hهای رتبهدار و اجرای همان request در container و local را بخواهد. log باید zone/offset/instant/correlation را بدون PII ثبت کند. fix باید به conversion boundary محدود و regression برای transition/non-transition باشد. حذف instrumentation، `git diff` و full targeted suite لازماند؛ «تغییر ساعت +۱» بدون علت امتیاز ندارد.
## جمعبندی
Debug سیستماتیک یک حلقهٔ شواهد است: reproduce، runtime evidence، hypothesisهای رتبهدار، instrumentation کمینه، isolate، minimal fix، regression و cleanup. در DST، local time بهتنهایی معنا ندارد؛ zone، offset، rule version و instant باید کنار هم دیده شوند. هدف، patch سریع نیست؛ توضیحی است که آزمون مستقل بتواند دوباره آن را بررسی کند.
## منابع مستقیم
- [Google SRE — Effective Troubleshooting](https://sre.google/sre-book/effective-troubleshooting/) — چارچوب شواهد و فرضیه؛ هر policy booking را تعیین نمیکند.
- [OpenTelemetry Documentation](https://opentelemetry.io/docs/) — trace و instrumentation ساختاریافته.
- [MDN — Temporal concepts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) — مفاهیم زمان و zone؛ API قابل استفاده باید با runtime پروژه بررسی شود.
- [IANA Time Zone Database](https://www.iana.org/time-zones) — منبع قواعد zone و ضرورت ثبت نسخه.
- [Playwright debugging](https://playwright.dev/docs/debug) — شواهد اجرای browser؛ جایگزین evidence سرور نیست.
به زبان ساده
دیباگ سیستماتیک از reproduce و ثبت محیط شروع میشود، سپس hypothesisهای رقیب را با آزمونهای متمایزکننده بررسی میکند. Instrumentation باید کمینه، privacy-safe و موقت باشد و پس از fix حذف یا رسمی شود.
مثال واقعی
در بررسی فرضی DST، اجرای همان fixture با `TZ=UTC` و `TZ=America/New_York` میتواند نشان دهد parser به timezone ماشین وابسته است؛ این مشاهده از hard-code کردن ساعت دو قابلاعتمادتر است.