Ali Fazeli
Ali Fazeli
خواندن ۸ دقیقه·۳ سال پیش

تحول برنامه نویسی با کد تمیز (بخش دوم - توابع)

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

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

کوچکتر بهتره!

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

قدیم ها یه قاعده ای وجود داشت که تابع باید انقدری کوچیک باشه که توی مانیتور بشه کلش رو یکجا دید ولی این برای زمانی بود که مانیتور ها کوچیک بودن و پیکسل های کمی داشتن و حداکثر ۲۰ خط کد ۸۰ کاراکتری رو می‌شد توی مانیتور دید. الان مانیتور های مدرن می‌تونن ۱۰۰ خط و شاید بیشتر کد ۱۵۰ کاراکتری رو نشون بدن. حالا پس تابع های ما می‌تونن ۱۰۰ خط باشن؟ نه!

من فکر می‌کنم توابع باید خیلی کوتاه تر باشن شاید حداکثر ۲۰ خط.




برای این که به تابع های به اندازه کافی کوچک برسین به این نکته ها توجه کنین:

داخل هر دستور if یا for نباید بیشتر از یک خط کد باشه. اگه لازمه که بیشتر باشه اونو تبدیلش کنین به یه متد جدا. مثال:

که بعد از تغییر به این شکل در میاد:

یه متد نباید بیشتر از یک یا حداکثر دو لایه indent داشته باشه. توی همین مثال بالا می‌بینید که چطوری با انتقال بخشی از کد به یک متد جدا جلوی indent اضافی گرفته شده و به یک متد دیگه منتقل شده.

منظور از indent کاراکتر های خالی (space, tab) هست که باعث جلوتررفتن بخشی از کد می‌شه. توی دستور های if , for , while , … معمولا indent اتفاق میافته. پس وقتی میگیم بیشتر از دو مرحله indent نداشته باشیم یعنی حداکثر دو تا حلقه تو در تو باشه یا یک if داخل یک for و ... (چه بهتر که بیشتر از یکی نباشه کلا).

لازم نیست تک خط کد های بدیهی رو تبدیل به متد های جدا بکنین! فراموش نکنید که اضافه کردن هر متد هزینه اجرای کد رو (هرچند ناچیز) بالا می‌بره. برای مثال واضحه که این کار زیاده رویه:

a + b; → add(a,b);

فقط یک کار

اگر با مفاهیم SOLID توی برنامه نویسی شی گرا آشنا باشید حتما این مسئله به گوشتون خورده. اینجا ولی فقط در باره تابع صحبت می‌کنیم و کاری با شی گرایی و مسائلش به طور مستقیم نداریم.

تابع باید یک کار و فقط یک کار رو به طور کامل و درست انجام بده.

تنها مشکلی که با این جمله باقی می‌مونه اینه که «یک کار» رو چطور تعریف کنیم؟

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

راه دیگه ای که توی کتاب بهش اشاره میشه استفاده از جمله هایی با شروع «برای این‌که» هست. سعی می‌کنم با یک مثال این رو توضیح بدم.

توجه کنین که توی مثال کد ها کامل نوشته نشده و یک سری بخش ها رو با کامنت توضیح دادم که چه کاری انجام میشه.

add-to-cart-1
add-to-cart-1

کاری که این کد انجام میده به این شکل هست که فرض میکنیم میخوایم یک محصول رو به سبد خرید کاربر اضافه کنیم. برای این کار باید ببینیم سبد خرید فعالی برای کاربر وجود داره یا نه و بعد با محاسبه قیمت نهایی محصول رو به سبد اضافه می‌کنیم.

تاکید میکنم که این فقط یک مثال ابتدایی هست و منطق و کد هردو خلاصه شده.

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

خب بریم سراغ نسخه refactor شده این کد با در نظر گرفتن حرف هایی که تا اینجا زدیم:

add-to-cart-refactored
add-to-cart-refactored

ما داریم راجع به تابع addProductToCart صحبت می‌کنیم. ببینید الان میشه چیزی رو با اسم معنی دار ازش خارج کرد؟

روش دوم میگه که برای تشخیص این که بیشتر از یک کار انجام میشه بیایم یک پاراگراف بسازیم که با کلمه To و اسم تابع شروع میشه:

To addProductToCart...

و بعد این جمله رو ادامه بدیم، هرجا که دیدیم بیشتر از یک لایه از اسم تابع دور شدیم اون نشون میده که تابع ما بیشتر از یک کار رو داره انجام میده.

همین کار رو برای کد نسخه اول انجام بدیم. برای راحتی بیشتر من این کار رو به فارسی انجام می‌دم.

برای این‌که محصول رو به سبد خرید اضافه کنیم: (ُTo addProductToCart)

  • سبد خرید فعال کاربر رو بگیر
    • اگر سبد خرید فعالی نداشت یک سبد خرید ایجاد کن
  • ببین محصول انتخاب شده معتبر هست؟
    • ببین فعال شده؟
    • ببین توی انبار موجودی داره؟
    • ...
  • قیمت رو محاسبه کن
    • … جزییات محاسبه قیمت (که در کد هم خلاصه شده)
  • محصول رو با قیمت محاسبه شده به سبد اضافه کن.

حالا این کار رو برای همین تابع بر اساس کد اصلاح شده انجام بدیم:

  • سبد خرید رو با شناسه کاربر بگیر
  • اگر محصول انتخاب شده معتبر بود اون رو به سبد خرید اضافه کن

مشخصه که طبق این روش هایی که معرفی شده این تابع جایی برای بهبود نداره و واقعا داره یک کار رو انجام میده.

توجه کنین که این روش ها، روش های ریاضی نیستند که با فرمول به نتیجه برسیم، بلکه ابزار هایین برای این که تمرین کنید تا توابع تمیز تری داشته باشید و کد هاتون به راحتی قابل خوندن و توسعه دادن باشن.



ورودی های تابع

هرچی کمتر بهتر!

ورودی های یه تابع باید محدود بشن. ایده آل ترین حالت اینه که تابع ورودی نداشته باشه. بعد ۱، ۲ و نهایتا و در صورت نیاز واقعی ۳ ورودی برای تابع قابل قبوله. اگر بیشتر از سه ورودی نیاز دارید برای تابع احتمالا مشکل جدی توی نحوه نگاه به تابع و ساختار برنامه‌تون وجود داره!

ورودی های زیاد چه مشکلی ایجاد می‌کنن؟

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

از ورودی های Flag استفاده نکنید. استفاده از ورودی های flag حسابی گمراه کنندست. منظور از ورودی های flag متغیر های boolean ای هست که رفتار تابع رو تغییر میدن. به جای استفاده از این روش از دو تابع مجزا استفاده کنین.

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

یک مثالی که توی خیلی از فریم ورک ها و زبان ها وجود داره رو ببینید:

assert-function(clean-code)
assert-function(clean-code)

تقریبا هربار چنین تابعی رو بخوایم توی کد فراخوانی کنیم باید بریم و تعریفش رو بخونیم تا ترتیب ورودی ها رو درست وارد کنیم.

حالا توی مواقعی که نیاز به ۳ یا بیشتر ورودی داریم چکار کنیم؟ یکی از راه هایی که برای این زمان ها وجود داره استفاده از Object هاست. خیلی وقت ها می‌تونیم ۲ یا چند مقدار رو در قالب یک Object به تابع پاس بدیم. این کار تقلب نیست! با این کار یک ساختار معنا دار ایجاد می‌کنیم.

مثال:

با تبدیل کردن مقادیر x و y به یک Object هم تعداد ورودی ها کم شده و هم مفهوم اون ها مشخص تر شده.

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

array-of-inputs-clean-code
array-of-inputs-clean-code

تابع String.format بی شمار ورودی میتونه بگیره اما از مقدار دوم به بعد در واقع دنباله ای از ورودی ها هستند که همه به ترتیب داخل string ای که در ورودی اول مشخص شده قرار می‌گیرند. از نظر ظاهری این تابع چندین ورودی داره ولی در واقع از ورودی دوم به بعد همگی دنباله ی یکسانی هستن و به همین خاطر از زاویه دید کد تمیز این تابع دو ورودی داره، یک رشته متنی و یک دنباله ای از مقدار ها برای قرار گرفتن داخل رشته متنی.

من برای جلوگیری از طولانی شدن بیش از حد این پست رو همینجا تموم می‌کنم. نکته های مربوط به توابع تمام نشده و پست بعدی این سری ادامه توابع خواهد بود.

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

در صورت علاقه می‌تونید بخش های قبلی این سری رو هم مطالعه کنید:


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