دنیای برنامه نویسی با انتشار کتاب Clean Code نوشته Robert C.Martin تغییرات مهمی به خود دید، این کتاب استاندارد هایی برای نوشتن کد ها تعیین کرد که بسیاری از مشکلاتی که برنامه نویس ها با آن ها دست به گریبان بودند را برطرف میکرد. این کتاب، آموزش زبان c یا java نبود، این کتاب برای برنامه نویس هایی نوشته شده بود، که میخواهند کدهایی بنویسند که قابل توسعه باشد، خوانایی بالایی داشته باشد و برای همکارانشان قابل فهم باشد.
امروزه نوشتن کدی که اجرا شود کافی نیست، فاکتور های زیادی برای بررسی کد ها مد نظر قرار گرفته است، مثلا کدی که به خوبی اجرا شود ولی بسیار کثیف و پیچیده نوشته شده باشد، ارزش ندارد، چرا که مدتی بعد، این کد قابل توسعه یا حتی بهبود نیست و باید از ابتدا کد را بنویسیم. یا اگر نامگذاری اولیه کد درست و اصولی نباشد، عملا امکان رفع باگ یا توسعه کد منتفی است و باز هم باید از ابتدا کد را بنویسیم. دنیای برنامه نویسی با انتشار کتاب Clean Code نوشته Robert C.Martin تغییرات مهمی به خود دید، این کتاب استاندارد هایی برای نوشتن کد ها تعیین کرد که بسیاری از مشکلاتی که آن زمان برنامه نویس ها با آن ها دست به گریبان بودند را برطرف میکرد. این کتاب، آموزش زبان c یا java نبود، بلکه برای برنامه نویس هایی نوشته شده بود، که میخواهند کدهایی بنویسند که قابل توسعه باشد، خوانایی بالایی داشته باشد و برای همکارانشان قابل فهم باشد. امروزه یک برنامه نویس خوب از تعداد function هایی که از زبان c حفظ است، سنجیده نمیشود، چرا که این توابع بسیار زیاد هستند و از طرفی با یک سرچ ساده میتوان به اطلاعات بسیار بیشتر از چیزی که که انسان میتواند حفظ کند، رسید، بلکه برنامه نویس های حرفه ای را از میزان خوانایی و سادگی کدهایشان میشناسند.
مارتین فولر از تاثیرگذارترین برنامه نویس های دنیا در جمله ای معروف میگوید: "هر نادانی میتواند کدی بنویسد که کامپیوتر بفهمد، یک برنامه نویس خوب کدی مینویسد که توسط انسان قبل فهم باشد". این جمله باید سرلوحه تمام کسانی باشد که میخواهد برنامه نویس های حرفه ای بشوند، مهم نیست کد شما چقدر سریع است، یا فریمورک شما چقدر قابلیت و توانایی دارد یا شما چه الگوریتم های پیچیده ای را میتوانید پیاده کنید، مهم این است که برنامه ی سریع شما توسط دیگر برنامه نویس ها قابل فهم باشد، مهم این است که فریمورک شما علاوه بر قابلیت های زیاد، توانایی گسترش داشته باشد، یا اگر شما میتوانید الگوریتم های پیچیده ای را طراحی و پیاده سازی کنید، دیگران بتوانند کد شما را بفهمند و کد شما یک بار مصرف نباشد. تمامی صحبت های بالا برای تبیین موضوعی است که اگر نگوییم اصلی ترین، ولی جزو فاکتور های اصلی است که یک برنامه نویس خوب را از سایر برنامه نویس ها جدا میکند. در این مقاله سعی میکنیم ابتدا شما را با کد تمیز آشنا کنیم و ببینیم که اصلا کد تمیز به چه کدی گفته میشود و سپس به نکاتی اشاره میکنیم که به شما کمک میکند که کد خود را به کد تمیز تبدیل کنید.
کد تمیز (Clean Code) چیست؟
احتمالا به تعداد برنامه نویس های روی زمین، تعریف برای کد تمیز وجود دارد. حتی نظرات برنامه نویس های بزرگ هم درباره ی تعریف کد تمیز مشترک نیست. بعضی، قابلیت توسعه را در اولویت قرار میدهند، تعدادی، قابلیت خوانایی برای سایر برنامه نویس ها را در اولویت قرار میدهند، عدهای قابلیت تست نویسی برای برنامه را معیار قرار میدهند و برخی دیگر نوع نامگذاری متغیر ها و توابع را مورد توجه قرار میدهند.
اما شاید هرکدام از این موارد به تنهایی کافی نیست، از نظر نویسنده یک کد خوب یا به اصطلاح یک کد تمیز باید مجموعه ای از این ویژگی ها را داشته باشد، یعنی کد شما همانطور که خوانایی بالایی دارد، باید قابلیت توسعه ی خوبی هم داشته باشد چون در واقع این دو مکمل یکدیگرند، کدی که قابل توسعه نباشد، مفید نیست و کدی که خوانا نباشد قابل توسعه نیست. پس با توجه کردن به فقط یک مورد از موارد بالا نمیتوان کدی تمیز نوشت. در ادامه لیستی از مواردی که برای نوشتن کد تمیز نیاز است را می آوریم، این موارد میتواند شخص به شخص اضافه یا کم شود ولی سعی شده [F-D1] که چارچوب کلی مشترک باشد و برای طیف وسیعی از برنامه نویس ها قابل استفاده باشد.
لیست ویژگی های کد تمیز
1 - طول توابع نباید بیش از 25 تا30 خط شود
توابع شما نباید بیشتر از 25 تا30 خط بشود، به چند دلیل. توابع کوچک به شما کمک میکند که برنامه خود را ماژول بندی کنید، بتوانید به صورت استراتژیک فکر کنید و فلو (Flow)ی برنامه را در ذهن خود بهتر پیاده کنید. توابع بزرگ کار های بیشتری انجام میدهند پس به ورودی های بیشتری هم نیاز دارند، این باعث میشود برنامه شما سخت تر دیباگ شود و همچنین برای توابع بزرگ تر، سخت تر میتوان مورد استفادهی مجدد پیدا کرد، همچنین نام گذاری این توابع سخت تر است چون چند کار را انجام میدهند پس باید نام های بزرگ و زشت برای آنها انتخاب شود. در ادامه به مثال زیر توجه کنید. یک متد داریم که میخواهد یک فایل را برای دانلود در اختیار کاربر قرار دهد:
public function downloadFileForUser ($user, $filename)
{
/** check if file name is lower case */
/** check file name is only letter */
/** check max length of file name */
/** check min length of file name */
/** check the existence of a file with given file name */
/** connect to DB */
/** is file private or public */
/** if file is private does user have access to download the file */
/** close DB connection */
/** load the file from storage */
/** if user have permission to download -> download the file for user */
/** end of function */
}
همانطور که مشاهده میکنید، این متد خیلی طولانی است. هر سطر معادل یکی از کار هایی است که باید در روند متد انجام شود. هر کار میتواند خود بین 2 تا 3 خط کد داشته باشد. فرض کنید که شما با یک متد 50 تا 60 خطی مواجه میشوید. شاید حتی شما زحمت خواندن کل تابع را به خود ندهید، حال فرض کنید اگر شما چنین تابع بلندی بنویسید، کسی که بعد از شما روی این کد کار خواهد کرد چه حالی خواهد داشت.
حال بیاید تکه کد بالا را به سه متد بشکنیم. نتیجه به صورت زیر خواهد بود:
public function downloadFileForUser($user, $filename)
{
$this->validateFileName($filename);
$this->validateUserAccess($filename, $user);
$this->downloadFile($filename);
}
public function validateFileName($fileName)
{
/** check if file name is lower case */
/** check file name is only letter */
/** check max length of file name */
/** check min length of file name */
/** check the existence of a file with given file name */
}
public function validateUserAccess($fileName, $user)
{
/** connect to DB */
/** is file private or public */
/** if file is private does user have access to download the file */
/** close DB connection */
}
public function downloadFile($fileName, $user)
{
/** load the file from storage */
/** if user have permission to download -> download the file for user */
/** end of function */
}
حال به تکه کد بالا نگاه کنید، میزان خوانایی دو کد به هیچ وجه قبل مقایسه نیست. هر بخش از کد قبل به یک تابع جدید تبدیل شد که به راحتی میتوانند بدون دست زدن به تابع اصلی توسعه پیدا کنند.
مثلا فرض کنید بعد ها میخواهید سیستم validate کردن نام فایل (filename) را تغییر دهید در این صورت، تنها کد داخل متد validateFileName را تغییر میدهیم و به کد سایر متد ها و متد اصلی کاری نداریم.
2 – سعی کنید گویا ترین نام را برای اجزای برنامه تان انتخاب کنید
یک نام مناسب کوتاه و کامل است. یعنی در کمترین کلمات، کاملا مشخص میکند که این متغیر حاوی چه داده ای است، یا اینکه تابع چه کاری انجام میدهد. انواع روش های نامگذاری شامل camelCase، PascalCase، snake_case، kebab-case و ... است.
تقریبا زبان های برنامه نویسی مشکلی با استفاده از هیج یک از روش های نامگذاری بالا ندارند، ولی مهم این است که در تمام برنامه از یک روش واحد برای نامگذاری استفاده کنید. مثلا کلاس ها را با PascalCase نامگذاری نکنید و توابع را با snake_case.
اسم گذاری درست، باعث میشود که نیاز شما به کامنت هم کم شود. نکته ای که در اینجا باید به آن اشاره کنیم، این است که نام یک متغیر یا کلاس یا تابع نباید نیاز به فکر کردن و پیدا کردن اسم داشته باشد، اگر برای پیدا کردن اسم هر بخشی از برنامه به تقلا افتاید، یعنی کد شما به اندازه کافی شکسته نشده و به بخش های کوچکتری میتواند تقسیم شود. شاید تابع یا کلاس شما بیش از یک کار را انجام میدهد. در این شرایط حتما باید در طراحی خود بازنگری کنید.
در ادامه چند نمونه نامگذاری صحیح و اشتباه میآوریم:
#wrong
int d; // elapsed time in days
#good
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
یا در مثال زیر مثالی دیگر از نام گذاری بد میبینیم (سعی کنید از نام های قابل تلفظ استفاده کنید):
#wrong
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
};
#good
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;;
private final String recordId = "102";
/* ... */
};
3 - تعداد ورودی های توابع نباید بیشتر از 3 تا 4 تا شود
اگر تابعی بیش از 3 تا 4 ورودی نیاز داشته باشد، یحتمل بیش از 1 کار انجام میدهد. یا اگر کلاسی برای ساخته شدن به بیش از چند مقدار نیاز داشته باشد، چند وظیفه بر عهده دارد. در اینجا هم مانند مورد قبل احتمالا باید بازنگری در طراحی برنامه داشته باشیم و سعی کنیم برنامهمان را به بخش های کوچکتر و ساده تر تقسیم کنیم.
به کد زیر توجه کنید:
public Person createNewPerson(
String lastName,
String firstName,
String middleName,
String salutation,//words that are used as greetings
String suffix,
String streetAddress,
String city,
String state,
boolean isFemale,
boolean isEmployed,
boolean isHomeOwner)
{
// implementation goes here
}
اما آیا واقعا این کلاس و متد فقط یک کار انجام میدهد. حال به کد زیر توجه کنید:
public Person createNewPerson(
FullName fullName,
Address address,
boolean isFemale,
boolean isEmployed,
boolean isHomeOwner)
{
// implementation goes here
}
در واقع ما باید کلاس های بیشتری داشته باشیم، مثل FullName و Address. اکنون تعداد ورودی های متد createPerson کاهش پیدا کرد و دلیل نوشته شدن کد اول این بود که ما چندین وظیفه را بر عهده کلاس person قرار داده بودیم. کلاس person لزومی ندارد که خودش یک نمونه از کلاس های FullName یا Address را بسازد بلکه فقط باید از کلاس های ساخته شده استفاده کند. در واقع دیتا هایی که به هم بسیار مرتبط بودند و میتوانستیم با در کنار هم قرار دادن آنها یک آبجکت جدید بسازیم را دسته بندی کردیم که نتیجه ی آن ساخت کلاس های FullName و Address شد.
4 – از نوشتن کلاس های بزرگ در برنامه تان خود داری کنید
کلاس های بزرگ یعنی اپلیکیشن ما به خوبی به چند بخش تقسیم نشده است. اگر کلاسی خیلی بزرگ شده یعنی بیش از اندازه کار انجام میدهد و به اصطلاح بیش از اندازه میداند. برای رفع این مشکل میتوان از ارث بری از یک کلاس پدر یا اگر طراحی اولیه مان خیلی بد باشد، شکستن به کلاس های جدا و باز طراحی استفاده کرد.
مثلا به قسمت قبل توجه کنید ما ابتدا تمامی کار های مربوط به Person و Address و Name را در کلاس person انجام میدادیم اما بعد از بازنگری این وظایف را بر عهده ی سه کلاس Person و Address و FullName قرار دادیم. از طرفی اکنون در تمام برنامه میتوانیم به کلاس Address دسترسی داشته باشیم مثلا در کلاس Company هم میتوانیم از Address استفاده کنیم. بدین ترتیب اگر بخواهیم تغییری در سیستم ذخیره آدرس های برنامه بدهیم، فقط در یک جا این تغییر را اعمال میکنیم و همه کلاس هایی که از Address استفاده میکنند هم بروزرسانی میشوند.
نوشتن کلاس های بزرگ چندین نتیجه دارد که اولین موردش وابستگی زیاد کد کلاس های مختلف به هم میشود که این خود باعث کاهش توسعه پذیری کد شما میشود. در ثانی اگر چندین و چند کار را در یک کلاس انجام دهید، در هنگام نامگذاری کلاستان به مشکل بر میخورید و بدین ترتیب خوانایی کدتان هم کاهش مییابد. اگر کلاس شما چندین و چند کار انجام دهد، مجبور خواهید بود در جاهای مختلف کدتان از ان استفاده کنید و در نتیجه بخش های مختلف کدتان به یک کلاس وابسته میشود و اگر بخواهید این کلاس را تغییر دهید، با کوچکترین تغییری ممکن است که سایر بخش های کد که از این کلاس استفاده میکنند، از کار بیافتند.
5 – در استفاده از برنامه های Third-Party نهایت دقت را به کار ببرید
در هنگام استفاده از برنامه های third-party توجه کنید که این کار وظایفی را در آینده بر دوش ما میگذارد. به طور مرتب با انتشار نسخه های جدید هر پکیج، ممکن است پشتیبانی از نسخه های قبلی قطع شود. بدین ترتیب ما نیاز به بروزرسانی برنامه مان فقط به دلیل تغییرات در api مربوط به برنامه third-party داریم. یا اگر برنامه third-party سرویس دهی خود را قطع کند، در آن صورت برنامه ما هم از کار میافتد و جایگزین کردن برنامه دیگر ممکن است به این راحتی ها نباشد. به طور کلی فقط در صورتی اقدام به استفاده از برنامه های third-party کنید که زمان و منابع لازم برای توسعه سرویسی مشابه را نداشته باشید، وگرنه هیچ توجیهی وجود ندارد که بخواهید از آن برنامه third-party استفاده کنید.
6 – دیزاین پترن ها را بشناسید و از آن ها استفاده کنید
یکی از مهم ترین توانایی هایی که هر برنامه نویس خوبی باید در خود توسعه دهد و روی آن سرمایه گذاری کند، شناخت و توانایی استفاده از دیزاین پترن ها در پروژه ها است. در واقع دیزاین پترن ها best practice هایی هستند که برای حل یک مشکل مشخص توسعه داده شده اند. عدم استفاده از دیزاین پترن ها در جایی که نیاز است، مانند اختراع کردن دوباره ی چرخ است. البته نباید پافشاری در استفاده از دیزاین پترن ها داشته باشیم، در خیلی از موارد صرفا برای اینکه خود را ملزم به استفاده از دیزاین پترن ها کرده ایم، فقط پیچیدگی برنامه مان را افزایش داده ایم. اگر دیزاین پترن ها را خوب بشناسیم، در جای مناسب خود به خود به سمت استفاده از آن دیزاین پترن سوق داده میشویم.
از جمله مهم ترین دیزاین پترن هایی که بهتر است برنامه نویس ها بلد باشند میتوان به: Factory، Adapter، Proxy و Chain of Responsibility اشاره کرد. همچنین میتوانید با مراجعه به این لینک، از آموزش رایگان و بسیار خوب دیزاین پترن ها استفاده کنید.
7 – همیشه نفر بعدی که از کد شما استفاده میکند را مد نظر بگیرید
همیشه وقتی میخواهید کدی بنویسید، تابعی را نام گذاری کنید یا برای توضیح موردی کامنتی بنویسید، خود را جای برنامه نویسی که بعدا میخواهد کد یا کامنت شما را بخواند بگذارید، این کار ساده ترین و موثر ترین کار برای افزایش خوانایی کدمان است. با این کار خود به خود به سمت استفاده از اسم های دقیق تر سوق داده میشویم و در همین راستا از نوشتن کلاس ها و تابع های پیچیده که چند وظیفه دارند خودداری میکنیم، بدین ترتیب هر چه بیشتر به سمت نوشتن کد تمیز سوق پیدا میکنیم.
8 – یاد بگیرید کد های دیگران را بخوانید
خواندن کد های دیگران میتواند مهم ترین منبع برای یادگیری تمیز کد نوشتن باشد. همانطور که ضرب المثل قدیمی میگوید "ادب از که آموختی از بی ادبان". وقتی که ما کد دیگران را بخوانیم و با اسم تابعی مواجه شویم که هیچ ارتباطی با کارکرد آن ندارد، و بعد از 3 ساعت بالا و پایین کردن کد، بفهمیم تابع چه کار انجام میدهد، از آن پس در هنگام نوشتن کد خودمان توجه بیشتری به نام های توابع، کلاس ها و متغیر هایمان میکنیم. همینطور اگر کد های یک برنامه نویس حرفه ای را بخوانیم و خیلی سریع بفهمیم هر بخش از کد چه کار میکند و بدون مشکل خاصی بتوانیم کد را توسعه دهیم، در هنگام نوشتن کد های خودمان هم سعی میکنیم مانند آن برنامه نویس حرفه ای قواعد های نام گذاری و کامنت نویسی را رعایت کنیم.
میتوانید با جست و جوی best practice های مربوط به زبان برنامه نویسی خود هم کد های دیگران را بخوانید و هم اینکه بهترین نمونه کد ها را برای حالت های مختلف ببینید.
9 – ساده ترین و کامل ترین راه را برای حل مشکل استفاده کنید
وقتی در حال توسعه و طراحی ساختار پروژه هستید، سعی کنید ساده ترین و در عین حال کاملترین راه حل را انتخاب کنید، همانطور که جمله معروف میگوید "زیبایی در سادگی است". شما میتوانید با استفاده از انبوهی از دیزاین پترن ها و architectural pattern ها برنامه خود را خیلی پیچیده طراحی و پیاده سازی کنید. بدین ترتیب کار خود را برای توسعه و رفع مشکلات احتمالی برنامه در آینده سخت کردهاید. ساده ترین راه حل لزوما کثیف ترین و ناکارامد ترین راه حل نیست. وظیفه شما است که با داشتن یک سری از فاکتور ها مثل خوانایی کد و توسعه پذیری، ساده ترین روش ها را پیاده سازی کنید. در واقع هدف عالی یک برنامه نویس یافتن یک توازن پایدار بین سادگی، اثرگذاری و توسعه پذیری است و همین نکته خود باعث میشود شما کدی تمیز بنویسید و عمده تفاوت برنامه نویس ارشد و کاربلد با یک برنامه نویس کم تجربه همین توانایی در یافتن توازن مذکور است..
10 – سعی کنید، آنقدر خوب کد بنویسید که به کامنت نیازی نداشته باشید
یک جمله در کتاب Clean Code گفته شده که، یک برنامه نویس برای پوشش دادن مهمل کاری خود برای نوشتن یک کد تمیز از کامنت استفاده میکند. شاید در نظر اول مخالف باشید ولی اگر کد خود را در حد اعلای سادگی و تمیز بودن بنویسید، دیگر نیازی به نوشتن کامنت ندارید، نام متغیر ها، کلاس ها و توابع، خود گویای عملکرد هر کدام است و کامنت نوشتن فقط توضیح واضحات است. پس آنقدر تمیز کد بنویسید که نیاز به کامنت نوشتن نداشته باشید:D
11 – رعایت قوانین SOLID مهم تر از استفاده از design pattern ها است
شما ممکن است هنگام طراحی پروژه، از توجه و استفاده از دیزاین پترن ها باز بمانید، این نکته اگر چه ممکن است در آینده در بحث توسعه پذیری کد شما اثر گذار باشد ولی آن را مختل نمیکند. در عوض اگر شما از صحیح ترین و مناسب ترین دیزاین پترن ها استفاده کنید ولی آن را بدون استفاده از قوانین SOLID پیاده کنید، نه تنها کد شما توسعه پذیر نخواهد بود بلکه حتی خوانایی هم نخواهد داشت. پس بهتر است از هر دو ابزاری که در اختیار ما قرار گرفته است استفاده کنیم تا از همه ی پتانسیل برنامه مان بهرهمند شویم. البته دیزاین پترن ها مغایرتی با قوانین SOLID ندارند و حتی بعضی از آن ها بدون رعایت قوانین SOLID قابل پیاده سازی نیستند. پس توجه کنید که استفاده همزمان از دیزاین پترن های و قوانین SOLID میتواند ترکیب برنده ی شده باشد.
12 – قانون DRY را فراموش نکنید
قانون Don’t Repeat Yourself یا به اختصار DRY، به سادگی میتواند نقش به سزایی در تمیزی و بهبود وضعیت کد شما ایفا کند. در دنیای کد نویسی تمیز، تکرار کد هیچ معنا و جایگاهی ندارد. اگر تکه کدی تکرار میشود، در صورت امکان باید به صورت یک تابع تعریف شود، اگر مقداری به صورت hard code استفاده میشود باید به عنوان یک متغیر constant تعریف شود. فرض کنید که شما یک تکه کد را که عمل ذخیره ی لاگ در فایلی را انجام میدهد، در چندین جای برنامه خود استفاده کردهاید. حال اگر بخواهید فرمت ذخیره سازی لاگ ها را تغییر دهید، باید تمام جاهایی که این تکه کد استفاده شده را پیدا کنید و آن را تغییر دهید که کار زمان بر و همراه با درصد خطای بالا است. اما اگر این تکه کد را به صورت یک تابع درآورید، آنگاه هر جا که بخواهید فقط با صدا زدن آن تابع، کار مد نظر شما انجام میشود و اگر بخواهید تغییری در سیستم لاگ گیری برنامه ایجاد کنید، آنگاه فقط کافی است، کد تابع را یک مرتبه تغییر دهید.
13 – بخش های مرتبط را encapsulate کنید
به طور کلی هر جای برنامه که میتوانید، ترجیحا از encapsulation استفاده کنید. مثلا اگر برنامه شما، سرویسی به رستوران ها و مشتریانشان میدهد، میتوانید کلاس های restaurant و user داشته باشید و با تعریف کردن ویژگی های هر مورد به صورت property یا متد آن ها را به صورت دو موجود مستقل در بیاورید. متد encapsulation میتواند در سرتاسر کد استفاده شود مثلا اگر شرطی دارید به این صورت ()timer.hasExpired() && !timer.isRecurrent
، میتوانید آن را به صورت یک متد تعریف کنید و چنین استفاده کنید (shouldBeDeleted(timer
، شرط بالا چک میکند که ایا تایمری به اتمام رسیده است یا نه و اگر به اتمام رسیده باشد و تکراری برای تایمر ست نشده باشد، شرط true میشود. حال میتوانیم به جای این شرط متد shouldBeDeleted
را قرار دهیم که با چک کردن همان شرط خروجی true یا false را در اختیار ما قرار می دهد. این متد همان کار را انجام میدهد ولی آن شرط اولیه به صورت یک متد، کپسول و خلاصه شده است و میتوان با صدا زدن آن همان کار را انجام داد. همین اتفاق برای کلاس های Restaurant و User میتواند اتفاق بیافتد که ویژگی های مرتبط در کنار هم یک کلاس را بسازد و ما با صدا زدن ان کلاس بتوانی به صورت یکجا به آن ویژگی ها دسترسی داشته باشیم. بحث encapsulation بسیار گسترده است و توضیحات آن در حوصله ی این مطلب نمیگنجد، نویسنده توصیه میکند که با سرچ در گوگل درباره ی encapsulation مطالعه کنید.
14 – قانون Single Responsibility راهنمای شما در مسیر نوشتن کد تمیز است
هیچ کلاس و متدی نباید بیشتر از یک وظیفه داشته باشد. یکی از راه های تشخیص اینکه آیا این نکته را رعایت کرده ایم یا نه مراجعه به اسم متد یا کلاس است. اگر در هنگام نامگذاری کلاس یا متدی با مشکل مواجه شدیم و در همان لحظه اول نتوانستیم عملکرد و وظیفه ی کلاس و متدمان را تشخیص دهیم یعنی به شکستن و ایجاد کلاس ها یا متد های بیشتری نیاز داریم. اینکه هر کلاس یا متد وظیفه ی مشخص مربوط به خود را داشته باشد، از چند جهت برای ما مفید است. اگر بخواهیم تغییری در عملکردی از برنامه مان بدهیم، خیلی راحت میدانیم کدام کلاس یا متد باید تغییر کند و چون این کلاس یا متد یک وظیفه بیشتر ندارد، تغییری که در آن میدهیم باعث از کار افتادن آن متد یا کلاس نمیشود، استفاده ی دیگری که میکنیم این است که در هنگام نامگذاری با چالش های کمتری مواجه میشویم و در نتیجه ی نامگذاری بهتر، کد ما تمیز تر میشود و خوانایی بیشتری دارد و در هنگام توسعه ی کد با مشکلات کمتری مواجه میشویم چون بخش های مختلف برنامه وابستگی اضافی به هم ندارند.
15 – قانون Open/Closed را برای راحتی آینده خود رعایت کنید
این قانون بیان میکند که یک کلاس باید طوری طراحی و پیاده سازی شود که فقط زمانی نیاز به ایجاد تغییر در کلاس داشته باشیم که بخواهیم چیزی به آن بیافزاییم. مثل تعریف کردن متد یا متغیر جدید، نباید متد ها یا متغیر هایی که از قبل تعریف شده است را تغییر دهیم. رعایت این نکته به ما کمک میکند که وقتی از یک کلاس یا تابعی در جایی از کد استفاده کردیم، مطمئن باشیم که همیشه کار میکند چون ما کلاس ها و متد هایی که در آن تکه کد استفاده شده را ممکن است گسترش دهیم ولی هیج گاه متد های قبل را تغییر نمیدهیم.
16 – قانون Dependency Inversion میتواند توسعه پذیری برنامه تان را متحول کند
استفاده از این قانون باعث میشود که بخش های مختلف برنامه ما به هم وابسته نشوند. به طور به خصوص کامپوننت های سطح بالای برنامه به کامپوننت های سطح پایین برنامه ما وابسته نمیشوند. بهترین راه درک این مورد ذکر یک مثال است. فرض کنید ما یک فرم ثبت نام کاربر داریم. وقتی این فرم توسط کاربر ارسال میشود، در برنامه ما یک متد صدا زده میشود. در این متد ما میخواهیم با استفاده از بخش data access layer(DLA) یک رکورد در دیتابیس ذخیره کنیم. اگر به طور مستقمیم DLA را در کنترلر استفاده کنیم، اگر بعد ها بخواهیم دیتابیس خود را مثلا از mysql به mongo تغییر دهیم با مشکلات مختلفی مواجه میشویم. بدین منظرو یک interface برای DLA تعریف میکنیم و در کنترلر خود از ان interface استفاده میکنیم. با این کار لایه ی controller از لایه ی DLA مستقل میشود و کنترلر دیگر توجهی ندارد که DLA به چه صورت implement شده است و ما بعد ها میتوانیم دیتابیس خود را تغییر دهیم یا اینکه طریقه و پروسه ی ذخیره کردن رکورد در دیتابیس را عوض کنیم بدون اینکه نیازی داشته باشیم که کنترلر را تغییر دهیم. تاثیر رعایت این قانون در تمیزی کد ما آن است که قابلیت تست نویسی برای کدمان را به شدت افزایش میدهد و از طرفی قابلیت توسعه پذیری بیشتری هم در اختیار ما قرار میدهد.
17 – از تعداد زیادی if نباید در کد استفاده کنید
نوشتن تعداد زیاد if نه تنها باعث بزرگ شدن کلاس یا تابعمان میشود، بلکه خوانایی و دیباگ کردن کد را هم بسیار مشکل میکند. اگر در یک تابعی یا کلاسی تعداد زیادی if استفاده کردید، حتما در اولین قدم بررسی کنید که آیا قوانین single responsibility را رعایت کرده اید یا نه. در بسیاری از مواقع علت نیاز به استفاده از تعداد زیاد if این است که ما بیش از یک وظیفه بر عهده ی تابع یا کلاسمان قرار داده ایم و بدین ترتیب نیاز خواهیم داشت که با استفاده از if های متعدد حالت های مختلف را پوشش دهیم. اما اگر واقعا با رعایت تمام اصول solid در کدی به تعداد زیاد if برخورد کنیم، چند راه حل میتوان برای کاهش تعداد if ها و همچنین افزایش خوانایی استفاده کرد. در اولین قدم ممکن است بعضی از این شرط ها برای حذف کردن حالت ناکارآمد ما باشند. مثلا اگر در تعداد از این if ها از break یا return بدون انجام عملیات خاصی استفاده میکنید کار صحیح تر آن است که همه ی این شرط ها را در یک if قرار داده و در ابتدای سری if های خود قرار دهید. بدین ترتیب تعدادی از شروط شما کم میشود. راه دیگر آن است که به جای syntax مربوط به if از syntax مربوط به switch case استفاده کنیم. درست است که در این روش تعداد ifهای کد ما کاهشی نمیابد ولی حداقل خوانایی بهتری دارد.
برای کاربر اول که مربوط به merge کردن شروط با خروجی یکسان بود به مثالی که در ادامه میآوریم توجه کنید. فرض کنید ما در ابتدا کد زیر را در بخشی از برنامه مان نوشته ایم:
{
if (condition1) {
return response a;
}
if (condition2) {
return response a;
}
if (condition3) {
return response b;
}
if (condition4) {
return response b;
}
}
همانطور که مشاهده میکنید، در این بلاک کد ما چهار if داریم. اما خروجی تعدادی از این if ها یکسان است و یک response مشترک را بازمیگردانند. حال به کد زیر توجه کنید:
{
if (condition1 && condition2) {
return response a
}
if (condition3 && condition4) {
return response b;
}
}
ما تعداد if ها را نصف کردیم، فقط با merge کردن شرط هایی که خروجی یکسانی داشتند.
18 – در تمام کد از یک سری از قوانین و الگو ها استفاده کنید
تفاوتی نمیکند که شما از چه روش ها یا معیار هایی برای تمیز نگه داشتن کدتان یا نامگذاری توابع، کلاس ها و متغیر ها استفاده میکنید. یا اینکه سیستم فایل بندی پروژه تان چگونه است. مهم این است که در تمام طول توسعه برنامه تان از یک روش و معیار ثابت استفاده کنید. مثلا در بخشی از کد از روش نامگذاری camelCase استفاده نکنید و در بخش دیگر روش snake_case.
یک کد که با الگوی بد توسعه داده شده باشد بهتر از کدی است که بدون الگو توسعه داده شده باش. البته اشاره کنیم که نیازی نیست که حتما یک الگو یا روش مشخص برای خودتان تعریف کنید، میتوانید از روش ها و الگو هایی که توسط جامعه ی بزرگی از برنامه نویس ها تایید شده است استفاده کنید، برای مثال در PHP این قوانین تحت نام PSR انتشار مییابند و با مراجعه به سایت مربوطه میتوانید با معیار ها و روش های نامگذاری کلاس ها و توابع، پوشهبندی برنامه و کدنویسی که باید رعایت کنید، آشنا شوید. این مجموعه قوانین موردِ توافق ترین مجموعه در بین تمام برنامه نویس های PHP است و بهتر است که اگر به پیروی از قانون خاصی عادت ندارید و یا به تازگی شروع به برنامه نویسی کرده اید، با پیروی از این قوانین راه را برای خود هموار سازید. همچنین از این دست قوانین معتبر برای زبان های برنامه نویسی دیگر هم وجود دارد مثلا Python Enhancement Proposals PEPs که برای زبان Python است.
19 – روزانه یا هفتگی کد خود را ریفکتور کنید
نکته ی بسیار مهم بعدی که میخواهیم به آن اشاره کنیم آن است که روزانه یا در حالت خاص هفتگی کد خود را ریفکتور کنید. این کار چندین و چند مزیت دارد. اول از همه کد شما همیشه در حالت بهینه و تمیز است و تمیز بودن کد های قبلی شما را وا میدارد که کد های جدید را هم تمیز بنویسید. مورد دوم آنکه وقتی به طور مستمر کد های خود را ریفکتور کنید، به طور مستمر کد های قبلی را دوره میکنید و این باعث میشود همیشه یک تصویر واضح و مشخص از بخش های مختلف برنامه خود داشته باشید. بدین ترتیب در طراحی و پیاده سازی بخش های جدید برنامه با داشتن دیدِ از بالا، طراحی و پیاده سازی صحیح تر و سریع تری خواهید داشت.
20 – به ازای 2 واحد فکر کردن، یک واحد کد بزنید
همیشه قبل از اینکه کد بنویسید، روی کاغذ طراحی و موارد استفاده ی تابع یا کلاس مورد نظر و توابع و کلاس های مرتبط را پیاده کنید. نوشتن و طراحی روی کاغذ میتواند به طرز چشم گیری باعث افزایش تمرکز شما روی موضوع شده و در نتیجه کد تمیز تری بنویسید. همچنین با کشیدن طراحی کلاس و توابع مربوط به کلاس یا تابع مورد نظر میتوانید راحت تر anti-pattern ها و اتصالات اشتباه بین ابجکت های برنامه را تشخیص دهید. به همین ترتیب ممکن است به این نتیجه برسید که این طراحی به اندازه کافی نشکسته و به بخش های کوچک تر تقسیم نشده است. دوبار فکر کردن به مراتب کم هزینه تر و سریع تر از دو بار کد زدن یک کلاس از ابتدا است.
21 – برای کد خود تست بنویسید
تست مانند معیاری است که شما برای برنامه خود تنظیم میکنید. شما در تست ها انتظار خود از برنامه تان رو مشخص میکنید و به نوعی معیاری برای کارکرد صحیح برنامه تان تبیین میکنید. بدین ترتیب هر بار که تغییر در کدتان اعمال میکنید، با اجرا کردن تست ها میتوانید اطمینان حاصل کنید که ایا برنامه تان هنوز هم متناسب با انتظارات و نیاز های شما کار میکند یا نه؟ برای اینکه بتوانید به راحتی برای بخش های مختلف برنامه تان تست بنویسید مجبور خواهید بود اصول مختلف گفته شده در بالا مثل single responsibility یا open/closed را رعایت کنید، بدین ترتیب کدی تمیز خواهید داشت که با اعمال هر تغییر به شما هشدار میدهد که تغییر اعمال شده صحیح یا اشتباه است.
جمع بندی
در مجموع باز هم یادآوری میکنیم که عمده ی تفاوت برنامه نویس ارشد و برنامه نویس تازه کار در استفاده از قوانین و نکات مربوط به پیاده سازی کد است. هر چند که این نکات بر حسب تجربه و با استمرار در کدزنی حاصل میشود ولی با مطالعه و خواندن تجربه ی سایر برنامه نویس ها میتوان این امر را سرعت بخشید. برنامه شما آیینه ی شخصیت شماست، برنامه ای که به خوبی توسعه داده شده باشد و انواع نکات مربوط به کد تمیز در آن رعایت شده باشد نظر کارفرما و سایر برنامه نویس ها را به خود جلب میکند. پس از امروزتلاش کنید هر روز بهتر و هوشمندانه تر از روز قبل کد بنویسید.