۱۳۸۷/۱۲/۰۵

زيباتر كد بنويسيم


داشتن آگاهي در مورد ساختارهاي داده‌‌ها، الگوريتم‌ها و يا عملگرهاي بيتي بسيار عالي است و يا تسلط بر نحوه‌ي كاركرد ابزارهايي مانند SharePoint و امثال آن اين روزها ضروري است. اما بايد در نظر داشت، كدي كه امروز تهيه مي‌شود شايد فردا يا ماه ديگر يا چند سال بعد نياز به تغيير داشته باشد، بنابراين دانش زيبا نوشتن يك قطعه كد كه خواندن آن‌را ساده‌تر مي‌كند و در آينده افرادي كه از آن نگهداري خواهند كرد زياد "زجر" نخواهند كشيد، نيز ضروري مي‌باشد. (اگر كامنت‌هاي سايت را خوانده باشيد يكي از دوستان پيغام گذاشته بود، اگر به من بگويند يك ميليون بگيريد و برنامه فعلي را توسعه دهيد يا رفع اشكال كنيد، حاضرم 10 هزارتومان بگيرم و آن‌را از صفر بنويسم! متاسفانه اين يك واقعيت تلخ است كه ناشي از عدم خوانا بودن كدهاي نوشته شده مي‌باشد.)
در ادامه يك سري از اصول زيبا نويسي كدها را بررسي خواهيم كرد.


1- سعي كنيد ميزان تو در تو بودن كدهاي خود را محدود كنيد.
لطفا به مثال زير دقت نمائيد:

void SetA()
{
if(a == b)
{
foreach(C c in cs)
{
if(c == d)
{
a = c;
}
}
}
}

توصيه شده است فقط يك سطح تو در تو بودن را در يك تابع لحاظ كنيد. تابع فوق 4 سطح تو رفتگي را نمايش مي‌دهد (براي رسيدن به a=c بايد چهار بار از tab استفاده كنيد). براي كاهش اين تعداد سطح مي‌توان به صورت زير عمل كرد:

void SetA()
{
if(a != b)
return;

foreach(C c in cs)
a = GetValueOfA(c);
}

TypeOfA GetValueOfA(C c)
{
if(c == d)
return c;

return a;
}

خواناتر نشد؟!

افزونه‌هاي CodeRush و refactor pro مجموعه‌ي DevExpress از لحاظ مباحث refactoring در ويژوال استوديو حرف اول را مي‌زنند. فقط كافي است براي مثال قطعه كد if داخلي را انتخاب كنيد، بلافاصله سه نقطه زير آن ظاهر شده و با كليك بر روي آن امكان استخراج يك تابع از آن‌را براي شما به سرعت فراهم خواهد كرد.



مثالي ديگر:

if (foo) {
if (bar) {
// do something
}
}

به صورت زير هم قابل نوشتن است (جهت كاهش ميزان nesting):

if (foo && bar) {
// do something
}

افزونه‌ي Resharper امكان merge خودكار اين نوع if ها را به همراه دارد.



و يا يك مثال ديگر:
ميزان تو در تو بودن اين تابع جاوا اسكريپتي را ملاحظه نمائيد:

function findShape(flags, point, attribute, list) {
if(!findShapePoints(flags, point, attribute)) {
if(!doFindShapePoints(flags, point, attribute)) {
if(!findInShape(flags, point, attribute)) {
if(!findFromGuide(flags,point) {
if(list.count() > 0 && flags == 1) {
doSomething();
}
}
}
}
}
}

آن‌را به صورت زير هم مي‌توان نوشت با همان كارآيي اما بسيار خواناتر:

function findShape(flags, point, attribute, list) {
if(findShapePoints(flags, point, attribute)) {
return;
}

if(doFindShapePoints(flags, point, attribute)) {
return;
}

if(findInShape(flags, point, attribute)) {
return;
}

if(findFromGuide(flags,point) {
return;
}

if (!(list.count() > 0 && flags == 1)) {
return;
}

doSomething();
}

2- نام‌هاي با معنايي را براي متغيرها وتوابع خود انتخاب كنيد.
با وجود پيشرفت‌هاي زيادي كه در طراحي و پياده سازي IDE ها صورت گرفته و با بودن ابزارهاي تكميل سازي خودكار متن تايپ شده در آن‌ها، اين روزها استفاده از نام‌هاي بلند براي توابع يا متغيرها مشكل ساز نيست و وقت زيادي را تلف نخواهد كرد. براي مثال به نظر شما اگر پس از يك سال به كدهاي زير نگاه كنيد كداميك خود توضيح دهنده‌تر خواهند بود (بدون مراجعه به مستندات موجود)؟

void UpdateBankAccountTransactionListWithYesterdaysTransactions()
//or?
void UpdateTransactions()

3- تنها زماني از كامنت‌ها استفاده كنيد كه لازم هستند.
اگر مورد 2 را رعايت كرده باشيد، كمتر به نوشتن كامنت نياز خواهد بود. از توضيح موارد بديهي خودداري كنيد، زيرا آن‌ها بيشتر سبب اتلاف وقت خواهند شد تا كمك به افراد ديگر يا حتي خود شما. همچنين هيچگاه قطعه كدي را كه به آن نياز نداريد به صورت كامنت شده به مخزن كد در يك سيستم كنترل نگارش ارسال نكنيد.

//function thisReallyHandyFunction() {
// someMagic();
// someMoreMagic();
// magicNumber = evenMoreMagic();
// return magicNumber;
//}

زمانيكه از ورژن كنترل استفاده مي‌كنيد نيازي به كامنت كردن قسمتي از كد كه شايد در آينده قرار است مجددا به آن بازگشت نمود، نيست. اين نوع سطرها بايد از كد شما حذف شوند. تمام سيستم‌هاي ورژن كنترل امكان revert و بازگشت به قبل را دارند و اساسا اين يكي از دلايلي است كه از آن‌ها استفاده مي‌شود!
به صورت خلاصه جهت نگهداري سوابق كدهاي قديمي بايد از سورس كنترل استفاده كرد و نه به صورت كامنت قرار دادن آن‌ها.

از كامنت‌هاي نوع زير پرهيز كنيد كه بيشتر سبب رژه رفتن روي اعصاب خواننده مي‌شود تا كمك به او! (خواننده را بي‌سواد فرض نكنيد)

// Get the student's id
thisId = student.getId();

كامنت زير بي معني است!

// TODO: This is too bad. FIX IT!

اگر شخص ديگري به آن مراجعه كند نمي‌داند كه منظور چيست و دقيقا مشكل كجاست. شبيه به افرادي كه به فوروم‌ها مراجعه مي‌كنند و مي‌گويند برنامه كار نمي كند و همين! طرف مقابل علم غيب ندارد كه مشكل شما را حدس بزند! به توضيحات بيشتري نياز است.


4- عدم استفاده از عبارات شرطي بي‌مورد هنگام بازگشت دادن يك مقدار bool:
مثال زير را درنظر بگيريد:

if (foo>bar) {
return true;
} else {
return false;
}

آن‌را به صورت زير هم مي‌توان نوشت:

return foo>bar;

5- استفاده از متغيرهاي بي مورد:
براي مثال:

Something something = new Something(foo);
return something;

كه مي‌شود آ‌ن را به صورت زير هم نوشت:

return new Something(foo);

البته يكي از خاصيت‌هاي استفاده از افزونه‌ي Resharper ويژوال استوديو، گوشزد كردن و اصلاح خودكار موارد 4 و 5 است.



6- در نگارش‌هاي جديد دات نت فريم ورك استفاده از ArrayList منسوخ شده است. بجاي آن بهتر است از ليست‌هاي جنريك استفاده شود. كدي كه در آن از ArrayList استفاده مي‌شود طعم دات نت فريم ورك 1 را مي‌دهد!

7- لطفا بين خطوط فاصله ايجاد كنيد. ايجاد فواصل مجاني است!

دو تابع جاوا اسكريپتي زير را (كه در حقيقت يك تابع هستند) در نظر بگيريد:

function getSomeAngle() {
// Some code here then
radAngle1 = Math.atan(slope(center, point1));
radAngle2 = Math.atan(slope(center, point2));
firstAngle = getStartAngle(radAngle1, point1, center);
secondAngle = getStartAngle(radAngle2, point2, center);
radAngle1 = degreesToRadians(firstAngle);
radAngle2 = degreesToRadians(secondAngle);
baseRadius = distance(point, center);
radius = baseRadius + (lines * y);
p1["x"] = roundValue(radius * Math.cos(radAngle1) + center["x"]);
p1["y"] = roundValue(radius * Math.sin(radAngle1) + center["y"]);
pt2["x"] = roundValue(radius * Math.cos(radAngle2) + center["y"]);
pt2["y"] = roundValue(radius * Math.sin(radAngle2) + center["y");
// Now some more code
}

function getSomeAngle() {
// Some code here then
radAngle1 = Math.atan(slope(center, point1));
radAngle2 = Math.atan(slope(center, point2));

firstAngle = getStartAngle(radAngle1, point1, center);
secondAngle = getStartAngle(radAngle2, point2, center);

radAngle1 = degreesToRadians(firstAngle);
radAngle2 = degreesToRadians(secondAngle);

baseRadius = distance(point, center);
radius = baseRadius + (lines * y);

p1["x"] = roundValue(radius * Math.cos(radAngle1) + center["x"]);
p1["y"] = roundValue(radius * Math.sin(radAngle1) + center["y"]);

pt2["x"] = roundValue(radius * Math.cos(radAngle2) + center["y"]);
pt2["y"] = roundValue(radius * Math.sin(radAngle2) + center["y");
// Now some more code

}

كداميك خواناتر است؟
استفاده از فاصله بين خطوط در تابع دوم باعث بالا رفتن خوانايي آن شده است و اين طور به نظر مي‌رسد كه سطرهايي با عملكرد مشابه در يك گروه كنار هم قرار گرفته‌اند.

8- توابع خود را كوتاه كنيد.
يك تابع نبايد بيشتر از 50 سطر باشد (البته در اين مورد بين علما اختلاف هست!). اگر بيشتر شد بدون شك نياز به refactoring داشته و بايد به چند قسمت تقسيم شود تا خوانايي كد افزايش يابد.
به صورت خلاصه يك تابع فقط بايد يك كار را انجام دهد و بايد بتوان عملكرد آن‌را در طي يك جمله توضيح داد.

9- از اعداد جادويي در كدهاي خود استفاده نكنيد!
كد زير هيچ معنايي ندارد!

if(mode == 3){ ... }
else if(mode == 4) { ... }

بجاي اين اعداد بي مفهوم بايد از enum استفاده كرد:

if(mode == MyEnum.ShowAllUsers) { ... }
else if(mode == MyEnum.ShowOnlyActiveUsers) { ... }

10- توابع شما نبايد تعداد پارامتر زيادي داشته باشند
اگر نياز به تعداد زيادي پارامتر ورودي وجود داشت (بيش از 6 مورد) از struct و يا كلاس جهت معرفي آن‌ها استفاده كنيد.