در این آموزش میخواهیم در مورد clean code و نحوهی بازنویسی و تبدیل کدهای آشفته و کثیف به کد تمیز صحبت کنیم. هدف اصلی بازنویسی، مبارزه با بدهی فنی است. بازنویسی، آشفتگی را به کد تمیز و طراحی ساده تبدیل می کند. بسیار خوب! پس بیایید تا ببینیم clean code چیست؟ در اینجا به برخی از ویژگی های مهم آن اشاره می کنیم:
clean code چیست؟
- clean code برای برنامه نویسان دیگر قابل درک است. صراحتا منظور ما الگوریتم های فوق العاده پیچیده نیست و دربارهی آنها صحبت نمیکنیم. اما مواردی مانند نامگذاری بد متغیرها، کلاسها و متدهای طولانی و متورم که چندین کار را انجام میدهند، اعداد جادویی و تمام مواردی ازاین دست، همه باعث میشود کد شلخته شود و درک آن دشوار باشد. کد تمیز تا حد امکان از این موارد دوری میکند.
- کد clean شامل تکرار نیست. هر بار که باید تغییری در یک کد ایجاد کنید که در چند جای دیگر برنامه تکرار شده است، باید به یاد داشته باشید که همان تغییر را در سایر قسمت های تکرار شده ایجاد کنید. این تکرار، باعث افزایش بار شناختی و کاهش سرعت پیشرفت می شود.
- کد clean حاوی حداقل تعداد کلاس ها و سایر اجزا است. واضح است که حجم کمتر کد، موارد کمتری برای در ذهن نگه داشتن دارد پس به راحتی قابل فهم است. کد کمتر به معنای نگهداری کمتر است. کد کمتر باگ کمتری دارد. به خاطر داشته باشید که کد یک مسئولیت است، آن را کوتاه و ساده نگه دارید.
- کد clean تمام تست ها را با موفقیت پشت سر می گذارد. حتی زمانی که ۹۵ درصد از تست های شما pass شده باشند، باید بدانید که کد شما هنوز کثیف است. زیرا ۵ درصد از تست ها ناموفق هستند و این نشان دهندهی این است که بخشی از پروژه درست کار نمیکند. وقتی تست پوشش یا همان code coverage به میزان ۰٪ است، می دانید که دچار مشکل شده اید. این وضعیت نشانگر این است که تستهای شما هیچ بخشی از کدهای شما را تست نمیکنند. تنها زمانی که code coverage به عدد ۱۰۰ درصد برسد، میتوانید اطمینان داشته باشید که تمام قسمت های کد شما در تست ها ارزیابی شده اند و مجموعه تست های شما تمام بخش های کد را درگیر میکند.
- کد clean برای نگهداری ساده تر و ارزانتر است. گر چه ابتدای کار ممکن است معکوس به نظر برسد اما با گذشت زمان و بزرگتر شدن پروژه، خواهید دید که کد تمیز بسیار ساده تر و ارزانتر از کدکثیف قابل نگهداری است.
بدهی فنی
همانطور که در ابتدای این مطلب اشاره کردیم، بدهی فنی جایی برای تولد کد کثیف است. همهی ما میدانیم که برنامه نویسان تلاش خود را می کنند تا از ابتدای پروژه کدهای عالی بنویسند. احتمالاً برنامه نویسی وجود ندارد که عمداً کدهای کثیف که به ضرر پروژه هستند بنویسد. اما چه اتفاقی می افتد که کد تمیز، کثیف می شود؟
استعارهی «بدهی فنی» در رابطه با کد کثیف در ابتدا توسط وارد کانینگهام معرفی شد. بدهی فنی مانند بدهی مالی در دنیای واقعی است. اگر از بانک وام بگیرید، این توانایی را خواهید داشت که سریعتر خرید کنید. شما برای تسریع فرآیند، هزینه اضافی می پردازید یعنی نه تنها اصل پول را به بانک پس میدهید، بلکه سود اضافی وام را نیز پرداخت خواهید کرد. نیازی به گفتن نیست که با این روش وام گرفتن، اگر خود را کنترل نکنید، ممکن است آنقدر پول قرض بگیرید که میزان اقساط وامها از کل درآمد شما بیشتر شود و بازپرداخت کامل اقساط برای شما غیرممکن شود. در چنین وضعیتی شما از لحاظ مالی ورشکسته میشوید.
همین اتفاق ممکن است در دنیای برنامه نویسی هم اتفاق بیوفتد. می توانید به طور موقت بدون نوشتن تست برای Feature های جدید سرعت کار خود را افزایش دهید. یا حتی برخی اصول کدنویسی تمیز را موقتا زیر پا بگذارید. در ابتدای مسیر که پروژه هنوز کوچک است، همه چیز به نظر خوب میرسد. مشاهده میکنید که سرعت انجام کارها چندین برابر شده و حتی ممکن است از این سرعت عالی خود برداشت های غلط کنید. خودتان را برنامهنویسی سریع و قدرتمند بدانید که هر کاری را با سرعتی باورنکردنی انجام میدهد. تعریف و تمجید اطرافیان نیز ممکن است شما را گول بزند. اما این کار به تدریج سرعت پیشرفت شما را کند می کند. در ابتدا این کندی خیلی مشهود نیست. مثلا یک ویژگی جدید تعریف میشود و شما سریع دست به کار میشوید و کدنویسی میکنید، اما پروژه با اضافه شدن کدهای جدیدتان دچار مشکلاتی میشود. در نتیجه مجبور میشوید زمانی بیشتر برای سازگاری کدهای جدید با کدهای قدیمیتر صرف کنید. این زمان معادل همان بهرهی وام است.
خبر بد این است که مشکل به همینجا ختم نمیشود. مدیران شما که دیگر به توانایی ها و قابلیت ها و از همه مهمتر، سرعت بالای شما ایمان آوردهاند، شروع به کشف و تولید Feature های جدید میکنند و بر اساس سوتفاهمی که برایشان ایجاد شده، توقع دارند که شما در کمترین زمان ممکن این امکانات جدید را به پروژه اضافه کنید. اگر شما دچار غرور کاذب شده باشید یا برای اینکه بخواهید همه را راضی نگهدارید، بدترین راه ممکن، یعنی ایجاد بدهی فنی بیشتر را انتخاب میکنید. پس از گذشت مدتی، مشاهده میکنید که دیگر توان راضی نگهداشتن مدیرتان را نخواهید داشت زیرا کد پروژه به حدی کثیف شده که اضافه کردن چیزی به آن ممکن است بخشهای زیادی را از کار بیاندازد. در این وضعیت ممکن است حس کنید که دیگر عملکرد و بهرهوری گذشته را ندارید یا از لحاظ روحی آسیب دیدهاید و تمرکز سابق را ندارید. حتی اگر بخواهید از این حس فرار کنید، دیر یا زود مدیر مستقیمتان یا مدیر منابع انسانی در این باره با شما صحبت خواهند کرد. دیگر نسبت به خودتان هم احساس شرمندگی دارید. هر دفاعی که از خودتان بکنید، مورد قبول دیگران واقع نمیشود. آنها دائم وضعیت فعلی را با ماههای گذشته مقایسه میکنند و شما را مسبب این نابسامانیها میدانند. اگر از بدهی فنی هم بگویید، پاسخ آنها این است که ما تصور نمیکردیم که بدهی فنی چنین آسیبی به پروژه بزند و باز شما را مقصر میدانند که چرا عواقب این موضوع را زودتر به آنها اعلام نکردید.
بدانید برای برون رفت از این وضعیت، راهی میانبری وجود ندارد. مجبور خواهید شد تا شروع به بازپرداخت بدهی فنی کنید. یعنی شروع به نوشتن تستها کنید و مواردی که دارای مغایرت هستند را اصلاح کنید. اصول کدنویسی تمیز را بر روی کد اعمال کنید و در نهایت به یک برنامه با کد تمیز برسید. البته اگر این فرصت را داشته باشید و با دعوا از شرکت اخراج نشوید!
اما همانطور که بارها شنیدهاید، پیشگیری بهترین درمان است. در دنیای واقعی همیشه بهتر است خود را از رسیدن به این وضعیت دور کنید. از همان روزهای آغازین پروژه اگر مدیران از شما میخواهند که با سرعت کدنویسی کنید، یا زیر بار این مسئولیت خطرناک نروید یا اینکه بدهی فنی را گوشزد کنید. همانطور که ممکن است تجربه کرده باشید، در روزهای کدنویسی کثیف و با سرعت همه خوشحال و راضی هستند. حتی از شما تعریف و تمجید میکنند. اما وقتی برنامه به یک منجلاب تبدیل شد، همه شانه خالی خواهند کرد و در نهایت کسی که سرزنش یا اخراج خواهد شد، شما هستید. کدنویسی کثیف شما را به جایی میرساند که عملا توسعهی پروژه به طور کلی متوقف شود و بدیهی است که در چنین شرایطی کسی که بی کفایت و اضافی به نظر میرسد، شما هستید!
علل بدهی فنی
فشار تجاری
گاهی اوقات ممکن است شرایط کسب و کار، شما را مجبور کند که Feature ها را قبل از اتمام کامل به محیط production ببرید. در این حالت، وصله ها و پینههایی در کد ظاهر می شوند تا قسمت های ناتمام پروژه پنهان شوند.
عدم درک عواقب بدهی فنی
گاهی اوقات کارفرمای شما ممکن است درک نکند که بدهی فنی تا آنجایی بهره دارد که سرعت توسعه را با انباشته شدنش کاهش ندهد. از آنجایی که مدیریت، ارزش پرداخت بدهی فنی را نمیداند، برای اختصاص زمان تیم به امر بازنویسی و اصلاح نیز همراهی نمیکند.
ناتوانی در مبارزه با انسجام دقیق اجزا
این مورد زمانی اتفاق میافتد که بخشهای مختلف کد وابستگی شدیدی به همدیگر داشته باشند و پروژه شبیه یک محصول monolithic باشد به جای اینکه مجموعهای از ماژولها باشد که با هم در تعامل هستند. در این صورت، هر گونه تغییر در یک قسمت از پروژه، سایر قسمت ها را تحت تأثیر قرار می دهد. توسعهی تیمی به شدت دشوارتر می شود زیرا جدا کردن کار تک تک اعضا دشوار است.
عدم وجود آزمایشات
فقدان بازخورد فوری، مشوق راهحلهای سریع اما پرخطر است. در بدترین موارد، این تغییرات بدون هیچ گونه آزمایش قبلی، مستقیماً در production پیادهسازی و deploy میشوند. عواقب آن می تواند فاجعه بار باشد. به عنوان مثال، یک Hotfix با ظاهری بیخطر ممکن است یک ایمیل آزمایشی عجیب به هزاران مشتری ارسال کند یا حتی بدتر از آن، کل پایگاه داده را پاک یا خراب کند.
عدم وجود مستندات
این امر ورود افراد جدید به پروژه را کند می کند و در صورتی که برنامه نویسان قدیمی پروژه را ترک کنند، می تواند توسعه را متوقف کند.
عدم تعامل بین اعضای تیم
اگر پایگاه دانش در سراسر شرکت توزیع نشود، افراد در نهایت با درک قدیمی از فرآیندها و اطلاعات مربوط به پروژه کار خواهند کرد. این وضعیت زمانی تشدید می شود که توسعه دهندگان جوان توسط مربیان خود آموزش نادرست دیده باشند.
توسعه بلند مدت همزمان در چندین branch
این می تواند منجر به انباشت بدهی فنی شود که پس از ادغام تغییرات افزایش می یابد. هرچه تغییرات بیشتری به صورت جداگانه ایجاد شود، کل بدهی فنی بیشتر می شود.
بازنویسی با تاخیر
الزامات پروژه دائماً در حال تغییر است و در برخی مواقع ممکن است مشخص شود که بخشهایی از کد منسوخ شده است، دست و پا گیر شده است و باید برای برآورده کردن نیازهای جدید دوباره طراحی شود. از سوی دیگر، سایر برنامه نویسان پروژه هر روز کد جدیدی می نویسند که با قطعات منسوخ شده کار می کند. بنابراین، هر چه refactoring بیشتر به تأخیر بیفتد، کدهای وابسته بیشتری باید در آینده دوباره نوشته شوند.
عدم نظارت بر انطباق
این حالت زمانی اتفاق میافتد که هرکسی که روی پروژه کار میکند، آنطور که مناسب میداند، کد مینویسد (یعنی به همان روشی که آخرین پروژهشان را نوشتند).
بی کفایتی
این زمانی است که توسعه دهنده نمی داند چگونه کد مناسب بنویسد.
چه زمانی کد را بازنویسی کنیم؟
قانون سه
۱- وقتی برای اولین بار کاری را انجام می دهید، فقط آن را انجام دهید.
۲- وقتی برای بار دوم کاری مشابه انجام میدهید، از اینکه مجبور به تکرار آن هستید بغض کنید اما به هر حال همان کار را انجام دهید.
۳- وقتی برای سومین بار کاری مشابه را انجام میدهید، بازنویسی را شروع کنید.
هنگام اضافه کردن یک Feature
Refactoring به شما کمک می کند کد دیگران را درک کنید. اگر مجبور به مقابله با کد کثیف شخص دیگری هستید، ابتدا سعی کنید آن را اصلاح کنید. درک کد تمیز بسیار ساده تر است. شما آن را نه تنها برای خودتان، بلکه برای کسانی که بعد از شما از آن استفاده می کنند نیز بهبود میدهید.
Refactoring اضافه کردن ویژگی های جدید را آسان تر می کند. ایجاد تغییرات در کد تمیز بسیار ساده تر است.
هنگام رفع باگ
باگهای موجود در کد دقیقاً مانند مشکلات موجود در زندگی واقعی رفتار می کنند: آنها در تاریک ترین و کثیف ترین مکان های کد زندگی می کنند. کد خود را تمیز کنید، باگها عملا خود را نمایان خواهند کرد.
مدیران از بازنویسی پیشگیرانه قدردانی می کنند زیرا نیاز به انجام بازنویسی مجدد در آینده را از بین میبرند. رئیس های شاد برنامه نویسان را خوشحال می کنند!
در طول code review
بررسی کد ممکن است آخرین فرصت برای مرتب کردن کد قبل از دسترسی عموم به آن باشد. بهتر است چنین بررسی هایی را به صورت دو نفره با یک برنامهنویس دیگر انجام دهید. به این ترتیب می توانید مشکلات ساده را به سرعت برطرف کنید و زمان رفع مشکلات سخت تر را بسنجید.
چگونه کد را بازنویسی کنیم؟
Refactoring باید به عنوان یک سری تغییرات کوچک انجام شود، که هر کدام کد موجود را کمی بهتر می کند و در عین حال برنامه را در حالت کار نگه میدارد. در واقع تغییرات نباید به حدی باشد که تمام یا بخشی از پروژه را از کار بیاندازد.
چک لیست بازنویسی مجدد به روش درست
۱- کد باید تمیزتر شود. اگر کد پس از بازآفرینی به همان اندازه کثیف باقی بماند… خوب، متاسفم، شما فقط یک ساعت از عمر خود را تلف کرده اید. سعی کنید بفهمید چرا این اتفاق افتاده است.
این اتفاق اغلب زمانی رخ میدهد که با تغییرات کوچک از دوبارهسازی فاصله میگیرید و یک مجموعه کامل از بازسازیها را در قالب یک تغییر بزرگ ایجاد میکنید. بنابراین به سادگی تمرکز خود را از دست میدهید، به خصوص اگر محدودیت زمانی هم داشته باشید.
اما ممکن است هنگام کار با کد بسیار شلخته نیز اتفاق بیفتد. هرچه بهبود ببخشید، کد به طور کلی یک فاجعه باقی میماند.در این مورد، ارزش آن را دارد که به بازنویسی کامل بخشهایی از کد فکر کنید. اما قبل از آن باید تست های نوشته شدهای داشته باشید و زمان خوبی را برای بازنویسی کنار بگذارید. در غیر این صورت، با انواع نتایجی که در بخش اول در مورد آنها صحبت کردیم، به پایان خواهید رسید.
۲- نباید در طول بازنویسی عملکرد جدیدی ایجاد شود.
بازنویسی و توسعه مستقیم Feature های جدید را با هم قاطی نکنید. سعی کنید این فرآیندها را حداقل در محدوده commit های خود، جدا کنید.
۳- بعد از بازنویسی، تمام تست هایی که از قبل موجود بود باید مجدد pass شوند.
دو علت وجود دارد که تست ها پس از بازنویسی مجدد خراب می شوند:
- شما در حین بازنویسی خطا کردید. این وضعیت عقلانی نیست: مجدد به سراغ کدهای بازنویسی شده رفته و خطا را برطرف کنید تا تستها به درستی اجرا شوند.
- تست های شما خیلی سطح پایین بود(Unit Test). به عنوان مثال، شما متدهای داخلی کلاس ها را آزمایش میکردید و در جریان بازنویسی برخی از این متدها تغییر کرده و یا حذف شدهاند.
در این مورد، مقصر تستها هستند. میتوانید خود تستها را اصلاح کنید یا مجموعهای کاملاً جدید از تستهای سطح بالاتر بنویسید. یک راه عالی برای جلوگیری از چنین موقعیتی، نوشتن تست های سبک BDD است. BDD با تمرکز روی نیاز مشتری تلاش می کند تا نرم افزاری را که نیاز او را برطرف سازد تولید کرده و تحویل دهد.
برای آشنایی بیشتر با قواعد کدنویسی تمیز میتوانید از منابع زیر استفاده کنید: