خلاصه مطلب
اینجا ۱۰ نکته سریع آوردم که بهت کمک میکنه کدی تمیزتر و حرفهایتر بنویسی.

یکی از مهمترین ویژگیهای سیستمهایی که طراحی خوبی دارن و عمر طولانی میکنن، اینه که بتونن با تغییر نیازمندیها سازگار بشن و در عین حال نگهداری سادهای داشته باشن. وقتی اصول کدنویسی تمیز (Clean Code) رو رعایت کنیم، کدهامون خواناتر میشن؛ و این خوانایی خیلی مهمه چون واقعیت اینه که بخش عمده زمان یک برنامهنویس صرف خواندن کد میشه، نه نوشتن اون.
اینجا ۱۰ نکته سریع آوردم که بهت کمک میکنه کدی تمیزتر و حرفهایتر بنویسی.
۱. متدها رو کوتاه نگه دار — ترجیحاً زیر ۱۰ خط
سعی کن متدها بیشتر از ۱۰ خط نشه. متدهای کوتاه هم راحتتر خونده میشن، هم سادهتر تست و نگهداری میشن. هر وقت دیدی یک متد زیادی بزرگ شده، بهترین کار اینه که اون رو به چند متد کوچیکتر تقسیم کنی؛ متدهایی که هر کدوم یک وظیفه مشخص رو انجام بدن. یه نشونه خوب برای فهمیدن اینکه متدت زیادی بزرگ شده اینه که نتونی براش یه اسم کوتاه و دقیق پیدا کنی. وقتی اسمگذاری برات سخت میشه، یعنی احتمالا متدت داره چندین کار رو همزمان انجام میده و باید خرد بشه.
| / Original public double calculatePrice(int customerId, int productId, int quantity) { Product product = productRepository.findById(productId); double singlePrice = product.getSinglePrice(); double baseTotal = singlePrice * quantity; double taxRate = 0.07; double totalWithTax = baseTotal + (baseTotal * taxRate); double discount = 0.0; double loyaltyDiscount = 0.0; double bulkDiscount = 0.0; Customer customer = customerRepository.findById(customerId); if (customer.isSilver()) { discount = 0.10; if (quantity > 10) { bulkDiscount = 0.05; } } else if (customer.isGold()) { discount = 0.15; if (quantity > 5) { bulkDiscount = 0.10; } } else if (customer.isPlatinum()) { discount = 0.20; if (quantity > 3) { bulkDiscount = 0.15; } } if (customer.getYearsAsCustomer() > 5) { loyaltyDiscount = 0.03; } double totalDiscount = discount + bulkDiscount + loyaltyDiscount; if (totalDiscount > 0.30) { totalDiscount = 0.30; } double discountedPrice = totalWithTax - (totalWithTax * totalDiscount); double finalPrice = Math.round(discountedPrice * 100.0) / 100.0; return finalPrice; } |
| // After Refactoring public double calculatePrice(int customerId, int productId, int quantity) { Product product = productRepository.findById(productId); Customer customer = customerRepository.findById(customerId); double baseTotal = calculateBaseTotal(product, quantity); double totalWithTax = applyTax(baseTotal); double totalDiscount = calculateTotalDiscount(customer, quantity); double discountedPrice = applyDiscount(totalWithTax, totalDiscount); return roundToTwoDecimalPlaces(discountedPrice); }private double calculateBaseTotal(Product product, int quantity) { return product.getSinglePrice() * quantity; }private double applyTax(double baseTotal) { double taxRate = 0.07; return baseTotal + (baseTotal * taxRate); }private double calculateTotalDiscount(Customer customer, int quantity) { double tierDiscount = calculateTierDiscount(customer, quantity); double loyaltyDiscount = calculateLoyaltyDiscount(customer); double totalDiscount = tierDiscount + loyaltyDiscount; return Math.min(totalDiscount, 0.30); }private double calculateTierDiscount(Customer customer, int quantity) { double discount = 0.0; double bulkDiscount = 0.0; if (customer.isSilver()) { discount = 0.10; if (quantity > 10) bulkDiscount = 0.05; } else if (customer.isGold()) { discount = 0.15; if (quantity > 5) bulkDiscount = 0.10; } else if (customer.isPlatinum()) { discount = 0.20; if (quantity > 3) bulkDiscount = 0.15; } return discount + bulkDiscount; }private double calculateLoyaltyDiscount(Customer customer) { return customer.getYearsAsCustomer() > 5 ? 0.03 : 0.0; }private double applyDiscount(double total, double discountRate) { return total - (total * discountRate); }private double roundToTwoDecimalPlaces(double value) { return Math.round(value * 100.0) / 100.0; } |
۲. کلاسها رو کوتاه نگه دار — ترجیحاً زیر ۲۰۰ خط
این نکته تقریباً واضحه. وقتی یک کلاس زیر ۲۰۰ خط باشه، معمولاً داری یکی از اصول مهم SOLID یعنی Single Responsibility رو رعایت میکنی. کلاسهای کوچکتر چندتا مزیت خیلی مهم دارن:
۳. تعداد متدهای عمومی رو به یک یا دو تا محدود کن
وقتی یک کلاس فقط برای انجام یک کار مشخص طراحی شده (طبق اصل Single Responsibility)، معمولاً نیازی نداره چندین متد عمومی داشته باشه. اگه بیشتر از یک متد پابلیک تعریف میکنی، باید دلیل منطقی پشتش باشه؛ مثلاً وقتی داری متدها رو Overload میکنی تا استفاده از کلاس برای Caller سادهتر بشه. مهم اینه که این کار رو آگاهانه انجام بدی، نه از روی عادت.
۴. از کامنتگذاری زیاد خودداری کن
در بیشتر مواقع کامنتها رو میشه با نامگذاری درست متدها جایگزین کرد. بذار کدت خودش حرف بزنه. اگه اسم متدها و کلاسهات واضح و گویا باشه، نیاز به توضیح اضافه توی کامنتها از بین میره.
| // With Comments private void validateProduct(Product product) { // Check if the product is null if (product == null) { throw new IllegalArgumentException("Product cannot be null"); } // Check if the product has a valid name if (product.getName() == null || product.getName().trim().isEmpty()) { throw new IllegalArgumentException("Product must have a name"); } // Check if the product price is greater than zero if (product.getSinglePrice() |
| // Without comments private void validateProduct(Product product) { ensureProductIsNotNull(product); ensureProductHasName(product); ensurePriceIsPositive(product); ensureCategoryIsValid(product); ensureProductIsActive(product); }private void ensureProductIsNotNull(Product product) { if (product == null) { throw new IllegalArgumentException("Product cannot be null"); } }private void ensureProductHasName(Product product) { if (product.getName() == null || product.getName().trim().isEmpty()) { throw new IllegalArgumentException("Product must have a name"); } }private void ensurePriceIsPositive(Product product) { if (product.getSinglePrice() |
5. ترجیح بده فقط یک Return داشته باشی
وجود چندین دستور return همیشه مشکلساز نیست، اما میتونه باعث بروز خطا بشه؛ مخصوصاً وقتی که تستها کامل و پوششدهی خوبی نداشته باشن. داشتن یک نقطه خروجی مشخص باعث میشه منطق شرطی (branching logic) توی کدت شفافتر بشه و قبل از انتشار راحتتر فرصت ریفکتور کردن داشته باشی.
| // With multiple returns private double calculateProductDiscount(Customer customer, int quantity) { if (customer.isPlatinum()) { if (quantity > 10) { return 0.25; } return 0.20; } if (customer.isGold()) { if (quantity > 10) { return 0.20; } return 0.15; } if (customer.isSilver()) { if (quantity > 10) { return 0.15; } return 0.10; } return 0.0; }کد بعدی//With single return private double calculateProductDiscount(Customer customer, int quantity) { double discount = 0.0; if (customer.isPlatinum()) { discount = (quantity > 10) ? 0.25 : 0.20; } else if (customer.isGold()) { discount = (quantity > 10) ? 0.20 : 0.15; } else if (customer.isSilver()) { discount = (quantity > 10) ? 0.15 : 0.10; } return discount; } |
6. از final برای متغیرها و پارامترها استفاده کن
در برنامههای مدرن، بهخصوص وقتی با عملیات بیتمحور حساس به عملکرد سروکار نداری، بهتره متغیرها و پارامترها رو final اعلام کنی. وقتی یک مقدار بهش اختصاص داده شد، دیگه نباید تغییر کنه. این کار باعث میشه اسم متغیر با هدفش همخوانی داشته باشه و احتمال اشتباه کمتر بشه. البته گاهی متغیرها باید تغییر کنن (مثل همون چیزی که اسمشون نشون میده)، اما این موارد باید استثنا باشن، نه قاعده.
۷. کلاسهای Utility بدون Attribute
این نکته خودش واضحه. اگه به یک کلاس Utility ویژگی (attribute) اضافه میکنی، دیگه اون کلاس Utility محسوب نمیشه. کلاسهای Utility باید فقط شامل عملیات استاتیک باشن که روی پارامترهایی که دریافت میکنن کار میکنن، مثل متدهای دستکاری رشتهها (toLowerCase، isEmpty) که تو خیلی از کتابخونهها هستن.
۸. کلاسهای Utility رو به کلاسهای Instantiable منتقل کن
اگه چند کلاس Utility داری، یا یک کلاس Utility با متدهای زیاد، ممکنه وقتش رسیده باشه بعضی متدها یا حتی کل کلاس رو به کلاسهای معمولی قابل نمونهسازی (instantiable) منتقل کنی. این روش نگهداری کد رو بهتر میکنه، تست واحد (unit testing) رو راحتتر میکنه، امکان Dependency Injection رو فراهم میکنه و با اصول طراحی شیءگرای SOLID همخوانی داره.
۹. پیچیدگی سایکلوماتیک (Cyclomatic Complexity) رو زیر ۵ نگه دار
پیچیدگی سایکلوماتیک یکی از معیارهای مهم کیفیت کده. نگه داشتن این عدد زیر ۵ باعث میشه کدت راحتتر فهمیده، تست و نگهداری بشه.
| public String determineAccessLevel(User user, Resource resource) { if (user == null || resource == null) { return "DENIED"; } if (!user.isActive()) { return "DENIED"; } if (user.isAdmin()) { return "FULL_ACCESS"; } else if (user.isManager() && resource.isInternal()) { return "limitED_ACCESS"; } else if (user.isEmployee()) { switch (resource.getType()) { case "PUBLIC": return "READ_ONLY"; case "CONFIDENTIAL": if (user.hasClearance()) { return "limitED_ACCESS"; } else { return "DENIED"; } default: return "DENIED"; } } return "DENIED"; } |
برای کاهش پیچیدگی، کد را به متدهای با نام مناسب تقسیم کن
برای کاهش پیچیدگی، بخشهای مختلف کد رو به متدهای کوچک و با اسمهای واضح و گویا تقسیم کن. این کار هم خوانایی منطق کد رو بالا میبره و هم باعث میشه ساختار برنامه ماژولار باشه، یعنی هر بخش وظیفه مشخص خودش رو داشته باشه و تغییر یا تستش راحتتر باشه.
| public String determineAccessLevel(User user, Resource resource) { String accessLevel; if (isInvalid(user, resource)) { accessLevel = "DENIED"; } else if (isAdmin(user)) { accessLevel = "FULL_ACCESS"; } else if (isManagerAccess(user, resource)) { accessLevel = "limitED_ACCESS"; } else if (isEmployee(user)) { accessLevel = getEmployeeAccessLevel(user, resource); } else { accessLevel = "DENIED"; } return accessLevel; }private boolean isInvalid(User user, Resource resource) { return user == null || resource == null || !user.isActive(); }private boolean isAdmin(User user) { return user.isAdmin(); }private boolean isManagerAccess(User user, Resource resource) { return user.isManager() && resource.isInternal(); }private boolean isEmployee(User user) { return user.isEmployee(); }private String getEmployeeAccessLevel(User user, Resource resource) { switch (resource.getType()) { case "PUBLIC": return "READ_ONLY"; case "CONFIDENTIAL": return user.hasClearance() ? "limitED_ACCESS" : "DENIED"; default: return "DENIED"; } } |
10. به جای Constants از Enums استفاده کن
استفاده از Enums به جای Constants چند مزیت مهم داره:
ایمنی نوع (Type safety): Enums در زمان کامپایل بررسی میشن و از اختصاص دادن مقادیر اشتباه جلوگیری میکنن.
محدودهبندی مقادیر (Scoped values): مقادیر Enum فقط در همون نوع Enum معتبر هستن و باعث نمیشن فضای نام جهانی آلوده بشه.
قابلیت توسعه (Extensibility): Enums میتونن فیلد، سازنده و متد داشته باشن و رفتارهای پیچیدهتر مثل toString() یا fromString() رو ارائه بدن.
نگهداری آسان (Maintainability): اضافه کردن یا تغییر مقادیر Enum امنتر و واضحتر از ویرایش String Constants هست.
هماهنگی با ابزارها (Tool integration): Enums به خوبی با قابلیتهای IDE مثل Autocomplete و Refactoring کار میکنن و با فریمورکهایی مثل Jackson یا JPA هم به راحتی ادغام میشن.
جلوگیری از استفاده نادرست (Prevents misuse): برخلاف Interface Constants، نمیتونی مقادیر Enumهای غیرمرتبط رو با هم قاطی کنی یا ازشون نمونه بسازی.
منبع: نوشته شده توسط Renan Schmitt

