Hassan Amini
همه‌ی نوشته‌ها
توسعه نرم‌افزار و هوش مصنوعی

این کد را پاک کنم؟ تشخیص Dead Code با کمک AI

· ۱۰ دقیقه مطالعه

Read this article in English

این کد واقعاً بلااستفاده است؟ با بررسی مسیر اجرا، تست‌ها و کمک AI، کاندیداهای Dead Code را پیدا کنید و دربارهٔ حذف امن تصمیم بگیرید.

قرار است یک تغییر کوچک در پروژه بدهی. پوشهٔ مربوط را باز می‌کنی و با سه نسخه از یک component، چند helper قدیمی و فایلی روبه‌رو می‌شوی که هیچ‌کس نمی‌داند چرا هنوز آنجاست. قبل از نوشتن تغییر، باید بفهمی کدام بخش واقعاً کار می‌کند و کدام فقط جا مانده است.

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

برای تشخیص Dead Code، از فایل‌های بدون مصرف مشخص شروع کن؛ بعد مسیرشان را تا entry point، تست‌ها، تنظیمات و consumerهای بیرونی دنبال کن. اگر استفادهٔ معتبری باقی نمانده، حذف را با بررسی‌های قبل و بعد تأیید کن. فایل بدون import یک کاندیداست، نه یک حکم حذف.

این کد را پاک کنم؟ تشخیص Dead Code با کمک AI

چه چیزی واقعاً کد بلااستفاده است؟

Dead Code ممکن است یک شاخهٔ غیرقابل‌اجرا، تابع بدون caller یا مجموعه‌ای جداافتاده از component، hook و سرویس باشد. هشدار editor، lint و تنظیماتی مثل noUnusedLocals در TypeScript برای بعضی موارد محلی مفیدند، ولی فعال‌بودن تمام قابلیت‌های پروژه را ثابت نمی‌کنند.

سؤال اصلی این است: «از کدام نقطهٔ معتبر اجرا می‌توان به این کد رسید؟» آن نقطه ممکن است صفحه، API، command یا worker باشد. یک migration لازم نیست در UI مصرف شود. یک component هم صرفاً به‌خاطر داشتن تست، بخشی از محصول فعال محسوب نمی‌شود.

همچنین حذف source الزاماً سایت را سریع‌تر نمی‌کند؛ bundler ممکن است از قبل کد را کنار گذاشته باشد. Tree shaking به خروجی build مربوط است. کاهش ابهام و هزینهٔ نگه‌داری، ارزش مستقلی دارد؛ ادعای performance نیاز به اندازه‌گیری جدا دارد.

۱. محدوده و وضعیت پایه را مشخص کن

از «کل پروژه را پاک کنیم» شروع نکن. یک قابلیت قدیمی یا پوشهٔ مشخص انتخاب کن. commit، تغییرات محلی و محدودهٔ بررسی را ثبت کن:

git status --short
git rev-parse HEAD

بعد typecheck، تست‌های مرتبط و build را طبق scriptهای واقعی پروژه اجرا کن. نام و رفتارشان را در package.json بررسی کن. این نتیجهٔ اولیه همان baseline است: وقتی بعد از حذف تستی شکست خورد، بتوانی تشخیص بدهی خطا تازه است یا از قبل وجود داشته.

اگر بررسی‌ای از قبل شکست می‌خورد یا اجرا نمی‌شود، همین محدودیت را بنویس. لازم نیست برای حذف هر helper تمام پروژه را تعمیر کنی، ولی اگر آن خطا مانع سنجیدن رفتار متأثر می‌شود، ابتدا باید راه اعتبارسنجی را روشن کنی.

۲. کاندیداها را پیدا کن و خروجی ابزار را تفسیر کن

نام component، export و مسیر فایل مشکوک را جست‌وجو کن. برای نمونهٔ فرضی PromoBanner:

rg -n 'PromoBanner' src tests scripts

پوشه‌ها را با ساختار پروژهٔ خودت جایگزین کن. نتیجه را بخوان، فقط matchها را نشمار. نام ممکن است در کامنت، تست یا یک export مرکزی آمده باشد. alias و بارگذاری غیرمستقیم هم ممکن است از این جست‌وجو پنهان بمانند.

در JavaScript و TypeScript، ابزارهایی مثل Knip برای گزارش فایل، export و dependency مشکوک مفیدند. اگر ابزار نصب است، می‌توانی تحلیل را با pnpm exec knip اجرا کنی. برای راه‌اندازی و قراردادها، راهنمای رسمی Knip را ببین.

پیش از اعتماد به گزارش، ورودی برنامه، worker، command و API عمومی packageها را مشخص کن. هر فایل را entry نکن تا هشدارش ناپدید شود، و مورد نامعلوم را بی‌دلیل ignore نکن. خروجی اولیه فهرست تحقیق است؛ auto-fix هنوز انتخاب مناسبی برای حذف گسترده نیست.

۳. reference را تا اجرای واقعی دنبال کن

برای component بپرس: چه چیزی آن را import کرده؟ آیا در JSX رندر می‌شود؟ والدش در کدام صفحه mount می‌شود؟ آیا آن صفحه در برنامهٔ فعلی قابل‌دسترسی است؟ در اولین import متوقف نشو.

برای تابع هم caller را تا ریشه دنبال کن. ممکن است caller خودش مصرف‌کننده نداشته باشد. Re-export در index.ts به‌تنهایی استفادهٔ واقعی نیست؛ باید ببینی چه چیزی آن export را می‌خواند.

اثر جانبی را هم بررسی کن. بعضی importها هنگام بارگذاری registration یا initialization انجام می‌دهند. نداشتن متغیر مصرف‌شده به‌معنی نداشتن رفتار نیست. در package عمومی، مصرف ممکن است بیرون repository باشد و قرارداد export باید بررسی شود.

قابل‌دسترسی‌بودن هم به‌معنی اجرای همیشگی نیست. dialog فقط پس از کلیک و گزارش مدیریتی فقط برای نقش مناسب اجرا می‌شود. نبود مشاهدهٔ runtime را با نبود مسیر معتبر یکی نکن.

۴. راه‌های اجرای غیرمستقیم را بررسی کن

route ممکن است با نام فایل discover شود، command از package script اجرا شود و یک job از CI یا cron شروع شود. وقتی import معمولی پیدا نشد، configuration، workflow و deployment را بررسی کن.

بارگذاری پویا هم اهمیت دارد: نام ماژول ممکن است از تنظیمات ساخته شود یا pluginها از یک پوشه خوانده شوند. در چنین حالتی قرارداد کشف فایل، خودش بخشی از شواهد مصرف است.

برای endpoint، نبود درخواست از frontend خودت کافی نیست؛ سرویس دیگری ممکن است consumer باشد. اگر قرارداد بیرونی روشن نیست، وضعیت را «نامعلوم» ثبت کن و سؤال بعدی را مشخص بنویس. «در این repository مصرف پیدا نشد» ادعای محدودتری از «هیچ مصرف‌کننده‌ای وجود ندارد» است.

یک مثال: بنری که ظاهراً استفاده می‌شود

فرض کن فروشگاه قبلاً صفحهٔ کمپین داشته است. برای PromoBanner سه reference پیدا می‌کنی: یک export، یک تست و componentی به نام CampaignPanel. در نگاه اول بنر استفاده شده است.

بررسی را ادامه می‌دهی: پنل به یک hook و سرویس کمپین وصل است، اما route قدیمی حذف شده و هیچ صفحهٔ فعالی پنل را رندر نمی‌کند. ارتباط داخل مجموعه برقرار است؛ اتصال مجموعه به برنامه پیدا نمی‌شود. این همان جزیرهٔ کد یا subgraph جداافتاده است.

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

پس فایل‌های پایین‌دستی را فقط به‌خاطر داشتن importer فعال ندان. ریشهٔ مجموعه و نیاز فعلی محصول را بررسی کن؛ شاید مسئله کد قدیمی باشد، شاید هم قابلیتی که مسیر ورودش ناقص مانده است.

۵. تست و کار پس‌زمینه را جداگانه ببین

اگر implementation فقط از تست قابل‌دسترسی است، آن را test-only ثبت کن و علت نبود مصرف production را پیدا کن. رفتار ممکن است قدیمی شده باشد یا اتصالش جا افتاده باشد. fixture و mock تست را با implementation محصول اشتباه نگیر؛ آن‌ها طبیعی است که consumer تولیدی نداشته باشند.

برای جریان background، producer و consumer را کنار هم بررسی کن. scheduler ممکن است job بسازد ولی worker در deployment اجرا نشود. حذف worker در این وضعیت می‌تواند مشکل اتصال را پنهان کند.

اگر قابلیت لازم است، اتصال را اصلاح کن. اگر بازنشسته می‌شود، producer، زمان‌بندی و کارهای موجود هم نیاز به تصمیم دارند. نبود اجرای یک روز، بی‌استفاده‌بودن جریان ماهانه را ثابت نمی‌کند؛ بازهٔ مشاهده باید با دورهٔ اجرا متناسب باشد.

۶. یافته را به یک تصمیم روشن تبدیل کن

دسته‌بندی ساده کافی است، به‌شرط اینکه «مصرف پیدا نشد» را با «حذف تأیید شد» مخلوط نکند:

وضعیتاقدام بعدی
کاندیدای حذفبررسی نهایی و حذف محدود
فقط تستتعیین نیاز فعلی رفتار و علت نبود اتصال
عمداً غیرفعالنگه‌داری با دلیل، مالک و زمان بازبینی
اتصال ناقصتکمیل قابلیت یا بازنشستگی کل جریان
خروجی تولیدشدهبررسی generator و سیاست نگه‌داری
نامعلومتعیین شاهد موردنیاز؛ فعلاً بدون حذف

برای هر مورد مسیر، consumerهای پیدا‌شده، سؤال باز و اقدام پیشنهادی را بنویس. برای کمپین می‌توان ثبت کرد: «مصرف داخل پنل و تست وجود دارد؛ route فعال پیدا نشد؛ مصرف بیرونی سرویس هنوز بررسی نشده؛ پنل به‌تنهایی حذف نشود.»

اگر سطح اطمینان می‌نویسی، دلیلش را هم اضافه کن. پوشش مسیرهای مرتبط مهم‌تر از قاطعیت ابزار است. کد پشت feature flag و خروجی generated هم باید با قرارداد خودشان بررسی شوند.

۷. کوچک حذف کن و متناسب بررسی کن

وقتی تصمیم روشن شد، تغییر را محدود نگه دار. حذف را با refactor نامرتبط مخلوط نکن. dependencyهای مجموعه را بررسی کن؛ helper مشترک با checkout فعال، همراه کمپین قدیمی حذف نمی‌شود.

کنترل‌های baseline را تکرار کن. Typecheck وابستگی شکسته را پیدا می‌کند، تست رفتار پوشش‌داده‌شده را می‌سنجد و build برخی مشکلات خروجی و discovery را نشان می‌دهد. در تغییر حساس، مسیر کاربر یا process مربوط را هم اجرا کن.

سبزشدن build مصرف‌کنندهٔ بیرونی را تأیید نمی‌کند. کنترل باید به همان قرارداد و محیط مربوط باشد. در PR توضیح بده چه چیزی حذف شد، چرا و چه بررسی‌هایی نتیجه را پشتیبانی می‌کنند.

استفاده از AI برای تشخیص Dead Code: از تحقیق تا تغییر

AI در این کار برای جمع‌آوری referenceها، دنبال‌کردن زنجیرهٔ مصرف و مرتب‌کردن یافته‌ها مفید است. اما درخواست «فایل‌های unused را پاک کن» مسئله را بیش از حد ساده می‌کند. بهتر است کار را به سه مرحله تقسیم کنی: ممیزی، تصمیم و تغییر محدود.

مرحلهٔ اول: شواهد بخواه، نه حذف

به AI محدودهٔ مشخص بده و از آن بخواه برای هر ادعا مسیر فایل و reference ارائه کند. یک prompt قابل‌استفاده:

«ماژول کمپین را برای کد بلااستفاده بررسی کن. برای هر کاندیدا، consumerها را تا entry point دنبال کن؛ تست و production را جدا کن؛ script، registration و بارگذاری پویا را بررسی کن. مصرف‌کنندهٔ بیرونی احتمالی و سؤال‌های بی‌پاسخ را ثبت کن. در این مرحله هیچ فایلی را تغییر نده.»

این درخواست کمک می‌کند خروجی به‌جای فهرست قاطع حذف، یک مجموعهٔ قابل‌بررسی باشد. اگر AI می‌گوید «پنل unused است»، باید بتوانی ببینی کدام مسیرها را بررسی کرده و کدام هنوز باز مانده‌اند.

مرحلهٔ دوم: نتیجه‌گیری را بررسی کن

از AI بخواه گزارش اولیه را با نقش منتقد مرور کند: «برای هر پیشنهاد حذف، چه مسیر اجرایی یا قرارداد بیرونی ممکن است از تحلیل جا مانده باشد؟» این مرور سرنخ تازه می‌دهد، اما تأیید مستقل محسوب نمی‌شود؛ همان ابزار می‌تواند فرض قبلی را تکرار کند.

چند reference کلیدی را خودت بررسی کن. ادعای «هیچ مصرفی نیست» را با scope واقعی تطبیق بده و نبود شاهد را به قطعیت تبدیل نکن. تصمیم محصول، مثل ادامه یا پایان کمپین، باید از نیاز معتبر پروژه بیاید.

مرحلهٔ سوم: یک تغییر مشخص درخواست کن

پس از روشن‌شدن تصمیم، prompt بعدی را محدود کن: «فقط مجموعهٔ کمپین بازنشسته را حذف کن. helper مشترک checkout را نگه دار. تغییر رفتار نامرتبط انجام نده. diff و نتیجهٔ بررسی‌های مرتبط را گزارش کن؛ هر مورد نامعلوم را قبل از حذف متوقف کن.»

AI باید بگوید چه بررسی‌هایی واقعاً اجرا شده‌اند و کدام اجرا نشده‌اند. عبارت «تست‌ها باید پاس شوند» با نتیجهٔ واقعی تست فرق دارد. در پایان diff را ببین و شواهد قبل و بعد را مقایسه کن. AI سرعت تحقیق را بیشتر می‌کند؛ اطمینان از شواهد و اعتبارسنجی می‌آید.

چطور خروجی AI را قابل‌بررسی نگه داریم؟

محدودهٔ دسترسی ابزار را مشخص کن. اگر فقط component را می‌بیند، نمی‌تواند دربارهٔ کل قابلیت نتیجهٔ قطعی بدهد. scriptها، تنظیمات framework و ورودی‌های شناخته‌شده را در اختیارش بگذار؛ قرارداد بیرونیِ بررسی‌نشده باید در گزارش باز بماند.

خروجی را به‌صورت جدول شواهد بخواه: مسیر، consumer، entry point، وضعیت تست، سؤال باز و اقدام پیشنهادی. برای مثال کمپین، جدول باید نبود route و نامعلوم‌بودن مصرف سرویس را نشان دهد. برچسب «unused» به‌تنهایی برای review کافی نیست.

همچنین پیشنهاد را از اجرای واقعی جدا کن. ارائهٔ دستور تست با اجرای تست فرق دارد. این تفکیک خروجی AI را قابل‌اعتمادتر و تصمیم نهایی را قابل‌پیگیری‌تر می‌کند.

چطور دوباره به همین وضعیت برنگردیم؟

وقتی route یا قابلیتی بازنشسته شد، component، hook، تست و سرویس وابسته را هم بررسی کن. برای کد غیرفعال، مالک و معیار بازبینی تعیین کن. «شاید بعداً لازم شود» نباید وضعیت دائمی بسازد.

ابزار تحلیل را تدریجی وارد CI کن: ابتدا configuration و false positiveها را روشن کن، بعد جلوی موارد تازه را بگیر. صدها هشدار توضیح‌نداده معمولاً به خاموش‌کردن ابزار منتهی می‌شوند.

از یک قابلیت شروع کن

یک قابلیت قدیمی انتخاب کن، مسیر مصرفش را دنبال کن و سؤال‌های باز را پاسخ بده. نتیجه ممکن است حذف چند فایل یا بازیابی یک اتصال باشد. در هر دو حالت باید بتوانی توضیح بدهی چرا آن اقدام درست است.

پرسش‌های متداول

آیا فایل بدون import را می‌توان حذف کرد؟

بعد از بررسی entry point، اجرای غیرمستقیم و مصرف بیرونی. نبود import فقط شروع تحقیق است.

آیا تست داشتن یعنی کد باید بماند؟

خیر. نیاز فعلی رفتار و علت نبود اتصال production تعیین می‌کند implementation باید حذف شود یا دوباره به محصول وصل شود.

آیا AI یا ابزار تحلیل به‌تنهایی کافی است؟

برای کشف کاندیداها مفیدند؛ قرارداد بیرونی، تصمیم محصول و رفتار واقعی نیاز به شواهد و کنترل مرتبط دارند.

منابع رسمی و تاریخ بررسی

منابع در ۱۴ مهر ۱۴۰۵ بررسی شده‌اند.

تماس

بیایید چیزی قابل‌اعتماد بسازیم.

آماده‌ی همکاری در کارهای فول‌استکِ ارشد، بک‌اند، داشبورد و Real-time.