کد بدون ایراد تقریبا وجود نداره. معیارهای کد خوب اینقدر متفاوت و زیادند که عملا نمیشه کدی رو پیدا کرد که در یک یا چند معیار نقص نداشته باشه. این رو گفتم که بدونیم ایراد گرفتن از کد دیگران، یا کدهای قدیمی خودمون شاید سادهترین کاری باشه که بشه انجام داد. پس اگه میخوایم از یه کد ایراد بگیریم، باید خیلی دلیلهای موجهی برای نسخه جایگزینش داشته باشیم. صرف اینکه نواقص کد فعلی رو شناسایی کنیم، دلیل نمیشه که شروع کنیم به بازنویسی کد. چون احتمالا با این شیوه، کد جدید هم خودش تبدیل میشه به یه «کد بد» جدید و صرفا کلی هزینه زمانی و مالی روی دست صاحبان پروژه گذاشتیم.
قبل از اینکه بخوام بگم چه نکاتی در بازنویسی کد اهمیت دارن، میخوام در مورد نشانههای کد خوب صحبت کنم. در مورد «کد خوب» اگه تو اینترنت جستجو کنیم احتمالا کلی آیتم کوچیک و بزرگ لیست میشه که هر کدومش سر جای خودش میتونه مهم باشه. اما من اینجا سعی میکنم آیتمهایی رو ذکر کنم که بر اساس تجربه و دیدگاه شخصی خودم مهم هستن، از جمله:
- کد واضح باشه. کد واضح، به کدی گفته میشه که دو تا سوال رو جواب بده، اول اینکه «کد داره چیکار میکنه» و دوم اینکه «کد چرا داره این کار رو میکنه». بهش Clarity هم گفته میشه که اینجا در موردش بیشتر توضیح داده شده، میتونید مطالعه کنید.
- کد یکپارچه باشه. کدی که ساختارش بد باشه، اما یکپارچه باشه، یعنی اون ساختار بد در همه جای پروژه رعایت شده باشه، به مراتب بسیار بهتر از کدی هست که دچار «شلختگی ساختاری» باشه. به شخصه پروژههایی داشتم که دچار شلختگی ساختاری بودن، تو این دست پروژهها هر خط کد جدید نوشتن یعنی اضافه کردن یه کد کثیف جدید. چون هیچ ساختار و نظم واحدی رو دنبال نمیکنه و معلوم نیست بر طبق کدوم استاندارد داره نوشته میشه. به این موضوع Consistency هم میگن که از نظر من جز مهمترین ویژگیهای کد خوب هست.
- کد ساده باشه. ساده بودن جنبههای مختلفی داره، یه کد میتونه الگوریتم و راهحل سادهای رو برای حل مسأله انتخاب کرده باشه و یا اینکه یک اینترفیس و API ساده به کلاینت ارائه کنه. خیلی وقتها شما نمیتونید هر دو نوع سادگی رو با هم داشته باشید و باید به عنوان یه trade-off بهش نگاه کنید. فارغ از اینکه تصمیم گرفتید پیادهسازی ساده داشته باشید و یا API ساده ارائه بدید، واقعا سعی کنید که کد نوشته شده حداقل در یکی از این دو جنبه ساده باشه.
- کد تست کافی داشته باشه. نوشتن تست برای هر پروژهای تقریبا ضروریه. اینکه اولش تست بنویسید یا آخرش بستگی به تیم و شرایط پروژه داره که میتونید در موردش فکر کنید، اما فارغ از تصمیم نهایی، لطفا برای کدهاتون حتما تست بنویسید. اینکه از کلمه «کافی» استفاده کردم به این دلیل هست که انواع مختلفی از تست وجود داره، مثل unit test و integration test و end-to-end test که هر کدوم سر جای خودشون کاربرد دارن. یه پروژه لازم نیست همه این تستها رو داشته باشه. و اساسا بهتره بگم یه پروژه بهتره یه ترکیبی از این تستها رو داشته باشه. مثلا میگم، اگه تو پروژهتون یه سری functionality رو به صورت یه ماژول یا پکیج به اسم utility و غیره دارید (که با چنین نامگذاریهایی مخالفم البته…) بهتره برای این پکیج unit test بنویسید. چون توابع موجود در این پکیج یک سری پردازش انجام میدن که وابستگی خاصی به متغیرهای محیطی نداره، و صرف اینکه ورودی و خروجی همون تابع تست بشه کافیه. اما در قسمتی از پروژه اگه دارید با یه سرویس بیرونی صحبت میکنید مثل دیتابیس یا یه third-party service، اونجا بهتره که integration test نوشته بشه. چون یه عامل بیرونی وجود داره که صحت عملکرد رو تحت تاثیر قرار میده، پس بهتره که حتما خروجی عامل بیرونی هم در نظر گرفته بشه. و در آخر، اگه دارید یه سری api رو به کلاینتها serve میکنید، برای controller یا handlerهاتون میتونید یه سری end-to-end test بنویسید. تو این دسته از تست، همه قسمتهای مختلف سرویس از جمله قسمتهای بیرونی مثل دیتابیس درگیر میشن و شما به طور کامل رفتار سیستم رو تست میکنید.
- کد وابسته نباشه. کد وابسته کدی میشه که به شدت به عوامل و انتخابهای خارجی وابسته باشه. مثلا این طبیعیه که کدی که مینویسیم اطلاعات پردازش شده رو در یه جایی مثل دیتابیس ذخیره کنه. اما این اصلا طبیعی نیست که کد به نحوی نوشته شده باشه که اگه بخوایم محل ذخیرهسازی داده رو از دیتابیس MySQL به MongoDB تغییر بدیم، این کار امکانپذیر نباشه یا با هزینه زیادی قابل انجام باشه. مدیریت وابستگیها در کد به شدت در اینکه چقدر اون کد قابل نگهداری هست و تغییرات در آینده به راحتی قابل انجام هست، تاثیر داره.
- کد قابل مشاهده باشه. کد قابل مشاهده یا اصطلاحا کد observable کدی هست که وقتی روی سرور در دسترس کلاینتها و کاربران قرار گرفت ما بتونیم به راحتی بر وضعیت سرویس نظارت کنیم. سه عامل مختلف معمولا در این قضیه تاثیر گذار هستن: یکی metrics هست، یکی logs و دیگری traces که در اینجا میتونید بیشتر در موردش مطالعه کنید.
- کد کارآمد باشه. کد کارآمد یعنی کدی که کارش رو دقیق انجام بده، در حد امکان سریع انجام بده، و در حین انجام از منابع سیستم به صورت بهینه استفاده کنه.
- کد ابری باشه. در سالهای اخیر به دلیل پیچیدگی فرآیند تولید و ارائه محصولات نرمافزاری، زیرساختهای ابری مورد توجه قرار گرفتن. شرکتهای بزرگ مثل گوگل و آمازون سرویسهای ابری متنوعی در مقیاسهای مختلف ارائه میدن. کد ابری به کدی میگن که ملاحظات دنیای ابری رو رعایت کنه. مثلا یکی از معیارها اینه که کدی که نوشته میشه stateless باشه، چون تو این حالت اجرا کردن چندین نسخه از کد روی چند ماشین مختلف برای تحمل لود زیاد در زمانهای اوج مصرف کار راحتیه. به جز در نظر گرفتن ملاحظات ابری، بهتره که در کد نوشته شده جایی رو هم برای پروسه CI/CD در نظر بگیریم. تست و استقرار کد در دنیای ابری به خصوص برای Microserviceها فرآیند پیچیدهای هست که به کمک فرهنگ DevOps و پروسههای CI/CD قابل تسهیل شدنه. پس بهتره که کد نوشته شده شامل کانفیگهای مورد نیاز CI/CD هم باشه.
قطعا آیتمهای خیلی بیشتری رو میشه برای «کد خوب» ذکر کرد، اما این موارد از نظر من جز مهمترین آیتمها هستند. بعد از اینکه مطمئن شدیم یه سری معیار مناسب برای تفکیک «کد خوب» از «کد بد» داریم، میتونیم کد فعلی پروژه رو ارزیابی کنیم و بر طبق معیارهامون اشکالات موجود رو لیست کنیم. بازنویسی کد یه سری مقدمات داره، که بهتره حتما بهشون توجه کنیم. از جمله:
- انتخاب معیارهای کیفیت. خوب یا بد بودن یک سیستم نرمافزاری باید از جنبههای مختلفی ارزیابی بشه. یک سیستم میتونه از نظر امنیتی بسیار امن باشه، اما از نظر بهینگی مصرف منابع اصلا بهینه نباشه. نوشتن سیستمی که از نظر همه معیارهای مهندسی نرمافزار کیفیت خوب داشته باشه تقریبا غیرممکنه. بنابراین شما باید بین معیارهای مختلف یک اولویتبندی داشته باشید و بعدش کد نوشته شده رو ارزیابی کنید.
- تخمین زمان و هزینه. اینکه بازنویسی بخشی از کد که ایراد داره چقدر زمان و هزینه لازم داره تاثیر زیادی در انتخاب اولویتهای بازنویسی داره. تصور کنید که شما بنا به دلایلی تصمیم گرفتید شیوه نوشتن پیام خطا در کدهارو تغییر بدید. این تغییر احتمالا همه بخشهای مختلف کد رو دچار تغییر میکنه. باید ارزیابی کنید که آیا چنین تغییری لازمه؟ چه میزان زمان و هزینه لازمه برای این تغییر؟
- تخمین اثربخشی تغییر. در مثال بالا که در مورد تغییر شیوه نوشتن پیامهای خطا در کد صحبت کردم، علاوه بر تخمین زمان و هزینه شما لازم دارید میزان اثربخشی رو هم در نظر بگیرید. اینکه این بازنویسی چه میزان اثربخش هست بستگی به عوامل مختلف داره، ممکنه نرمافزار مذکور یه سیستم مدیریت خطا باشه، خب تو این سیستم احتمالا تغییر مورد نظر میتونه اثربخشی زیادی داشته باشه. ممکن هم هست که سرویس مد نظر صرفا جهت اطلاع توسعهدهنده یه سری خطاها رو در کنسول نمایش میده، خب در این مورد میزان اثربخشی تغییر بسیار پایینه.
- امکانسنجی تغییر. امکانسنجی تغییر یعنی اینکه از نظر عملیاتی با توجه به دانش فنی تیم، اولویتبندی تسکها و در نظر گرفتن محدودیتهای بیزینس، تغییر مورد نظر واقعا چه میزان شدنیه. یادمون نره عموما ما همیشه در حال نوشتن «کدهای کثیف آینده» هستیم. اگه در مورد فرآیند بازنویسی کد وسواس به خرج بدیم، در یه چرخه بیپایان قرار میگیریم که نتیجهای جز پشیمانی و شکست نخواهد داشت.