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

۱۰ نکته ضروری برای نوشتن Clean Code

در این بخش قصد داریم تا ۱۰ نکته عملی برای این که کد خوانا، ساختاریافته و کارآمد داشته باشیم را باهم بررسی کنیم.

۱- استفاده از نام‌های معنی‌دار

هنگام نام‌گذاری متغیرها، توابع و کلاس‌ها، بهتر است نام‌هایی انتخاب کنیم که به وضوح هدف آن‌ها را توصیف می‌کند.

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

// Good
let numberOfUsers = 5; // Clear and easy to understand

// Bad
let b = 5; // Vague and unclear

نکات نام‌گذاری

// Variable: Describes the data it holds
let userAge = 25;

// Function: Uses an action word to describe what it does
function calculateTotal(price, quantity) {
    return price * quantity;
}

// Class: Singular noun representing a type of object
class User {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

۲- پیروی از اصل Single Responsibility Principle (SRP)

اصل SRP یا مسئولیت‌پذیری تک‌گانه یعنی هر تابع یا متد باید یک کار مشخص و واحد را انجام دهد. این کار باعث می‌شود تا توابع ما کوتاه و متمرکز باشند که در این صورت خواندن، تست و نگه‌داری آن‌ها راحت‌تر می‌شود.

برای مثال، اگر تابعی به نام calculateTotal داشته باشیم، باید فقط مسئول محاسبه مجموع باشد. اگر کارهای اضافی به آن اضافه نماییم، کد ما گیج‌کننده شده و نگه‌داری آن دشوار می‌شود. در ادامه یک مثال برای نشان دادن اهمیت متمرکز نگه داشتن توابع را با هم بررسی می‌کنیم:

فرض کنید می‌خواهیم یک مجموع محاسبه کنیم و یک آبجکت با اطلاعات اضافی، مثل اینکه چه کسی در چه زمانی آن را محاسبه کرده است را return نماییم. به جای این که این اطلاعات را مستقیما به calculateTotal بیفزاییم، می‌توانیم از یک تابع دوم استفاده کنیم.

۱- مثال برای زمانی که تسک‌ها به صورت جداگانه هستند:

// This function only calculates the total
 function calculateTotal(a, b) {
     return a + b;
 }

 // This function creates an object with extra details
 function createCalculationRecord(a, b, user) {
     let sum = calculateTotal(a, b); // Calls the calculate function
     return {
         user: user,
         total: sum,
         timestamp: new Date()
     };
 }

 let record = createCalculationRecord(5, 10, "Shahan");
 console.log(record);

مزایا:

در این مثال، هر تابع وظیفه واضح و متمرکزی دارد. تابع calculateTotal فقط محاسبات ریاضی را انجام می‌دهد، در حالی که تابع createCalculationRecord جزئیات اضافی را اضافه می‌کند. اگر بخواهیم نحوه محاسبه مجموع را تغییر دهیم، فقط باید تابع calculateTotal را به‌روزرسانی نماییم و اگر بخواهیم فرمت رکورد را تغییر دهیم، فقط تابع createCalculationRecord را تغییر می‌دهیم.

۲- مثال برای زمانی که تسک‌ها در یک تابع باهم ترکیب شده‌اند:

// This function calculates the total and creates an object in one step
function calculateTotalAndReturnRecord(a, b, user) {
    let sum = a + b;
    return {
        user: user,
        total: sum,
        timestamp: new Date()
    };
}

let record = calculateTotalAndReturnRecord(5, 10, "Shahan");
console.log(record);

معایب:

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

۳- خودداری از کامنت‌های غیرضروری

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

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

چه زمانی باید از کامنت‌ها استفاده کنیم:

به عنوان مثال:

// Clear name, no comment needed
let userAge = 25;

// Unclear name, comment needed
let a; // age of the user

۴- بهبود خوانایی کد

کد خوانا از تورفتگی‌ها، line breakها و spaceها استفاده می‌کند تا همه چیز مرتب و سازمان‌یافته باشد.

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

// Good Code
if (isLoggedIn) {
    console.log("Welcome!");
} else {
    console.log("Please log in.");
}

// Bad Code
if(isLoggedIn){console.log("Welcome!");}else{console.log("Please log in.");}

Prettier در VS Code، از formatterهای محبوب است که به‌طور خودکار استایل Clean Code را برای زبان‌های مختلف اعمال می‌کند. این ابزارها تضمین می‌کنند که کد ما در سراسر پروژه‌ها خوانا و منظم باشد و کم‌ترین تلاش دستی را نیاز داشته باشد.

۵- نوشتن تست واحد

تست‌های واحد کمک می‌کنند تا مطمئن شویم که هر بخش از کد ما همانطور که انتظار می‌رود عمل می‌کند. با تست کردن قسمت‌های کوچک و جداگانه (مثل توابع)، می‌توانیم خطاها را زود تشخیص دهیم و از گسترش آن‌ها به سایر بخش‌های کد جلوگیری نماییم. به طور مشخص، تست‌های واحد در واقع چک‌های کیفیت کوچک برای هر بخش از کد ما هستند تا مطمئن شویم همانطور که باید، عمل می‌کنند.

بررسی مثال دنیای واقعی:

در ادامه نحوه تست یک آبجکت جاوااسکریپت پیچیده با چندین متد را با استفاده از یک کلاس Calculator بررسی می‌کنیم.

این روش به ما کمک می‌کند تا بفهمیم چرا مهم است که هر متد روی یک تسک متمرکز باشد و با استفاده از تست‌های واحد مطمئن شویم که هر کدام به درستی کار می‌کنند.

در این مثال، کلاس Calculator است که شامل متدهای عملیات ریاضی پایه مانند جمع، تفریق، ضرب و تقسیم می‌باشد.

class Calculator {
    constructor() {
        this.result = 0;
    }

    add(a, b) {
        return a + b;
    }

    subtract(a, b) {
        return a - b;
    }

    multiply(a, b) {
        return a * b;
    }

    divide(a, b) {
        if (b === 0) throw new Error("Cannot divide by zero");
        return a / b;
    }
}

همانطور که مشاهده می‌کنیم، هر متد یک عملیات خاصی را انجام می‌دهد. متد divide دارای منطق اضافی برای مدیریت تقسیم بر صفر است که در غیر این صورت باعث خطا می‌شود.

اکنون قصد داریم تا تست‌های واحد بنویسیم تا تأیید کنیم که هر متد همانطور که انتظار می‌رود عمل می‌کند.

نوشتن تست‌های واحد برای هر متد

برای تست کلاس Calculator، می‌توانیم تست‌هایی بنویسیم که هم موارد عادی و هم موارد حاشیه‌ای را پوشش دهند. در ادامه نحوه راه‌اندازی تست‌ها برای هر متد را داریم:

// Initialize the Calculator instance
const calculator = new Calculator();

// Test add method
console.assert(calculator.add(2, 3) === 5, 'Test failed: 2 + 3 should be 5');
console.assert(calculator.add(-1, 1) === 0, 'Test failed: -1 + 1 should be 0');

// Test subtract method
console.assert(calculator.subtract(5, 3) === 2, 'Test failed: 5 - 3 should be 2');
console.assert(calculator.subtract(0, 0) === 0, 'Test failed: 0 - 0 should be 0');

// Test multiply method
console.assert(calculator.multiply(2, 3) === 6, 'Test failed: 2 * 3 should be 6');
console.assert(calculator.multiply(-1, 2) === -2, 'Test failed: -1 * 2 should be -2');

// Test divide method
console.assert(calculator.divide(6, 3) === 2, 'Test failed: 6 / 3 should be 2');
try {
    calculator.divide(1, 0);
    console.assert(false, 'Test failed: Division by zero should throw an error');
} catch (e) {
    console.assert(e.message === "Cannot divide by zero", 'Test failed: Incorrect error message for division by zero');
}

توضیح تست‌ها:

جمع (متد add): تست می‌کنیم که add(2, 3) مقدار ۵، و add(-1, 1) مقدار ۰ را return کند. اگر این تست‌ها قبول شوند، می‌دانیم که منطق جمع به درستی عمل می‌کند.

تفریق (متد subtract): بررسی می‌کنیم که subtract(5, 3) مقدار ۲، و subtract(0, 0) مقدار ۰ را return کند. این چک‌ها تأیید می‌کنند که تفریق دقیق است.

ضرب (متد multiply): تست می‌کنیم که تابع ضرب با مقادیر مثبت و منفی به درستی عمل کند، مانند اینکه multiply(2, 3) مقدار ۶، و multiply(-1, 2) مقدار  را return کند.

تقسیم (متد divide): تأیید می‌کنیم که تقسیم ۶ بر ۳ مقدار ۲ را return کند. برای تقسیم بر صفر، از بلاک try...catch استفاده می‌کنیم تا مطمئن شویم که یک خطا با پیام صحیح تولید می‌شود. این تست اطمینان می‌دهد که متد به درستی خطاها را مدیریت می‌کند.

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

۶- مراقبت از dependencyها

dependencyها قطعات نرم‌افزاری هستند که کد ما به آن‌ها وابسته است.

تصور کنید وب اپلیکیشنی می‌سازیم که ایمیل ارسال می‌کند. به جای این که خودمان کد ارسال ایمیل را بنویسیم می‌توانیم از یک کتابخانه خارجی مانند Nodemailer استفاده کنیم. در این مثال، Nodemailer یک dependency است؛ اپلیکیشن ما برای مدیریت قابلیت ارسال ایمیل به آن وابسته می‌باشد. به عنوان مثال:

const nodemailer = require('nodemailer');

function sendEmail(to, subject, message) {
    const transporter = nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: 'your-email@gmail.com',
            pass: 'your-email-password'
        }
    });

    const mailOptions = {
        from: 'your-email@gmail.com',
        to: to,
        subject: subject,
        text: message
    };

    return transporter.sendMail(mailOptions);
}

در این مثال، nodemailer import می‌شود و برای ایجاد یک transporter برای ارسال ایمیل‌ها مورد استفاده قرار می‌گیرد. بدون آن، باید تمام قابلیت‌های ارسال ایمیل را از ابتدا خودمان بسازیم، که بسیار پیچیده و وقت‌گیر خواهد بود. با استفاده از Nodemailer به عنوان dependency، اپلیکیشن ما می‌تواند ایمیل‌ها را به راحتی ارسال کند.

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

مدیریت مؤثر dependencyها برای نوشتن Clean Code حیاتی است. در ادامه چند نکته داریم که عبارتند از:

اکنون قصد داریم تا یک مثال از کد قبلی Nodemailer برای پیاده‌سازی مفهوم جدا کردن منطق در کد خود را باهم بررسی کنیم.

می‌توانیم یک تابع Wrapper بسازیم که جزئیات ارسال ایمیل را مخفی کند. به این ترتیب، می‌توانیم سرویس ایمیل زیرین را تغییر دهیم یا dependency به Nodemailer را بدون تأثیر بر کد باقی‌مانده حذف نماییم. در ادامه نحوه ساختاردهی کد برای رسیدن به این هدف را مشاهده می‌کنیم:

const nodemailer = require('nodemailer');

// Core function to send email
function sendEmail(to, subject, message) {
    const transporter = createTransporter();
    const mailOptions = createMailOptions(to, subject, message);
    return transporter.sendMail(mailOptions);
}

// Function to create the transporter
function createTransporter() {
    return nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: 'your-email@gmail.com',
            pass: 'your-email-password'
        }
    });
}

// Function to create mail options
function createMailOptions(to, subject, message) {
    return {
        from: 'your-email@gmail.com',
        to: to,
        subject: subject,
        text: message
    };
}

// Example usage
sendEmail('recipient@example.com', 'Test Subject', 'Hello, this is a test email.')
    .then(() => {
        console.log('Email sent successfully!');
    })
    .catch((error) => {
        console.error('Error sending email:', error);
    });

نکات کلیدی:

۷- سازماندهی کردن پروژه

یک ساختار پروژه سازمان‌یافته به اندازه خود کد اهمیت دارد. این کار را باید مثل سازماندهی فضای کار خود بدانیم. ما به مکان‌های مشخصی برای هر چیز نیاز داریم تا بتوانیم به راحتی آن‌ها را پیدا کنیم. برای پروژه‌های برنامه‌نویسی، باید پوشه‌هایی برای بخش‌های مختلف مانند components، utils و services ایجاد کنیم.

چگونه باید پروژه خود را سازماندهی کنیم؟

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

myProject
├── src
│   ├── components
│   ├── services
│   ├── utils
└── tests

تحلیل ساختار پروژه:

مثالی از ساختار درون پوشه components:

components
├── Button.js
├── Header.js
└── Form.js

مثالی از ساختار درون پوشه services:

services
├── emailService.js
├── userService.js
└── productService.js

ساختار درون پوشه utils:

utils
├── formatDate.js
├── validateEmail.js
└── generateId.js

ساختار نمونه از درون پوشه tests:

tests
├── emailService.test.js
├── userService.test.js
└── component.test.js

بررسی یک مثال واقعی: کار با  Nodemailer

فرض کنید در حال ساخت اپلیکیشنی هستیم که ایمیل‌هایی را به کاربران ارسال می‌کند. ممکن است ساختار پروژه‌ای که داریم به این شکل باشد:

myEmailApp
├── src
│   ├── components
│   │   ├── EmailForm.js
│   │   └── SuccessMessage.js
│   ├── services
│   │   └── emailService.js
│   ├── utils
│   │   └── validateEmail.js
└── tests
    ├── emailService.test.js
    └── EmailForm.test.js

مزایای ساختاردهی خوب پروژه

  1. راحتی در مسیریابی: هر توسعه‌دهنده‌ای که پروژه ما را می‌بیند، به‌راحتی می‌تواند قسمت‌های مختلف کد را پیدا کند.
  2. همکاری بهتر: اگر با دیگران کار می‌کنیم، ساختار واضح کمک می‌کند تا همه بدانند، بدون این که کارها با هم تداخل پیدا کنند کجا باید مشارکت نمایند.
  3. مقیاس‌پذیری: با رشد پروژه، ساختار شفاف به مدیریت پیچیدگی و تمیز نگه‌داشتن کد کمک می‌کند.
  4. نگه‌داری بهبودیافته: وقتی نیاز به به‌روزرسانی یا رفع مشکلی داریم، به‌سرعت می‌توانیم فایل‌های مربوطه را پیدا کنیم؛ که این موضوع باعث صرفه‌جویی در زمان شده و خطاها را کاهش می‌دهد.

۸- استفاده از فرمت‌بندی منسجم

فرمت‌بندی منسجم، خوانایی کد را بهبود می‌بخشد.

بهتر است یک الگو ثابت برای نوشتن کد خود ایجاد کنیم، مانند استفاده از دو space برای تو رفتگی یا قرار دادن یک line break قبل از commentها.

پیروی از فرمت‌بندی ثابت باعث می‌شود کد ما تمیز و سازمان‌یافته به نظر برسد.

ابزارهای فرمت‌بندی

۹- اجتناب از مقادیر هاردکد شده

هاردکد کردن به معنای تعبیه مستقیم مقادیر داده در کد است، مثل تنظیم یک ID کاربر به ۱۲۳ به جای استفاده از یک متغیر.

اجتناب از مقدارهای هاردکد شده به ما این امکان را می‌دهد که کد خود را بدون تغییرات مکرر استفاده کنیم. بهتر است مقادیر را در متغیرها، ثابت‌ها یا فایل‌های پیکربندی ذخیره نماییم.

مشکلی که هاردکد کردن می‌تواند ایجاد کند:

// Bad: Hardcoding user limit
function createUser(name) {
    let numberOfUsers = 100; // Hardcoded value
    if (numberOfUsers >= 100) {
        return 'User limit reached.';
    }
    // Code to create the user
    return 'User created.';
}

در این مثال، مقدار numberOfUsers برابر با ۱۰۰ هاردکد شده است. اگر بخواهیم محدودیت تعداد کاربران را تغییر دهیم، باید این مقدار را در کد پیدا کرده و اصلاح کنیم. اگر این مقدار در چندین مکان استفاده شده باشد، انجام این کار دشوار بوده و مستعد خطا می‌باشد.

مثال بهبود یافته با استفاده از Constantها
اکنون این کد را به گونه‌ای بازنویسی می‌کنیم که از یک constant استفاده کند:

// Good: Using a constant
const MAX_USERS = 100; // Store the limit in a constant

function createUser(name) {
    let numberOfUsers = getCurrentUserCount(); // Get the current count from a function or database
    if (numberOfUsers >= MAX_USERS) {
        return 'User limit reached.';
    }
    // Code to create the user
    return 'User created.';
}

// Example function to get current user count
function getCurrentUserCount() {
    // Simulate fetching the current count, e.g., from a database
    return 90; // Example count
}

تحلیل مثال بهبود یافته

  1. استفاده از constantها: constant MAX_USERS در بالای کد تعریف شده است. با این روش، اگر نیاز به تغییر حداکثر تعداد کاربران داشته باشیم، تنها کافی است این مقدار را در یک مکان به‌روزرسانی نماییم.
  2. مقادیر داینامیک: تابع getCurrentUserCount() تعداد کاربران فعلی را به صورت پویا از یک دیتابیس یا سورس دیگر دریافت می‌کند. این رویکرد از هاردکد کردن تعداد جلوگیری کرده و تغییرات را آسان می‌کند.
  3. نگه‌داری بهتر: با ذخیره مقادیر در constantها، نگه‌داری کد ساده‌تر می‌شود. اگر نیاز به تغییر محدودیت کاربران به ۱۵۰ باشد، می‌توانیم مقدار MAX_USERS را به‌سادگی از ۱۰۰ به ۱۵۰ تغییر دهیم و این تغییر در تمام برنامه ما اعمال خواهد شد.
  4. شفافیت: استفاده از نام‌های توصیفی برای constantها (مانند MAX_USERS) خوانایی کد ما را بهبود می‌بخشد. هر توسعه‌دهنده‌ای که کد ما را مشاهده کند، می‌تواند به سرعت متوجه شود که این مقدار چه معنایی دارد.

چه زمانی باید از فایل‌های پیکربندی استفاده کنیم؟

در اپلیکیشن‌های بزرگ‌تر، می‌توانیم از فایل‌های پیکربندی (مانند JSON ،YAML یا متغیرهای محیطی) برای ذخیره مقادیری که ممکن است بین محیط‌ها (توسعه، آزمایشی، تولید) تغییر کنند استفاده کنیم.

برای مثال، می‌توانیم در فایل config.json مقدار maxUsers را به صورت زیر هاردکد کنیم ( باید به این نکته توجه داشته باشیم که در config.json بهتر است از سبک نام‌گذاری camelCase استفاده کنیم تا فرمت‌بندی ثابت بماند):

{
    "maxUsers": 100,
    "emailService": {
        "service": "gmail",
        "user": "your-email@gmail.com",
        "pass": "your-email-password"
    }
}

استفاده از پیکربندی در کد:

const config = require('./config.json');

function createUser(name) {
    let numberOfUsers = getCurrentUserCount(); 
    if (numberOfUsers >= config.maxUsers) {
        return 'User limit reached.';
    }
    // Code to create the user
    return 'User created.';
}

۱۰- محدود کردن طول توابع

درک توابع طولانی سخت‌تر بوده و نگه‌داری آن‌ها دشوارتر است.

هیچ قانون سخت‌گیرانه‌ای در این رابطه وجود ندارد، اما به طور کلی، توابع نباید بیشتر از ۲۰-۳۰ خط باشند. اگر یک تابع مسئولیت‌های متعددی دارد یا شامل مراحل زیادی است، احتمالاً طولانی است. تقسیم این توابع به «توابع کمکی» کوچک‌تر می‌تواند مدیریت آن‌ها را ساده‌تر کرده و خود توابع را نیز قابل‌درک‌تر کند.

به طور کلی در نوشتن Clean Code، کوتاه و متمرکز نگه داشتن توابع یکی از اصول کلیدی است که باعث بهبود خوانایی و ساده‌تر شدن کد می‌شود.

نمونه‌ای از یک تابع طولانی و پیچیده:

function updateCart(cart, item, discountCode) {
    // Add the item to the cart
    cart.items.push(item);

    // Calculate the new total
    let total = 0;
    cart.items.forEach(cartItem => {
        total += cartItem.price * cartItem.quantity;
    });

    // Apply discount if available
    if (discountCode) {
        total = applyDiscount(total, discountCode);
    }

    // Log the transaction
    console.log(`Item added: ${item.name}, New total: $${total}`);

    return total;
}

این تابع کارهای متعددی انجام می‌دهد که عبارتند از:

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

در ادامه، این تابع را بازنویسی کرده و آن را به توابع کوچک‌تر و با یک هدف مشخص تقسیم می‌کنیم:

function updateCart(cart, item, discountCode) {
    addItemToCart(cart, item);
    let total = calculateTotal(cart);

    if (discountCode) {
        total = applyDiscount(total, discountCode);
    }

    logTransaction(item, total);
    return total;
}

function addItemToCart(cart, item) {
    cart.items.push(item);
}

function calculateTotal(cart) {
    return cart.items.reduce((total, cartItem) => total + cartItem.price * cartItem.quantity, 0);
}

function logTransaction(item, total) {
    console.log(`Item added: ${item.name}, New total: $${total}`);
}

توضیح این مثال به صورت زیر می‌باشد:

  1. addItemToCart: این تابع فقط مسئول اضافه کردن یک آیتم به سبد خرید است. ساده بوده و هدف مشخصی دارد.
  2. calculateTotal: این تابع قیمت کل آیتم‌های موجود در سبد را محاسبه می‌کند. خواندن و درک آن آسان است و اگر نیاز به تغییر نحوه محاسبه قیمت باشد، تنها باید این تابع را تغییر دهیم.
  3. logTransaction: این تابع مسئولیت ثبت جزئیات تراکنش را بر عهده دارد. اگر بخواهیم چیزی که ثبت می‌شود (مثلاً اضافه کردن timestamp) را تغییر دهیم، می‌توانیم این کار را در این تابع انجام دهیم بدون اینکه به بقیه کد دست بزنیم.
  4. updateCart: تابع اصلی اکنون مانند یک خلاصه از اقداماتی که انجام می‌شود خوانده می‌شود: اضافه کردن یک آیتم، محاسبه قیمت کل، اعمال تخفیف‌ها، و ثبت نتیجه. این تابع در نگاه اول قابل فهم‌تر است.

خلاصه‌ای از محدود کردن طول توابع:

  1. تمرکز بر یک تسک: هر تابع باید در ایده‌آل‌ترین حالت فقط یک تسک را انجام دهد. اگر یک تابع چندین تسک انجام می‌دهد، باید آن را تقسیم کنیم.
  2. استفاده از توابع کمکی: توابع کمکی کوچک و متمرکز به یک تابع اصلی کمک می‌کنند تا تسک خاصی را انجام دهد. در مثال بالا، addItemToCart،calculateTotal و logTransaction نمونه‌هایی از توابع کمکی هستند.
  3. نام‌های توصیفی: باید توابع خود را بر اساس تسک‌هایشان نام‌گذاری کنیم (مثلاً addItemToCart) تا کدی که داریم به خودی خود واضح و شفاف باشد.

بررسی بهترین روش‌ها برای نوشتن Clean Code

اکنون که نکات مهمی را بررسی کردیم، بهتر است به اصول کلی بپردازیم که فلسفه پشت Clean Code نوشتن را تشکیل می‌دهند:

این اصول، کدنویسی را از صرف نوشتن، به طراحی راه‌حل‌ها تبدیل می‌کند. نوشتن Clean Code یک مهارت است که با تمرین رشد می‌کند، بنابراین باید به یادگیری و بهبود این مهارت ادامه دهیم.

نکته‌ای درباره dependencyها
به جای هاردکد کردن مستقیم dependencyها در کد، بهتر است از package managerهایی مانند npm استفاده کنیم. این روش به ما این امکان را می‌دهد تا به راحتی dependencyها را به‌روزرسانی یا حذف نماییم.

جمع‌بندی

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

با رعایت این نکات، می‌توانیم عادت‌هایی ایجاد کنیم که کدمان را خواناتر، قابل‌نگه‌داری‌تر و لذت‌بخش‌تر برای کار کردن کند.