سیستم لگسی معمولاً به این دلیل ترسناک نیست که «قدیمی» است؛ ترس از جایی می‌آید که تیم نمی‌داند با تغییر یک شرط، کدام قرارداد پنهان، گزارش مالی یا فرآیند عملیاتی می‌شکند. در چنین وضعی، بازنویسی عجولانه فقط ابهام را از کد قدیمی به کد جدید منتقل می‌کند. راه امن‌تر این است که پیش از هر تغییر، رفتار قابل اتکا را ثبت کنیم، درز یا Seam مناسبی بسازیم، شواهد را در چند مرز جمع کنیم و برای هر موج مهاجرت، معیار روشن Cutover و بازگشت داشته باشیم.

این راهنما یک نسخهٔ اجرایی از تست سیستم‌های لگسی ارائه می‌کند: از کشف رفتار و Characterization Testing تا Golden Master، تست قرارداد، Shadow/Dual Run، اعتبارسنجی داده و خروج امن از سیستم قدیمی. هدف، رسیدن به عدد دلخواه پوشش نیست؛ هدف این است که یک تصمیم تغییر یا مهاجرت با شواهد کافی، محدودیت‌های معلوم و راه برگشت قابل تمرین گرفته شود.

سیستم لگسی دقیقاً چیست؟ سن مهم نیست، هزینهٔ تغییر مهم است

نرم‌افزاری که ده سال عمر دارد اما Build تکرارپذیر، قراردادهای روشن، تست‌های قابل اعتماد و مالک مشخص دارد، الزاماً مسئلهٔ لگسی نیست. در مقابل، سرویس دو ساله‌ای که فقط روی لپ‌تاپ یک نفر Build می‌شود، به دیتابیس مشترک می‌نویسد و تغییر آن قابل پیش‌بینی نیست، عملاً لگسی است. در این مقاله، «لگسی» یعنی سیستمی که هزینه و عدم‌قطعیت تغییر آن نسبت به ارزش تغییر بالاست.

نشانه‌های رایج عبارت‌اند از:

  • Build یا استقرار غیرتکرارپذیر و وابسته به ابزار، سیستم‌عامل یا لایسنس قدیمی؛
  • وابستگی‌های پنهان به جدول، فایل، صف، ساعت سیستم، سخت‌افزار یا Job زمان‌بندی‌شده؛
  • مستندات منسوخ و دانش انباشته در ذهن چند کاربر یا اپراتور؛
  • Oracle ضعیف؛ یعنی تیم ورودی را می‌داند اما خروجی درست یا اثر جانبی مجاز را نه؛
  • تغییرات کوچک با Blast Radius نامعلوم، Rollback دشوار یا Reconciliation دستی؛
  • تست‌هایی که سبز می‌شوند اما اجرا نشده‌اند، Exception را می‌بلعند یا فقط HTTP ۲۰۰ را می‌سنجند.

بنابراین نسخهٔ فنی، زبان برنامه‌نویسی یا معماری Monolith به‌تنهایی تشخیص نیست. نخست باید تصمیم کسب‌وکار، ریسک عملیاتی و قابلیت ایجاد Evidence را دید.

قبل از نوشتن تست، تصمیم تغییر را نام‌گذاری کنید

تست بدون تصمیم هدف به انبار Assertion تبدیل می‌شود. یک «کارت تغییر لگسی» کوتاه بسازید:

Change ID: LEG-PAY-017
Business outcome: کاهش خطای محاسبه تخفیف و امکان انتشار هفتگی
Scope: محاسبه فاکتور؛ نه تسویه PSP و نه دفترکل
Decision: Refactor | Replace | Retire | Retain
Critical invariants: مبلغ منفی نشود؛ هر سفارش حداکثر یک بدهی قطعی
Known consumers: Checkout UI, invoice PDF, accounting export
Unknowns: Job شبانه و گزارش شعبه
Evidence owner: تیم پرداخت
Rollback owner: مدیر عملیات انتشار
Evidence expiry: پس از تغییر schema یا نرخ/قاعده تخفیف

چهار تصمیم را با هم قاطی نکنید:

  • Retain: سیستم فعلاً می‌ماند؛ تست باید ریسک عملیات و تغییرات محدود را کنترل کند.
  • Refactor: ساختار داخلی عوض می‌شود و قرارداد مطلوب باید ثابت بماند.
  • Replace: پیاده‌سازی تازه جایگزین می‌شود؛ برابری فقط برای رفتارهای مورد تأیید لازم است.
  • Retire: قابلیت حذف می‌شود؛ Evidence باید نبود مصرف‌کننده، بایگانی داده و خاموشی امن را ثابت کند.

اگر هدف و محدوده مبهم است، ابتدا از چارچوب تست مبتنی بر ریسک برای اولویت‌بندی پیامدها استفاده کنید. پوشاندن همهٔ خطوط کد، جای انتخاب روشن ریسک را نمی‌گیرد.

Baseline رفتاری، Spec و رفتار مطلوب یک چیز نیستند

در سیستم قدیمی دست‌کم سه منبع حقیقت وجود دارد:

  1. Observed behavior: آنچه نسخهٔ فعلی واقعاً انجام می‌دهد؛
  2. Intended behavior: آنچه قانون، قرارداد، مستند یا مالک کسب‌وکار انتظار دارد؛
  3. Desired behavior: آنچه پس از تغییر باید انجام شود.

تست Characterization رفتار مشاهده‌شده را ثبت می‌کند؛ این رفتار ممکن است شامل نقص باشد. Pass شدن آن فقط می‌گوید رفتار نسبت به Baseline عوض نشده، نه اینکه رفتار درست، امن یا مطلوب است. هر تفاوت را به یکی از این وضعیت‌ها ببرید:

  • Preserve: قرارداد معتبر یا وابستگی واقعی مصرف‌کننده است؛
  • Fix: نقص تأییدشده است و تغییر آن باید با Acceptance جدید همراه شود؛
  • Deprecate: موقتاً برای سازگاری نگه داشته و زمان حذف آن مشخص می‌شود؛
  • Unknown: هنوز مالک و شواهد کافی ندارد؛ اجازهٔ Cutover نمی‌دهد.

Characterization Testing چیست و Golden Master چه تفاوتی دارد؟

Characterization Test آزمونی است که رفتار فعلی یک جزء را با ورودی، محیط و مشاهده‌گر معلوم توصیف می‌کند. Golden Master فقط یکی از روش‌های مقایسه است که خروجی مرجع را ذخیره می‌کند. هر Characterization Test الزاماً Snapshot بزرگ نیست و هر Snapshot نیز آزمون معناداری نیست.

سه شکل مفید Characterization

  • Focused pinning: چند ویژگی کسب‌وکاری مشخص مانند مبلغ، وضعیت و اثر دفترکل را قفل می‌کند؛
  • Approval/Golden Master: خروجی بزرگ مانند فایل، گزارش یا پیام را پس از بازبینی انسانی ذخیره می‌کند؛
  • Differential characterization: دو نسخه را روی ورودی یکسان اجرا و خروجی Canonical آن‌ها را مقایسه می‌کند.

چه زمانی Golden Master مناسب نیست؟

وقتی خروجی پر از زمان، شناسهٔ تصادفی یا ترتیب بی‌اهمیت است؛ وقتی تغییر کوچک صدها خط Diff غیرقابل‌فهم می‌سازد؛ وقتی Snapshot شامل دادهٔ شخصی یا Secret است؛ یا وقتی تیم بدون فهم تغییر فقط دکمهٔ Update را می‌زند. در این حالت، Oracle متمرکز، Contract test یا Invariant بهتر است.

پروتکل ساخت Baseline قابل اعتماد

  1. نسخهٔ کد، Build، وابستگی، Schema، داده، Locale، Timezone و Flagها را ثبت کنید.
  2. Fixtureهای نماینده را از مسیرهای حیاتی، مرزها، خطاها و رخدادهای تولید بسازید؛ دادهٔ تولید را بی‌محابا کپی نکنید.
  3. یک ورودی را دست‌کم دو بار در محیط ثابت اجرا کنید تا ناپایداری Baseline دیده شود.
  4. فقط فیلدهای واقعاً غیرقطعی را با دلیل، مالک و تست جداگانه Normalize کنید.
  5. اثرهای جانبی—DB، صف، فایل، ایمیل، پیامک، PSP و Audit log—را نیز مشاهده کنید.
  6. خروجی مرجع را انسان و مالک دامنه بازبینی کنند؛ «از Production آمده» معادل «درست است» نیست.
  7. Baseline را با شناسهٔ نسخه و تاریخ انقضا نگه دارید و تغییر آن را مانند کد Review کنید.

قرارداد Baseline پیشنهادی

baseline_id: invoice-v3-2026-08
subject_version: legacy-7.4.2+build.981
input_fixture: iran-checkout-risk-set-v2
environment_manifest: env-legacy-pay-12
observers: [response, ledger, outbox, audit]
normalizers:
  - field: trace_id
    reason: correlation-only; format tested separately
  - field: generated_at
    reason: clock-owned; valid range asserted separately
preserved_fields: [status, payable_rial, discount_rial]
reviewers: [payment-owner, qa-lead]
expiry_triggers: [discount-rule, schema, currency-contract]
pii_policy: synthetic-only

Normalization؛ چگونه Flake را حذف کنیم بدون اینکه Oracle کور شود؟

قاعدهٔ امن این است: هر فیلدی که از مقایسه حذف می‌شود باید یک دلیل و یک Oracle جایگزین داشته باشد. مثلاً مقدار دقیق Timestamp ممکن است حذف شود، اما Format، محدودهٔ زمانی و ترتیب رخدادها همچنان باید سنجیده شود. اگر کل بخش مبلغ حذف شود چون «گاهی فرق دارد»، شما Flake را درمان نکرده‌اید؛ یک خطای مالی را نامرئی کرده‌اید.

const stable = ({ trace_id, generated_at, ...business }) => business;

const same = (legacy, candidate) =>
  JSON.stringify(stable(legacy)) === JSON.stringify(stable(candidate));

پیش از پذیرش Normalizer، چهار سؤال بپرسید: آیا فیلد برای تصمیم کسب‌وکار مهم است؟ آیا مصرف‌کننده به آن وابسته است؟ آیا حذف آن امکان False Pass می‌سازد؟ کدام تست مستقل هنوز قرارداد آن را می‌سنجد؟

آزمایش تکرارپذیر: Snapshot خام، Normalization امن و False Pass

برای این مقاله یک Harness کوچک با Node.js ساختیم. شش فاکتور ساختگی، شامل مبلغ صفر، مشتری VIP و مرز گردکردن، به نسخهٔ لگسی و Candidate داده شدند. نسخهٔ قدیمی تخفیف ۱۰٪ را Round و نسخهٔ جدید عمداً Floor می‌کرد. هر دو نسخه همچنین `trace_id` و زمان متفاوت تولید می‌کردند.

node=v16.14.2
fixtures=6
raw_legacy_repeat_mismatches=6/6
safe_normalized_repeat_mismatches=0/6
safe_differential_mismatches=1/6
over_normalized_differential_mismatches=0/6

مقایسهٔ خام حتی بین دو اجرای همان نسخه در ۶ از ۶ مورد شکست خورد؛ پس Baseline ناپایدار بود. پس از حذف فقط زمان و شناسهٔ رهگیری، اختلاف اجرای تکراری به صفر رسید. مقایسهٔ امن نسخه‌ها، خطای واقعی را در سفارش `ORD-۰۰۳` پیدا کرد:

legacy:    payable_rial=94, discount_rial=11
candidate: payable_rial=95, discount_rial=10

اما Normalizer افراطی که فقط `order_id` و `status` را نگه داشت، همان نقص را پنهان کرد و صفر اختلاف گزارش داد. این آزمایش ساختگی و کوچک، Benchmark محصول یا اثبات صحت نیست؛ فقط نشان می‌دهد «Snapshot بیشتر» الزاماً Evidence بهتر نیست و سیاست Canonicalization بخشی از Oracle است.

Seam؛ کم‌خطرترین نقطه برای مشاهده، کنترل و جایگزینی

مایکل فیدرز Seam را نقطه‌ای می‌داند که بتوان رفتار را بدون ویرایش همان نقطه تغییر داد. شرح عملی Legacy Seam در Martin Fowler سه کاربرد را برجسته می‌کند: شکستن وابستگی برای تست، افزودن Probe و هدایت جریان به ماژول جایگزین.

انواع Seam قابل استفاده

  • API/HTTP: Proxy، Adapter یا Stub جلوی سرویس؛
  • Database: Repository، View، CDC یا خوانندهٔ فقط‌خواندنی؛
  • Message: Topic/Queue و Consumer آزمایشی با Correlation ID؛
  • File/Batch: ورودی/خروجی نسخه‌دار و Sandbox مسیر فایل؛
  • Clock/Random/Config: تزریق زمان، Seed و Flag؛
  • UI: مسیر کاربری محدود، وقتی هیچ مرز پایین‌تری در دسترس نیست.

امتیازدهی برای انتخاب Seam

هر نامزد را از ۱ تا ۵ بر اساس مشاهده‌پذیری، کنترل‌پذیری، Fidelity، Blast Radius، هزینه و برگشت‌پذیری امتیاز دهید. Seam خوب فقط «آسان برای Mock» نیست؛ باید همان Failure mechanism مهم را حفظ کند. اگر خطر در Transaction دیتابیس است، Mock کردن Repository ممکن است Evidence لازم را حذف کند.

Test Harness برای سیستم قدیمی چه قراردادی دارد؟

Harness مجموعه‌ای از Runner، Adapter، Fixture، Double، Observer و Cleanup است که Subject را تکرارپذیر اجرا می‌کند. قرارداد آن باید صریح باشد:

  • نسخه و روش راه‌اندازی Subject؛
  • ورودی Canonical و مالک Fixture؛
  • وابستگی واقعی، Stub، Fake یا Simulator و محدودیت Fidelity هرکدام؛
  • Clock، Randomness، Concurrency و Locale کنترل‌شده؛
  • Oracle پاسخ، State، Side effect، Absence و زمان؛
  • Timeout، Retry و معنای نتیجه‌های Failed، Blocked، Invalid و Inconclusive؛
  • Isolation، Cleanup و اثبات اینکه اجرای قبلی بر اجرای بعدی اثر ندارد؛
  • Artifactهای تشخیصی بدون PII و Secret.

برای دادهٔ ساختگی، Subsetting و سیاست نگهداری به راهنمای مدیریت داده تست و برای Drift، نسخهٔ وابستگی و Parity به مدیریت محیط تست رجوع کنید.

سبد تست سیستم لگسی؛ از Probe تا E2E

یک Pyramid ثابت برای همهٔ سیستم‌های قدیمی وجود ندارد. ترتیب مناسب بر اساس Seam در دسترس، پیامد شکست و نوع تغییر تعیین می‌شود.

لایه Evidence پرسش نمونه محدودیت
Probe/Telemetry سیستم اکنون چه می‌کند؟ فراوانی مسیر، Error، Query، اثر جانبی مشاهده به‌تنهایی Expected را تعیین نمی‌کند
Focused Characterization رفتار انتخابی ثابت مانده؟ مبلغ، وضعیت، انتقال State ممکن است نقص موجود را حفظ کند
Component منطق پشت Seam درست است؟ Parser، محاسبه، Adapter Fidelity وابستگی محدود است
Contract مصرف‌کننده و Provider هم‌فهم‌اند؟ HTTP/message schema و مثال جریان کامل و State مشترک را ثابت نمی‌کند
Integration مرزهای واقعی با هم کار می‌کنند؟ DB، صف، PSP sandbox کندتر و محیط‌حساس‌تر است
E2E/Business flow مسیر حیاتی از دید کاربر کامل است؟ خرید تا فاکتور و Reconciliation تشخیص علت ضعیف و نگهداشت گران
Shadow/Production نسخهٔ جدید زیر شکل ترافیک واقعی چه می‌کند؟ مقایسهٔ Canonical بدون اثر حریم خصوصی و Side effect باید مهار شود

طراحی مرزها و سهم هر لایه را با راهنمای عملی هرم تست تطبیق دهید. قانون مفید این است: کم‌هزینه‌ترین مرزی را انتخاب کنید که Failure mechanism و Oracle موردنیاز تصمیم را از بین نبرد.

تست قرارداد؛ سپر مصرف‌کنندگان پنهان

در سیستم قدیمی، API فقط Endpoint رسمی نیست. فایل CSV، ترتیب ستون، Stored Procedure، پیام صف، جدول خوانده‌شده توسط گزارش و حتی متن خطا می‌تواند قرارداد Published باشد. ابتدا Consumer inventory بسازید: مالک، نسخه، فیلدهای مصرفی، حساسیت به ترتیب/Null/Encoding و زمان مهاجرت.

در مستندات Pact، Contract test فهم مشترک پیام میان Consumer و Provider را با مثال‌های اجرایی می‌سنجد. این روش برای بخشی که واقعاً مصرف می‌شود مفید است، اما جای تست Integration، اثرهای جانبی و مهاجرت داده را نمی‌گیرد. برای طراحی کامل‌تر تعامل‌های واقعی، مقالهٔ تست یکپارچه‌سازی را ببینید.

Contract inventory حداقلی

consumer | interface | used fields | null/encoding | timing | owner | migration state
checkout | invoice API | id,status,payable_rial | JSON/UTF-8 | <800ms | pay-team | verified
finance  | nightly CSV | 1,3,7,9 | Windows-1256 | 02:00 | finance | unknown
branch   | shared table | status,total | DB collation | hourly | unknown | blocker

تست Oracle در Legacy؛ پاسخ، State و نبود اثر نامجاز

مقایسهٔ Response کافی نیست. برای هر سناریوی پرخطر، Oracleهای مستقل را لایه‌بندی کنید:

  • Response: Status، Payload، خطا و Header؛
  • State: رکورد، State transition و Version؛
  • Side effect: Ledger، Outbox، صف، فایل یا اعلان؛
  • Absence: عدم برداشت دوم، عدم نشت Tenant و عدم ارسال پیام واقعی؛
  • Temporal: Deadline، ترتیب رخداد و Eventually-consistent window؛
  • Reconciliation: جمع مبالغ، تعداد و شناسه‌های منبع/مقصد.

اگر Expected از همان کد یا همان Query پیاده‌سازی تولید شود، هر دو ممکن است یک خطا را تکرار کنند. برای Source hierarchy، استقلال Expected و وضعیت Verdict، راهنمای اوراکل تست را به کار ببرید.

آزمون‌های غیرعملکردی را به پایان پروژه موکول نکنید

نسخهٔ جدید ممکن است در پاسخ‌های نمونه برابر باشد اما زیر بار، خطای وابستگی یا دادهٔ واقعی شکست بخورد. بر اساس ریسک، این Evidenceها را اضافه کنید:

  • Performance baseline با شکل بار، Warm-up، Percentile، Query count و ظرفیت منبع؛
  • Timeout-after-commit، Retry، Idempotency، Failover و Backlog drain؛
  • Authorization، Secret، Dependency آسیب‌پذیر و Sanitization؛
  • Backup/restore، Reconciliation و سنجش RPO/RTO در محدودهٔ تعریف‌شده؛
  • سازگاری Browser/OS/Encoding/Printer یا سخت‌افزار قدیمی در صورت نیاز واقعی.

برای طراحی Failure mode، Recovery timeline و Invariantهای داده از راهنمای تست تاب‌آوری استفاده کنید.

از Characterization تا مدرن‌سازی؛ کدام الگو را انتخاب کنیم؟

Strangler Fig برای مرز قابل رهگیری

وقتی درخواست‌ها در لبهٔ Monolith قابل Intercept هستند، Proxy می‌تواند بخشی از ترافیک را به سیستم قدیمی و بخشی را به قابلیت جدید ببرد. شرح به‌روز Martin Fowler از Strangler Fig بر نتیجهٔ مطلوب، شکستن مسئله به قطعات کوچک و ارزش معماری گذار تأکید دارد. راهنمای AWS Strangler Fig نیز Proxy و Anti-Corruption Layer را توضیح می‌دهد.

تست موردنیاز: Contract در دو طرف، Routing rule، Header/Auth propagation، Timeout، Rollback مسیر، دادهٔ مشترک و تفاوت Canonical خروجی. Proxy نباید به Single Point of Failure نامرئی تبدیل شود.

Branch by Abstraction برای جزء عمیق داخل Monolith

اگر فراخوانی در لبه قابل رهگیری نیست، ابتدا یک Abstraction پایدار میان Clientها و پیاده‌سازی قدیمی بسازید؛ سپس نسخهٔ جدید را پشت همان Interface اضافه کنید. راهنمای رسمی AWS برای Branch by Abstraction امکان هم‌زیستی و برگشت را توضیح می‌دهد، اما هشدار می‌دهد این الگو برای مسئلهٔ پیچیدهٔ سازگاری داده به‌تنهایی کافی نیست.

Parallel Change برای قرارداد ناسازگار

برای تغییر Interface در سه گام پیش بروید: Expand؛ قرارداد جدید را بدون شکستن قدیم اضافه کنید، Migrate؛ مصرف‌کنندگان را منتقل و مشاهده کنید، Contract؛ مسیر قدیمی را پس از اثبات نبود مصرف حذف کنید. این همان الگوی Parallel Change یا Expand–Migrate–Contract است.

Shadow Traffic، Dual Run و Canary را یکی نگیرید

  • Shadow: ورودی واقعی Sanitized به Candidate نیز Replay می‌شود، اما پاسخ Candidate به کاربر برنمی‌گردد و اثر جانبی آن باید مسدود یا به Sink مصنوعی هدایت شود.
  • Dual Run: دو پیاده‌سازی اجرا و نتیجه‌ها مقایسه می‌شوند؛ Source of Truth و سیاست اختلاف باید معلوم باشد.
  • Canary: درصد کوچکی از ترافیک واقعی پاسخ و اثر Candidate را دریافت می‌کند؛ Rollback و Guardrail فوری لازم است.

Shadow برای Email، پیامک، پرداخت، حذف و Write غیرقابل‌بازگشت ذاتاً امن نیست. فقط «پاسخ را نادیده گرفتن» اثر جانبی را خنثی نمی‌کند. Egress، Credential و Destination باید ایزوله شوند و Correlation ID امکان مقایسهٔ دو مسیر را بدهد.

Differential Testing؛ اختلاف را طبقه‌بندی کنید، فقط نشمارید

نرخ Match بالا می‌تواند فریبنده باشد. اختلاف‌ها را به کلاس‌های زیر ببرید:

  • Defect در Candidate؛
  • Defect موجود در Legacy که قرار است اصلاح شود؛
  • تفاوت مجاز و مستند؛
  • Fixture یا Replay نامعتبر؛
  • Normalization یا Comparator معیوب؛
  • اختلاف زمان‌بندی/Eventually consistent که هنوز در Window معتبر است؛
  • Unknown با مالک و Deadline بررسی.

برای هر اختلاف Artifactی شامل Input hash، نسخهٔ دو سیستم، Environment، خروجی قبل/بعد از Canonicalization، اثرهای جانبی و تصمیم Reviewer نگه دارید. «۹۹٫۹٪ Match» اگر یک اختلاف باقیمانده برداشت تکراری باشد، Gate مناسبی نیست.

مهاجرت داده؛ Row count کافی نیست

تست داده باید قبل، حین و پس از Cutover انجام شود:

  1. Schema/type/collation/timezone/encoding mapping را نسخه‌دار کنید.
  2. Count و Null distribution را بسنجید، اما به آن‌ها محدود نشوید.
  3. کلیدهای موجود/گمشده/اضافی و Row-level hash را در Snapshot سازگار مقایسه کنید.
  4. قواعد Transformation و Round-trip نمونه‌های مرزی را تست کنید.
  5. Aggregateهای کسب‌وکاری مانند جمع بدهکار/بستانکار و تعداد وضعیت‌ها را مستقل بسنجید.
  6. برای CDC، Lag، ترتیب، Duplicate، Delete و رخدادهای در حال تغییر را بررسی کنید.
  7. مغایرت را Repair و سپس دوباره Validate کنید؛ Repair بدون Revalidation Evidence نیست.

AWS DMS Data Validation مقایسهٔ ردیف‌های Source و Target، گزارش رکورد گمشده/متفاوت و هزینهٔ منابع Validation را مستند می‌کند. ابزار خاص مهم نیست؛ اصل مهم Snapshot سازگار، کلید معتبر، محدودیت معلوم و Reconciliation قابل ممیزی است.

هشدار دربارهٔ Dual Write

نوشتن هم‌زمان در دو مقصد، Atomicity خودکار نمی‌سازد. Timeout بین دو Write، Retry، ترتیب رویداد، Partial success و Failback می‌تواند Divergence بسازد. Source of Truth، Idempotency key، Outbox/CDC، صف Repair، Lag budget و نحوهٔ Reconciliation را پیش از فعال‌سازی تعیین کنید.

سناریوی کامل ایرانی: مهاجرت محاسبه فاکتور و پرداخت

فرض کنید یک Marketplace ایرانی، محاسبهٔ فاکتور COBOL/Java قدیمی را به سرویس جدید منتقل می‌کند. قرارداد Evidence فقط «صفحه باز شد» نیست:

  • مبلغ Canonical در IRR ذخیره و تبدیل نمایشی تومان صریح باشد؛
  • اعداد فارسی، عربی و لاتین با سیاست روشن پذیرفته یا رد شوند؛
  • `ی/ی`، `ک/ک`، نیم‌فاصله، RTL و Encoding فایل مالی پوشش داشته باشد؛
  • تخفیف، مالیات و کارمزد در صفر، یک ریال، مرز سقف و Round تست شوند؛
  • Callback تکراری PSP و Timeout-after-commit فقط یک اثر قطعی بسازند؛
  • UTC، `Asia/Tehran`، مرز روز/ماه و تاریخ نمایشی جلالی از هم تفکیک شوند؛
  • Ledger، Outbox، وضعیت سفارش و Reconciliation جمع مبالغ با هم سازگار باشند؛
  • کد ملی، PAN، OTP، Token و دادهٔ مشتری وارد Snapshot، Log یا Diff نشوند.

Evidence موج اول

موج اول فقط محاسبهٔ بدون اثر جانبی را جدا می‌کند. ۲۵۰ Fixture تأییدشده، Contract مصرف‌کنندگان، مرزهای گردکردن و سه ماه نمونهٔ Sanitized در Shadow اجرا می‌شوند. اختلاف بحرانی مبلغ یا State برابر با Stop است؛ Aggregate Match اجازه نمی‌دهد آن را در میان اختلاف‌های کم‌اهمیت پنهان کنیم.

Evidence موج دوم

در موج دوم، ۱٪ ترافیک داخلی Canary می‌شود. Guardrailها شامل Duplicate charge، اختلاف Ledger، نرخ خطا، P95 latency و Queue lag هستند. Feature flag فقط وقتی Rollback واقعی است که Schema و Writeهای جدید با مسیر قدیمی سازگار باشند و Failback قبلاً تمرین شده باشد.

Cutover Contract؛ چه شواهدی اجازهٔ جابه‌جایی می‌دهد؟

scope: invoice-calculation-v1
candidate_version: calc-2.8.1
required evidence:
  - zero unresolved critical contract mismatches
  - zero money/state mismatches in approved risk fixtures
  - data reconciliation within field-specific rules
  - canary SLO and dependency guardrails healthy
  - rollback drill passed on release candidate
stop conditions:
  - duplicate financial effect > 0
  - unknown consumer discovered
  - reconciliation lag above 5 minutes
decision rights:
  recommend: QA lead
  release: product + operations owner
  stop/rollback: on-call incident commander
observation_window: 24h plus nightly settlement
legacy_decommission: prohibited until retention window ends

Thresholdها باید بر اساس ریسک و سیستم شما تعیین شوند؛ اعداد بالا نمونه‌اند، استاندارد عمومی نیستند. مهم‌تر از عدد، Denominator، Window، وضعیت Unknown و اختیار Stop است.

Rollback با Failback و Recovery یکسان نیست

Rollback کد ممکن است ساده باشد، اما داده‌ای که Candidate نوشته، پیام‌هایی که منتشر شده یا Schemaای که Contract شده را برنمی‌گرداند. برنامهٔ بازگشت باید این موارد را پوشش دهد:

  • Route/Flag و زمان لازم برای بازگرداندن ترافیک؛
  • سازگاری رو‌به‌عقب Schema و API؛
  • مالکیت Writeها و روش Merge یا Reverse migration؛
  • پیام‌های In-flight، Duplicate و Side effect غیرقابل‌لغو؛
  • Reconciliation پس از بازگشت و معیار پایان Incident؛
  • Retention نسخهٔ قدیمی، Artifact و دسترسی عملیاتی.

مشاهده‌پذیری قبل از تغییر؛ Probe جای Spec را نمی‌گیرد

اگر نمی‌دانید چه کسی یک Endpoint یا جدول را مصرف می‌کند، ابتدا Probe کم‌خطر اضافه کنید: Correlation ID، شمارش فراخوانی، Trace مرزها، Query audit و Log ساختاریافتهٔ بدون PII. OpenTelemetry سیگنال‌های Trace، Metric و Log را برای دیدن فعالیت سیستم توضیح می‌دهد.

اما «سه ماه مصرف ندیدیم» تنها وقتی Evidence است که Coverage مشاهده، Sampling، مسیرهای Batch/فصلی و کیفیت Instrumentation معلوم باشد. نبود Signal ممکن است نبود مصرف‌کننده یا خرابی Probe باشد.

CI/CD برای Legacy؛ سریع‌ترین تست‌ها را در PR، واقعی‌ترین را در موج مناسب

Lane Evidence بودجه Gate
PR Build، lint، focused characterization، component، contract دقیقه تغییر Critical و Contract ناشناخته ممنوع
Merge integration، migration transform، selected regression ده‌ها دقیقه Artifact و Environment معتبر
Nightly Golden Master بزرگ، E2E، data reconciliation، performance sample ساعت Trend و Triage مالک‌دار
Pre-cutover full reconciliation، shadow/differential، rollback drill موج انتشار Cutover Contract
Canary/Post business invariant، SLO، side effect، backlog drain Window عملیاتی Stop/rollback خودکار یا انسانی روشن

برای Feedback budget، انتخاب تست و نگهداشت Suite بزرگ از راهنمای بهینه‌سازی تست رگرسیون استفاده کنید. Quarantine نباید Fail بحرانی را به سبز تبدیل کند؛ نتیجهٔ Flaky یا Invalid باید جدا و مالک‌دار بماند.

متریک‌هایی که واقعاً روند کاهش ریسک را نشان می‌دهند

  • درصد ریسک‌های Critical با Evidence معتبر و تازه؛
  • تعداد Consumerهای Unknown و سن هر Unknown؛
  • نرخ اختلاف Differential به تفکیک Critical/Allowed/Invalid/Unknown؛
  • First-attempt pass، Flake و Invalid rate؛
  • زمان Triage اختلاف و درصد اختلاف‌های بدون مالک؛
  • تعداد Baseline update به تفکیک Reviewed/Blind update؛
  • Migration mismatch، Repair و Revalidation rate؛
  • زمان Detect، Stop، Rollback/Failback و Reconciliation؛
  • درصد ترافیک/مصرف‌کننده منتقل‌شده همراه با Evidence؛
  • هزینه نگهداشت Harness و زمان Feedback.

Code Coverage فقط Reachability را می‌سنجد و برای تصمیم مدرن‌سازی کافی نیست. تعریف Denominator، Evidence state و Ratchet کد قدیمی در مقالهٔ پوشش تست نرم‌افزار آمده است.

خطاهای متناوب را با Retry پنهان نکنید

سیستم قدیمی ممکن است به Clock، ترتیب داده، Lock، ظرفیت یا Job مشترک حساس باشد. Retry کور نرخ Pass را زیبا می‌کند اما Evidence اجرای اول را از بین می‌برد. Seed، زمان، Thread/Process، داده، Dependency version و Trace را ثبت کنید، نتیجهٔ اجرای اول را نگه دارید و برای هر باگ متناوب بستهٔ شواهد بازتولیدپذیر بسازید.

امنیت، حریم خصوصی و مجوز اجرای تست

  • Shadow و Replay فقط با مجوز، دادهٔ حداقلی و مقصدهای مسدودشده اجرا شوند.
  • Snapshot و Artifact نباید PAN، CVV، OTP، Token، کد ملی یا Secret داشته باشند.
  • حساب‌های تست کمترین دسترسی را داشته و عملیات حذف/پرداخت در Sandbox مهار شود.
  • کتابخانه و Runtime قدیمی را نمی‌توان فقط با تست رفتاری «ایمن» اعلام کرد؛ Inventory و Patch/Compensating control لازم است.
  • تغییر Authorization باید با Cross-tenant، IDOR، Role و Audit evidence مستقل سنجیده شود.

۱۵ ضدالگوی رایج در تست سیستم‌های قدیمی

  1. بازنویسی Big Bang بدون Inventory مصرف‌کننده و داده؛
  2. تبدیل رفتار فعلی به Spec مقدس؛
  3. برابر دانستن Characterization با Snapshot حجیم؛
  4. حذف همهٔ فیلدهای متفاوت در Normalizer؛
  5. Update خودکار Golden Master بدون Review؛
  6. Mock کردن همان Failure mechanism که باید آزموده شود؛
  7. تکیه بر E2E به‌عنوان تنها شبکهٔ ایمنی؛
  8. اعتماد به Pass rate یا Code Coverage کلی؛
  9. Shadow با Credential و Side effect واقعی؛
  10. Dual Write بدون Source of Truth و Reconciliation؛
  11. Row count به‌عنوان تنها تست مهاجرت داده؛
  12. Feature flag بدون تمرین Failback و سازگاری Schema؛
  13. Retry تا سبز شدن تست Flaky؛
  14. حذف سیستم قدیمی پیش از پایان Window مشاهده و Retention؛
  15. ثبت PII یا Secret در Snapshot، Log و Diff.

برنامهٔ ۳۰ روزه برای شروع ایمن

روزهای ۱ تا ۷: کشف و محدودکردن

یک جریان حیاتی انتخاب کنید؛ Change Card، Dependency map، Consumer inventory و پنج Invariant بسازید. Build را تکرارپذیر و محیط را Manifest کنید. Probeهای کم‌خطر اضافه و Unknownها را ثبت کنید.

روزهای ۸ تا ۱۴: Baseline و Harness

Fixtureهای ریسک‌محور، Harness، اجرای تکراری و Normalization review را بسازید. Focused characterization را بر Golden Master بزرگ مقدم بدانید و اثرهای جانبی را وارد Oracle کنید.

روزهای ۱۵ تا ۲۱: Seam و نسخهٔ جایگزین

Seam کم‌خطر را انتخاب و Contractها را نسخه‌دار کنید. Candidate را پشت Strangler یا Abstraction اجرا کنید؛ Differential run و طبقه‌بندی اختلاف را خودکار کنید.

روزهای ۲۲ تا ۳۰: داده، Cutover و تمرین بازگشت

Validation داده و Reconciliation را اجرا کنید. Cutover Contract، Stop condition و Decision rights را تصویب کنید. Canary/Shadow و Rollback drill را روی Release candidate تمرین و فقط یک موج محدود را منتقل کنید.

چک‌لیست نهایی تست و مدرن‌سازی Legacy

  • □ تصمیم Retain/Refactor/Replace/Retire و محدوده روشن است.
  • □ رفتار مشاهده‌شده از رفتار مطلوب جدا شده است.
  • □ Risk، Consumer، Dependency و Data flow مالک دارند.
  • □ Build و Environment تکرارپذیر و نسخه‌دارند.
  • □ Seam انتخابی Failure mechanism مهم را حفظ می‌کند.
  • □ Harness ورودی، Double، Observer، Cleanup و Verdict دارد.
  • □ Baseline در اجرای تکراری پایدار و Human-reviewed است.
  • □ هر Normalizer دلیل و Oracle جایگزین دارد.
  • □ Response، State، Side effect، Absence و Time بررسی می‌شوند.
  • □ Consumer contractهای رسمی و پنهان Inventory شده‌اند.
  • □ Shadow هیچ اثر جانبی واقعی یا نشت داده ندارد.
  • □ اختلاف‌ها Critical/Allowed/Invalid/Unknown طبقه‌بندی می‌شوند.
  • □ Migration با Row، Aggregate، Transformation و CDC سنجیده می‌شود.
  • □ Critical gate پشت Average Match یا Pass rate پنهان نیست.
  • □ Rollback، Failback و Reconciliation تمرین شده‌اند.
  • □ مالک Stop و Release و Window مشاهده مشخص است.
  • □ Legacy فقط پس از اثبات نبود مصرف و پایان Retention حذف می‌شود.
  • □ Artifactها PII و Secret ندارند.

جمع‌بندی

تست سیستم‌های لگسی پروژه‌ای برای تولید انبوه Test Case نیست؛ فرآیندی برای کاهش تدریجی عدم‌قطعیت است. ابتدا تصمیم و ریسک را محدود کنید، رفتار موجود را بدون مقدس‌کردن آن ثبت کنید، Seam و Harness بسازید، Oracleهای کسب‌وکاری و اثرهای جانبی را مستقل نگه دارید و نسخهٔ جدید را در موج‌های برگشت‌پذیر مقایسه کنید. مدرن‌سازی زمانی «ایمن» است که Cutover Contract، دادهٔ سازگار، Stop condition، مالک تصمیم و راه بازگشتِ تمرین‌شده داشته باشد—نه صرفاً یک داشبورد سبز.

سؤالات متداول

۱. تست Characterization یعنی باگ‌های سیستم قدیمی را برای همیشه حفظ کنیم؟

خیر. این تست ابتدا رفتار مشاهده‌شده را آشکار می‌کند. سپس هر رفتار باید Preserve، Fix، Deprecate یا Unknown شود. نقص تأییدشده با Acceptance جدید اصلاح می‌شود و Baseline نیز با Review و دلیل تغییر می‌کند.

۲. برای سیستم بدون تست، از Unit Test شروع کنیم یا E2E؟

پاسخ ثابت وجود ندارد. از کم‌هزینه‌ترین مرزی شروع کنید که ریسک و Failure mechanism مهم را حفظ کند. گاهی یک Focused Characterization در API بهترین شروع است؛ گاهی تنها نقطهٔ امن یک جریان E2E محدود است و بعد باید Seam پایین‌تری ساخته شود.

۳. آیا Golden Master همان Snapshot Testing است؟

Golden Master معمولاً خروجی تأییدشده‌ای برای مقایسه‌های بعدی است و Snapshot یک سازوکار رایج پیاده‌سازی آن محسوب می‌شود. اما Characterization می‌تواند Assertion متمرکز، Invariant، Contract یا Differential test باشد و الزاماً Snapshot کامل نیست.

۴. چه زمانی می‌توان سیستم لگسی را خاموش کرد؟

وقتی همهٔ مصرف‌کنندگان و مسیرهای Batch/فصلی تعیین تکلیف شده‌اند، ترافیک و داده منتقل شده، Reconciliation و Observation window موفق بوده، Rollback دیگر لازم نیست، الزامات بایگانی و Audit رعایت شده و مالک کسب‌وکار و عملیات Decommission را تأیید کرده‌اند.

۵. آیا Match صددرصد در Dual Run شرط Cutover است؟

نه همیشه. برخی تفاوت‌ها مانند شناسه یا قالب جدید ممکن است مجاز باشند و برخی رفتارهای لگسی عمداً اصلاح شوند. معیار درست، صفر اختلاف Critical حل‌نشده، تفاوت‌های مجاز مستند، Comparator معتبر و برآورده‌شدن Invariantها و Cutover Contract است.

دیدگاهتان را بنویسید