کدنویسی تمیز: قوانین نام‌گذاری و مثال‌های آن

Clean Coding: Naming Rules

17 فروردین 1400
درسنامه درس 3 از سری کدنویسی تمیز
کدنویسی تمیز: قوانین نام گذاری و مثال های آن (قسمت 3)

قوانین نام گذاری متغیرها، ثابت ها و خصوصیات

در این بخش می خواهیم به صورت خاص روی متغیرها، ثابت ها و خصوصیات (data container ها) تمرکز کنیم. ما می توانیم سه نوع مقدار خاص را در نظر بگیریم که درون این data container ها ذخیره می شوند.

  • مقادیر ذخیره شده ممکن است Object یا شیء باشند. در این حالت نام انتخاب شده برای این متغیر باید محتوای درون آن را توصیف کند. به طور مثال اگر شیء شما کاربر خاصی را درون خود دارد نام آن را user بگذارید یا اگر آدرس ایمیل را دارد نامش را email بگذارید.
  • مقادیر ذخیره شده ممکن است عدد یا رشته باشند. در این حالت نیز نام انتخاب شده برای این متغیر باید محتوای درون آن را توصیف کند. به طور مثال اگر نام کاربر را به صورت رشته در یک متغیر ذخیره کرده اید نام آن name یا userName بگذارید.
  • مقادیر ذخیره شده ممکن است boolean باشند. در این حالت مقادیر ممکن فقط true یا false هستند بنابراین نام گذاری باید این حالت «بله» یا «خیر» را نمایش بدهد. به طور مثال برای فعال بودن یک modal از نامی مانند isActive استفاده می کنیم یا برای لاگین بودن کاربر از loggedIn یا isLoggedIn استفاده می کنیم.

شما می توانید به هر کدام از نام های ذکر شده توضیحات دیگری را نیز اضافه کنید اما این توضیحات باید هدف خاصی داشته باشند. به طور مثال اگر بخشی از برنامه فقط کاربران احراز هویت شده را قبول می کند می توانیم به جای نام user از authenticatedUser استفاده کنیم، حتی اگر کاربر ما در قسمتی از برنامه خریدار محسوب می شود می توانیم نام customer را به جای user در نظر بگیریم. همچنین اگر می خواهید فقط نام خانوادگی کاربر را ذخیره کنید به جای name از lastName استفاده کنید. این مسئله برای boolean ها نیز صادق است؛ به طور مثال گفتیم که isActive برای یک modal است بنابراین بهتر است از نامی مثل isModalActive استفاده کنیم تا هنگام خواندن آن دقیقا بدانیم چه چیزی active یا «فعال» است (کاربر یا modal یا هر چیز دیگری)؟

با اضافه کردن این توضیحات به نام متغیرها می توانیم تا حد زیادی وظیفه آن ها را مشخص کنیم اما حواستان باشد که بدون توجه به معنی شروع به اضافه کردن توضیحات نکنید تا حدی که متغیرهایتان نام هایی بسیار طولانی داشته باشند.

مثال هایی از نام گذاری متغیرها، ثابت ها و خصوصیات

ما در این بخش سه دسته از نام های مختلف را بر اساس محتوای آن ها بررسی می کنیم: نام های بد، نام های متوسط (نام هایی که قابل قبول هستند اما می توانند بهتر شوند) و نام های خوب.

برای مثال اول فرض کنید شیء ای را داریم که اطلاعات کاربری را درون خود ذخیره می کند (مثلا ایمیل و نام و سن کاربر را دارد). نام بد در این حالت u یا data یا object است. زمانی که به این نام ها نگاه می کنید هیچ چیزی از آن ها نمی فهمید و اصلا مشخص نیست که درون آن ها چه نوع داده ای داریم. نام های متوسط نام هایی مانند userData یا person هستند که کلیت داده های درون متغیر را مشخص می کنند. مشکل userData چیست؟ userData حشو دارد! یعنی اگر نام user را انتخاب می کردیم، مشخص بود که داده های کاربر در این متغیر وجود دارد و کلمه data اضافی است. نام person (به معنی «فرد») نیز بیش از حد کلی است؛ آیا این فرد یک خریدار است یا یک ادمین است؟ نام خوب نیز نامی مانند user (به معنی «کاربر») یا customer (به معنی «خریدار») است چرا که به دقت مشخص می کند درون خود چه چیز هایی دارد.

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

class User:

    def __init__(self, name, age, email):

        self.name = name

        self.age = age

        self.email = email







user_data = {

    'entered_name': {

        'value': 'Amir',

        'is_valid': True

    },

    'entered_age': {

        'value': '25',

        'is_valid': True

    },

    'entered_email': {

        'value': 'test@email.com',

        'is_valid': True

    },

}




user = User('Amir', 31, 'test@email.com')

در اینجا user_data نام خوبی است چرا که user_data فقط داده های خام کاربر را در خود دارد و باید اعتبارسنجی شود و هنوز تبدیل به کاربر (user) نشده است. در چنین حالتی تفاوت بزرگی بین user_data و user وجود دارد و استفاده از این نام مشکلی ندارد. هدف من از ذکر این مثال این است که بدانید یک نام در یک کد ممکن است خوب باشد اما در کد دیگری بد باشد و همه چیز به وضعیت برنامه و کدهای شما مربوط است.

حالا یک سوال ساده از شما دارم؛ قرار است داده های ارسال شده توسط کاربری را اعتبارسنجی کنیم. شما چه نامی را برای چنین متغیری که حاوی نتیجه این اعتبارسنجی باشد (boolean) را در نظر می گیرید؟ نام های بد، متوسط و خوب خود را مثال بزنید. نام بد، هر نامی است که توصیفی نباشد بنابراین مقادیری مثل input یا val یا v است. input بیش از حد کلی است، val ممکن است به جای validation مخفف value باشد و v نیز که فقط یک حرف است و بدترین نام است. نام های متوسط نام هایی مانند validatedInput و correct است. مشکل این نام ها این است که نشان دهنده نتیجه (true یا false) نیستند بلکه با دیدن validatedInput ممکن است تصور کنیم که داده اعتبارسنجی شده داخل متغیر هستند نه اینکه نتیجه اعتبارسنجی را داشته باشیم. نام های خوب نام هایی مانند isCorrect یا isValid هستند تا طبیعت boolean را نیز به خوبی نشان دهند.

قوانین نام گذاری متدها و توابع

حالا که با قوانین نام گذاری متغیرها آشنا شده ایم باید به سراغ متدها و توابع برویم. در این بخش می توانیم دو نوع تابع خاص را در نظر بگیریم: توابعی که عملیات خاصی را انجام می دهند (این عملیات می تواند هر چیزی باشد) و توابعی که یک مقدار boolean را بررسی کرده و برمی گردانند. در توابع دسته اول باید عملیات را توصیف کنیم. به طور مثال getUser یک نام مناسب است که نشان می دهد در حال دریافت کاربر هستیم (حدس می زنیم که این کار در پایگاه داده اتفاق می افتد). در دسته دوم این توابع نیز باید نامی را انتخاب کنیم که به سوال true یا false جواب بدهد، در این دسته نباید خود عملیات را توصیف کنیم. یک مثال ساده از نام مناسب برای این دسته از توابع ()isValid است.

مثل همیشه اضافه کردن توضیحات و توصیفات به این نام ها کار مناسبی است البته به شرطی که حشو و اضافه نویسی نداشته باشد. به طور مثال برای تابع getUser می توانیم از getUserByEmail استفاده کنیم تا مشخص کنیم که این تابع بر اساس ایمیل، یک کاربر را از پایگاه داده دریافت می کند یا به جای isValid از emailIsvalid استفاده کنیم تا دقیقا مشخص شود که چه چیزی را اعتبارسنجی می کنیم.

مثال هایی از نام گذاری متدها و توابع

در این بخش مثال هایی از نام های بد، متوسط و خوب را با هم بررسی می کنیم. تصور کنید تابعی داشته باشیم که بخواهد داده های کاربر را در پایگاه داده ذخیره کند. در این حالت نام بد، نام هایی مانند process (به معنی «پردازش کردن») و handle (به معنی «مدیریت کردن») هستند چرا که بسیار کلی بوده و هیچ فرآیند خاصی را توصیف نمی کنند. به طور مثال process یا پردازش کردن کاربر می تواند به معنی ذخیره کردن کاربر در پایگاه داده باشد اما در عین حال می تواند به معنی پردازش کردن فرم یا هزار عملیات دیگر نیز باشد و اصلا توصیفی نیست. همچنین پردازش کردن یعنی چه؟ پردازش می تواند به معنی ذخیره کردن یا به روز رسانی یا حذف و امثال این عملیات ها باشد:

def process(user):

    del user




user = {'first_name': 'Amir', 'age': 25}




process(user)

می بینید که در کد بالا منظورمان از process حذف کاربر بوده است!

نام های متوسط نام هایی مانند save یا storeData هستند. این نام ها مشخص می کنند که چیزی در حال ذخیره شده (save) است اما هنوز هم مشخص نیست که چه چیزی در حال ذخیره شدن است. همانطور که در بخش نام گذاری متغیرها و ثابت ها نیز توضیح دادم نام های متوسط بسته به شرایط محیطی می توانند نام های خوبی باشند. به کد زیر توجه کنید:

class User:

    def __init__(self, first_name, age):

        self.first_name = first_name

        self.age = age




    def save(self):

        print('Saving!')







user = User('Amir', 25)




user.save()

در این حالت متد save روی کلاس کاربر (User) صدا زده می شود. در این کد نام save کاملا نام مناسب و بی نقصی است چرا که روی کلاس user صدا زده شده و دقیقا مشخص است که در حال ذخیره چه چیزی هستیم اما اگر فقط یک تابع داشتید که نامش save بود بهتر است نامش را به نام مناسب تری تغییر بدهید. نام های خوب نام هایی مانند saveUser یا product.store است. saveUser کاملا مشخص می کند که در حال ذخیره سازی کاربر هستیم و product.store نیز مشخص می کند که متد store (ذخیره سازی) روی کلاس product (محصول) صدا زده شده است.

حالا تصور کنید که تابعی برای اعتبارسنجی داده های کاربر دارید. چه نامی را برایش انتخاب می کنید؟ نام بد نام هایی مانند process یا save هستند. چرا؟ به دلیل اینکه تابع ما می خواهد داده های کاربر را اعتبارسنجی کند بنابراین چیزی را ذخیره نمی کند که نامش را save بگذاریم. نام process نیز بیش از حد کلی است. نام های متوسط نام هایی مانند validateSave و check هستند. validateSave نام خوبی نیست چرا که به ما می گوید که تابع ما هر دو عملیات اعتبارسنجی (validation) و ذخیره سازی (save) را انجام می دهد در حالی که اینطور نیست. check نیز به صورت کامل مشخص نمی کند که در حال چک کردن چه چیزی هستیم. نام های خوب نام هایی مانند validate هستند یا اگر تابع شما فقط نتیجه را به صورت boolean برمی گرداند نام هایی مانند isValid یا isValidated بسیار مناسب هستند.

قوانین نام گذاری کلاس ها

کلاس ها انواع خاصی ندارند و زمینه نام گذاری آن ها فقط یک دستورالعمل داریم: همیشه شیء ای را توصیف کنید که بر اساس آن کلاس ساخته می شود. به طور مثال اگر قرار است یک شیء برای کاربر را از کلاس مورد نظر بسازیم، نام آن کلاس را User می گذاریم. البته مثل همیشه دقیق تر بودن در نام گذاری (مثلا انتخاب نام Customer به جای User) پیشنهاد می شود. شما می توانید نام کلاس هایتان را چند کلمه ای انتخاب کنید اما این نام نباید دارای حشو باشد. به طور مثال DatabaseManager نام خوبی نیست چرا که دارای حشو است. ما می توانستیم نام این کلاس را فقط Database بگذاریم و واضح خواهد بود که این کلاس مسئول مدیریت پایگاه داده ما است (کلمه manager اضافی است). معمولا از این دسته از نام ها برای کلاس های استاتیک استفاده می کنند که پر از کلاس های utility و کمکی هستند.

مثال هایی از نام گذاری کلاس ها

در این قسمت باز هم مثال هایی از نام های بد، متوسط و خوب را بررسی می کنیم. تصور کنید کلاسی را می خواهیم که یک شیء User را می سازد. نام های بد نام هایی مانند UEntity یا ObjA هستند. این نام ها آنچنان کلی هستند که به هیچ عنوان به جزئیات مهم User اشاره نمی کنند بنابراین نباید از آن ها استفاده کنید. نام های متوسط نام هایی مانند UserObj یا AppUser هستند. این نام ها دارای حشو زیادی هستند؛ به طور مثال طبیعتا کلاسی که برای ساخت کاربر تعریف می شود شیء کاربر را می سازد بنابراین به جای UserObj می توانیم فقط از User استفاده کنیم چرا که Obj یا Object بودن آن واضح و مبرهن است. همچنین «کاربر» یعنی کاربری که در برنامه ما است بنابراین AppUser حشو دارد و بهتر است از User خالی استفاده شود. با این حساب حتما متوجه شده اید که نام های خوب نام هایی مانند User یا Admin یا Customer هستند چرا که به صورت جزئی مشخص می کنند این کاربر چه نوع کاربری خواهد بود (کاربر عادی، ادمین،‌ خریدار و غیره).

حالا از شما می پرسم: نامی را برای کلاسی انتخاب کنید که قرار است با پایگاه داده در ارتباط باشد. نام بد در این حالت Data یا DataStorage است چرا که Data (به معنی «داده») بیش از حد کلی است و DataStorage (به معنی محل ذخیره داده) نیز مشخص نمی کند چه نوع storage ای خواهیم داشت (شاید محل ذخیره پایگاه داده باشد، شاید یک فایل متنی ساده باشد و الی آخر). نام های متوسط  نام هایی مانند DB هستند اما هنوز هم ممکن است برای بعضی از افراد واضح نباشند. نام های خوب نیز نام هایی مانند Database یا حتی دقیق تر SQLDatabase می باشند چرا که به صورت دقیق مشخص می کنند که با چه چیزی سر و کار داریم.

چرا استثناء؟

قوانین که تا این بخش بررسی کردیم، قوانین کلی بودند اما هنوز دارای استثناء هستند. یک استثناء ساده به شکل زیر است:

from datetime import datetime




class DateUtil:

    @staticmethod

    def get_formatted_today():

        date_today = datetime.now()

        formatted_date = date_today.strftime('%Y-%m-%d')

        return formatted_date







print(DateUtil.get_formatted_today())

اگر به این کد توجه کنید متوجه می شوید که نام هایی مانند strftime دقیقا در تضاد با چیزی است که برایتان توضیح دادم. کسی که با زبان پایتون کار کرده باشد می داند که strftime مخفف string from time است چرا که از یک شیء تاریخ، یک رشته را برمی گرداند. چرا؟ توسعه دهندگان زبان های برنامه نویسی معمولا سعی می کنند نام متدها را تا حد ممکن خلاصه کنند و در این راه خوانایی کد را نیز فدای خلاصه بودن آن می کنند. افراد مختلف در این زمینه نظر های مختلفی دارند؛ افرادی که موافق این روش هستند می گویند که با انتخاب نام های طولانی باعث می شویم توسعه دهندگان در هنگام کدنویسی کاراکتر های بیشتری را تایپ کرده و سرعت آنها کمتر شود و افراد مخالف می گویند که سرعت توسعه دهندگان در این حالت بیشتر کم می شود چرا که باید در مورد این متدها جست و جو کنند تا بدانند هر متد چه کاری را انجام می دهد. من به شخصه مخالف چنین نام گذاری هایی هستم چرا که تایپ کردن نام های طولانی در برنامه نویسی مشکلی جدی نیست. ما در ویرایشگر های خود autocompletion داریم و با فشردن کلید tab کل آن نام برایمان تایپ می شود. از نظر من جست و جوی نام متدها برای درک نحوه کارشان آزاردهنده تر است. شما سعی کنید کدهای خودتان را همیشه توصیفی و خوانا نگه دارید حتی اگر موافق این روش خلاصه نویسی هستید چرا که همه مجبور به یادگیری یک زبان برنامه نویسی هستند و شاید خیلی مهم نباشد چرا که فقط یک زبان است اما اگر قرار باشد همه بدین شکل کد بنویسند آنگاه برای خواندن هر کد باید وقت زیادی گذاشته و آن را رمزگشایی کنیم!

نکته مهم دیگر این است که من نام کلاس خودم را DateUtil گذاشته ام در حالی که به شما گفتم از نام هایی مانند DatabaseManager دوری کنید. چرا؟ اگر کلاس ما بیشتر شامل متدهای استاتیک باشد (مانند متد get_formatted_today در کد بالا) و به طور کل مجموعه ای از متدهای کمکی باشد، استفاده از چنین نامی کاملا مناسب است.

یک استثناء دیگر را در کد زیر مشاهده می کنید:

class Database {

  private client: any;




  get connectedClient() {

    if (!this.client) {

      throw new Error('Database not connected!');

    }

    return this.client;

  }




  connect() {

    // Establishing connection ...

    this.client = {};

  }

}




const db = new Database();

db.connectedClient.query();

در این کد متدی به نام connectedClient را داریم. نام این متد مانند نام یک خصوصیت یا متغیر است و از قوانین نام گذاری متدها طبعیت نمی کند بنابراین نام مناسبی نیست، درست است؟ خیر! در اینجا connectedClient یک getter است که خصوصیت private ما به نام client را برمی گرداند و در انتهای کد نیز می بینید که به آن به صورت یک خصوصیت db.connectedClient.query دسترسی پیدا کرده ایم. بنابراین از شما می خواهم که از دستورالعمل های توضیح داده شده با توجه به موقعیتشان استفاده کنید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری کدنویسی تمیز توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

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