Skip to content

Latest commit

 

History

History
197 lines (151 loc) · 18 KB

Emergence.md

File metadata and controls

197 lines (151 loc) · 18 KB

ظهور

classes image

پاک‌سازی از طریق طراحی پدیداری

اگر چهار قانون ساده وجود داشت که می‌توانستید از آن‌ها پیروی کنید تا در حین کار، طراحی‌های خوبی ایجاد کنید، چه می‌شد؟ اگر با پیروی از این قوانین، بینش‌هایی درباره ساختار و طراحی کد خود به دست می‌آوردید که به شما کمک می‌کرد اصولی مانند SRP (اصل مسئولیت واحد) و DIP (اصل وابستگی معکوس) را به راحتی اعمال کنید، چه می‌شد؟ اگر این چهار قانون به بروز طراحی‌های خوب کمک می‌کرد، چه می‌شد؟

بسیاری از ما احساس می‌کنیم که چهار قانون طراحی ساده کنت بک کمک زیادی به ایجاد نرم‌افزارهایی با طراحی خوب می‌کند. طبق نظر کنت، طراحی «ساده» است اگر این قوانین را رعایت کند:

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

این قوانین به ترتیب اهمیت ارائه شده‌اند.

قانون طراحی ساده ۱: تمام آزمون‌ها را اجرا کند

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

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

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

پیوندهای نزدیک (Tight Coupling) نوشتن آزمون‌ها را دشوار می‌کند. بنابراین، هر چه تعداد آزمون‌های بیشتری بنویسیم، بیشتر از اصولی مانند DIP (اصل وابستگی معکوس) و ابزارهایی مانند تزریق وابستگی، اینترفیس‌ها و انتزاع استفاده می‌کنیم تا پیوند را به حداقل برسانیم. طراحی‌های ما حتی بیشتر بهبود می‌یابند.

به طرز شگفت‌انگیزی، پیروی از یک قانون ساده و واضح که می‌گوید باید آزمون‌هایی داشته باشیم و آن‌ها را به‌طور مداوم اجرا کنیم، تأثیر زیادی بر رعایت اهداف اصلی شی‌گرایی (OO) یعنی پیوند کم و همبستگی بالا دارد. نوشتن آزمون‌ها به طراحی‌های بهتر منجر می‌شود.

قوانین طراحی ساده ۲ تا ۴: بازسازی (Refactoring)

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

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

حذف تکرار

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

int size() {}
boolean isEmpty() {}
ما می‌توانیم پیاده‌سازی‌های جداگانه‌ای برای هر متد داشته باشیم. متد isEmpty می‌تواند یک مقدار بولی را دنبال کند، در حالی که متد size می‌تواند یک شمارنده را پیگیری کند. یا می‌توانیم این تکرار را با مرتبط کردن isEmpty به تعریف size از بین ببریم:
boolean isEmpty() {
 return 0 == size();
}
ایجاد یک سیستم تمیز نیازمند اراده برای حذف تکرار، حتی در چند خط کد است. به عنوان مثال، به کد زیر توجه کنید:
  public void scaleToOneDimension(
   float desiredDimension, float imageDimension) {
   if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
     return;
   float scalingFactor = desiredDimension / imageDimension;
   scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01 f);
   RenderedOp newImage = ImageUtilities.getScaledImage(
     image, scalingFactor, scalingFactor);
   image.dispose();
   System.gc();
   image = newImage;
 }
 public synchronized void rotate(int degrees) {
   RenderedOp newImage = ImageUtilities.getRotatedImage(
     image, degrees);
   image.dispose();
   System.gc();
   image = newImage;
 }
برای حفظ تمیزی این سیستم، باید مقدار کمی تکرار بین متدهای scaleToOneDimension و rotate را حذف کنیم:
public void scaleToOneDimension(
  float desiredDimension, float imageDimension) {
  if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
    return;
  float scalingFactor = desiredDimension / imageDimension;
  scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01 f);
  replaceImage(ImageUtilities.getScaledImage(
    image, scalingFactor, scalingFactor));
}
public synchronized void rotate(int degrees) {
  replaceImage(ImageUtilities.getRotatedImage(image, degrees));
}
private void replaceImage(RenderedOp newImage) {
  image.dispose();
  System.gc();
  image = newImage;
}
زمانی که ما اشتراکات را در این سطح بسیار کوچک استخراج می‌کنیم، شروع به شناسایی نقض‌های SRP (اصل مسئولیت واحد) می‌کنیم. بنابراین ممکن است یک متد تازه استخراج‌شده را به کلاس دیگری منتقل کنیم. این کار موجب افزایش قابلیت دید آن می‌شود. ممکن است شخص دیگری در تیم فرصتی برای بیشتر انتزاعی کردن این متد جدید و استفاده مجدد از آن در یک زمینه متفاوت شناسایی کند. این «استفاده مجدد در سطح کوچک» می‌تواند باعث کاهش چشمگیر پیچیدگی سیستم شود. درک چگونگی دستیابی به استفاده مجدد در سطح کوچک برای دستیابی به استفاده مجدد در سطح بزرگ ضروری است.

الگوی TEMPLATE METHOD یک تکنیک رایج برای حذف تکرار در سطوح بالاتر است. به عنوان مثال:

public class VacationPolicy {
  public void accrueUSDivisionVacation() {
    // code to calculate vacation based on hours worked to date
    // ...
    // code to ensure vacation meets US minimums
    // ...
    // code to apply vaction to payroll record
    // ...
  }
  public void accrueEUDivisionVacation() {
    // code to calculate vacation based on hours worked to date
    // ...
    // code to ensure vacation meets EU minimums
    // ...
    // code to apply vaction to payroll record
    // ...
  }
}
کدهای موجود در متدهای accrueUSDivisionVacation و accrueEuropeanDivisionVacation عمدتاً مشابه هستند، به جز در محاسبه حداقل‌های قانونی. این بخش از الگوریتم بسته به نوع کارمند تغییر می‌کند.

ما می‌توانیم با اعمال الگوی TEMPLATE METHOD تکرار واضح را حذف کنیم.

abstract public class VacationPolicy {
  public void accrueVacation() {
    calculateBaseVacationHours();
    alterForLegalMinimums();
    applyToPayroll();
  }
  private void calculateBaseVacationHours() {
    /* ... */ };
  abstract protected void alterForLegalMinimums();
  private void applyToPayroll() {
    /* ... */ };
}
public class USVacationPolicy extends VacationPolicy {
  @Override protected void alterForLegalMinimums() {
    // US specific logic
  }
}
public class EUVacationPolicy extends VacationPolicy {
  @Override protected void alterForLegalMinimums() {
    // EU specific logic
  }
}
کلاس های مشتق شده (subclasses) «حفره» موجود در الگوریتم accrueVacation را پر می‌کنند و تنها بخش‌های اطلاعاتی که تکرار نشده‌اند را فراهم می‌آورند.

بیان واضح

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

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

شما می‌توانید با انتخاب نام‌های مناسب خود را بیان کنید. ما می‌خواهیم نام یک کلاس یا تابع را بشنویم و وقتی مسئولیت‌های آن را کشف می‌کنیم، شگفت‌زده نشویم.

شما همچنین می‌توانید با کوچک نگه‌داشتن توابع و کلاس‌ها، خود را بیان کنید. کلاس‌ها و توابع کوچک معمولاً نام‌گذاری‌شان آسان‌تر، نوشتنشان ساده‌تر و درکشان راحت‌تر است.

استفاده از نام‌گذاری استاندارد نیز می‌تواند به بیان شما کمک کند. الگوهای طراحی، به‌عنوان مثال، عمدتاً در مورد ارتباط و بیان واضح هستند. با استفاده از نام‌های استاندارد الگو، مانند COMMAND یا VISITOR، در نام کلاس‌هایی که این الگوها را پیاده‌سازی می‌کنند، می‌توانید طراحی خود را به‌طور مختصر به سایر توسعه‌دهندگان توصیف کنید.

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

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

کلاس‌ها و متدهای حداقلی

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

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

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

نتیجه‌گیری

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