در این راهنما قصد داریم در مورد نوشتن کد “تمیز” کلین کد صحبت کنیم. این موضوعی است که در ابتدای کار به عنوان یک برنامه نویس شما را کمی گیجم می کند. در این مقاله در مورد این صحبت خواهیم کرد که اصطلاح “کد تمیز” به چه معناست، چرا مهم است، چگونه می توانیم ارزیابی کنیم که آیا یک پایگاه کد تمیز است یا خیر. همچنین برخی از بهترین روش ها و قراردادهایی را که می توانید برای تمیزتر کردن کد خود دنبال کنید، یاد خواهید گرفت.
“کد تمیز” چیست و چرا باید به آن اهمیت دهم؟
کد تمیز اصطلاحی است که برای توصیف کدهای کامپیوتری استفاده می شود که خواندن، درک و نگهداری از آنها آسان است. کد تمیز به گونه ای نوشته می شود که ساده، مختصر و رسا باشد. این کد از مجموعه ای از قراردادها، استانداردها و رویه هایی پیروی می کند که خواندن و دنبال کردن آن را آسان می سازد.
کد تمیز عاری از پیچیدگی، افزونگی و سایر code smells و الگوهای نامناسبی است که می توانند نگهداری، اشکال زدایی و اصلاح آن را دشوار کنند.
اهمیت کد تمیز را نمی توان نادیده گرفت. وقتی کد به راحتی خوانده و فهمیده شود، کار کردن توسعه دهندگان بر روی پایگاه کد آسان تر می شود. این امر می تواند منجر به افزایش بهره وری و کاهش خطاها شود.
همچنین، وقتی کد به راحتی قابل نگهداری باشد، تضمین می شود که پایگاه کد می تواند در طول زمان بهبود یافته و به روز شود. این امر به ویژه برای پروژه های بلندمدت که کد باید برای سال ها نگهداری و به روز شود، مهم است.
چگونه می توانم ارزیابی کنم که آیا کدهای نوشته شده تمیز است یا خیر؟
می توانید کد تمیز را به روش های مختلفی ارزیابی کنید. مستندات خوب، قالب بندی سازگار و یک پایگاه کد سازمان یافته، همگی نشانگر کد تمیز هستند.
بررسی کد (Code reviews)همچنین می تواند به شناسایی مشکلات احتمالی و اطمینان از این که کد از بهترین روش ها و قراردادها پیروی می کند، کمک کند.
تست کردن نیز جنبه مهمی از کد تمیز است. این کار کمک می کند تا اطمینان حاصل شود که کد همانطور که انتظار می رود کار می کند و می تواند خطاها را در مراحل اولیه تشخیص دهد.
ابزارها، رویه ها و قراردادهای متعددی وجود دارد که می توانید برای اطمینان از تمیز بودن پایگاه کد خود پیاده سازی کنید. با پیاده سازی این ابزارها و رویه ها، توسعه دهندگان می توانند یک پایگاه کد ایجاد کنند که خواندن، درک و نگهداری از آن آسان باشد.
همچنین مهم است که به یاد داشته باشید که مقدار اجتناب ناپذیری از ذهنیت مربوط به این موضوع وجود دارد و نظرات و نکات مختلفی در این زمینه وجود دارد. چیزی که ممکن است برای یک شخص یا یک پروژه تمیز و عالی به نظر برسد، ممکن است برای شخص دیگر یا پروژه دیگر اینطور نباشد.
اما هنوز هم چند قرارداد کلی وجود دارد که می توانیم برای دستیابی به کد تمیزتر دنبال کنیم، پس بیایید اکنون به آن ها بپردازیم.
نکات و قراردادهایی برای نوشتن کد تمیزتر
هر زمان که نیاز دارم در مورد چگونگی پیادهسازی یک ویژگی جدید در یک پایگاه کد موجود یا چگونگی حل یک مشکل خاص فکر کنم، همیشه این سه مورد ساده را در اولویت قرار میدهم.
اثربخشی
اول از همه، کد ما باید مؤثر باشد، به این معنی که باید مشکلی را که قرار است حل کند، حل کند. البته این ابتدایی ترین انتظاری است که می توانیم از کد خود داشته باشیم، اما اگر پیاده سازی ما در واقع کار نکند، فکر کردن به هر چیز دیگری بی ارزش است.
کارایی
دوم، هنگامی که فهمیدیم کد ما مشکل را حل می کند، باید بررسی کنیم که آیا این کار را به طور کارآمد انجام می دهد یا خیر. آیا برنامه با استفاده از مقدار معقولی از منابع از نظر زمان و فضا اجرا می شود؟ آیا می تواند سریعتر و با فضای کمتری اجرا شود؟
پیچیدگی الگوریتمی چیزی است که برای ارزیابی این موضوع باید از آن آگاه باشید. اگر با آن آشنا نیستید، می توانید این مقاله ای را که نوشتم بررسی کنید.
برای توضیح بیشتر در مورد کارایی، در اینجا دو مثال از تابعی وجود دارد که مجموع تمام اعداد موجود در یک آرایه را محاسبه می کند.
// Inefficient Example
function sumArrayInefficient(array) {
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
این پیادهسازی از تابع sumArrayInefficient با استفاده از یک حلقه for روی آرایه میچرخد و هر عنصر را به متغیر sum اضافه میکند. این یک راه حل معتبر است، اما به دلیل اینکه نیاز به پیمایش کل آرایه دارد، صرف نظر از طول آن، کارآمد نیست.
// Efficient example
function sumArrayEfficient(array) {
return array.reduce((a, b) => a + b, 0);
}
این پیادهسازی از تابع sumArrayEfficient از روش reduce برای جمع کردن عناصر آرایه استفاده میکند. روش reduce یک تابع را روی هر عنصر آرایه اعمال میکند و نتیجه را تجمیع میکند. در این مورد، تابع به سادگی هر عنصر را به تجمیعکننده اضافه میکند که از 0 شروع میشود.
این راه حل کارآمدتر است زیرا فقط یک بار نیاز به پیمایش آرایه دارد و عملیات جمع را روی هر عنصر در حین پیمایش انجام میدهد.
سادگی
و در آخر، سادگی قرار دارد. این سختترین بخش برای ارزیابی است زیرا ذهنی است و به شخص خواننده کد بستگی دارد. اما برخی راهنماییهایی که میتوانیم دنبال کنیم عبارتند از:
• آیا میتوانید به راحتی بفهمید که برنامه در هر خط چه کاری انجام میدهد؟
• آیا نامهای توابع و متغیرها به وضوح مسئولیتهای خود را نشان میدهند؟
• آیا کد به درستی از نظر فاصلهگذاری و قالببندی در کل پایگاه کد یکسان است؟
• آیا مستندات کد در دسترس است؟ آیا از کامنتها برای توضیح بخشهای پیچیده برنامه استفاده شده است؟
• چقدر سریع میتوانید بفهمید که ویژگیهای خاص برنامه در کدام قسمت از پایگاه کد قرار دارند؟ آیا میتوانید بدون نیاز به تغییر بخشهای زیادی از کد، ویژگیهای جدید را اضافه یا حذف کنید؟
• آیا کد از رویکرد ماژولار پیروی میکند و ویژگیهای مختلف در اجزای جداگانه قرار دارند؟
• آیا در صورت امکان از کد تکراری استفاده میشود؟
• آیا تصمیمات معماری، طراحی و پیادهسازی یکسان در کل پایگاه کد دنبال میشود؟
با دنبال کردن و اولویتبندی این سه مفهوم اثربخشی، کارایی و سادگی، همیشه میتوانیم یک راهنمای پیروی کنیم که به ما کمک میکند تا در مورد چگونگی پیادهسازی یک راه حل فکر کنیم. حالا بیایید برخی از راهنماییهایی را که میتوانند به ما کمک کنند تا کدمان را سادهتر کنیم، گسترش دهیم.
فرمت و نحو
استفاده از فرمت و نحو یکسان در سراسر پایگاه کد، یک جنبه مهم از نوشتن کد تمیز است. دلیل این امر این است که فرمت و نحو یکسان، کد را خواناتر و درک آن را آسانتر میکند.
وقتی کد یکسان است، توسعهدهندگان میتوانند به راحتی الگوها را شناسایی کنند و بفهمند که کد چگونه کار میکند، که این امر باعث میشود اشکالزدایی، نگهداری و بهروزرسانی پایگاه کد در طول زمان آسانتر شود. یکپارچگی همچنین به کاهش خطاها کمک میکند، زیرا اطمینان میدهد که همه توسعهدهندگان از استانداردهای و کنوانسیونهای یکسانی پیروی میکنند.
برخی از مواردی که باید در مورد فرمت و نحو به آنها فکر کنیم عبارتند از:
تورفتگی و فاصلهگذاری
// bad indentation and spacing
const myFunc=(number1,number2)=>{
const result=number1+number2;
return result;
}
// good indentation and spacing
const myFunc = (number1, number2) => {
const result = number1 + number2
return result
}
در اینجا مثالی از یک تابع مشابه داریم، یکی بدون تورفتگی و فاصلهگذاری و دیگری با فاصلهگذاری و تورفتگی مناسب. میتوانیم ببینیم که دومین مورد به وضوح خواناتر است.
نحو یکسان
استفاده از نحو یکسان در کد، به خوانایی و درک بهتر آن کمک میکند. این شامل استفاده از ساختارهای تکراری برای حل مشکلات مشابه، استفاده از نامگذاری یکسان برای متغیرها و توابع، و پیروی از یک الگوی مشخص برای نوشتن کد است.
// Arrow function, no colons, no return
const multiplyByTwo = number => number * 2
// Function, colons, return
function multiplyByThree(number) {
return number * 3;
}
باز هم در اینجا دو تابع بسیار مشابه را داریم که با نحو متفاوت پیادهسازی شدهاند. اولین تابع یک تابع پیکانی است که از دو نقطه و دستور بازگشت استفاده نمیکند، در حالی که دیگری یک تابع معمولی است که از دو نقطه و دستور بازگشت استفاده میکند.
هر دوی آنها کار میکنند و کاملاً خوب هستند، اما باید هدفمان این باشد که همیشه از همان نحو برای عملیات مشابه استفاده کنیم، زیرا این باعث میشود که کد در سراسر پایگاه کد یکدستتر و خواناتر شود.
ابزارهای لینتر و فرمتکننده کد ابزارهای عالی هستند که میتوانیم در پروژههایمان استفاده کنیم تا نحو و کنوانسیونهای فرمتبندی را در پایگاه کدمان خودکار کنیم. اگر با این ابزارها آشنا نیستید، مقاله دیگر من را بررسی کنید.
کنوانسیونهای یکسان برای حروف بزرگ و کوچک
استفاده از یک کنوانسیون یکسان برای حروف بزرگ و کوچک در نامگذاری متغیرها، توابع و کلاسها، به خوانایی و درک بهتر کد کمک میکند. این شامل استفاده از یک روش مشخص برای نامگذاری، مانند استفاده از حروف بزرگ برای کلاسها و حروف کوچک برای متغیرها، میشود.
// camelCase
const myName = ‘John’
// PascalCase
const MyName = ‘John’
// snake_case
const my_name = ‘John’
همین امر در مورد کنوانسیون حروف بزرگ و کوچک انتخابی ما نیز صدق میکند. همه اینها کار میکنند، اما باید هدف ما این باشد که به طور مداوم از یک کنوانسیون در کل پروژه خود استفاده کنیم.
نامگذاری
نامگذاری واضح و توصیفی متغیرها و توابع یک جنبه مهم از نوشتن کد تمیز است. این به بهبود خوانایی و قابلیت نگهداری پایگاه کد کمک میکند. وقتی نامها به خوبی انتخاب شده باشند، سایر توسعهدهندگان میتوانند به سرعت بفهمند که متغیر یا تابع چه کاری انجام میدهد و چگونه با بقیه کد مرتبط است.
در اینجا دو مثال در جاوا اسکریپت آورده شده است که اهمیت نامگذاری واضح و توصیفی را نشان میدهد:
// Example 1: Poor Naming
function ab(a, b) {
let x = 10;
let y = a + b + x;
console.log(y);
}
ab(5, 3);
// Example 1: Good Naming
function calculateTotalWithTax(basePrice, taxRate) {
const BASE_TAX = 10;
const totalWithTax = basePrice + (basePrice * (taxRate / 100)) + BASE_TAX;
console.log(totalWithTax);
}
calculateTotalWithTax(50, 20);
در این مثال، یک تابع داریم که قیمت کل یک محصول شامل مالیات را محاسبه میکند. نام تابع و نام متغیرها به خوبی انتخاب شدهاند و نشانهای واضح از اینکه تابع چه کاری انجام میدهد و متغیرها چه چیزی را نشان میدهند، ارائه میدهند.
این باعث میشود که کد آسانتر خوانده و درک شود، به ویژه برای سایر توسعهدهندگانی که ممکن است در آینده با پایگاه کد کار کنند.
ایجاز در مقابل وضوح
وقتی صحبت از نوشتن کد تمیز میشود، مهم است که بین ایجاز و وضوح تعادل برقرار کنید. در حالی که مهم است که کد را مختصر نگه داریم تا خوانایی و قابلیت نگهداری آن بهبود یابد، به همان اندازه مهم است که اطمینان حاصل کنیم که کد واضح و قابل فهم است. نوشتن کد بیش از حد مختصر میتواند منجر به سردرگمی و خطا شود و کار با کد را برای سایر توسعهدهندگان دشوار کند.
در اینجا دو مثال آورده شده است که اهمیت ایجاز و وضوح را نشان میدهد:
// Example 1: Concise function
const countVowels = s => (s.match(/[aeiou]/gi) || []).length;
console.log(countVowels(“hello world”));
این مثال از یک تابع پیکانی مختصر و regex برای شمارش تعداد حروف صدادار در یک رشته معین استفاده میکند. در حالی که کد بسیار کوتاه و نوشتن آن آسان است، ممکن است بلافاصله برای سایر توسعهدهندگان مشخص نباشد که الگوی regex چگونه کار میکند، به خصوص اگر با نحو regex آشنا نباشند.
// Example 2: More verbose and clearer function
function countVowels(s) {
const vowelRegex = /[aeiou]/gi;
const matches = s.match(vowelRegex) || [];
return matches.length;
}
console.log(countVowels(“hello world”));
این مثال از یک تابع سنتی و regex برای شمارش تعداد حروف صدادار در یک رشته معین استفاده میکند، اما این کار را به روشی انجام میدهد که واضح و قابل فهم است. نام تابع و نام متغیرها توصیفی هستند و الگوی regex در یک متغیر با نام واضح ذخیره میشود. این باعث میشود که به راحتی بتوان فهمید که تابع چه کاری انجام میدهد و چگونه کار میکند.
هنگام نوشتن کد، مهم است که بین ایجاز و وضوح تعادل برقرار کنید. در حالی که کد مختصر میتواند خوانایی و قابلیت نگهداری را بهبود بخشد، مهم است که اطمینان حاصل شود که کد هنوز واضح و قابل فهم برای سایر توسعهدهندگانی است که ممکن است در آینده با پایگاه کد کار کنند.
با استفاده از نامهای توصیفی تابع و متغیر، و استفاده از قالببندی و نظرات واضح و خوانا در کد، میتوان کد تمیز و مختصری نوشت که درک و کار با آن آسان باشد.
قابلیت استفاده مجدد
قابلیت استفاده مجدد کد یک مفهوم اساسی در مهندسی نرمافزار است که به توانایی استفاده از کد به طور مکرر بدون تغییر اشاره دارد.
اهمیت قابلیت استفاده مجدد کد در این واقعیت نهفته است که میتواند با کاهش میزان کدی که باید نوشته و آزمایش شود، کارایی و بهرهوری توسعه نرمافزار را تا حد زیادی بهبود بخشد.
با استفاده مجدد از کد موجود، توسعهدهندگان میتوانند در زمان و تلاش صرفهجویی کنند، کیفیت و سازگاری کد را بهبود بخشند و خطر معرفی باگ و خطا را به حداقل برسانند. کد قابل استفاده مجدد همچنین امکان ایجاد معماریهای نرمافزاری ماژولارتر و مقیاسپذیرتر را فراهم میکند و نگهداری و بهروزرسانی پایگاههای کد را در طول زمان آسانتر میکند.
// Example 1: No re-usability
function calculateCircleArea(radius) {
const PI = 3.14;
return PI * radius * radius;
}
function calculateRectangleArea(length, width) {
return length * width;
}
function calculateTriangleArea(base, height) {
return (base * height) / 2;
}
const circleArea = calculateCircleArea(5);
const rectangleArea = calculateRectangleArea(4, 6);
const triangleArea = calculateTriangleArea(3, 7);
console.log(circleArea, rectangleArea, triangleArea);
این مثال سه تابع را تعریف میکند که به ترتیب مساحت یک دایره، مستطیل و مثلث را محاسبه میکنند. هر تابع یک کار خاص را انجام میدهد، اما هیچکدام از آنها برای سایر کارهای مشابه قابل استفاده مجدد نیستند.
به علاوه، استفاده از یک مقدار PI به صورت hard-coded میتواند در صورت نیاز به تغییر مقدار در آینده منجر به خطا شود. این کد ناکارآمد است زیرا منطق یکسانی را چندین بار تکرار میکند.
// Example 2: Implementing re-usability
function calculateArea(shape, …args) {
if (shape === ‘circle’) {
const [radius] = args;
const PI = 3.14;
return PI * radius * radius;
} else if (shape === ‘rectangle’) {
const [length, width] = args;
return length * width;
} else if (shape === ‘triangle’) {
const [base, height] = args;
return (base * height) / 2;
} else {
throw new Error(`Shape “${shape}” not supported.`);
}
}
const circleArea = calculateArea(‘circle’, 5);
const rectangleArea = calculateArea(‘rectangle’, 4, 6);
const triangleArea = calculateArea(‘triangle’, 3, 7);
console.log(circleArea, rectangleArea, triangleArea);
این مثال یک تابع منفرد به نام calculateArea را تعریف میکند که یک آرگومان shape و یک تعداد متغیر از آرگومانها را دریافت میکند. بر اساس آرگومان shape، تابع محاسبه مناسب را انجام میدهد و نتیجه را برمیگرداند.
این رویکرد بسیار کارآمدتر است زیرا نیاز به تکرار کد برای کارهای مشابه را از بین میبرد. همچنین انعطافپذیرتر و قابل گسترشتر است، زیرا میتوان به راحتی اشکال اضافی را در آینده اضافه کرد.
جریان اجرای واضح
داشتن یک جریان اجرای واضح برای نوشتن کد تمیز ضروری است، زیرا باعث میشود کد راحتتر خوانده، درک و نگهداری شود. کدی که از یک ساختار واضح و منطقی پیروی میکند، کمتر مستعد خطا است، اصلاح و گسترش آن آسانتر است و از نظر زمان و منابع کارآمدتر است.
از طرف دیگر، کد اسپاگتی اصطلاحی است که برای توصیف کدی استفاده میشود که پیچیده و پیگیری آن دشوار است، که اغلب با بلوکهای کد طولانی، درهم تنیده و سازماندهی نشده مشخص میشود. کد اسپاگتی میتواند نتیجه تصمیمات طراحی ضعیف، اتصال بیش از حد یا عدم وجود مستندات و نظرات مناسب باشد.
در اینجا دو مثال از کد جاوا اسکریپت آورده شده است که یک کار را انجام میدهند، یکی با جریان اجرای واضح و دیگری با کد اسپاگتی:
// Example 1: Clear flow of execution
function calculateDiscount(price, discountPercentage) {
const discountAmount = price * (discountPercentage / 100);
const discountedPrice = price – discountAmount;
return discountedPrice;
}
const originalPrice = 100;
const discountPercentage = 20;
const finalPrice = calculateDiscount(originalPrice, discountPercentage);
console.log(finalPrice);
// Example 2: Spaghetti code
const originalPrice = 100;
const discountPercentage = 20;
let discountedPrice;
let discountAmount;
if (originalPrice && discountPercentage) {
discountAmount = originalPrice * (discountPercentage / 100);
discountedPrice = originalPrice – discountAmount;
}
if (discountedPrice) {
console.log(discountedPrice);
همانطور که میبینیم، مثال 1 از یک ساختار واضح و منطقی پیروی میکند، با تابعی که پارامترهای لازم را دریافت میکند و نتیجه محاسبه شده را برمیگرداند. از طرف دیگر، مثال 2 بسیار پیچیدهتر است، با متغیرهایی که خارج از هر تابع تعریف شدهاند و چندین دستور if برای بررسی اینکه آیا بلوک کد با موفقیت اجرا شده است یا خیر استفاده میشود.
اصل مسئولیت واحد
اصل مسئولیت واحد (SRP) یک اصل در توسعه نرمافزار است که بیان میکند هر کلاس یا ماژول باید فقط یک دلیل برای تغییر داشته باشد، یا به عبارت دیگر، هر موجودیت در پایگاه کد ما باید یک مسئولیت واحد داشته باشد.
این اصل به ایجاد کدی کمک میکند که درک، نگهداری و گسترش آن آسان باشد.
با اعمال SRP، میتوانیم کدی را ایجاد کنیم که آزمایش، استفاده مجدد و بازسازی آن آسانتر باشد، زیرا هر ماژول فقط یک مسئولیت را بر عهده دارد. این باعث میشود که احتمال داشتن عوارض جانبی یا وابستگیهایی که کار با کد را سختتر میکنند، کمتر شود.
// Example 1: Withouth SRP
function processOrder(order) {
// validate order
if (order.items.length === 0) {
console.log(“Error: Order has no items”);
return;
}
// calculate total
let total = 0;
order.items.forEach(item => {
total += item.price * item.quantity;
});
// apply discounts
if (order.customer === “vip”) {
total *= 0.9;
}
// save order
const db = new Database();
db.connect();
db.saveOrder(order, total);
در این مثال، تابع processOrder چندین مسئولیت را بر عهده دارد: سفارش را اعتبارسنجی میکند، کل را محاسبه میکند، تخفیفها را اعمال میکند و سفارش را در یک پایگاه داده ذخیره میکند. این باعث میشود که تابع طولانی و درک آن دشوار باشد، و هر گونه تغییر در یک مسئولیت ممکن است بر سایر مسئولیتها تأثیر بگذارد و نگهداری آن را سختتر کند.
// Example 2: With SRP
class OrderProcessor {
constructor(order) {
this.order = order;
}
validate() {
if (this.order.items.length === 0) {
console.log(“Error: Order has no items”);
return false;
}
return true;
}
calculateTotal() {
let total = 0;
this.order.items.forEach(item => {
total += item.price * item.quantity;
});
return total;
}
applyDiscounts(total) {
if (this.order.customer === “vip”) {
total *= 0.9;
}
return total;
}
}
class OrderSaver {
constructor(order, total) {
this.order = order;
this.total = total;
}
save() {
const db = new Database();
db.connect();
db.saveOrder(this.order, this.total);
}
}
const order = new Order();
const processor = new OrderProcessor(order);
if (processor.validate()) {
const total = processor.calculateTotal();
const totalWithDiscounts = processor.applyDiscounts(total);
const saver = new OrderSaver(order, totalWithDiscounts);
saver.save();
}
در این مثال، تابع processOrder چندین مسئولیت را بر عهده دارد: سفارش را اعتبارسنجی میکند، کل را محاسبه میکند، تخفیفها را اعمال میکند و سفارش را در یک پایگاه داده ذخیره میکند. این باعث میشود که تابع طولانی و درک آن دشوار باشد، و هر گونه تغییر در یک مسئولیت ممکن است بر سایر مسئولیتها تأثیر بگذارد و نگهداری آن را سختتر کند.
// Option 1: No “single source of truth”
// file 1: weatherAPI.js
const apiKey = ‘12345abcde’;
function getCurrentWeather(city) {
return fetch(`https://api.weather.com/conditions/v1/${city}?apiKey=${apiKey}`)
.then(response => response.json());
}
// file 2: weatherComponent.js
const apiKey = ‘12345abcde’;
function displayCurrentWeather(city) {
getCurrentWeather(city)
.then(weatherData => {
// display weatherData on the UI
});
}
در این گزینه، کلید API در دو فایل مختلف تکرار شده است که نگهداری و بهروزرسانی آن را دشوارتر میکند. اگر بخواهیم کلید API را تغییر دهیم، باید به خاطر داشته باشیم که آن را در هر دو مکان بهروزرسانی کنیم.
// Option 2: “Single source of truth”
// file 1: weatherAPI.js
const apiKey = ‘12345abcde’;
function getCurrentWeather(city) {
return fetch(`https://api.weather.com/conditions/v1/${city}?apiKey=${apiKey}`)
.then(response => response.json());
}
export { getCurrentWeather, apiKey };
// file 2: weatherComponent.js
import { getCurrentWeather } from ‘./weatherAPI’;
function displayCurrentWeather(city) {
getCurrentWeather(city)
.then(weatherData => {
// display weatherData on the UI
});
}
در این گزینه، کلید API در یک مکان ذخیره میشود (در فایل weatherAPI.js) و برای استفاده سایر ماژولها صادر میشود. این تضمین میکند که تنها یک منبع حقیقت برای کلید API وجود دارد و از تکرار و ناسازگاری جلوگیری میکند.
اگر بخواهیم کلید API را بهروزرسانی کنیم، میتوانیم این کار را در یک مکان انجام دهیم و تمام ماژولهای دیگری که از آن استفاده میکنند، بهطور خودکار مقدار بهروزرسانی شده را دریافت خواهند کرد.
فقط دادههایی را که نیاز دارید در معرض نمایش و استفاده قرار دهید
یکی از اصول مهم نوشتن کد تمیز این است که فقط اطلاعاتی را که برای یک کار خاص ضروری است در معرض نمایش و استفاده قرار دهید. این به کاهش پیچیدگی، افزایش کارایی و جلوگیری از خطاهایی که میتوانند از استفاده از دادههای غیرضروری ناشی شوند، کمک میکند.
وقتی دادههایی که نیازی به آنها نیست در معرض نمایش یا استفاده قرار میگیرند، میتوانند منجر به مشکلات عملکرد شوند و درک و نگهداری کد را دشوارتر کنند.
فرض کنید یک شیء با چندین ویژگی دارید، اما فقط باید از چند مورد از آنها استفاده کنید. یک راه برای انجام این کار این است که هر بار که به آنها نیاز دارید، به شیء و ویژگیهای خاص ارجاع دهید. اما این میتواند پرحرف و مستعد خطا شود، به ویژه اگر شیء عمیقاً تو در تو باشد. یک راه حل تمیزتر و کارآمدتر این است که از تخریب شیء استفاده کنید تا فقط اطلاعاتی را که نیاز دارید در معرض نمایش و استفاده قرار دهید.
// Original object
const user = {
id: 1,
name: ‘Alice’,
email: ‘alice@example.com’,
age: 25,
address: {
street: ‘123 Main St’,
city: ‘Anytown’,
state: ‘CA’,
zip: ‘12345’
}
};
// Only expose and consume the name and email properties
const { name, email } = user;
console.log(name); // ‘Alice’
console.log(email); // ‘alice@example.com’
ماژولار سازی
ماژولار سازی یک مفهوم ضروری در نوشتن کد تمیز است. این به عمل شکستن کد بزرگ و پیچیده به ماژولها یا توابع کوچکتر و قابل مدیریتتر اشاره دارد. این کار باعث میشود کد راحتتر درک، آزمایش و نگهداری شود.
استفاده از ماژولار سازی مزایای متعددی را ارائه میدهد، مانند:
• قابلیت استفاده مجدد: ماژولها را میتوان در بخشهای مختلف برنامه یا در برنامههای دیگر مورد استفاده مجدد قرار داد که باعث صرفه جویی در زمان و تلاش در توسعه میشود.
• کپسولهسازی: ماژولها به شما امکان میدهند جزئیات داخلی یک تابع یا شیء را پنهان کنید و فقط رابط ضروری را به دنیای بیرون نشان دهید. این به کاهش جفت شدن بین بخشهای مختلف کد و بهبود کیفیت کلی کد کمک میکند.
• مقیاسپذیری: با شکستن کد بزرگ به قطعات کوچکتر و ماژولار، میتوانید به راحتی عملکرد را بدون تأثیر بر کل پایگاه کد اضافه یا حذف کنید.
در اینجا یک مثال در جاوا اسکریپت از یک قطعه کد آورده شده است که یک کار ساده را انجام میدهد، یکی بدون استفاده از ماژولار سازی و دیگری با پیادهسازی ماژولار سازی.
// Without modularization
function calculatePrice(quantity, price, tax) {
let subtotal = quantity * price;
let total = subtotal + (subtotal * tax);
return total;
}
// Without modularization
let quantity = parseInt(prompt(“Enter quantity: “));
let price = parseFloat(prompt(“Enter price: “));
let tax = parseFloat(prompt(“Enter tax rate: “));
let total = calculatePrice(quantity, price, tax);
console.log(“Total: $” + total.toFixed(2));
در مثال بالا، تابع calculatePrice برای محاسبه قیمت کل یک مورد با توجه به کمیت، قیمت و نرخ مالیات آن استفاده میشود. با این حال، این تابع ماژولار نشده و به شدت با منطق ورودی و خروجی کاربر مرتبط است. این میتواند آزمایش و نگهداری آن را دشوار کند.
اکنون، بیایید مثالی از همین کد با استفاده از ماژولار سازی را ببینیم:
// With modularization
function calculateSubtotal(quantity, price) {
return quantity * price;
}
function calculateTotal(subtotal, tax) {
return subtotal + (subtotal * tax);
}
// With modularization
let quantity = parseInt(prompt(“Enter quantity: “));
let price = parseFloat(prompt(“Enter price: “));
let tax = parseFloat(prompt(“Enter tax rate: “));
let subtotal = calculateSubtotal(quantity, price);
let total = calculateTotal(subtotal, tax);
console.log(“Total: $” + total.toFixed(2));
در مثال بالا، تابع calculatePrice به دو تابع کوچکتر تقسیم شده است: calculateSubtotal و calculateTotal. این توابع اکنون ماژولار شدهاند و به ترتیب مسئول محاسبه مبلغ جزء و کل هستند. این امر باعث میشود کد آسانتر درک، آزمایش و نگهداری شود و همچنین در سایر قسمتهای برنامه قابل استفاده مجدد باشد.
ماژولار سازی همچنین میتواند به عمل تقسیم فایلهای کد منفرد به چندین فایل کوچکتر اشاره داشته باشد که بعداً دوباره در یک فایل (یا تعداد کمتری فایل) کامپایل میشوند. این عمل همان مزایایی را دارد که در مورد آنها صحبت کردیم.
اگر میخواهید بدانید چگونه این را در جاوا اسکریپت با استفاده از ماژولها پیادهسازی کنید، این مقاله دیگر من را بررسی کنید.
ساختارهای پوشه
انتخاب یک ساختار پوشه خوب بخش مهمی از نوشتن کد تمیز است. یک ساختار پروژه خوشسازماندهی به توسعهدهندگان کمک میکند تا به راحتی کد را پیدا کرده و اصلاح کنند، پیچیدگی کد را کاهش میدهد و مقیاسپذیری و نگهداری پروژه را بهبود میبخشد.
از طرف دیگر، یک ساختار پوشه ضعیف میتواند درک معماری پروژه، پیمایش در پایگاه کد را دشوار کند و منجر به سردرگمی و خطا شود.
در اینجا نمونههایی از یک ساختار پوشه خوب و بد با استفاده از یک پروژه React به عنوان مثال آورده شده است:
// Bad folder structure
my-app/
├── App.js
├── index.js
├── components/
│ ├── Button.js
│ ├── Card.js
│ └── Navbar.js
├── containers/
│ ├── Home.js
│ ├── Login.js
│ └── Profile.js
├── pages/
│ ├── Home.js
│ ├── Login.js
│ └── Profile.js
└── utilities/
├── api.js
└── helpers.js
در این مثال، ساختار پروژه بر اساس انواع فایلها مانند کامپوننتها، کانتینرها و صفحات سازماندهی شده است.
اما این رویکرد میتواند منجر به سردرگمی و تکرار شود، زیرا مشخص نیست کدام فایلها به کجا تعلق دارند. برای مثال، کامپوننت Home هم در پوشه کانتینرها و هم در پوشه صفحات وجود دارد. همچنین میتواند پیدا کردن و ویرایش کد را دشوار کند، زیرا ممکن است توسعهدهندگان برای پیدا کردن کد مورد نظر خود، مجبور به جستجو در چندین پوشه شوند.
// Good folder structure
my-app/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.js
│ │ │ ├── Button.module.css
│ │ │ └── index.js
│ │ ├── Card/
│ │ │ ├── Card.js
│ │ │ ├── Card.module.css
│ │ │ └── index.js
│ │ └── Navbar/
│ │ ├── Navbar.js
│ │ ├── Navbar.module.css
│ │ └── index.js
│ ├── pages/
│ │ ├── Home/
│ │ │ ├── Home.js
│ │ │ ├── Home.module.css
│ │ │ └── index.js
│ │ ├── Login/
│ │ │ ├── Login.js
│ │ │ ├── Login.module.css
│ │ │ └── index.js
│ │ └── Profile/
│ │ ├── Profile.js
│ │ ├── Profile.module.css
│ │ └── index.js
│ ├── utils/
│ │ ├── api.js
│ │ └── helpers.js
│ ├── App.js
│ └── index.js
└── public/
├── index.html
└── favicon.ico
در این مثال، ساختار پروژه حول ویژگیها سازماندهی شده است، مانند کامپوننتها، صفحات و ابزارها. هر ویژگی پوشه خاص خود را دارد که شامل تمام فایلهای مربوط به آن ویژگی است.
این رویکرد یافتن و تغییر کد را آسان میکند، زیرا تمام فایلهای مربوط به یک ویژگی در یک پوشه قرار دارند. همچنین تکرار کد و پیچیدگی را کاهش میدهد، زیرا ویژگیها از هم جدا شده و فایلهای مربوط به آنها به صورت یکجا سازماندهی شدهاند.
به طور کلی، یک ساختار پوشه خوب باید حول ویژگیها سازماندهی شود، نه انواع فایلها، و باید یافتن و تغییر کد را آسان کند. یک ساختار واضح و منطقی میتواند نگهداری، درک و مقیاسپذیری یک پروژه را آسانتر کند، در حالی که یک ساختار گیجکننده و ناسازگار میتواند منجر به خطا و سردرگمی شود.
اگر علاقه مند به یادگیری بیشتر در این مورد هستید، در این مقاله ای که در مورد معماری نرم افزار نوشتم، موضوع ساختارهای پوشه و الگوهای شناخته شده ای که می توانید دنبال کنید را گسترش دادم.
مستندات
مستندات یک بخش ضروری از نوشتن کد تمیز است. مستندسازی مناسب نه تنها به توسعهدهندهای که کد را نوشته است کمک میکند تا در آینده آن را بهتر درک کند، بلکه درک و خواندن پایگاه کد را برای سایر توسعهدهندگان نیز آسانتر میکند. وقتی کدی به خوبی مستند شده باشد، میتواند در زمان و تلاش برای رفع اشکال و نگهداری کد صرفه جویی کند.
مستندسازی به ویژه در مواردی که راه حل های ساده و آسان برای درک قابل پیاده سازی نیستند، مواردی که منطق تجاری نسبتاً پیچیده است و مواردی که افرادی که با پایگاه کد آشنا نیستند باید با آن تعامل داشته باشند، مهم است.
یکی از راههای مستندسازی کد، استفاده از کامنت است. کامنتها میتوانند زمینه را فراهم کرده و توضیح دهند که کد چه کاری انجام میدهد. اما مهم است که از کامنت ها عاقلانه استفاده کنید، فقط در صورت لزوم کامنت بگذارید و از کامنت های زائد یا غیر ضروری خودداری کنید.
راه دیگر برای مستندسازی کد، استفاده از مستندات درون خطی است. مستندات درون خطی در خود کد تعبیه شده است و می تواند برای توضیح اینکه یک تابع یا قطعه کد خاص چه کاری انجام می دهد استفاده شود. مستندات درون خطی اغلب در ترکیب با ابزارهایی مانند JSDoc استفاده می شود که استانداردی برای مستندسازی کد در جاوا اسکریپت ارائه می دهد.
ابزارهایی مانند Typescript نیز می توانند مستندات خودکار را برای پایگاه کد ما ارائه دهند که بسیار مفید است.
اگر میخواهید درباره Typescript بیشتر بدانید، مدتی پیش یک راهنمای مبتدی پسند نوشتم.
و در آخر، ابزارهایی مانند Swagger و Postman را میتوان برای مستندسازی APIها استفاده کرد و راهی برای درک آسان نحوه تعامل با آنها ارائه داد.
اگر علاقه مند به دانستن نحوه پیاده سازی کامل، آزمایش، مصرف و مستندسازی API ها هستید، اخیراً دو راهنما برای REST API ها و GraphQL API ها نوشته ام.
https://www.freecodecamp.org/news/how-to-write-clean-code/
نظرات در مورد : “کد تمیز” یا Clean Code چیست و چرا باید به آن اهمیت دهم؟