بسیاری از ما احساس میکنیم که چهار قانون طراحی ساده کنت بک کمک زیادی به ایجاد نرمافزارهایی با طراحی خوب میکند. طبق نظر کنت، طراحی «ساده» است اگر این قوانین را رعایت کند:
- تمام آزمونها را اجرا کند
- هیچ نوع تکراری نداشته باشد
- قصد برنامهنویس را به خوبی بیان کند
- تعداد کلاسها و متدها را به حداقل برساند
این قوانین به ترتیب اهمیت ارائه شدهاند.
نخست و مهمتر از همه، طراحی باید سیستمی را تولید کند که طبق انتظارات عمل کند. ممکن است سیستمی طراحی بینقص روی کاغذ داشته باشد، اما اگر هیچ راه سادهای برای تأیید اینکه سیستم واقعاً طبق برنامه کار میکند وجود نداشته باشد، تلاشهای کاغذی قابل سوال است.
سیستمی که بهطور جامع تست شده و تمام آزمونها را در همهجا پاس میکند، یک سیستم قابل آزمون است. این یک اظهار واضح اما مهم است. سیستمهایی که قابل آزمون نیستند، قابل تأیید نیستند. به نوعی، سیستمی که نمیتوان آن را تأیید کرد نباید به هیچ وجه به کار گرفته شود.
خوشبختانه، قابل آزمون کردن سیستمها ما را به سمت طراحیهایی سوق میدهد که کلاسهایمان کوچک و با هدف واحد هستند. تست کردن کلاسهایی که مطابق با SRP (اصل مسئولیت واحد) هستند، بسیار آسانتر است. هر چه تعداد آزمونهایی که مینویسیم بیشتر باشد، بیشتر به سمت چیزهایی پیش میرویم که آزمایش آنها سادهتر است. بنابراین، اطمینان از اینکه سیستم ما کاملاً قابل آزمون است، به ما کمک میکند طراحیهای بهتری ایجاد کنیم.
پیوندهای نزدیک (Tight Coupling) نوشتن آزمونها را دشوار میکند. بنابراین، هر چه تعداد آزمونهای بیشتری بنویسیم، بیشتر از اصولی مانند DIP (اصل وابستگی معکوس) و ابزارهایی مانند تزریق وابستگی، اینترفیسها و انتزاع استفاده میکنیم تا پیوند را به حداقل برسانیم. طراحیهای ما حتی بیشتر بهبود مییابند.
به طرز شگفتانگیزی، پیروی از یک قانون ساده و واضح که میگوید باید آزمونهایی داشته باشیم و آنها را بهطور مداوم اجرا کنیم، تأثیر زیادی بر رعایت اهداف اصلی شیگرایی (OO) یعنی پیوند کم و همبستگی بالا دارد. نوشتن آزمونها به طراحیهای بهتر منجر میشود.
پس از اینکه آزمونها را داریم، قدرت این را داریم که کد و کلاسهایمان را تمیز نگهداریم. ما این کار را با بازسازی تدریجی کد انجام میدهیم. برای هر چند خط کدی که اضافه میکنیم، توقف میکنیم و به طراحی جدید فکر میکنیم. آیا طراحی را خراب کردیم؟ اگر بله، آن را اصلاح میکنیم و آزمونهایمان را اجرا میکنیم تا نشان دهیم که چیزی را خراب نکردهایم. این که ما این آزمونها را داریم، ترس از اینکه تمیز کردن کد آن را خراب کند را از بین میبرد!
در این مرحله بازسازی، میتوانیم هر چیزی را از تمام دانش موجود درباره طراحی نرمافزار خوب به کار بگیریم. میتوانیم همبستگی را افزایش دهیم، پیوند را کاهش دهیم، مسائل را جدا کنیم، نگرانیهای سیستم را ماژولار کنیم، توابع و کلاسهایمان را کوچکتر کنیم، نامهای بهتری انتخاب کنیم و غیره. اینجا جایی است که سه قانون نهایی طراحی ساده را نیز به کار میبریم: حذف تکرار، اطمینان از بیان خوب و به حداقل رساندن تعداد کلاسها و متدها.
تکرار دشمن اصلی یک سیستم با طراحی خوب است. این امر به معنای کار اضافی، ریسک اضافی و پیچیدگی غیرضروری اضافی است. تکرار در اشکال مختلفی بروز میکند. خطوط کدی که دقیقاً شبیه به هم هستند، البته تکرار به شمار میآیند. خطوط کدی که مشابه هستند، اغلب میتوانند به گونهای تغییر یابند که حتی بیشتر شبیه هم شوند تا بازسازی آنها آسانتر باشد. و تکرار میتواند در اشکال دیگر نیز وجود داشته باشد، مانند تکرار در پیادهسازی. به عنوان مثال، ممکن است دو متد در یک کلاس مجموعه داشته باشیم:
int size() {}
boolean isEmpty() {}
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;
}
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;
}
الگوی 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
// ...
}
}
ما میتوانیم با اعمال الگوی 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
}
}
بسیاری از ما تجربه کار بر روی کدهای پیچیده را داشتهایم. بسیاری از ما خودمان نیز کدهای پیچیدهای تولید کردهایم. نوشتن کدی که خودمان بهخوبی درک میکنیم، آسان است، زیرا در زمان نوشتن آن، عمیقاً درگیر مسئلهای هستیم که سعی داریم حل کنیم. اما سایر افرادی که به نگهداری کد میپردازند، این درک عمیق را نخواهند داشت.
بیشتر هزینه یک پروژه نرمافزاری در نگهداری بلندمدت آن است. برای به حداقل رساندن احتمال بروز اشکالات هنگام ایجاد تغییر، ضروری است که بتوانیم درک کنیم سیستم چه کاری انجام میدهد. با پیچیدهتر شدن سیستمها، زمان بیشتری برای درک آنها نیاز است و احتمال بروز سوءتفاهم نیز بیشتر میشود. بنابراین، کد باید بهطور واضح قصد نویسندهاش را بیان کند. هر چه نویسنده بتواند کد را واضحتر بنویسد، زمان کمتری برای درک آن توسط دیگران صرف خواهد شد. این کار اشکالات را کاهش داده و هزینه نگهداری را پایین میآورد.
شما میتوانید با انتخاب نامهای مناسب خود را بیان کنید. ما میخواهیم نام یک کلاس یا تابع را بشنویم و وقتی مسئولیتهای آن را کشف میکنیم، شگفتزده نشویم.
شما همچنین میتوانید با کوچک نگهداشتن توابع و کلاسها، خود را بیان کنید. کلاسها و توابع کوچک معمولاً نامگذاریشان آسانتر، نوشتنشان سادهتر و درکشان راحتتر است.
استفاده از نامگذاری استاندارد نیز میتواند به بیان شما کمک کند. الگوهای طراحی، بهعنوان مثال، عمدتاً در مورد ارتباط و بیان واضح هستند. با استفاده از نامهای استاندارد الگو، مانند COMMAND یا VISITOR، در نام کلاسهایی که این الگوها را پیادهسازی میکنند، میتوانید طراحی خود را بهطور مختصر به سایر توسعهدهندگان توصیف کنید.
آزمونهای واحد بهخوبی نوشتهشده نیز بیانی واضح دارند. هدف اصلی آزمونها عمل بهعنوان مستندات از طریق مثال است. کسی که آزمونهای ما را میخواند باید بتواند درک سریعی از آنچه یک کلاس به آن مربوط میشود، به دست آورد. اما مهمترین راه برای بیان واضح، تلاش کردن است. اغلب اوقات، ما کد خود را کار میاندازیم و سپس به مشکل بعدی میپردازیم بدون اینکه به اندازه کافی به آسانی خواندن آن کد برای شخص بعدی فکر کنیم. به خاطر داشته باشید که محتملترین فرد بعدی که کد را خواهد خواند، خود شما هستید.
پس کمی به کار خود افتخار کنید. کمی زمان برای هر یک از توابع و کلاسهایتان بگذارید. نامهای بهتری انتخاب کنید، توابع بزرگ را به توابع کوچکتر تقسیم کنید و بهطور کلی به آنچه ایجاد کردهاید، توجه کنید. توجه یک منبع ارزشمند است.
حتی مفاهیم بنیادی مانند حذف تکرار، بیان واضح کد و SRP نیز میتوانند به افراط کشیده شوند. در تلاشی برای کوچک نگهداشتن کلاسها و متدهایمان، ممکن است کلاسها و متدهای بسیار کوچکی ایجاد کنیم. بنابراین این قانون پیشنهاد میکند که ما همچنین تعداد توابع و کلاسهایمان را پایین نگه داریم.
تعداد بالای کلاسها و متدها گاهی نتیجهی دگماتیسم بیفایده است. بهعنوان مثال، یک استاندارد کدنویسی که بر ایجاد یک اینترفیس برای هر کلاس تأکید دارد را در نظر بگیرید. یا توسعهدهندگانی که بر این باورند که فیلدها و رفتارها همیشه باید در کلاسهای داده و کلاسهای رفتار جدا شوند. چنین دگماتی باید مقاومت شود و رویکردی عملیتر اتخاذ گردد.
هدف ما این است که سیستم کلی خود را کوچک نگهداریم در حالی که همچنین توابع و کلاسهایمان را کوچک نگهداشتهایم. اما به خاطر داشته باشید که این قانون پایینترین اولویت را در میان چهار قانون طراحی ساده دارد. بنابراین، اگرچه مهم است که تعداد کلاسها و توابع را پایین نگه داریم، مهمتر است که آزمونها را داشته باشیم، تکرار را حذف کنیم و بهطور واضح بیان کنیم.
آیا مجموعهای از روشهای ساده وجود دارد که بتواند تجربه را جایگزین کند؟ به وضوح نه. از طرف دیگر، روشهای توصیفشده در این فصل و در این کتاب، شکل متمرکزی از تجربیات چند دههای نویسندگان است. پیروی از روش طراحی ساده میتواند و به توسعهدهندگان این امکان را میدهد که به اصول و الگوهای خوب پایبند باشند که در غیر این صورت سالها طول میکشد تا یاد بگیرند.