بایگانی

بایگانی برای دسته ی ‘Clean Code’

اندازه‌گیری پیچیدگی نرم‌افزار

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

تعداد مسیرها که آیسا می‌توانست برای رسیدن به هدفش را طی کند میزان پیچیدگی شرایط‌ش در نظر گرفتیم. در سال ۱۹۷۶ شخصی به Thomas McCabe متریکی را برای اندازه‌گیری پیچیدگی در نرم‌افزار به نام Cyclomatic complexity معرفی کرد. او نیز تعداد مسیرهای مستقلی که کل یک ماژول یا متد را پوشش بدهد به عنوان پیچیدگی آن در نظر گرفت. برای محاسبه تعداد مسیرهای مستقل در متد روشهای مختلفی وجود دارد، رسمی‌ترین روش برای این کار رسم گراف کنترل جریان آن متد می‌باشد. در تصویر زیر گراف کنترل جریان متد حستجوی باینری رسم شده است.

گراف کنترل جریان

گراف کنترل جریان متد جستجوی باینری

بعد از رسم گراف کنترل جریان با استفاده از فرمول زیر تعداد مسیرهای مستقل را محاسبه می‌کنیم.

Cyclomatic complexity = E – N + 2

تعداد یالهای گراف = E

تعداد گره‌های گراف = N

بر اساس تصویر بالا گراف کنترل جریان متد دارای ۱۲ گره و ۱۴ یال می‌با‌شد پس :

CC = 14 – ۱۲ + ۲ = ۴

پس متد جستجوی باینری دارای پیچیدگی ۴ می‌باشد، یعنی ۴ مسیر مستقل وجود دارد که کل متد را پوشش می‌دهد.

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

if | while | for | for each | case | default | continue | goto | && | || | catch | ?: | ??

 

اگر حوصله رسم گراف و شمردن نقاط تصمیم‌گیری را ندارید، استفاده از ابزارهای که متریک‌های کد را محاسبه کرده و نمایش می‌دهند بهترین گزینه هست. یکی از بهترین گزینه‌ها برای اینکار می‌تواند XDepend باشد برای دات نت NDepend، برای جاوا JDepend و برای پی‌اچ‌پی می‌توانید از PDepend استفاده کنید.  البته اگر از ویژوال استودیو استفاد می‌کنید نیازی به نصب ابزار دیگری نیست می‌توانید از منوی ANALYAZ بر حسب نیاز خود یکی از دو گزینه مشخص شده در تصویر را انتخاب کنید و نتیجه را مشاهده کنید.

منوی اندازه گیری متریک‌های کد

منوی اندازه گیری متریک‌های کد

صفحه خروجی متریک‌های کد نرم‌افزار

صفحه خروجی متریک‌های کد

مقدار قابل قبول برای Cyclomatic Complexity یک متد چقدر هست؟

یک مقدار ایده‌ ال برای محدود کردن Cyclomatic Complexity در یک متد وجود ندارد. تا ۱۰ را یک مقدار قابل قبول برای یک متد میدانند. و با افزایش آن میزان پیچیدگی افزایش و در نتیجه آن تست پذیری و قابلیت نگهداری کاهش می‌یابد. بازه‌های زیر را SEI برای مقدار  Cyclomatic Complexity منتشر کرده است.

Cyclomatic Complexity سطح پیچدگی ریسک
۱-۱۰ ساده کم
۱۱-۲۰ نسبتا پیچیده متوسط
۲۱-۵۰ پیچیده زیاد
بزرگتر از ۵۰ غیرقابل تست خیلی زیاد

 

آیا ارتباطی بین مقدار Cyclomatic Complexity و تعداد تست‌های واحد (unit test)  وجود دارد؟

مقدار Cyclomatic Complexity برابر حداقل تعداد تست‌های هست که می‌توان نوشت تا تمام مسیر‌های برنامه را پوشش بدهد. یعنی اگر برای هر مسیری که مشخص شده هست یک تست بنویسیم می‌توانیم از پوشش تمام مسیرهای برنامه توسط تست‌هایمان مطمئن شویم. در پست بعدی سعی می‌کنم در مورد میزان پوشش تست‌ها (test coverage) بنویسم و درباره انواع پوشش تست‌ها توضیح خواهم داد.

به‌عنوان نمونه تست‌های نوشته شده برای مسیر ۱ و ۲ در متد جستجوی باینری در پایین نمایش داده شده است. سطرهای که با رنگ سبز مشخص شده است مسیر اجرای آن تست را نشان می‌دهد و سطرهای که با رنگ قرمز مشخص شده است سطرهای هست که آن مسیر پوشش نمی‌دهد.

مسیر ۱:  ۰->1->2->3->11

تست مسیر 1

تست مسیر ۱ در متد جستجوی باینری

مسیر ۲: ۰->1->2->4->5->6->11

 

تست مسیر 2

تست مسیر ۲ در متد جستجوی باینری

قوانین طراحی ساده

همه ما از سادگی صحبت می‌کنیم و می‌گوییم عاشق سادگی هستیم. ولی شاید جمله «برای آن مسئله یک راه‌حلی پیدا کردم که عمراً کسی بتواند بفهمد چیکار کردم.» را بعضی وقت‌ها با افتخار بزبان می‌آوریم. پیدا کردن یک راه‌حل پیچیده برای یک مسئله که برای دیگران قابل درک نباشد یک حس نبوغ در ما القا می‌کند؛ اما اگر کسی بتواند برای همان مسئله راه حلی ارائه بدهد که به سادگی توسط همه قابل درک باشد، راه حل پیچیده من یک راه‌حل احمقانه و شاید من یک احمق نامیده شوم. پس قبل از اینکه هر لقبی بگیرید لطفاً این سؤال را از خودتان بپرسید «ساده‌ترین چیزی که ممکن است کار کند، چیست؟”.» این سوالی است که توسعه دهندگان چابک در هنگام نوشتن کد از خودشان می‌پرسند و بدین معناست که ساده‌ترین کاری را انجام دهید که شما را قادر می‌سازد تا در مسیر درست به پیش بروید.

 یک از ارزش‌های XP، سادگی و یکی از تکنیک‌های آن طراحی ساده (Simple Design) هست. Kent Beck مبدع XP برای داشتن یک طراحی ساده چهار قانون تعریف کردن که عبارت‌اند از:

  1. پاس شدن همه تست‌ها
  2. عدم وجود تکرار
  3. بیان مقصود برنامه نویس
  4. کاهش تعداد کلاس‌ها و متدها

پاس شدن همه تست‌ها

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

همه سوالهای بالا بر وجود تست در سیستم تاکید دارند. Unit Test ها کمک می‌کنند که شما بدانید چه زمانی وظیفه شما به پایان رسیده است – زمانیکه شما تمام تست‌های مرتبط با وظیفه‌ای که انجام می‌دهید را نوشته باشید و همه آن‌ها پاس شده باشند. اگر تست یا تست‌هایی پاس نشده باشد شما باید به فعالیت خودتان ادامه بدهید تا همه آن‌ها پاس بشود.

عدم وجود تکرار

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

این اصل اغلب بین توسعه دهنده‌های نرم‌افزار با نام DRY(Don’t Repeat Yourself.) یا Once And Only Once شناخته می‌شود. البته اصل DRY درباره تکرار کد نیست و درباره تکرار دانش هست، این اصل بیان می‌کند که  هر قسمت از دانش ما در سیستم باید جایگاه واحد، بدون ابهام و معتبری داشته باشد. (حسین بقایی عزیز چند ماه قبل لینک یک پست را در مورد DRY توئیت کرده بود که به صورت عالی این اصل را تشریح کرده بود خواندنش را از دست ندهید.)

بیان مقصود برنامه‌‌نویس

شاید این اصل یکی از اولین اصول و در ظاهر ساده‌ترین اصلی است که تعدادی زیادی از برنامه‌نویس‌ها سعی می‌کنند آنرا رعایت کنند. تاکید این اصل بر روی این هست که اسامی متغیرها، متدها، کلاس‌ها و … باید به هدف وجود آن‌ها اشاره کند.

کاهش تعداد کلاس‌ها و متدها

این یکی از اصولی هست که شاید در نگاه اول با اصول بالا در تضاد باشد. وقتی برای نمونه شما برای حذف کدهای تکراری کدهای موجود را ریفکتور می‌کنید به احتمال زیاد تعداد کلاس‌ها و متدها شما افزایش می‌یابد. پس اصل چهارم به خودی‌خود نقض می‌شود؛ اما این اصل به تعداد کلاس‌ها اشاره نمی‌کند و در واقع اشاره آن به اصل YAGNI (You Aren’t gonna need it) می‌باشد.  اصل YAGNI بیان می‌کند که شما باید هر چیزی را وقتی پیاده سازی کنید که واقعاً به آن نیاز دارید و نه وقتی که می‌فهمید در آینده ممکن است به آن نیازمند شوید. این جمله یکی از جملاتی است که XP واقعاً روی آن تاکید دارد و اعلام می‌کند که شما حتی اگر اطمینان حاصل کنید که به این ویژگی در آینده نیاز خواهید داشت آنرا الان پیاده سازی نکنید. پس هدف اصل چهارم این است که نباید با پیاده‌سازی کلاس‌ها و متدهایی که احتمالاً در آینده فرضی به آن نیاز خواهیم داشت نعداد کلاس‌ها و متدها را افزایش بدهیم.

قانون بوی اسکات

۲۴ مرداد ۱۳۹۳ ۱ دیدگاه

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

یاد بگیریم وقتی در جایی کمپ می‌زنیم آنجا را تمیزتر از قبل‌اش ترک کنیم. این جمله به عنوان قانون بوی اسکات شناخته می‌شود و می‌تواند به عنوان راه حل ما برای حفظ کیفیت کد در طول زمان به‌کار برود. قانون بوی اسکات در شکل نرم‌افزاری آن تاکید می‌کند که با هر commit کیفیت کد را نسبت به قبل بهبود ببخشیم. در پست تئوری پنجره شکسته، سوال «در برابر پنجره‌های شکسته باید چگونه عمل کرد؟»  مطرح شد که، شکل دیگر پاسخی که برای آن ارائه شد همان قانون بوی اسکات هست. و باز در آخر توصیه می‌کنم که برای حفظ کیفیت کد در طول زمان هیچ فرصتی را از دست ندهید و به صورت پیوسته و در فواصل زمانی بسیار کوتاه Refactoring را بر روی کدهای خود انجام دهید.

تئوری پنجره شکسته

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

به‌صورت اتفاقی در جای مطلبی را می‌خواندم که در آن اشاره به تئوری پنجره شکسته داشت. تئوری پنجره شکسته محصول فکری دو جرم شناس آمریکائی (Criminologist) بود به اسامی جمس ویلسون (James Wilson) و جورج کلینگ(George Kelling).  این دو استدلال می کردند که جرم نتیجه یک نابسامانی است. اگر پنجره ای شکسته باشد و مرمت نشود آنکس که تمایل به شکستن قانون و هنجارهای اجتماعی را دارد با مشاهده بی تفاوتی جامعه به این امر دست به شکستن شیشه دیگری می زند. دیری نمی پاید که شیشه های بیشتری شکسته می شود و این احساس آنارشی و هرج و مرج از خیابان به خیابان و از محله ای به محله دیگر می رود و با خود سیگنالی را به همراه دارد از این قرار که هر کاری را که بخواهید مجازید انجام دهید بدون آنکه کسی مزاحم شما شود.

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

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