
نامها در همه بخشهای برنامه استفاده میشوند. نام متغیرها، کلاسها، فولدرها و فایلها. ما در برنامه به دفعات از نامهای مختلف استفاده میکنیم. به همین دلیل است که باید سعی کنیم به بهترین شکل نام گذاریها را انجام دهیم.
این مطلب برگرفته از فصل دوم کتاب Clean Code نوشته رابرت مارتین است. بعضی قسمتها ترجمه شده بعضی قسمتها هم خلاصه شده. نمونه کدهای موجود در این کتاب به زبان جاوا نوشته شده. اما کدهای موجود در این مقاله به زبان PHP نوشته شدهاند. کدهای زبان جاوا هم در خود کتاب موجود هستند و میتوانید هر زمان نیاز داشتید به آنها مراجعه کنید.
برای هر چیزی (متغیر، تابع، کلاس و …) باید یک نام انتخاب کنید. برای انتخاب نام با صرف وقت و دقت زیاد، گزینهای را انتخاب کنید که به بهترین شکل نشان دهنده هدف و کاربرد صاحب نام باشد. مطمئن باشید با صرف وقت بیشتر و انتخاب نامهای مناسب، در آینده و در هنگام نگهداری محصول زمان بیشتری صرفه جویی خواهد شد. به نامها اهمیت داده و هرگاه نام بهتری پیدا کردید آنها را عوض کنید.
نام یک متغیر، تابع و یا کلاس باید پاسخ تمامی سوالهای اساسی درباره آن باشد. اگر نامی انتخاب کردید که نیاز به توضیحات دارد تا خواننده متوجه کاربردش شود حتما آن را عوض کنید.
$d = 10; // elapsed time in days
این اسم هیچ مفهومی ندارد و هدف را نمایان نمیکند. باید نامی انتخاب کنیم که مشخص کند با چه سنجهای اندازه چه چیزی را در این متغیر ذخیره کردهایم:
$elapsedTimeInDays = 10; $daysSinceCreation = 10; $daysSinceModification = 10; $fileAgeInDays = 10;
نام مناسب فهم و تغییر کد را آسان میکند. کد زیر را بخوانید:
public getThem(): array { $list1 = []; foreach ($this->theList as $x) { If ($x[0] == 4) { array_push($list1, $x); } } return $list1; }
به نظر شما این کد چه کاری انجام میدهد؟ با اینکه این برنامه بسیار ساده است، کدها تو رفتگی درستی دارند و تعداد متغیرها زیاد نیست ولی درک آن سخت است. مشکل در اینجا سادگی کد نیست. مشکل این است که این کد در متن خود به صورت صریح و واضح کاربردش را به برنامه نویس منتقل نمیکند.
در صورتی که این کد به وضوح در محتوا کاربردش را منتقل کند. برنامه نویس بعد از مرور آن باید بتواند به سوالهای زیر پاسخ دهد:
فرض کنید این برنامه قسمتی از بازی کشتی مین جمع کن است. در اینصورت اگر بورد بازی لیستی از سلولها باشد که در اینجا theList نامگذاری شده. میتوانیم نام آن را به gameBoard تغییر دهیم. هر سلول بورد با یک آرایه مشخص شده که اولین عنصر آن وضعیت سلول را نشان میدهد و وضعیت ۴ به معنی نشانه گذاری شده است. در اینصورت قطعه کد قبلی به شکل زیر تغییر خواهد کرد.
public getFlaggedCells(): array { $flaggedCells = []; foreach ($this->gameBoard as $cell) { If ($cell[self::STATUS_VALUE] == self::FLAGGED) { array_push($flaggedCells, $cell); } } return $flaggedCells; }
هنوز میتوانیم با نوشتن یک کلاس برای سلولها این کد را بهینهتر کنیم. در صورت استفاده از این کلاس کد ما هم به شکل زیر تغییر خواهد کرد.
public getFlaggedCells(): array { $flaggedCells = []; foreach ($this->gameBoard as $cell) { If ($cell->isFlagged()) { array_push($flaggedCells, $cell); } } return $flaggedCells; }
با همین تغییرات جزئی فهمیدن این کد سادهتر شد. همین فهم آسان تر کد به ما نشان میدهد که انتخاب نامهای مناسب چه اهمیتی در برنامه نویسی دارد.
دقت کنید به اشتباه از کلماتی که معانی متفاوتی از آنچه مدنظر شماست دارند استفاده نکنید. به عنوان مثال hp، aix و sco نامهای مناسبی نیستند. به این دلیل که هرکدام نام یک پلتفرم یونیکس است. مثلا اگر نام وتر مثلث قائم الزاویه (hypotenuse) را در برنامه خود خلاصه کرده و hp بگذارید. احتمالا همکاران خود را گمراه خواهید کرد.
اسم یک گروه از حسابها را accountList نگذارید. کلمه list معنی خاصی برای برنامه نویسان برخی زبانهای برنامه نویسی مانند جاوا دارد. اگر این accountList یک list واقعی با مفهوم رایج آن در زبان برنامه نویسی نباشد میتواند آنها را گمراه کند. در نتیجه استفاده از نامهایی مانند accountGroup یا bunchOfAccounts یا حتی فقط accounts بهتر است.
از نامهای طولانی که فقط اختلافهای جزئی دارند پرهیز کنید. به عنوان مثال چقدر زمان لازم است تا متوجه تفاوت متغیری با نام XYZControllerForEfficientHandlingOfStrings از متغیر دیگری با نام XYZControllerForEfficientStorageOfStrings در یک برنامه شوید؟
نامگذاری هماهنگ مفاهیم مشابه در واقع ارائه اطلاعات مفید به دیگران است. در مقابل استفاده از املای متفاوت اطلاعات غلط است. محیطهای برنامه نویسی در زمان تایپ قسمتی از نام یک لیست پیشنهادی برای تکمیل آن ارائه میدهند. در زمان انتخاب نام برای مفاهیم مشابه دقت کرده و الگوی یکسانی را رعایت کنید. به صورتی که در هنگام ارائه پیشنهاد توسط محیط برنامه نویسی همه نامهایی که مربوط به یک مفهوم هستند در کنار یکدیگر نشان داده شوند. در عین حال تفاوت آشکاری هم بین نامها وجود داشته باشد تا بتوانید با یک نگاه سریع نام مورد نظر را در لیست پیدا کنید.
یک مثال بد از نامهایی که اطلاعات غلط میدهند استفاده از حرف ال کوچک (l) و یا او بزرگ (O) برای نام متغیر است. مخصوصا در کنار یکدیگر. این دو حرف خیلی شبیه صفر و یک انگلیسی میباشند.
$a = $l; if ( $O == $l ) { $a = $O1; } else { $l = 01; }
واضح است که امکان وجود دو متغیر با نام مشابه در برنامه وجود ندارد. در برخی شرایط ممکن است وسوسه شوید که نام یک متغیر را برای ایجاد تفاوت با غلط املایی انتخاب کنید، یا از یک یا چند عدد در نام آن استفاده کنید. شاید حتی کلمه اضافه و بی معنی به نام آن بیفزایید. با این کار مفسر یا کامپایلری بدون خطا کد شما را اجرا یا کامپایل میکند. ولی اگر در شرایطی قرار گرفتید که به نامهای متفاوت نیاز داشتید از این روشها استفاده نکنید. باید نامهایی انتخاب کنید که معنی آنها با هم متفاوت بوده و تفاوت با معنایی ایجاد کنند.
یک نمونه دیگر سری متغیرهایی هستند که با اعداد مشخص میشوند، این متغیرها نه تنها اطلاعات غلط به خواننده کد نمیدهند بلکه اصلا اطلاعاتی درباره هدف برنامه نویس در آنها وجود ندارد. به مثال زیر توجه کنید:
public static copyChars(array $a1, array $a2): void { for ($i = 0; $i < count($a1); i++) { $a2[$i] = $a1[$i]; } }
اگر به جای a1 و a2 از destination و source استفاده کنیم، این کد خوانایی بیشتری خواهد داشت.
برای تمایز نامها بدون هدف یک کلمه به آنها اضافه نکنید. فرض کنید کلاسی با نام Product داریم. اگر کلاسهای دیگری با نامهای ProductData یا ProductInfo درست کردهاید، در واقع تفاوت در نامها ایجاد کردهاید ولی معنی آنها با هم فرقی ندارند. در اینجا Data و Info کلماتی مانند a، an و the بوده و تفاوت معنایی خاصی ایجاد نمیکنند.
اما درباره استفاده از پیشوندهایی مانند a و the به خاطر داشته باشید. تا زمانی که این گونه پیشوندها تفاوت معنا داری ایجاد میکنند استفاده از آنها اشکالی ندارد. مثلا ممکن است از a برای متغیرهای محلی و the برای آرگومانهای تابع استفاده کنید. زمانی استفاده از آنها اشکال دارد که یک متغیر را theZork نامگذاری کنید به دلیل اینکه متغیر دیگری به نام zork دارید.
کلماتی مانند موارد ذکر شده در بالا در برنامه زائد هستند. هیچ وقت از کلمه variable در نام متغیر استفاده نکنید. از table در نام جدول استفاده نکنید. آیا nameString بهتر از name است؟ به نظر شما امکان دارد name یک عدد اعشاری باشد؟ حتی اگر name یک عدد اعشاری باشد، آنگاه قوانین ذکر شده درباره اطلاعات غلط را زیر پا گذاشتهایم. اکنون فرض کنید یک کلاس با نام customer و یکی با نام customerObject داریم. تفاوت اینها چیست؟
کدامیک مسیر بهتری برای رسیدن به تاریخچه سفارشات مشتری است؟
به برنامه دیگری فکر کنید که توابع زیر در آن تعریف شدهاند:
getActiveAccount(); getActiveAccounts(); getActiveAccountInfo();
چگونه یک برنامه نویس در این پروژه بداند کدام تابع را صدا بزند؟
بدون وجود یک روتین مشخص متغیر moneyAmount و money، customerInfo و customer ، accountData و account ، theMessage و message با هم تفاوتی ندارند. نامها را به گونهای انتخاب کنید که خواننده متوجه شود هر کدام چه ویژگیها و تفاوتهایی با دیگری دارند.
اگر برای متغیرها نامهایی انتخاب کنید که قابل بیان نباشند. هنگام صحبت درباره آنها و بردن نامشان مجبورید صداهای عجیب و غریبی ایجاد کنید!
برنامه نویسی یک فرایند اجتماعی است که در طول آن با همتیمیهای خود مبادله اطلاعات خواهید کرد پس حتما کدتان را به شکلی بنویسید که بتوانید درباره آن صحبت کنید.
دو کد زیر را مقایسه کنید:
class DtaRcrd102 { private $genymdhms; private $modymdhms; private $pszqint = "102"; /* ... */ }
و
class Customer { private $generationTimestamp; private $modificationTimestamp;; private $recordId = "102"; /* ... */ }
شما ترجیح میدهید درباره کد دوم با همکار خود صحبت کنید یا کد اول؟
از به کار بردن نامهای تک حرفی و ثابتهای عددی اجتناب کنید. جستجوی این نامها در برنامه بسیار سخت است. پیدا کردن Max_CLASSES_PER_STUDENT در برنامه بسیار ساده تر از عدد 7 است. ممکن است با جستجوی 7 هر چیزی ممکن است به عنوان جواب جستجو پیدا شود. حتی نام فایلی که این کاراکتر را در خود دارد.
استفاده از یک حرف مثل e هم به عنوان نام متغیر همین مشکل را بوجود میآورد. با جستجوی این حرف ممکن است صدها کلمه که شامل این حرف هستند به عنوان نتیجه جستجو در متن برنامه به شما نمایش داده شود.
شما میتوانید متغیرهای یک کاراکتری را فقط به عنوان متغیر محلی در متدهای کوتاه استفاده کنید. اگر متغیر یا ثابتی در جاهای مختلف کد استفاده میشود. بهتر است یک نام قابل جستجو برای آن انتخاب کنید. باز هم مقایسه کنید:
for ($j=0; $j<34; $j++) { $s += ($t[$j]*4)/5; }
و
$realDaysPerIdealDay = 4; const WORK_DAYS_PER_WEEK = 5; $sum = 0; for ($j=0; $j < NUMBER_OF_TASKS; $j++) { $realTaskDays = $taskEstimate[$j] * $realDaysPerIdealDay; $realTaskWeeks = ($realdays / WORK_DAYS_PER_WEEK); $sum += $realTaskWeeks; }
در این برنامه sum شاید نام خوبی نباشد ولی حداقل قابل جستجو است. برنامهای که نامگذاریهای بهتری دارد طولانی تر است. اما دقت کنید که پیدا کردن WORK_DAYS_PER_WEEK در برنامه چقدر از پیدا کردن همه مکانهایی که عدد 5 با معنی مورد نظر ما استفاده شده، راحت تر است.
نامها نباید با اطلاعاتی درباره نوع یا اسکوپ ترکیب و نشانه گذاری شوند. پیشوندهایی مثل _m یا f در برنامههایی که امروزه نوشته میشوند کاربردی ندارند. همچنین کدگذاری نامها با زیرسیستم یا نام پروژه هم کار اشتباهیست. به عنوان مثال ممکن است وسوسه شوید در یک سیستم کار با تصاویر که تیم شما با نام visual imaging system آن را میشناسند از پیشوند vis_ در همه نامها استفاده کنید.
از استفاده از نماد مجارستانی هم اجتناب کنید. استفاده از این نمادها در گذشته بسیار رایج بوده ولی اکنون نیازی به آنها نداریم.
در اینترفیسها و پیاده سازی آنها هم این مسئله رایج است که به عنوان مثال برنامه نویس نام اینترفیس را با پیشوند I انتخاب میکند. این پیشوند لازم نیست حتی اگر در شرایط خاصی مجبور شدید در اینترفیس و یا پیاده سازی آن کدگذاری کنید این کار را در نام پیاده سازی انجام دهید.
برای مثال نام اینترفیس را ShapeFactory بگذارید و نام پیاده سازی را ShapeFactoryImp.
وقتی برای نامگذاری از کلمات آشنا در ادبیات حوزه مسئله یا پاسخ آن استفاده نکنید. خواننده کد شما مجبور است نام انتخابی شما را در ذهن خود به سختی به خاطر بسپارد. اما با استفاده از کلمات آشنا در حوزه مسئله به خاطر سپردن آنها را برای همکاران خود سادهتر خواهید کرد.
متغیرهای یک کاراکتری هم این مشکل را در برنامه بوجود میآورند. از متغیرهای تک کاراکتری فقط در حلقههای کوچک و چند خطی استفاده کنید.
اسم کلاس باید یک نام باشد مانند Customer، WikiPage، Account و AddressParser. از کلماتی مانند Manager، Processor، Data یا Info در نام کلاس استفاده نکنید. از افعال هم برای اسم کلاس استفاده نکنید. دلیل آن را در بخش بعد متوجه خواهید شد.
نام متدها باید فعل باشد مثل postPayment، deletePage یا save. اکسسورها، mutatorها و predicateها باید بر اساس محتوا و با پیشوندهای get، set و is نامگذاری شوند.
$name = $employee->getName(); $customer->setName("mike"); if ($paycheck->isPosted())...
اگر از اسامی بامزه برای متغیر، تابع، کلاس و … استفاده کنید. فقط افرادی که شوخ طبعی شما را درک میکنند و از آنچه در ذهن شما میگذرد اطلاع دارند معنی آنها را متوجه میشوند. مثلا HollyHandGranade چکار میکند؟ اگر قرار است این نام تابعی باشد که چیزی را پاک میکند بهتر نیست نام آن را DeleteItems بگذاریم؟ باید همیشه واضح بودن معانی را به جنبه تفریحی نامی که انتخاب میکنیم اولویت دهیم. در یک جمله میتوانیم بگوییم:
Say what you mean, mean what you say.
برای هر مفهوم انتزاعی یک کلمه انتخاب کنید و از آن به بعد از همان کلمه برای آن مفهوم استفاده کنید. به عنوان مثال اگر در کلاسهای مختلف برنامه برای مفهوم دریافت اطلاعات از fetch، retrieve و get استفاده کنید. کسی که برنامه را مطالعه میکند احتمالا گیج خواهد شد. از کجا به خاطر خواهید داشت که در کدام کلاس برای گرفتن دیتا از get استفاده کردهاید و در کدام کلاس از fetch؟ یا باید همیشه به یاد داشته باشید که چه کسی یا گروهی آن قسمت از کد را نوشتهاند و از چه کلماتی استفاده کردهاند. یا وقت ارزشمند را به مرور کدها اختصاص دهید.
همچین اگر یک controller، یک manager و یک driver در یک برنامه دارید، تفاوت بین آنها چیست؟
فرق بین DeviceManager و ProtocolController چیست؟ چرا هردو کنترلر و یا منجر نیستند؟
اگر در کلاسهای مختلف از نامهای مشابه برای مفاهیم مشابه استفاده کردهاید. تا زمانی لیست پارامترها و مقدار بازگشتی متد های هم اسم از نظر مفهومی شبیه هم باشند مشکلی نخواهید داشت. در غیر اینصورت از نام دیگری استفاده کنید. برای مثال ممکن است در برنامه از نام add برای جمع کردن یا چسباندن دو مقدار به هم و بوجود آوردن یک مقدار جدید در جاهای مختلف استفاده کرده باشیم. اما در یک کلاس متدی داریم که یک ورودی گرفته و آن را به یک آرایه اضافه میکند. باید آن را هم add بنامیم؟ شاید به نظر بیاید که به دلیل اینکه در خیلی کلاسهای دیگر برنامه از این کلمه استفاده کردهایم کلمه مناسبی باشد. ولی مفهوم آن در این کلاس فرق دارد و بهتر است از نامهایی مانند insert یا append استفاده کنیم.
کسانی که کد را میخوانند برنامه نویس هستند، پس از واژگان علوم کامپیوتری استفاده کنید. نام الگوریتمها، نام پترنها و … همه نامها را از ادبیات خود مساله انتخاب نکنید تا همکارانتان مجبور نشوند از مشتری معنی تمام نامها را بپرسند.
نام AccountVisitor برای برنامه نویسی که با پترن ویزیتور آشناست معنی خاصی دارد. کدام برنامه نویسی نمیداند JobQueue چیست؟ اگر بخواهیم خلاصه بگوییم، برای کارهای تکنیکال نام تکنیکال انتخاب کنید.
وقتی واژه تخصصی برنامه نویسی برای موضوعی که میخواهید وجود ندارد از واژگانی در دامنه مسئله استفاده کنید. در اینصورت سایر برنامه نویسان اگر مفهوم نامی که انتخاب کردهاید را متوجه نشوند و شما هم در دسترس نباشید میتوانند از ذینفعان پروژه معنی آن را بپرسند.
اگر نامها به تنهایی بی معنی هستند. باید آنها را در متن قرار دهیم تا معنی به خوبی منتقل شود با انتخاب نامهای خوب برای کلاسها، توابع یا namespace ها این را میتوانید به بهترین شکل انجام دهید. اگر نتوانستید این روش را هم انجام دهید تنها راه باقی مانده استفاده از پیشوندها خواهد بود.
تصور کنید در یک برنامه متغیرهایی با نامهای firstName، lastName، street، city، state و zipcode وجود دارد. اگر همه را با هم در نظر بگیریم واضح است که یک آدرس را تشکیل میدهند. ولی اگر در یک متد فقط متغیر state را مشاهده کنید چطور؟ آیا باز هم بلافاصله نتیجه میگیرید که این هم یک قسمت از آدرس است؟
در اینگونه موارد میتوانید پیشوند مناسبی به نامها اضافه کنید. addrFirstName ، addrLastName ، addrState و …. با این روش حداقل خواننده متوجه میشود که این متغیرها قسمتی از یک ساختار بزرگتر هستند. البته راه حل بهتری هم وجود دارد و آن درست کردن یک کلاس با نام Address است. با این کار کامپایلر هم متوجه میشود که متغیرها متعلق به مفهوم بزرگتری هستند.
تابع زیر را مشاهده کنید. نام تابع قسمتی از مفهوم را بیان میکند. بقیه محتوا را باید با مطالعه الگوریتم دریابیم. با مطالعه تابع متوجه میشوید که سه متغیر number ،verb و pluralModifier قسمتی از پیغامی هستند که تابع چاپ میکند. متاسفانه محتوا و مفهوم تابع باید با مطالعه آن استنباط شود و در ابتدا معنی نام متغیرها واضح نیست.
private function printGuessStatistics(string $candidate, int $count): void { if ($count == 0) { $number = "no"; $verb = "are"; $pluralModifier = "s"; } elseif ($count == 1) { $number = "1"; $verb = "is"; $pluralModifier = ""; } else { $number = $count; $verb = "are"; $pluralModifier = "s"; } echo "There $verb $number {$candidate}{$pluralModifier}"; }
این تابع بلند است و متغیرها در کل تابع پخش شدهاند. برای اینکه تابع را به بخشهای کوچکتر تقسیم کنیم. باید کلاسی با نام GuessStatisticsMessage درست کنیم و متغیرها را در آن کلاس تعریف کنیم. این کار باعث میشود متغیرها در متن کلاس مفهوم واضحی داشته باشند. هر برنامه نویسی که کد را بخواند متوجه میشود که این متغیرها بخشی از GuessStatisticsMessage میباشند. بهبود محتوا الگوریتم را هم بهبود بخشیده و آن را به توابع کوچکتری تقسیم خواهد کرد. در ادامه نسخه بهبود یافته کد بالا را مشاهده کنید.
class GuessStatisticsMessage { private $number; private $verb; private $pluralModifier; public function make(string $candidate, int $count): string { $this->createPluralDependentMessageParts($count); return 'There '.$this->verb.' '.$this->number.' '.$candidate.$this->pluralModifier; } private function createPluralDependentMessageParts(int $count): void { if ($count == 0) { $this->thereAreNoLetters(); } elseif ($count == 1) { $this->thereIsOneLetter(); } else { $this->thereAreManyLetters(); } } private function thereAreManyLetters(int $count): void { $this->number = $count; $this->verb = "are"; $this->pluralModifier = "s"; } private function thereIsOneLetter(): void { $this->number = "1"; $this->verb = "is"; $this->pluralModifier = ""; } private function thereAreNoLetters(): void { $this->number = "no"; $this->verb = "are"; $this->pluralModifier = "s"; } }
در یک برنامه فرضی با نام Gas Station Deluxe شروع نام همه کلاسها با GSD ایده مناسبی نیست. در این صورت با تایپ G برنامه IDE که استفاده میکنید لیست بلند بالایی از تمامی کلاسهای برنامه به شما میدهد. با این کار در واقع کاری کردهاید که IDE نتواند به صورت مناسبی به شما کمک کند.
فرض کنید یک کلاس MailingAddress در ماژول حسابداری GSD نوشتهاید و نام آن را GSDAccountAddress گذاشتید. بعدا آدرس پستی مشتری را هم برای قسمت تماس با مشتری نیاز دارید. آیا نام آن را GSDCustomerAddress میگذارید؟ از ۱۸ کاراکتر آن ۱۰ کاراکتر تکراری یا غیر مرتبط است.
نامهای کوتاه تر تا زمانی که واضح باشند بهتر از نامهای بلند میباشند.
نامهایی مانند accountAddress و customerAddress برای دو آبجکت از کلاس Address مناسب ولی برای نام یک کلاس مناسب نیستند. برای کلاس Address مناسب تر است. اگر لازم باشد بین مک آدرس، آدرس پورت و آدرس وب تفاوتی ایجاد کنیم این نامها را در نظر بگیریم بهتر است. PostalAddress ، MAC و URI.
سخت ترین قسمت نام گذاری انتخاب نام خوب است. زیرا توانایی توصیف و حتی پیش زمینه فرهنگی میخواهد. افراد حتی از تغییر نام میترسند اما واقعا دلیلی برای ترس وجود ندارد. این تغییرات همواره در جهت بهبود برنامه بوده و مطمئن باشید در آینده همه تیم از شما تشکر خواهند کرد.
این را هم در نظر بگیرید که همه پیشنهادهای مطرح شده در این مقاله حاصل تجربه و نظرات رابرت مارتین است. تیم شما با توجه به تجربه و شرایط متفاوتی که دارد ممکن است تصمیمات متفاوتی بگیرد و لزوما این تصمیمات اشتباه نخواهند بود. اما به نظر من قطعا رابرت مارتین تجربه بیشتری نسبت ما دارد و میتوان به وی اعتماد کرد. نظر شما چیست؟