در ستایش کدِ تمیز؛ ۱۰۰+ توصیه از عمو باب

1684
کد تمیز | کدنویسی تمیز

ممکن است زمانی که کدتان مطابق انتظار عمل می‌کند فکر کنید که کارتان تمام شده است و به سراغ کار بعدی بروید اما ما فقط برای کامپیوترها کد نمی‌نویسیم. کدنویسی تمیز یک سبک توسعه‌ی متمرکز بر خواننده است که نوشتن، خواندن و نگهداری از کدها را آسان می‌کند.

هر کسی می‌تواند کدی بنویسد که یک کامپیوتر آن را بفهمد. برنامه‌نویس خوب کدی می‌نویسد که انسان‌ها آن را می‌فهمند.

مارتین فولر

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

کد تمیز | کدنویسی تمیز

بنابراین دانستن نحوه‌ی تولید کدهای تمیز، قابل‌فهم و قابل‌نگهداری مهارتی است که برای هر توسعه‌دهنده‌ای ضروری است. البته کدنویسی تمیز چیزی بیشتر از نام توابع، الگوهای طراحی و تورفتگی‌ها است. Robert C Martin معروف به عمو باب در کتاب معروفش، Clean Code، توصیه‌هایی را برای نوشتن کدهای تمیز و ساختاریافته ارائه کرده است. در این مطلب، خلاصه‌ای از توصیه‌های عمو باب درمورد کدنویسی تمیز را فصل به فصل مرور کرده‌ایم.

فصل یک: کدنویسی تمیز

  • هزینه‌های کدهای آشفته در طول زمان افزایش می‌یابد.
  • بازسازی یک سیستم قدیمی (legacy system) از پایه بسیار دشوار است. بازآرایی (refactoring) و اعمال بهبودهای تدریجی اغلب راه‌حل بهتری است.
  • در پایگاه‌های کد آشفته و نامرتب، انجام کارهایی که فقط باید در عرض چند ساعت انجام شود، ممکن است روزها یا هفته‌ها زمان ببرد.
  • کدهای تمیز یک کار را به‌خوبی انجام می‌دهند. در حالی که کدهای بد سعی می‌کنند کارهای زیادی انجام دهند.
  • کدهای تمیز به‌خوبی تست شده‌اند.
  • کدها بیشتر از آنکه نوشته شوند خوانده می‌شوند.
  • کدی که راحت‌تر خوانده می‌شود، راحت‌تر تغییر می‌کند.
  • پایگاه کد را بهتر از آنچه که تحویل گرفته‌اید تحویل دهید (The Boy Scout Rule).

فصل دو: اسامی معنادار

  • نام متغیرهای خود را با دقت انتخاب کنید.
  • نام‌ها باید به شما بگوید که یک متغیر یا تابع چیست و چگونه از آن استفاده می‌شود.
  • از نام متغیرهای تک‌کاراکتری خودداری کنید، به استثنای نام‌های رایج مانند i برای متغیر شمارنده در یک حلقه.
  • از مختصر‌شده کلمات به‌عنوان نام متغیرها خودداری کنید.
  • نام متغیرها باید قابل‌تلفظ باشند تا بتوان آن‌ها را با صدای بلند ادا کرد و درمورد آن‌ها صحبت کرد.
  • از نام متغیرهایی استفاده کنید که به‌راحتی قابل جستجو هستند.
  • کلاس‌ها و اشیا باید نام‌هایی داشته باشند که اسم هستند.
  • متدها و توابع باید دارای نام‌هایی باشند که فعل یا ترکیبی از فعل و اسم هستند.

فصل سوم: توابع

  • توابع باید کوچک باشد.
  • هر تابع باید یک کار انجام دهد.
  • توابع باید دارای نام‌های توصیفی باشند.
  • کدهای موجود در عبارات if/else یا switch را به توابع با نام واضح تبدیل کنید.
  • تعداد آرگومان‌هایی که یک تابع می‌پذیرد را محدود کنید.
  • اگر یک تابع به آرگومان‌های پیکربندی زیادی نیاز دارد، آنها را در قالب یک متغیر پیکربندی واحد ترکیب کنید.
  • توابع باید خالص باشند، به این معنی که اثر جانبی نداشته باشند و آرگومان‌های ورودی خود را تغییر ندهند.
  • یک تابع باید یک دستور یا یک کوئری باشد نه هر دو (Command Query Separation).
  • به جای برگرداندن کدهای خطا، خطاها و استثناها را صادر کنید.
  • کدهای تکراری را به توابع با نام واضح تبدیل کنید (Don’t Repeat Yourself).
  • تست‌های واحد، بازآرایی کد را آسان‌تر می‌کنند.

فصل چهارم: کامنت‌ها

  • کامنت‌ها می‌توانند دروغ بگویند. ممکن است از ابتدا اشتباه باشند، یا ممکن است در ابتدا دقیق باشند و سپس با تغییر کد مربوطه به مرور زمان قدیمی شوند.
  • از کامنت‌ها برای توضیح اینکه چرا چیزی به این شکل نوشته شده است استفاده کنید، نه برای توضیح اینکه چه اتفاق می‌افتد.
  • اغلب می‌توان با استفاده از متغیرهایی با نام واضح و تبدیل بخش‌هایی از کد در قالب توابعی با نام واضح، از درج کامنت جلوگیری کرد.
  • کامنت‌های TODO را به روشی هماهنگ پیشوندگذاری کنید تا جستجوی آن‌ها آسان‌تر شود. همچنین آن‌ها را به‌صورت دوره‌ای بازبینی کرده و تمیز کنید.
  • از Javadocs بی‌هدف استفاده نکنید. معمولاً کامنت‌هایی که توصیف می‌کنند یک متد چه کاری انجام می‌دهد، چه آرگومان‌هایی را می‌گیرد، و چه چیزی را برمی‌گرداند، در بهترین حالت اضافی و در بدترین حالت گمراه‌کننده هستند.
  • کامنت‌ها باید شامل تمام اطلاعاتی باشد که خواننده‌ی کد به آن نیاز دارد. کامنت‌ها را کوتاه یا مبهم ننویسید.
  • نوشتن کامنت‌های روزنوشت و نویسنده‌‌ی فایل به دلیل استفاده از کنترل نسخه و گیت غیرضروری است.
  • کد‌های بلااستفاده را کامنت‌گذاری نکنید. آن‌ها را حذف کنید. اگر فکر می‌کنید در آینده به این کدها نیاز خواهید داشت، می‌توانید از کنترل نسخه استفاده کنید.

فصل پنجم: قالب‌بندی

  • به‌عنوان یک تیم، مجموعه‌ای از قوانین را برای قالب‌بندی کد خود انتخاب کنید و سپس به‌صورت هماهنگ از آن قوانین استفاده کنید. خیلی مهم نیست که روی چه قوانینی توافق می‌کنید، اما باید به توافق برسید.
  • از یک شکل‌دهنده (Formatter) و لینتر (Linter) خودکار مانند Prettier استفاده کنید. به انسان‌ها برای پیدا کردن و تصحیح دستی خطاهای قالب‌بندی تکیه نکنید. این کار ناکارآمد و اتلاف وقت است.
  • برای جداسازی بصری بلوک‌های کد مرتبط، فضاهای خالی عمودی را به کد خود اضافه کنید.
  • خواندن، درک و پیمایش فایل‌های کوچک آسان‌تر از فایل‌های بزرگ است.
  • متغیرها باید نزدیک به محل استفاده‌ی خود معرفی شوند. به‌عنوان مثال در توابع کوچک، متغیرها را در بالای تابع موردنظر معرفی کنید.
  • حتی برای توابع کوتاه یا عبارات if هم به‌جای نوشتن آن‌ها در یک خط، آن‌ها را به‌درستی قالب‌بندی کنید.

فصل ششم: اشیا و ساختمان‌های داده

  • جزئیات پیاده‌سازی یک شیء باید در پشت اینترفیس شیء پنهان شود. با فراهم کردن یک اینترفیس برای مصرف‌کنندگان شیء، می‌توانید جزئیات پیاده‌سازی را بعداً بدون ایجاد تغییرات مشکل‌ساز تغییر دهید. انتزاع‌ها (abstract) بازآرایی را آسان‌تر می‌کنند.
  • هیچ قطعه کدی نباید درمورد جزئیات متغیرها و دیتای مخصوص شیئی که با آن کار می‌کند بداند.
  • هنگام کار با یک شیء، باید از آن بخواهید که دستورات یا کوئری‌ها را انجام دهد، نه اینکه از آن در مورد جزئیات درونی‌اش بپرسید.

فصل هفتم: مدیریت خطا

  • مدیریت خطا نباید بقیه کد موجود در ماژول را مبهم کند.
  • به‌جای بازگرداندن کدهای خطا، خطاها و استثناها را صادر کنید.
  • پیغام‌های خطا باید حاوی اطلاعات مفیدی باشند و تمام مواردی که دریافت‌کننده پیغام خطا برای عیب‌یابی مؤثر به آن نیاز دارد را ارائه کند.
  • از الگوی مورد خاص (Special Case pattern) یا الگوی شیء تهی (Null Object pattern) برای مدیریت رفتارهای خاص استفاده کنید؛ مانند زمانی که داده خاصی وجود ندارد.

فصل هشتم: مرزها

  • کتابخانه‌ها با فراهم کردن کدهای آماده به شما کمک می‌کنند تا محصول خود را سریع‌تر عرضه کنید.
  • برای اطمینان از اینکه استفاده شما از کتابخانه‌ها به‌درستی کار می‌کند، تست‌هایی را بنویسید.
  • از الگوی Adapter برای پر کردن شکاف بین API یک کتابخانه‌ی شخص ثالث و API موردنظر خود استفاده کنید.
  • قرار دادن APIهای شخص ثالث در لایه نازکی از انتزاع، مهاجرت از یک کتابخانه به کتابخانه‌ی دیگر را در آینده آسان‌تر می‌کند.
  • قرار دادن APIهای شخص ثالث در لایه نازکی از انتزاع، شبیه‌سازی کتابخانه را در طول تست آسان‌تر می‌کند.
  • اجازه ندهید بخش زیادی از برنامه شما به جزئیات پیاده‌سازی کتابخانه‌های شخص ثالث وابسته باشد.
  • بهتر است به چیزی که بر روی آن کنترل دارید متکی باشید تا به چیزی که کنترلی بر روی آن ندارید.

فصل نهم: تست‌های واحد

  • کد تست باید مانند کد اصلی تمیز نگه داشته شود. به استثنای مواردی که مربوط به حافظه یا کارایی کد است.
  • با تغییر کد، کد تست‌ نیز تغییر می‌کند.
  • تست‌ها به انعطاف‌پذیری و قابلیت نگهداری کد شما کمک می‌کنند.
  • تست‌ها به شما این امکان را می‌دهند با اطمینان و بدون نگرانی از خراب کردن کد، تغییرات را اعمال کنید.
  • تست‌های خود را با استفاده از الگوی Arrange-Act-Assert ساختاربندی کنید.
  • برای آسان کردن فرایند خواندن و نوشتن تست‌ها، از توابع اختصاصی هر حوزه استفاده کنید.
  • در هر تست یک مفهوم واحد را ارزیابی کنید.
  • تست‌ها باید سریع باشند.
  • تست‌ها باید مستقل باشند.
  • تست‌ها باید قابل‌تکرار باشند.
  • تست‌ها باید خودمعتبرکننده (self validating) باشند.
  • تست‌ها باید به موقع نوشته شوند، یا کمی قبل یا بعد از نوشتن کد، نه ماه‌ها بعد.

فصل دهم: کلاس‌ها

  • کلاس‌ها باید کوچک باشند.
  • کلاس‌ها فقط باید مسئول یک چیز باشند و فقط یک دلیل برای تغییر داشته باشند (Single Responsibility Principle).
  • اگر نمی‌توانید نام واضحی برای یک کلاس انتخاب کنید، احتمالاً آن کلاس خیلی بزرگ است.
  • کار شما با کارکردن کدتان تمام نمی‌شود. گام بعدی شما بازآرایی و تمیزکردن کد است.
  • استفاده از تعداد زیادی کلاس کوچک به‌جای چند کلاس بزرگ، میزان اطلاعاتی که یک توسعه‌دهنده باید در حین کار بر روی هر فرایند مشخص درک کند را کاهش می‌دهد.
  • داشتن یک test suite خوب به شما امکان می‌دهد تا با اطمینان خاطر، کلاس‌های بزرگ را به کلاس‌های کوچک‌تر تبدیل کرده و کد خود را بازآرایی (refactor) کنید.
  • کلاس‌ها باید برای گسترش باز، اما برای تغییر بسته باشند (Open-Closed Principle).
  • رابط‌ها و کلاس‌های انتزاعی با ایجاد seamهایی، انجام تست‌ها را آسان‌تر می‌کنند.

فصل یازدهم: سیستم‌ها

  • از تزریق وابستگی (Dependency Injection) برای دادن انعطاف‌پذیری برای انتقال هر شیء و رابط متناظرش به کلاس دیگر استفاده کنید.
  • برای ایجاد seam در برنامه و آسان کردن انجام تست‌ها از تزریق وابستگی استفاده کنید.
  • سیستم‌های نرم‌افزاری مانند یک ساختمان نیستند که باید از قبل طراحی شوند. آن‌ها بیشتر شبیه شهرهایی هستند که در طول زمان رشد می‌کنند و گسترش می‌یابند و با نیازهای فعلی سازگار می‌شوند.
  • تصمیم‌گیری را تا آخرین لحظه به تأخیر بیندازید.
  • برای اینکه کارشناسان و توسعه‌دهندگان یک حوزه از اصطلاحات مشابهی استفاده کنند، از زبان‌های اختصاصی آن حوزه استفاده کنید.
  • سیستم خود را بیش از حد پیچیده نکنید. از ساده‌ترین چیزی که کار می‌کند استفاده کنید.

فصل دوازدهم: جلوگیری از بحران

  • سیستم‌هایی که قابل تست نیستند قابل‌تأیید نیستند و سیستم‌هایی که قابل‌تأیید نیستند هرگز نباید مستقر شوند.
  • نوشتن تست‌ها منجر به طراحی‌های بهتری می‌شود، زیرا کدهایی که تست آن‌ها آسان است، اغلب از تزریق وابستگی، رابط‌ها و انتزاع استفاده می‌کنند.
  • یک test suite خوب نگرانی شما درمورد خراب شدن برنامه در حین بازآرایی را از بین می‌برد.
  • تکرار کد خطرناک است زیرا مکان‌های بیشتری در کد برای تغییر و مخفی کردن باگ‌ها به وجود می‌آورد.
  • درک کدی که در حال نوشتن آن هستید آسان است زیرا عمیقاً درگیر آن هستید. اما برای دیگران آسان نیست که به سرعت همان میزان درک را نسبت به کد شما به دست آورند.
  • عمده هزینه یک پروژه نرم‌افزاری مربوط به هزینه‌های نگهداری آن در طولانی‌مدت است.
  • تست‌ها به‌عنوان مستندی زنده از نحوه‌ی عملکرد برنامه شما عمل می‌کنند.
  • به محض اینکه کدتان کار کرد رهایش نکنید. برای تمیز کردن و آسان‌تر کردن درک آن وقت بگذارید.
  • فرد بعدی که در آینده‌ی نزدیک کد شما را می‌خواند به احتمال زیاد خود شما خواهید بود. با نوشتن کدی که به‌راحتی قابل درک است، با آینده‌ی خود مهربان باشید.
  • چند دهه‌ طول می‌کشد تا در مهندسی نرم افزار واقعاً خوب شوید. شما می‌توانید با یادگیری از متخصصان اطراف خود و الگوهای طراحی متداول، روند یادگیری خود را تسریع کنید.

فصل سیزدهم: هم‌روندی

  • نوشتن کد هم‌روند (concurrent) دشوار است.
  • باگ‌های تصادفی و مشکلاتی که بازتولید آن‌ها سخت است، اغلب مشکلات هم‌روندی هستند.
  • تست‌ها تضمین نمی‌کنند که هیچ باگی در برنامه شما وجود نداشته باشد، اما ریسک آن را به حداقل می‌رساند.
  • درباره مشکلات رایج هم‌روندی و راه‌حل‌های احتمالی آن‌ها بیاموزید.

فصل چهاردهم: اصلاحات متوالی

  • کد تمیز معمولاً از ابتدا تمیز نوشته نمی‌شود. ابتدا یک راه‌حل کثیف را می‌نویسید و سپس آن را اصلاح می‌کنید تا تمیزتر شود.
  • این رویکرد که وقتی کدی کار می‌کند، دیگر روی آن کار نکنید اشتباه است. بعد از اینکه کد شما بدون خطا کار کرد کمی زمان بگذارید تا آن را بهبود دهید.
  • آشفتگی‌ها به‌تدریج ایجاد می‌شوند.
  • اگر یک کد آشفته در اختیار دارید که افزودن ویژگی‌ها به آن بسیار دشوار یا زمان‌بر است، نوشتن ویژگی‌ها را متوقف کرده و شروع به بازآرایی آن کنید.
  • ایجاد تغییرات تدریجی اغلب انتخاب بهتری نسبت به بازسازی آن از ابتدا است.
  • از توسعه‌ی تست‌محور (TDD) برای ایجاد تعداد زیادی تغییر کوچک استفاده کنید.
  • طراحی خوب نرم‌افزار مستلزم جداسازی مسائل مختلف در کد شما و تقسیم کد به ماژول‌ها، کلاس‌ها و فایل‌های کوچکتر است.
  • مرتب‌کردن یک آشفتگی بلافاصله پس از ایجاد آن، آسان‌تر از آن است که بعداً آن را مرتب‌ کنید.

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

آموزش برنامه نویسی با کوئرا کالج
کوئرا بلاگ

اشتراک در
اطلاع از
guest

2 دیدگاه‌
قدیمی‌ترین
تازه‌ترین بیشترین واکنش
بازخورد (Feedback) های اینلاین
View all comments
mahdi ghaemi
mahdi ghaemi
1 سال قبل

سلام
این seams هایی که بالا بهش اشاره شد
چی هستن ؟
سرچ هم کردم مطلب خوبی پیدا نکردم
ممنون

نیما حیدری‌نسب
ادمین
پاسخ به  mahdi ghaemi

سلام.
seam به بخش‌هایی از کد گفته می‌شه که در اون چند تا کد یا رفتار مخلتف قابل جایگزینی هستن. مثلاً اگر پارامتر یه تابعی از نوع یه interface باشه، می‌شه پیاده‌سازی‌های مختلفی از اون interface به تابع پاس داد.