پرش به محتوای اصلی
پرش به محتوای مقاله

RealDiff با تحلیل رفتار زمان اجرا باگ‌های پنهان در کد را شکار می‌کند

·۵ شهریور ۱۴۰۵۲۰ دقیقه مطالعه۳ بازدید
مقایسه رفتار زمان اجرا برای درخواست‌های ادغام در گیت‌هاب
مقایسه رفتار زمان اجرا برای درخواست‌های ادغام در گیت‌هاب
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

جایگزینی تحلیل متنی کد با مقایسه درخت‌های فراخوانی زمان اجرا برای شناسایی تغییرات در فایل‌هایی که اصلاً ویرایش نشده‌اند.

تصور کنید یک تغییر تک‌خطی در یک کلاس کمکی، به‌طور خاموش موتور قیمت‌گذاری شما را در پروژه‌ای کاملاً مجزا خراب کند. این دقیقاً همان خطری است که RealDiff با تغییر تمرکز از «چه کدی ویرایش شد» به «این ویرایش در زمان اجرا چه کرد»، آن را برطرف می‌کند.

بیشتر برنامه‌نویسان برای تأیید درخواست‌های ادغام (Pull Requests)، به تفاوت‌های متنی کد (Source Diffs) و تست‌های واحد تکیه می‌کنند. اما بررسی کد فقط تغییرات متنی را نشان می‌دهد و تست‌ها تنها باگ‌هایی را می‌گیرند که برنامه‌نویس پیش‌بینی کرده و برایشان شرط (Assertion) گذاشته است. اگر تغییری مقدار بازگشتی یک تابع را عوض کند اما هیچ تستی آن مقدار خاص را چک نکند، باگ مستقیماً به محیط عملیاتی می‌رود. این چالش دقیقاً همان نقطه‌ای است که حتی ابزارهای پیشرفته‌تر نیز با آن دست‌وپنجه نرم می‌کنند؛ چنان‌که تحلیل‌های ما درباره‌ی رانش خاموش مدل‌های AI نشان داد که چگونه کاهش نرخ تشخیص باگ در مراحل بازبینی می‌تواند منجر به نفوذ خطاهای بحرانی به محیط عملیاتی شود.

RealDiff که در نسخه‌ی پیش‌نمایش عرضه شده است، مکانیزمی برای یافتن این «شکاف‌های رفتاری» معرفی می‌کند. این ابزار صرفاً به کد نگاه نمی‌کند؛ بلکه جریان اجرای واقعی برنامه را هنگام اجرای تست‌ها مشاهده می‌کند تا ببیند آیا منطق برنامه بین شاخه‌ی اصلی (Base Branch) و تغییرات پیشنهادی دچار انحراف شده است یا خیر.

همان‌طور که در تحلیل‌های قبلی ما درباره‌ی امنیت و پایداری سیستم‌های توزیع‌شده اشاره کردیم، شناسایی اثرات جانبی (Side Effects) در سیستم‌های پیچیده، سخت‌ترین بخش نگهداری کد است.

مکانیزم تفاضل رفتاری

طبق مستندات این ابزار، RealDiff با ساخت دو نسخه‌ی متفاوت از یک بازبینی گیت و مشاهده‌ی تست‌های آن‌ها عمل می‌کند. برای حذف نویزهای طبیعی سیستم، ابزار ابتدا نسخه‌ی اصلی را سه بار اجرا می‌کند تا یک خط مبنای نویز (Noise Baseline) بسازد. سپس تغییرات پیشنهادی را یک بار اجرا کرده و درخت‌های فراخوانی (Call Trees) حاصل را با هم مقایسه می‌کند.

وقتی انحرافی پیدا می‌شود، ابزار «مرز» (Frontier) را شناسایی می‌کند؛ یعنی پایین‌ترین عضو در درخت فراخوانی که رفتار در آن تغییر کرده است. این کار باعث می‌شود برنامه‌نویس با سیل هشدار برای تمام توابعی که متد خراب را فراخوانی کرده‌اند (هشارهای جانبی یا Collateral Alerts)، بمباران نشود.

معماری و جریان داده

این سیستم از یک قرارداد ردیابی بدون وابستگی به زبان استفاده می‌کند که در فایل TRACE-FORMAT.md تعریف شده است. معماری آن شامل یک لانچر سبک به زبان Rust است که مسیریابی آرگومان‌ها، بارگذاری تنظیمات مخزن و تشخیص تغییرات را مدیریت می‌کند. این لانچر یک مؤلفه مدیریت‌شده و مستقل برای حل ارجاعات (Ref Resolution)، بیلد، کش، ابزارگذاری (Instrumentation) و ارسال گزارشات را اجرا می‌کند.

جریان عملیاتی به این صورت است:

  • ورودی: مدیریت آرگومان‌های خط فرمان (argv)، تنظیمات و تشخیص محیط توسط Rust.
  • هماهنگ‌سازی: یک مؤلفه مدیریت‌شده، ردیاب‌ها را هماهنگ می‌کند.
  • لایه ردیاب: ردیاب‌های مخصوص هر زبان (.NET، Java، Node، Go، Rust، Python) ردهای NDJSON را در سطح پروسه تولید می‌کنند.
  • موتور: یک موتور استریمینگ تک-گذر (Single-pass) با زبان Rust، عملیات تطبیق، فیلتر نویز، تشخیص مرز و تولید یافته‌ها را انجام می‌دهد.
  • خروجی: نتایج در فایل findings.json نوشته شده و به GitHub، Azure DevOps یا MCP ارسال می‌شود.

ابزارگذاری در زبان‌های مختلف

این ابزار از یک موتور واحد اما ردیاب‌های اختصاصی برای هر زبان استفاده می‌کند تا رویدادهای زمان اجرا را ثبت کند:

  • .NET 8: از Mono.Cecil برای بافتن (Weaving) کد در زمان بیلد استفاده می‌کند تا به اجرا متصل شود. این روش از xUnit و PDBهای قابل حمل پشتیبانی می‌کند. لازم به ذکر است که ویژگی‌ها (Properties)، رویدادها، اپراتورها و مقداردهنده‌های نوع (Type Initializers) به دلیل احتمال بن‌بست در قفل‌های مقداردهی CLR، از سیاست‌های ردیابی حذف شده‌اند.
  • Java: از یک عامل java.lang.instrument با ASM برای تغییر بایت‌کد بهره می‌برد و با Maven/Gradle و JUnit/TestNG یکپارچه می‌شود. مقداردهنده‌های کلاس (Class Initializers) به دلیل قفل‌های مقداردهی JVM غیرقابل مشاهده هستند. در Gradle، استنتاج source-set شامل اعلان‌های صریح srcDir/srcDirs است و برای پیکربندی‌های پویا به source_roots نیاز است.
  • Node/TypeScript: از هوک‌های CommonJS و لودرهای ESM با Babel استفاده می‌کند و از npm، pnpm، Yarn و Bun پشتیبانی می‌کند. این ردیاب دقیقاً به یک فایل lockfile پشتیبانی‌شده نیاز دارد. ورکرها (Workers)، ژنراتورها و توابع غیرپشتیبانی نادیده گرفته می‌شوند. عدم یافتن Source Mapهای تایپ‌اسکریپت باعث کاهش سطح اطمینان در انتساب فایل می‌شود، اما منجر به اعلام فایل اشتباه نمی‌گردد.
  • Go: بازنویسی AST (درخت نحو انتزاعی) را به صورت Module-aware در یک کش بیلد خارجی پیاده می‌کند و از go test و موقعیت‌های اصلی پارسر .go بهره می‌برد. مرزهای پویا در اینترفیس‌ها/توابع و مرزهای goroutine که بازنویسی نشده‌اند، به‌طور صریح نادیده گرفته می‌شوند.
  • Rust: از بازنویسی syn/quote در یک کش SHA-256 استفاده می‌کند و از cargo test و ریشه‌های ساختاری #[test] پشتیبانی می‌کند. گسترش‌های ماکرو (Macro expansions)، اشیاء Trait و مقادیر متعلق به وابستگی‌ها (Dependencies) از نظر ساختاری غیرقابل دسترس هستند، زیرا بازنویسی پایدار نمی‌تواند وارد کدهای متعلق به کامپایلر شود.
  • Python 3.12+: از قابلیت sys.monitoring (PEP 669) برای ثبت رویدادها با کارایی بالا بدون نیاز به بافتن بایت‌کد استفاده می‌کند و از pytest و unittest پشتیبانی می‌کند. توابع Native/C و کدهای مصنوعی بدون منبع در مخزن غیرقابل مشاهده هستند. نسخه‌های ۳.۱۱ و قدیمی‌تر پذیرفته نمی‌شوند زیرا جایگزینی برای sys.settrace وجود ندارد.

جزئیات پیاده‌سازی در پایتون

به نقل از توسعه‌دهندگان، پایتون با زبان‌های کامپایل‌شده متفاوت است چون دستور بیلد برای تزریق کد ندارد. RealDiff یک فایل sitecustomize.py را به PYTHONPATH اضافه می‌کند و قبل از ایمپورت‌های هدف، sys.monitoring را فعال می‌کند. یک گذر AST بدون اثر جانبی، اعضای منبع را برای مانیفست پوشش (Coverage Manifest) فهرست می‌کند، اما رویدادهای زمان اجرا منحصراً از PEP 669 می‌آیند.

پشتیبانی از مقادیر در پایتون بر اساس اشکال اطمینان دسته‌بندی می‌شود:

  • دقیق (Exact): مقادیر None، بولی، اعداد صحیح دلخواه، اعداد اعشاری (شامل NaN استاندارد و 0.0-)، رشته‌ها، بایت‌ها و مجموعه‌های داخلی (list, tuple, dict, set, frozenset). در صورت نبود کانال دیگر، وضعیت کامل __dict__ نمونه‌ها ثبت می‌شود.
  • جزئی (Partial): ویژگی‌ها (Properties)، __slots__، __getattr__ و زیرکلاس‌های کانتینر که ممکن است با محدودیت‌های عمق و عرض مواجه شوند. مناطق خوانده‌نشده نشانگرهایی مانند <skipped:Python:...>، <error:Python:...> یا <truncated> را صادر می‌کنند.
  • پشتیبانی‌نشده (Unsupported): توابع Native/C و بدنه تنظیمات اجرایی ماژول‌ها یا کلاس‌ها.

برای تضمین پایداری، کانونی‌ساز (Canonicalizer) هرگز ویژگی‌ها، دیسکریپتورها، تکرارکننده‌های کاربر (User Iterators) یا کال‌بک‌های فرمت‌دهی را فراخوانی نمی‌کند.

تشخیص تغییرات «غیرمنتظره»

RealDiff یافته‌ها را به دو دسته «منتظره» و «غیرمنتظره» تقسیم می‌کند. تغییر منتظره زمانی رخ می‌دهد که رفتار در فایلی تغییر کند که واقعاً ویرایش شده است. اما تغییر غیرمنتظره بسیار خطرناک‌تر است: تغییر رفتار در فایلی که درخواست ادغام هرگز آن را لمس نکرده است.

در یک دموی ارائه شده، تغییر یک متد از List.Sort (ناپایدار) به OrderBy (پایدار) در یک کلاس کمکی زیرساختی (Infrastructure.Collections) باعث شد موتور قیمت‌گذاری در یک پروژه مجزا (Commerce.Pricing) مقادیر تخفیف متفاوتی برگرداند. به‌طور مشخص، متد DiscountEngine.SelectDiscount مقدار "CLEARANCE_40" را برمی‌گرداند اما اکنون "SEASONAL_15" را برمی‌گرداند، و CheckoutTotals.Compute مقدار ۶۰ را برمی‌گرداند اما اکنون ۸۵ است.

نکته تکان‌دهنده این بود که دو مورد از سه تست اجرا شده در این مسیر، هیچ شرطی (Assertion) برای واکنش به این تغییر نداشتند؛ یعنی CI استاندارد این باگ را تایید می‌کرد و به محیط عملیاتی می‌فرستاد. این یک «شکاف رفتاری» کلاسیک است که در آن منطق تغییر کرده اما اوراکل تست (Test Oracle) ساکت مانده است.

عملکرد و مقیاس‌پذیری

برای کاهش فشار روی CI، این ابزار یک کش برای ردهای پایه (Base-trace cache) دارد. با ذخیره ردهای تایید شده، ابزار می‌تواند در تحلیل‌های بعدی، سه مورد از چهار اجرای مورد نیاز را حذف کند. کلید این کش شامل SHA هدف، زبان، اثر انگشت ردیاب و پیکربندی محدوده/سانسور است.

در یک تست مقیاس‌پذیری با ۹۹,۰۰۰ رویداد در هر اجرا (با استفاده از FluentValidation)، تحلیل سرد (Cold Analysis) حدود ۳۳۹.۷۰۵ ثانیه زمان برد. اما در اجرای گرم (Warm Run) با استفاده از کش، این زمان به ۵۳.۹۶۲ ثانیه کاهش یافت که یعنی ۸۴.۱٪ کاهش در زمان اجرا. تفکیک زمان اجرای گرم شامل ۱۳.۲۶۴ ثانیه بیلد، ۲.۷۲۹ ثانیه بافتن، ۱۱.۸۴۴ ثانیه اجرای ابزارگذاری شده، ۱۰.۳۸۶ ثانیه تفاضل (Diffing) و ۶.۵۰۳ ثانیه یافتن مرز بود.

از نظر حافظه، پیک مصرف در کانتینر برای درخت پروسه در حالت سرد ۱,۷۷۷.۱۵۶ میبایت و در حالت گرم ۲,۰۶۴.۲۱۹ میبایت بود، در حالی که memory.current به ۲,۶۶۱.۴۶۱ میبایت رسید. بنابراین توصیه می‌شود کاربران بیش از ۲.۶۶ گیگابایت حافظه اختصاص دهند.

امنیت و سانسور داده‌ها

از آنجا که ردهای زمان اجرا شامل آرگومان‌ها و مقادیر بازگشتی واقعی هستند، ممکن است حاوی داده‌های حساس باشند. RealDiff یک سیستم سانسور پیش‌فرض دارد که رشته‌های مشابه 'password'، 'token'، 'secret'، 'ssn'، 'email'، 'auth' یا 'credential' را ماسک می‌کند. همچنین رشته‌هایی با ساختار اعتبارنامه‌ها مانند JWTها، AWS access-key IDها و هدرهای PEM را سانسور می‌کند.

نکته فنی این است که سانسور بعد از محاسبه هش SHA-256 انجام می‌شود. این یعنی ابزار همچنان می‌تواند تشخیص دهد که یک مقدار حساس بین دو نسخه تغییر کرده است، حتی اگر هر دو مقدار در گزارش نهایی به صورت <redacted> نمایش داده شوند. کاربران می‌توانند این رفتار را از طریق REALDIFF_REDACT_NAMES، REALDIFF_REDACT_TYPES و REALDIFF_REDACT_PATHS شخصی‌سازی کنند.

استقرار و پیکربندی

این ابزار به صورت یک لانچر Rust در قالب یک ایمیج داکر لینوکس (نسخه v0.4.0) با حجم تقریبی ۹۲۶.۵ مگابایت عرضه شده و با GitHub Actions و Azure Pipelines یکپارچه می‌شود و کامنت‌هایی را روی PRها می‌گذارد که علت (بخش ویرایش شده کد) را به اثر (منبع ویرایش نشده) متصل می‌کند. این رویکرد در کنار ابزارهای مدرن مدیریت کد، محیط توسعه را بهینه‌تر می‌کند؛ برای مثال به‌روزرسانی‌های اخیر GitHub Copilot در جداسازی محیط اکتشاف از کد فعال نیز با هدف کاهش تداخلات در زمان آزمایش تغییرات و افزایش دقت در بازبینی کد صورت گرفته است.

برای گزارش‌دهی با اطمینان بالا، ابزار تنها یافته‌هایی را علامت‌گذاری می‌کند که مرز آن‌ها تایید شده، هش‌ها دقیق باشند و انحراف مستقیماً از طریق درخت فراخوانی به تغییر متصل شود. کاربران می‌توانند از فلگ --strict برای مشاهده تمام یافته‌ها، حتی موارد با اطمینان پایین، استفاده کنند.

کاربران می‌توانند با فایل .realdiff/config.yml تنظیمات خودکار را بازنویسی کنند. این کار اجازه می‌دهد دستورات سفارشی build و test، source_roots و include_namespaces تعریف شوند. برای مثال، test_projects پروژه‌های .NET را از طریق glob انتخاب می‌کند، در حالی که source_roots دایرکتوری‌های جاوا را تامین می‌کند.

برای مدیریت مسائل شناخته‌شده، RealDiff از فایل .realdiff/baseline.yml استفاده می‌کند. این فایل به برنامه‌نویسان اجازه می‌دهد تغییرات رفتاری خاص را با ذکر دلیل و تاریخ انقضا (مثلاً expires: 2026-09-30) تایید کنند. تاییدات با یک عضو دقیق، مسیر و جفت-هش رفتار پایه/PR مطابقت دارند؛ اگر رفتار دوباره تغییر کند، یافته دوباره ظاهر می‌شود. نادیده گرفتن گسترده مسیرها و اعضا از globs حساس به حروف بزرگ و کوچک مانند *، ** و ? استفاده می‌کند.

اجرا و کدهای خروجی

اجرای یک تحلیل نیازمند مسیر مخزن و دو رفرنس گیت است:
realdiff <repo> --base origin/main --pr HEAD --findings findings.json

ابزار از کدهای خروجی خاص برای ارتباط نتایج استفاده می‌کند:

  • کد ۰: تحلیل تکمیل شد؛ هیچ تغییر رفتار غیرمنتظره‌ای یافت نشد.
  • کد ۱: تحلیل تکمیل شد؛ یافته‌های رفتاری وجود دارند.
  • کد ۳: تحلیل رد شد (شواهد ناکافی، عدم انتساب مسیر یا عدم پوشش).
  • کد ۴: ابزارگذاری (Instrumentation) شکست خورد.
  • کد ۵: مخزن بدون تغییر نتوانست بیلد شود.

این رویکرد فرض بنیادی فرآیند بررسی کد را تغییر می‌دهد. به جای پرسیدن «آیا این کد درست به نظر می‌رسد؟»، برنامه‌نویسان اکنون می‌توانند بپرسند «این تغییر دقیقاً چه اثری روی رفتار سیستم من گذاشت؟»

برای شروع، برنامه‌نویسان می‌توانند ایمیج رسمی را از GHCR دریافت کرده و آن را با فلگ‌های --base و --pr روی مخزن خود اجرا کنند تا آرتیفکت findings.json تولید شود.

گام بعدی شما

  • اگر از سیستم‌های CI/CD پیچیده استفاده می‌کنید، ایمیج داکر RealDiff را برای یک پروژه کوچک تست کنید تا شکاف‌های رفتاری (Behavior Gaps) را بیابید.
  • در فایل تنظیمات، source_roots را دقیق تعریف کنید تا دقت انتساب باگ به فایل‌ها افزایش یابد.
  • برای کاهش زمان اجرا در پروژه‌های بزرگ، حتماً قابلیت کش ردهای پایه را فعال کنید.

اما داستان سخت‌افزاری این تحول حتی شگفت‌انگیزتر است — به تحلیل ما درباره‌ی تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

این ابزار با شناسایی اثرات جانبی در بخش‌های دست‌نخورده کد، ریسک استقرار باگ‌های پنهان در سیستم‌های بزرگ را به‌شدت کاهش می‌دهد. اعتماد به این سیستم بر پایه تجربه عملی در ردیابی جریان اجرا (Execution Trace) است که فراتر از تحلیل استاتیک کد می‌رود.

تأثیر برای ایران

برنامه‌نویسان ایرانی در پروژه‌های بزرگ سازمانی که با Legacy Codeهای پیچیده سروکار دارند، می‌توانند از این ابزار برای جلوگیری از شکست‌های ناگهانی سیستم پس از Refactor استفاده کنند.

·نگاه ما
تحریریه دات‌هوش

RealDiff فرضیه سنتی «تست‌های پوششی کافی هستند» را به چالش می‌کشد و نشان می‌دهد که حتی با پوشش تست بالا، اگر Oracle تست (شرط خروجی) دقیق نباشد، رگرسیون‌های رفتاری رخ می‌دهند. این ابزار در واقع لایه‌ای از «مشاهده‌گری» را به جای «تاییدیه» جایگزین می‌کند که می‌تواند استانداردهای Code Review را از بررسی متنی به بررسی اثرات عملی تغییر دهد.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.