These notes provide a detailed summary of the “Clean Code” book, along with examples to help you understand and apply its principles effectively.
General Principles of Clean Code
-
Readability:
- Code is written for humans to read and understand.
- Prioritize clarity over clever tricks or brevity.
Example:
# Clear and readable code def calculate_total_price(prices, tax_rate): subtotal = sum(prices) tax = subtotal * tax_rate return subtotal + tax # Avoid unclear code def calc(p, t): return sum(p) + sum(p) * t -
Simplicity:
- Strive for simplicity by breaking down complex logic.
- Avoid overengineering solutions to problems.
Example:
# Simple and clear def is_eligible_for_discount(user): return user.age >= 18 and user.is_member -
Avoid Duplication (DRY Principle):
- Extract common functionality to avoid repetitive code.
Example:
# Before def calculate_area_of_circle(radius): return 3.14 * radius * radius def calculate_area_of_square(side): return side * side # After def calculate_area(shape, dimension): if shape == 'circle': return 3.14 * dimension * dimension elif shape == 'square': return dimension * dimension -
Testability:
- Write modular and decoupled code to make testing easier.
Example:
# Decoupled code def send_email(smtp_client, recipient, message): smtp_client.connect() smtp_client.send(recipient, message) # Easily testable by mocking smtp_client. -
Small and Focused:
- Follow the Single Responsibility Principle (SRP).
- Each class or function should have only one reason to change.
Naming Conventions
-
Descriptive Names:
- Use clear, descriptive, and unambiguous names.
Example:
# Good naming def calculate_interest(principal, rate, time): pass # Poor naming def calc(p, r, t): pass -
Consistent Patterns:
- Use
camelCasefor variables and functions,PascalCasefor classes.
- Use
-
Avoid Abbreviations:
- Write full names (e.g.,
totalAmountinstead oftotAmt).
- Write full names (e.g.,
Functions
-
Small and Focused:
- Functions should ideally have 5-20 lines of code and do one thing only.
Example:
# Before def process_order(order): # validate order if not order.is_valid(): return "Invalid order" # calculate total total = sum(order.items) # send confirmation send_confirmation(order.user) return total # After (refactored into smaller functions) def validate_order(order): return order.is_valid() def calculate_total(order): return sum(order.items) def process_order(order): if not validate_order(order): return "Invalid order" total = calculate_total(order) send_confirmation(order.user) return total -
Few Arguments:
- Limit arguments to three or fewer. Use parameter objects if needed.
Example:
# Use parameter object class OrderDetails: def __init__(self, items, user, tax_rate): self.items = items self.user = user self.tax_rate = tax_rate def calculate_total(details): subtotal = sum(details.items) tax = subtotal * details.tax_rate return subtotal + tax -
Avoid Side Effects:
- A function should not modify external variables or objects unexpectedly.
-
Use Meaningful Error Handling:
- Use exceptions and meaningful error messages.
Example:
# Good def divide(a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b # Poor def divide(a, b): return a / b # Risk of ZeroDivisionError
Comments
-
Minimize Comments:
- Let the code explain itself as much as possible.
Example:
# Good def get_user_age(user): return user.age # Poor # This function gets the age of the user def get_user_age(user): return user.age -
Use Comments for Clarification:
- Write comments to explain why something is done, not what the code does.
Example:
# Why we use this formula: it follows the standard tax calculation. tax = subtotal * 0.05
Classes
-
Small and Focused Classes:
- Each class should represent a single concept or responsibility.
-
Encapsulation:
- Keep internal details private and provide controlled access through public methods.
Example:
class BankAccount: def __init__(self, balance): self.__balance = balance # private attribute def deposit(self, amount): self.__balance += amount def get_balance(self): return self.__balance -
Dependency Injection:
- Pass dependencies as parameters instead of creating them internally.
Example:
class ReportGenerator: def __init__(self, data_fetcher): self.data_fetcher = data_fetcher # dependency injected def generate(self): data = self.data_fetcher.fetch() return f"Report: {data}"
Error Handling
-
Use Exceptions Instead of Error Codes:
- Avoid returning error codes from functions.
Example:
# Good def open_file(filename): if not os.path.exists(filename): raise FileNotFoundError(f"{filename} does not exist") return open(filename) # Poor def open_file(filename): if not os.path.exists(filename): return -1 # Error code return open(filename) -
Handle Exceptions Gracefully:
- Don’t let exceptions crash your program unnecessarily.
Example:
try: result = divide(10, 0) except ValueError as e: print(f"Error: {e}")
Code Smells and Refactoring
Common Code Smells
- Long Methods: Break them into smaller, focused methods.
- Duplicated Code: Extract reusable methods or functions.
- Large Classes: Split into smaller, cohesive classes.
- Too Many Arguments: Use parameter objects or simplify logic.
- Inconsistent Naming: Use a uniform naming style.
Refactoring Techniques
-
Extract Method: Move chunks of code into new functions.
-
Rename Variable/Method: Use meaningful names to improve clarity.
-
Replace Conditional with Polymorphism:
- Replace long
if-elseorswitchblocks with object-oriented design.
Example:
# Before def get_shipping_cost(country): if country == 'US': return 5 elif country == 'Canada': return 10 else: return 20 # After class ShippingCost: def get_cost(self): pass class USShipping(ShippingCost): def get_cost(self): return 5 class CanadaShipping(ShippingCost): def get_cost(self): return 10 shipping = USShipping() cost = shipping.get_cost() - Replace long