خلاصه کتاب کد تمیز – (فصل سوم : فانکشن‌ها - قسمت دوم)

08 خرداد 1400, خواندن در 8 دقیقه

دوستان راکتی بازم سلام، تو این قسمت میریم که ادامه‌ی فصل سوم کتاب کد تمیز یعنی فانکشن‌هارو بررسی کنیم،‌توی قسمت قبلی تا switch statement ها رو گفتیم، خب بریم ببینیم ادامه‌ی داستان فانکشن‌ها چیه …

 استفاده از اسم‌هایی که تابع رو توصیف کنه

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

آرگومان‌های فانکشن‌

شاید باورش مشکل باشه اما باید بدونین که تعداد ایده‌آل آرگومان برای یک فانکشن صفر هست(niladic) و بعدم یکی (monadic) و نهایتاً دوتا (dyadic) و تا می‌تونین باید از سه تا آرگومان (triadic) اجتناب کنین؛ و خب اگ­ه بیش از سه تا شد باید یه دلیل خیلی محکم داشته باشین، اما به هرحال نباید ازشون استفاده کنین.

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

از نظر تست هم که دیگه مشخصه! نوشتن تستی که برای همه‌ی حالت‌ها و ترکیب‌های مختلف آرگومان‌ها درست کار کنه سخته. البته اگه آرگومانی نباشه این مسأله دیگه مهم نیست و به همین ترتیب اگه یک آرگومان داشته باشیم کار آسون تره، با دو آرگومان مسأله چالشی تره و خب بیش‌تر از این دیگه ترسناکه!

( اینو بگم که استفاده از دو آرگومان کار اشتباهی نیس و باید استفاده کنین ولی تا میشه باید از مکانیسم‌های موجود استفاده کنین و اونو به تک آرگومانی تبدیل کنین)

نکته‌ی بعدی اینه که از آرگومان‌های فلگ طور به شدت خودداری کنین، در‌واقع اگه این کارو انجام بدین دارین داد میزنین که آقایون خانوما من بیشتر از یک کارو دارم انجام میدم ( اگه true باشم یه کار و اگه false باشم یه کار دیگه)

پس همانا کار درست این است که برای هریک از کارهای خود یک تابع داشته باشید.(ببینین چند بار تاحالا اینو گفتما پس خدایی رعایت کنین … )

 نکته‌ی آخرم که دیگه تکرار مکرراته : اسم درست حسابی واسه فانکشنتون استفاده کنین.

Have NO Side Effects!

این کار یه جور دورغ گفتنه؛ درواقع شما دارین می‌گین که تابع من یه کارو انجام می‌ده اما واقعیت اینه که یه سری کاره دیگه رو هم داره قایمکی انجام می‌ده. بزارین یه مثال بزنم :

لیست 6-3 رو یه نگاه بندازین ، ظاهر قضیه  اینطوریه که خب مثه یه پارچه آقا داره کارشو انجام میده( درواقع یه الگوریتم استاندارد برای مچ کردن یک نام‌کاربری به یک پسورد هست که اگه این مچ شدن صورت گرفت true و در غیر این صورت هر اتفاق دیگه‌ای که بیفته false برمی‌گردونه) اما یه ساید افکت یا عارضه جانبی داره، می‌تونین تشخیصش بدین؟

Listing 3-6

UserValidator.java
public class UserValidator 
{
        private Cryptographer cryptographer;
        public boolean checkPassword(String userName, String password) 
        {
                User user = UserGateway.findByName(userName);
                if (user != User.NULL) 
                {
                        String codedPhrase = user.getPhraseEncodedByPassword();
                        String phrase = cryptographer.decrypt(codedPhrase, password);
                        if ("Valid Password".equals(phrase)) 
                        {
                                Session.initialize();
                                return true;
                        }
                }
        return false;
        }
}

فکر کنم تشخیص دادنش آسون باشه، بله ساید افکت این فانکشن Session.initialize(); هست. این فانکشن همونطور که از اسمش هم پیداست (checkPassword)، پسورد رو چک می‌کنه اما اسمش نشون نمیده که session هم میسازه. پس هرموقع این تابع رو یه نفر صدا میزنه و فکرم می‌کنه که فانکشن داره مثل اسمش عمل می‌کنه، ریسک پاک شدن دیتای session موجود رو هم فراهم می‌کنه.

اگه بخواییم بیشتر توضیحش بدیم این ساید افکت یه  temporal coupling می‌سازه. فانکشن checkPassword فقط زمانی که ساخت یک session در حالت ایمن هست می‌تونه فراخوانی بشه و اگه خارج از ترتیب فراخوانی بشه ممکنه دیتای session از بین بره؛ و خب میبینید که چقدر این کار میتونه گیج‌کننده باشه.

البته که نباید همچین کاری کنین اما اگه مجبور به داشتن temporal coupling شدین حداقل یه اسم خوب و واضح براش انتخاب کنین مثل checkPasswordAndInitializeSession که البته باز با این کار قانون ( انجام فقط یک کار توسط فانکشن ) رو نقض می‌کنین.

Command query sepration

فانکشن یا باید یه کاری رو انجام بده یا به یه چیزی جواب بده، اما نه هر دوی اینها. یا باید وضعیت یک آبجکت رو تغییر بده یا یک سری اطلاعات مربوط به اون آبجکت رو برگردونه؛ اگه هر دو کار رو انجام بده گیجتون میکنه. برای مثال این فانکشن رو در نظر بگیرین:

 public boolean set(String attribute, String value); 

این فانکشن میاد مقدار یه attribute مشخص رو تعیین می‌کنه بعد اگه همه چی اوکی بودtrue و اگه چنین  attribute وجود نداشت false برمی‌گردونه و این کار باعث می‌شه یه statements عجیب غریب مثل این به وجود بیاد :

if (set("username", "unclebob"))...	

حالا فکر کنین که یه بنده‌خدایی داره اینو می‌خونه، خب طرز فکر این برنامه‌نویس نگون بخت که داره کد شمارو می‌خونه چیه؟

۱. آیا یوزرنیم قبلاً عموباب ست شده؟

۲. آيا ست شدن عموباب به عنوان یوزرنیم موفقیت آمیز بوده؟

و سوالای دیگه ….

یکی از مشکلات اینه که اصلاً مشخص نیست کلمه set فعله یا صفت و دوباره همون مشکل انتخاب اسم و این داستان که تابع داره دوتا کارو انجام میده پیش میاد. چطوری مشکلو حل کنیم؟ اینطوری :

if (attributeExists("username")) {
    setAttribute("username", "unclebob");
    ...
}

Exception هارو به برگردوندن کد خطا ترجیح بدین

به جای اینکه بیایین یه خطا یا ارور در یه سری شرط‌های پی در پی که به واسطه‌ی حالتای ناخوشایندی که در اون فانکشن میفته مشخص کنین،  خیلی شیک از try/catch استفاده کنین.

مثال :

این تیکه کد رو ببینین که اومده خطاهارو return کرده.

 if (deletePage(page) == E_OK) {
        if (registry.deleteReference(page.name) == E_OK) {
                if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
                        logger.log("page deleted");
                } 
        else {
                logger.log("configKey not deleted");
        }
        }
        else {
                logger.log("deleteReference from registry failed");
        }
        
} 
else {
        logger.log("delete failed");
        return E_ERROR;
}

و اما اینجا از exception ها استفاده می‌کنیم و میبیند که میشه پردازش خطا رو از مسیر کد جدا کرد و ساده سازی انجام داد.

try
{
   deletePage(page);
   registry.deleteReference(page.name);
   configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) 
{
    logger.log(e.getMessage());
}

و یه نکته‌ی مهم دیگه اینکه بلوک‌های try/catch رو استخراج کنین و بعد فانکشن‌هاتون رو بنویسین ( یه جور زیبا سازیه دیگه …)

حالا چطوری؟ مثلاً اینطوری :

public void delete(Page page) {
        try {
                deletePageAndAllReferences(page);
        }
        catch (Exception e) {
                logError(e);
        }
}


private void deletePageAndAllReferences(Page page) throws Exception {
        deletePage(page);
        registry.deleteReference(page.name);
        configKeys.deleteKey(page.name.makeKey());
}


private void logError(Exception e) {
        logger.log(e.getMessage());
}

میبینین که با جدا کردنشون چقدر فهم کد آسون‌تر شد.

Don’t repeat your self

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

البته که با این کار فقط داریم خوانایی کد رو کم می‌کنیم و تازه انرژی بیشتریم باید واسه تغییر دادنش و هزارتا مسئله دیگه بزاریم. پس بیایین عهد کنیم که دیگه هیچ وقت تو زندگیمون این کارو نکنیم :)

……..‌

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

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

نتیجه‌گیری

میدونین برنامه‌نویسای سنیور چطوری فکر میکنن؟ اونا به نرم‌افزار مثل یه داستان نگاه می‌کنن. در‌واقع از ابزار به بهترین شکل استفاده می‌کنن و همینطور، تابع یک سری قواعد و اصول هستن تا داستان بهتری رو شکل بدن. فانکشن‌ها هم مثل افعال این داستان می‌مونن ؛ هر چه که فانکشنای کوتاه‌تر و خوش اسم‌تری داشته باشین کد خفن‌تری خواهید داشت. وسلام، این فصلم تموم شد امیدوارم لذت برده باشین. هر پیشنهاد یا انتقادی که دارین توی بخش نظرات برامون بنویسین. از وقتی که گذاشتید ممنونم.

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
فاطمه شیرزادفر @Fatemeh.shirzadfar
تجربه کلمه‌ای هست که همه برای توصیف اشتباهاتشون ازش استفاده میکنن، و من همیشه دنبال اشتباهات جدیدم! برنامه‌نویس هستم و لینوکس‌ دوست
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو