قواعدی برای کدنویسی تمیز

گردآوری و تالیف : ارسطو عباسی
تاریخ انتشار : 30 تیر 1398
دسته بندی ها : برنامه نویسی

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

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

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

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

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

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

استفاده از یک قالب‌بندی ثابت

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

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

مثال خوب:

function getStudents(id) {
    if (id !== null) {
        go_and_get_the_student();
    } else {
        abort_mission();
    }
}
  • در نگاه اول می‌توان متوجه شد که در این کدها یک دستور if/else و یک تابع وجود دارد.
  • از آنجایی که کروشه‌ها با کناره‌ها فواصل معینی داشته و  همچنین در تمام حالت‌ها یک فضای خالی با دستور اصلی دارند، مشاهده و خواندن بلوک کدها بسیار ساده است.

مثال بد:

function getStudents(id) {
if (id !== null) {


        go_and_get_the_student();} 
    else 
    {
        abort_mission();
    }
    }

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

  • از آنجایی که کروشه‌ها با فواصل ثابتی همراه نیستند، خوانای بلوک کدها کار سختی است.
  • فواصل خطی ناسازگارند.

خبر خوب این است که نیازی به مرتب سازی کدها به صورت دستی نیست. شما می‌توانید از افزونه‌ها و ابزارهای مربوط به ویرایشگر کدتان استفاده کنید. 

استفاده از نام‌های واضح برای متغیرها و متدها

انتخاب مناسب نام برای نامگذاری متغیرها و متدها یکی از مهمترین جنبه‌های داشتن کد تمیز و خوانا است. مشکلی که اغلب برنامه‌نویسان تازه‌کار نیز دارند دقیقا همین است. بیایید یک مثال درست را از این وضعیت مشاهده کنیم:

function changeStudentLabelText(studentId){
	const studentNameLabel = getStudentName(studentId);
}


function getStudentName(studentId){
	const student = api.getStudentById(studentId);
	return student.name;
}

توابع و ورودی‌ها به خوبی نامگذاری شده‌اند. زمانی که یک توسعه‌دهنده تابع getStudentName() را مشاهده کند به سرعت متوجه می‌شود که یک تابع قرار است ورودی عددی id یک دانشجو را دریافت کرده و نام وی را به خروجی ارسال کند.

  • نامگذاری متغیرها و متدها در داخل تابع نیز مثال خوبی از یک نامگذاری درست است. 

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

  • یک روش ثابت برای نام‌گذاری را برای تمام پروژه انتخاب کنید. حالت camelCase و یا under_score روش‌های مناسبی هستند اما تنها از یک موردشان استفاده کنید.
  • برای نام‌گذاری به کاری که تابع، متد و یا متغیر انجام می‌دهد فکر کنید. اگر قرار است که متد کار دریافت اطلاعات را انجام دهد حتما کلمه get را به عنوان بخشی از نام متد در نظر بگیرید. برای مثال getStudentId.

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

استفاده از کامنت

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

کامنت‌های مستندساز

کامنت‌های مستندساز کامنت‌هایی هستند که معمولا به ابتدای کلاس‌ها و توابع اضافه می‌شوند. اگر یک کتابخانه را ایجاد کنید استفاده از این کامنت‌ها در ابتدای آن می‌تواند رویکردی مناسب برای توضیح بهتر کتابخانه باشد. در اینجا می‌توانید مثالی از useJSDoc را مشاهده کنید:

/**
 * Solves equations of the form a * x = b
 * @example
 * // returns 2
 * globalNS.method1(5, 10);
 * @example
 * // returns 3
 * globalNS.method(5, 15);
 * @returns {Number} Returns the value of x for the equation.
 */
globalNS.method1 = function (a, b) {
    return b / a;
};

کامنت‌های شفاف‌سازی

کامنت‌های شفاف‌سازی آن دسته از کامنت‌هایی هستند که یک دستور خاص را توضیح می‌دهند. معمولا از این حالت برای زمانی استفاده می‌شود که برنامه‌نویس بخواهد بعدها در زمان برگشت به کد آن را بهتر متوجه شود. البته بهتر است که از این نوع کامنت صرفنظر کرد چرا که باعث می‌شود قاعده «کدها باید خودشان را توضیح بدهند» به کلی زیر سوال برود. یک مثال از این حالت را می‌توانید در زیر مشاهده کنید:

/*
This function calls a third party API. Due to some issue with the API vender, the response returns "BAD REQUEST" at times. If it does, we need to retry
*/
function getImageLinks(){
	const imageLinks = makeApiCall();
	
	if(imageLinks === null){
		retryApiCall();
	} else {
		doSomeOtherStuff();
	}
}

به عنوان یک موضوع مهم دیگر باید گفت که خود کامنت‌نویسی نیز تکنیک‌های منحصر به فردی دارد. در زیر تلاش دارم تا چند مثال از کامنت‌های نامناسب را به شما معرفی کنم:

کامنت بی ارزش:

// this sets the students age 
function setStudentAge();

کامنت‌ گمراه‌کننده:

//this sets the fullname of the student
function setLastName();

کامنت‌های الکی (به معنای واقعی کلمه):

// this method is 5000 lines long but it's impossible to refactor so don't try
function reallyLongFunction();

قاعده DRY یا Don’t Repeat Yourself

در قاعده DRY گفته شده که:

هر قسمتی از دانش باید یک نماینده یکپارچه، معتبر و تنها در سیستم داشته باشد.

به صورتی ساده‌تر این موضوع بیان می‌کند که در یک سیستم یا در یک ساختار پروژه‌ای، باید از تکرار کدها جلوگیری کنید. 

کدهای تکراری می‌تواند برای نگه‌داری و استفاده دوباره از یک پروژه، فاجعه‌آمیز باشد. برای درک بهتر بیایید با یک مثال همراه شویم:

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

function addEmployee(){ 
    // create the user object and give the role
    const user = {
        firstName: 'Rory',
        lastName: 'Millar',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}

function addManager(){  
    // create the user object and give the role
    const user = {
        firstName: 'James',
        lastName: 'Marley',
        role: 'Admin'
    }
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}

function addAdmin(){    
    // create the user object and give the role
    const user = {
        firstName: 'Gary',
        lastName: 'Judge',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}

خب کدهای ما به خوبی کار می‌کنند. اما یک روز مشتری با ما تماس گرفته و می‌گوید:

سلام ما می‌خواهیم که همراه با پیام خطا یک جمله شبیه به «اینجا یک خطا وجود دارد:» نمایش داده شود. -همچنین برای اینکه بیشتر شما رو اذیت بکنم- لطف کنید که بجای استفاده از /user در endpoint مربوط به API از /users استفاده کنید. ممنون!

قبل از آنکه خواسته‌های مشتری را بررسی کنیم باید بگویم که کدهای ما یک مشکل اساسی دارند. آن هم این است که برای فراخوانی API و مدیریت خطا ما در ۳ جا کدها را تکرار کرده‌ایم. این بدان معناست که برای تغییرات مشتری نیاز است در ۳ جا کدها را تغییر دهیم. 

برای جلوگیری از این اتفاق بهتر است که یک تابع مخصوص را ساخته و کدهای تکرار شده را تنها یک بار در آنجا قرار دهیم:

function addEmployee(){ 
    // create the user object and give the role
    const user = {
        firstName: 'Rory',
        lastName: 'Millar',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}

function addManager(){  
    // create the user object and give the role
    const user = {
        firstName: 'James',
        lastName: 'Marley',
        role: 'Admin'
    }
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}

function addAdmin(){    
    // create the user object and give the role
    const user = {
        firstName: 'Gary',
        lastName: 'Judge',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}

function saveUserToDatabase(user){
    axios.post('/users', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log("there was an error " + error);
  });
}

همانطور که مشاهده می‌کنید ما فراخوانی API و مدیریت خطا را در یک تابع با نام saveUserToDatabase(user) قرار دادیم. بنابراین دیگر مشکلی از این بابت نخواهیم داشت. 

حال جدای از آنکه ما قاعده DRY را رعایت نمودیم، یکی دیگر از ویژگی‌های کد تمیز را نیز پیاده‌سازی کردیم. کدها باید متمرکز باشند؛ یعنی هر متد و تابعی باید کار خاصی را انجام بدهد.

بازسازی یک مثال براساس موضوعاتی که تا به حال یاد گرفته‌ایم

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

function addNumbers(number1, number2)
{
    const result = number1 + number2;
        const output = 'The result is ' + result;
        console.log(output);
}

// this function substracts 2 numbers
function substractNumbers(number1, number2){
    
    //store the result in a variable called result
    const result = number1 - number2;
    const output = 'The result is ' + result;
    console.log(output);
}

function doStuffWithNumbers(number1, number2){
    const result = number1 * number2;
    const output = 'The result is ' + result;
    console.log(output);
}

function divideNumbers(x, y){
    const result = number1 * number2;
    const output = 'The result is ' + result;
    console.log(output);
}

زمانی که به سورس کد بالا نگاه کنیم، متوجه خواهیم شد که میزان خوانایی آن کم بوده و نیاز به اصلاحات دارد.

مشکلات این سورس کد چیست؟

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

اگر کارهای گفته شده را روی سورس کد اعمال کنیم با خروجی زیر مواجه خواهیم شد:

function addNumbers(number1, number2){
	const result = number1 + number2;
	displayOutput(result)
}

function substractNumbers(number1, number2){
	const result = number1 - number2;
	displayOutput(result)
}

function multiplyNumbers(number1, number2){
	const result = number1 * number2;
	displayOutput(result)
}

function divideNumbers(number1, number2){
	const result = number1 * number2;
	displayOutput(result)
}

function displayOutput(result){
	const output = 'The result is ' + result;
	console.log(output);
}

وسواس‌گونه رفتار نکنید

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

در پایان

در این مطلب از وبسایت راکت به صورت کامل موضوعات زیر را مورد بررسی قرار دادیم:

  • استفاده از قالب‌بندی ثابت
  • استفاده از نام‌های شفاف و واضح برای متغیرها و متدها
  • استفاده از کامنت‌ها در صورت لزوم
  • استفاده از قاعده DRY

منبع

مقالات پیشنهادی

  • تمیز کردن کدها با استفاده از Prettier

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

    ارسطو عباسی
  • بهترین ابزارها برای همکاری در کدنویسی

    براساس آمار Forbes حدود ۵۰ درصد آمریکایی‌ها تا سال ۲۰۲۷ ترجیح خواهند داد که در خانه‌های‌شان کار بکنند. فریلنسینگ در حال تبدیل شدن به پدیده‌ای است که ت...

    ارسطو عباسی
  • ۱۳ قاعده ساده برای کدنویسی بهتر

    من بیشتر از ۱۵ سال است که مشغول برنامه‌نویسی هستم. در این مدت با زبان‌ها، فریمورک‌ها، پارادایم‌ها و... مختلفی کار کرده‌ام. اما امروز در این مطلب قصد د...

    ارسطو عباسی