Clean Code Principles

Learning Objectives

By the end of this reading, you will be able to:

  • Write meaningful and descriptive names for variables, functions, and classes
  • Create small, focused functions that do one thing well
  • Write effective comments and documentation
  • Apply SOLID principles to create maintainable code
  • Identify and eliminate code smells
  • Refactor code to improve quality without changing behavior
  • Understand the boy scout rule and leave code better than you found it

Introduction

Clean code is code that is easy to read, understand, and modify. It's not just about making code work - it's about making code maintainable, testable, and a pleasure to work with.

Why Clean Code Matters

  1. Readability: Code is read far more often than it's written
  2. Maintainability: Clean code is easier to modify and extend
  3. Fewer Bugs: Simple, clear code has fewer places for bugs to hide
  4. Team Productivity: Developers spend less time understanding code
  5. Professional Pride: Craft matters

The Boy Scout Rule

"""
Leave the code better than you found it.

Every time you touch code:
- Fix a small issue
- Improve a variable name
- Extract a function
- Add a comment
- Remove dead code

Small improvements compound over time.
"""

Meaningful Names

Names should reveal intent and make code self-documenting.

Variables

# BAD: Unclear, cryptic names
d = 86400  # What is d? What is 86400?
list1 = []
tmp = calculate()
x = get_user()

# GOOD: Clear, descriptive names
SECONDS_PER_DAY = 86400
active_users = []
monthly_revenue = calculate_monthly_revenue()
current_user = get_current_user()

# BAD: Abbreviated names
usr_cnt = 0
msg_qry = "SELECT * FROM messages"
addr = "123 Main St"

# GOOD: Full words
user_count = 0
message_query = "SELECT * FROM messages"
address = "123 Main St"

# BAD: Meaningless distinctions
user_info
user_data
user_object

# GOOD: Meaningful distinctions
user_profile
user_credentials
user_preferences

Functions

# BAD: Vague function names
def process():  # Process what?
    pass

def handle_data():  # Handle how?
    pass

def do_stuff():  # What stuff?
    pass

# GOOD: Specific, action-oriented names
def calculate_total_price():
    """Calculate total price including tax and shipping."""
    pass

def validate_email_format():
    """Validate that email address follows RFC 5322 format."""
    pass

def send_welcome_email(user):
    """Send welcome email to newly registered user."""
    pass

# BAD: Non-boolean names for boolean functions
def check(value):
    return value > 0

# GOOD: Boolean functions should ask yes/no questions
def is_positive(value):
    """Check if value is positive."""
    return value > 0

def has_permission(user, resource):
    """Check if user has permission to access resource."""
    return user.role in resource.allowed_roles

def can_withdraw(account, amount):
    """Check if account has sufficient funds for withdrawal."""
    return account.balance >= amount

Classes

# BAD: Generic or vague class names
class Manager:  # Manages what?
    pass

class Processor:  # Processes what?
    pass

class Data:  # What kind of data?
    pass

# GOOD: Specific, noun-based names
class UserAccount:
    """Represents a user account with authentication and profile data."""
    pass

class PaymentProcessor:
    """Processes payment transactions through payment gateway."""
    pass

class CustomerOrder:
    """Represents a customer's order with items and shipping info."""
    pass

# Follow naming conventions
class HTTPConnection:  # Acronyms in caps (Python convention)
    pass

class XMLParser:
    pass

class EmailValidator:
    pass

Constants

# BAD: Magic numbers without context
if age > 18:
    pass

if status == 2:
    pass

# GOOD: Named constants
LEGAL_ADULT_AGE = 18
STATUS_APPROVED = 2

if age > LEGAL_ADULT_AGE:
    grant_access()

if status == STATUS_APPROVED:
    process_order()

# Group related constants
class HttpStatus:
    """HTTP status codes."""
    OK = 200
    CREATED = 201
    BAD_REQUEST = 400
    UNAUTHORIZED = 401
    NOT_FOUND = 404
    SERVER_ERROR = 500

class OrderStatus:
    """Order status values."""
    PENDING = 'pending'
    PROCESSING = 'processing'
    SHIPPED = 'shipped'
    DELIVERED = 'delivered'
    CANCELLED = 'cancelled'

Naming Conventions

"""
Python Naming Conventions (PEP 8):

Variables and Functions:
- snake_case: user_name, calculate_total()

Classes:
- PascalCase: UserAccount, PaymentProcessor

Constants:
- UPPER_SNAKE_CASE: MAX_CONNECTIONS, DEFAULT_TIMEOUT

Private (internal use):
- _leading_underscore: _internal_method, _helper_function

Really private (name mangling):
- __double_leading: __private_attribute

Special methods:
- __double_surrounding__: __init__, __str__
"""

class Example:
    # Class variable (shared by all instances)
    class_variable = "shared"

    # Constant
    MAX_SIZE = 100

    def __init__(self):
        # Public instance variable
        self.public_attribute = "public"

        # Protected (convention - not enforced)
        self._protected_attribute = "protected"

        # Private (name mangling)
        self.__private_attribute = "private"

    def public_method(self):
        """Public method accessible from outside."""
        pass

    def _protected_method(self):
        """Protected method (convention only)."""
        pass

    def __private_method(self):
        """Private method (name mangled)."""
        pass

Functions

Functions should be small, do one thing, and do it well.

Function Size

# BAD: Long function doing too much
def process_order(order):
    """Process an order."""
    # Validate order
    if not order.items:
        raise ValueError("Order has no items")
    for item in order.items:
        if item.quantity <= 0:
            raise ValueError("Invalid quantity")

    # Calculate total
    subtotal = 0
    for item in order.items:
        subtotal += item.price * item.quantity
    tax = subtotal * 0.08
    shipping = 10 if subtotal < 50 else 0
    total = subtotal + tax + shipping

    # Process payment
    payment_data = {
        'amount': total,
        'currency': 'USD',
        'card': order.payment_method
    }
    response = requests.post('https://api.payment.com', json=payment_data)
    if response.status_code != 200:
        raise Exception("Payment failed")

    # Update inventory
    for item in order.items:
        product = Product.get(item.product_id)
        product.stock -= item.quantity
        product.save()

    # Send confirmation
    send_email(
        to=order.customer.email,
        subject="Order Confirmation",
        body=f"Your order #{order.id} has been confirmed"
    )

    return order

# GOOD: Small functions, each doing one thing
def process_order(order):
    """
    Process an order through complete workflow.

    Args:
        order: Order object to process

    Returns:
        Processed order with confirmation

    Raises:
        ValueError: If order is invalid
        PaymentError: If payment fails
    """
    validate_order(order)
    total = calculate_order_total(order)
    process_payment(order, total)
    update_inventory(order)
    send_order_confirmation(order)
    return order

def validate_order(order):
    """Validate order has valid items and quantities."""
    if not order.items:
        raise ValueError("Order has no items")

    for item in order.items:
        if item.quantity <= 0:
            raise ValueError(f"Invalid quantity for item {item.product_id}")

def calculate_order_total(order):
    """Calculate order total including tax and shipping."""
    subtotal = sum(item.price * item.quantity for item in order.items)
    tax = calculate_tax(subtotal)
    shipping = calculate_shipping(subtotal)
    return subtotal + tax + shipping

def calculate_tax(amount):
    """Calculate sales tax."""
    TAX_RATE = 0.08
    return amount * TAX_RATE

def calculate_shipping(subtotal):
    """Calculate shipping cost based on subtotal."""
    FREE_SHIPPING_THRESHOLD = 50
    STANDARD_SHIPPING_COST = 10
    return 0 if subtotal >= FREE_SHIPPING_THRESHOLD else STANDARD_SHIPPING_COST

def process_payment(order, amount):
    """Process payment for order."""
    payment_gateway = PaymentGateway()
    try:
        payment_gateway.charge(
            amount=amount,
            currency='USD',
            payment_method=order.payment_method
        )
    except PaymentGatewayError as e:
        raise PaymentError(f"Payment failed: {e}")

def update_inventory(order):
    """Update inventory for ordered items."""
    for item in order.items:
        product = Product.get(item.product_id)
        product.decrease_stock(item.quantity)

def send_order_confirmation(order):
    """Send order confirmation email to customer."""
    EmailService.send(
        to=order.customer.email,
        template='order_confirmation',
        context={'order': order}
    )

Single Responsibility Principle

# BAD: Function doing multiple things
def save_user(user_data):
    """Save user (but also validates, hashes password, sends email)."""
    # Validation
    if not user_data.get('email'):
        raise ValueError("Email required")

    # Password hashing
    import hashlib
    password = user_data['password']
    hashed = hashlib.sha256(password.encode()).hexdigest()
    user_data['password'] = hashed

    # Database save
    db.users.insert(user_data)

    # Email notification
    send_email(user_data['email'], "Welcome!", "Thanks for signing up")

# GOOD: Each function has single responsibility
def save_user(user_data):
    """
    Save user to database.

    Args:
        user_data: Dictionary containing user information

    Returns:
        Created user ID
    """
    validate_user_data(user_data)
    user_data['password'] = hash_password(user_data['password'])
    user_id = insert_user_to_database(user_data)
    send_welcome_email(user_data['email'])
    return user_id

def validate_user_data(user_data):
    """Validate user data is complete and valid."""
    required_fields = ['email', 'password', 'username']
    for field in required_fields:
        if not user_data.get(field):
            raise ValueError(f"{field} is required")

def hash_password(password):
    """Hash password using SHA-256."""
    import hashlib
    return hashlib.sha256(password.encode()).hexdigest()

def insert_user_to_database(user_data):
    """Insert user into database."""
    return db.users.insert(user_data)

def send_welcome_email(email):
    """Send welcome email to new user."""
    send_email(email, "Welcome!", "Thanks for signing up")

Function Arguments

# BAD: Too many arguments
def create_user(name, email, password, age, address, city, state, zip_code, phone):
    pass

# GOOD: Use object/dictionary for related parameters
def create_user(user_info):
    """
    Create new user.

    Args:
        user_info: Dictionary containing user information
    """
    pass

# Or use dataclass/named tuple
from dataclasses import dataclass

@dataclass
class UserInfo:
    name: str
    email: str
    password: str
    age: int
    address: str
    city: str
    state: str
    zip_code: str
    phone: str

def create_user(user_info: UserInfo):
    """Create new user from UserInfo object."""
    pass

# BAD: Boolean flag arguments
def save_document(doc, with_backup):
    if with_backup:
        create_backup(doc)
    save(doc)

# GOOD: Separate functions for different behaviors
def save_document(doc):
    """Save document."""
    save(doc)

def save_document_with_backup(doc):
    """Save document and create backup."""
    create_backup(doc)
    save(doc)

# BAD: Output arguments
def append_footer(report, footer):
    """Append footer to report (modifies report)."""
    report.pages.append(footer)

# GOOD: Return new value
def append_footer(report, footer):
    """Return report with footer appended."""
    return Report(pages=report.pages + [footer])

Command Query Separation

# BAD: Function does something AND returns status
def set_and_check_attribute(obj, name, value):
    """Set attribute and return True if successful."""
    if is_valid(value):
        setattr(obj, name, value)
        return True
    return False

# Usage is confusing
if set_and_check_attribute(user, 'age', 25):  # Unclear - checking or setting?
    print("Success")

# GOOD: Separate commands and queries
def set_attribute(obj, name, value):
    """Set attribute value (command - no return)."""
    if not is_valid(value):
        raise ValueError(f"Invalid value: {value}")
    setattr(obj, name, value)

def has_attribute(obj, name):
    """Check if object has attribute (query - no modification)."""
    return hasattr(obj, name)

# Usage is clear
set_attribute(user, 'age', 25)  # Clearly setting
if has_attribute(user, 'age'):  # Clearly checking
    print("Has age attribute")

Comments and Documentation

Comments should explain WHY, not WHAT. Code should be self-explanatory.

Good vs Bad Comments

# BAD: Comments stating the obvious
# Increment i
i += 1

# Check if user is adult
if age >= 18:
    pass

# Loop through users
for user in users:
    pass

# GOOD: Self-documenting code (no comment needed)
counter += 1

if is_adult(age):
    grant_access()

for user in active_users:
    send_notification(user)

# BAD: Commented-out code (use version control instead)
# def old_implementation():
#     # This was the old way
#     pass

# GOOD: Comment explaining WHY
# Use binary search instead of linear for performance
# with large datasets (tested with 1M+ records)
result = binary_search(sorted_list, target)

# Workaround for bug in third-party library v2.3
# See: https://github.com/library/issues/123
# TODO: Remove when upgrading to v2.4
workaround_for_bug_123()

# BAD: Redundant comment
# This function gets the user name
def get_user_name(user):
    return user.name

# GOOD: Docstring explaining behavior and edge cases
def get_user_display_name(user):
    """
    Get user's display name.

    Returns username if full name not available.
    Returns 'Anonymous' for deleted users.

    Args:
        user: User object

    Returns:
        str: Display name for user
    """
    if user.is_deleted:
        return 'Anonymous'
    return user.full_name or user.username

Docstrings

def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
    """
    Calculate compound interest.

    Formula: A = P(1 + r/n)^(nt)
    where:
        A = final amount
        P = principal
        r = annual interest rate (as decimal)
        n = number of times interest compounds per year
        t = time in years

    Args:
        principal (float): Initial investment amount
        rate (float): Annual interest rate as decimal (e.g., 0.05 for 5%)
        time (float): Time period in years
        compounds_per_year (int, optional): Compounding frequency. Defaults to 1.

    Returns:
        float: Final amount after interest

    Raises:
        ValueError: If principal, rate, or time is negative

    Examples:
        >>> calculate_compound_interest(1000, 0.05, 10)
        1628.89

        >>> calculate_compound_interest(1000, 0.05, 10, 12)
        1647.01
    """
    if principal < 0 or rate < 0 or time < 0:
        raise ValueError("Principal, rate, and time must be non-negative")

    amount = principal * (1 + rate / compounds_per_year) ** (compounds_per_year * time)
    return round(amount, 2)

class BankAccount:
    """
    Bank account with deposits and withdrawals.

    Attributes:
        account_number (str): Unique account identifier
        balance (float): Current account balance
        transactions (list): History of all transactions

    Example:
        >>> account = BankAccount("12345", initial_balance=1000)
        >>> account.deposit(500)
        >>> account.balance
        1500.0
    """

    def __init__(self, account_number, initial_balance=0):
        """
        Initialize bank account.

        Args:
            account_number (str): Unique account identifier
            initial_balance (float, optional): Starting balance. Defaults to 0.

        Raises:
            ValueError: If initial balance is negative
        """
        if initial_balance < 0:
            raise ValueError("Initial balance cannot be negative")

        self.account_number = account_number
        self.balance = initial_balance
        self.transactions = []

TODO Comments

"""
TODO comments for future work:

Format: TODO(author): Description
"""

# TODO(john): Optimize this algorithm - currently O(n²)
def slow_algorithm(data):
    pass

# TODO(team): Add input validation when API spec is finalized
def process_data(data):
    pass

# FIXME: Race condition when multiple threads access this
shared_resource = {}

# HACK: Temporary workaround for production issue
# Remove after proper fix in sprint 23
def workaround():
    pass

# NOTE: This relies on behavior in version 2.x of library
# May break in version 3.x
def library_dependent_code():
    pass

SOLID Principles

SOLID is an acronym for five design principles that make software more maintainable and flexible.

S - Single Responsibility Principle

A class should have only one reason to change.

# BAD: Class with multiple responsibilities
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def save_to_database(self):
        """Save user to database."""
        # Database logic
        pass

    def send_email(self, subject, body):
        """Send email to user."""
        # Email logic
        pass

    def generate_report(self):
        """Generate user activity report."""
        # Reporting logic
        pass

# GOOD: Separate classes for separate responsibilities
class User:
    """User data model - only responsible for user data."""

    def __init__(self, name, email):
        self.name = name
        self.email = email

class UserRepository:
    """Responsible for user persistence."""

    def save(self, user):
        """Save user to database."""
        pass

    def find_by_email(self, email):
        """Find user by email."""
        pass

class EmailService:
    """Responsible for sending emails."""

    def send_email(self, to, subject, body):
        """Send email."""
        pass

class UserReportGenerator:
    """Responsible for generating user reports."""

    def generate_activity_report(self, user):
        """Generate user activity report."""
        pass

O - Open/Closed Principle

Classes should be open for extension but closed for modification.

# BAD: Must modify class to add new shape
class AreaCalculator:
    def calculate_area(self, shapes):
        total = 0
        for shape in shapes:
            if shape.type == 'circle':
                total += 3.14 * shape.radius ** 2
            elif shape.type == 'rectangle':
                total += shape.width * shape.height
            # Need to modify this method for new shapes!
        return total

# GOOD: Can extend with new shapes without modifying existing code
from abc import ABC, abstractmethod

class Shape(ABC):
    """Abstract base class for shapes."""

    @abstractmethod
    def area(self):
        """Calculate area of shape."""
        pass

class Circle(Shape):
    """Circle shape."""

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

class Rectangle(Shape):
    """Rectangle shape."""

    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Triangle(Shape):
    """Triangle shape - NEW shape, no modifications needed."""

    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

class AreaCalculator:
    """Calculate total area of shapes."""

    def calculate_area(self, shapes):
        """Works with any shape that implements area()."""
        return sum(shape.area() for shape in shapes)

L - Liskov Substitution Principle

Subtypes must be substitutable for their base types.

# BAD: Violates LSP - Square changes Rectangle behavior
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Rectangle):
    """Square violates LSP."""

    def set_width(self, width):
        # Violates LSP: changes both width and height
        self.width = width
        self.height = width

    def set_height(self, height):
        # Violates LSP: changes both width and height
        self.width = height
        self.height = height

# This breaks
def test_rectangle(rect):
    rect.set_width(5)
    rect.set_height(4)
    assert rect.area() == 20  # Fails for Square!

# GOOD: Proper inheritance respecting LSP
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

I - Interface Segregation Principle

Clients should not depend on interfaces they don't use.

# BAD: Fat interface forces classes to implement unused methods
class Worker(ABC):
    @abstractmethod
    def work(self):
        pass

    @abstractmethod
    def eat(self):
        pass

class Human(Worker):
    def work(self):
        print("Human working")

    def eat(self):
        print("Human eating")

class Robot(Worker):
    def work(self):
        print("Robot working")

    def eat(self):
        # Robot doesn't eat! Forced to implement unused method
        raise NotImplementedError("Robots don't eat")

# GOOD: Segregated interfaces
class Workable(ABC):
    @abstractmethod
    def work(self):
        pass

class Eatable(ABC):
    @abstractmethod
    def eat(self):
        pass

class Human(Workable, Eatable):
    def work(self):
        print("Human working")

    def eat(self):
        print("Human eating")

class Robot(Workable):
    """Robot only implements interfaces it needs."""
    def work(self):
        print("Robot working")

D - Dependency Inversion Principle

Depend on abstractions, not concretions.

# BAD: High-level module depends on low-level module
class MySQLDatabase:
    def save(self, data):
        print(f"Saving to MySQL: {data}")

class UserService:
    def __init__(self):
        # Tightly coupled to MySQL
        self.database = MySQLDatabase()

    def create_user(self, user):
        self.database.save(user)

# GOOD: Both depend on abstraction
class Database(ABC):
    """Abstract database interface."""

    @abstractmethod
    def save(self, data):
        pass

class MySQLDatabase(Database):
    def save(self, data):
        print(f"Saving to MySQL: {data}")

class PostgreSQLDatabase(Database):
    def save(self, data):
        print(f"Saving to PostgreSQL: {data}")

class UserService:
    """Depends on abstraction, not concrete implementation."""

    def __init__(self, database: Database):
        self.database = database

    def create_user(self, user):
        self.database.save(user)

# Can use any database implementation
mysql_service = UserService(MySQLDatabase())
postgres_service = UserService(PostgreSQLDatabase())

Code Smells

Code smells are indicators of potential problems in code.

Common Code Smells

# SMELL: Duplicated Code
def calculate_employee_pay(employee):
    base_pay = employee.salary / 12
    bonus = employee.salary * 0.1
    tax = (base_pay + bonus) * 0.2
    return base_pay + bonus - tax

def calculate_contractor_pay(contractor):
    base_pay = contractor.rate * contractor.hours
    bonus = contractor.rate * contractor.hours * 0.05
    tax = (base_pay + bonus) * 0.2
    return base_pay + bonus - tax

# FIX: Extract common logic
def calculate_pay(base_pay, bonus_rate):
    bonus = base_pay * bonus_rate
    tax = (base_pay + bonus) * 0.2
    return base_pay + bonus - tax

def calculate_employee_pay(employee):
    monthly_pay = employee.salary / 12
    return calculate_pay(monthly_pay, 0.1)

def calculate_contractor_pay(contractor):
    base_pay = contractor.rate * contractor.hours
    return calculate_pay(base_pay, 0.05)

# SMELL: Long Parameter List
def create_address(street, city, state, zip_code, country, apartment, floor):
    pass

# FIX: Use object
@dataclass
class Address:
    street: str
    city: str
    state: str
    zip_code: str
    country: str
    apartment: str = ""
    floor: int = 0

def create_address(address: Address):
    pass

# SMELL: Large Class (God Object)
class User:
    # Hundreds of methods and attributes
    pass

# FIX: Split into smaller, focused classes
class User:
    """User identity and authentication."""
    pass

class UserProfile:
    """User profile information."""
    pass

class UserPreferences:
    """User preferences and settings."""
    pass

# SMELL: Long Method
def process():
    # 100+ lines of code
    pass

# FIX: Extract smaller methods
def process():
    validate_input()
    transform_data()
    save_results()
    send_notifications()

# SMELL: Feature Envy (method uses another class more than its own)
class Order:
    def get_price(self):
        product = self.product
        base_price = product.price
        discount = product.discount
        tax = product.tax_rate
        return base_price * (1 - discount) * (1 + tax)

# FIX: Move method to appropriate class
class Product:
    def calculate_price(self):
        base_price = self.price
        discount = self.discount
        tax = self.tax_rate
        return base_price * (1 - discount) * (1 + tax)

class Order:
    def get_price(self):
        return self.product.calculate_price()

Refactoring

Refactoring is improving code structure without changing its external behavior.

Extract Method

# BEFORE
def print_owing(invoice):
    print_banner()
    print(f"Name: {invoice.customer}")
    print(f"Amount: {invoice.amount}")

# AFTER
def print_owing(invoice):
    print_banner()
    print_details(invoice)

def print_details(invoice):
    print(f"Name: {invoice.customer}")
    print(f"Amount: {invoice.amount}")

Rename Variable

# BEFORE
a = (h + w) / 2

# AFTER
average = (height + width) / 2

Extract Variable

# BEFORE
if (platform.upper() == "MAC" and browser.upper() == "SAFARI" and
    version >= 4 and resolution > 1024):
    # do something

# AFTER
is_supported_browser = (
    platform.upper() == "MAC" and
    browser.upper() == "SAFARI" and
    version >= 4 and
    resolution > 1024
)
if is_supported_browser:
    # do something

Replace Magic Number with Constant

# BEFORE
def calculate_energy(mass):
    return mass * 299792458 ** 2

# AFTER
SPEED_OF_LIGHT = 299792458

def calculate_energy(mass):
    return mass * SPEED_OF_LIGHT ** 2

Exercises

Basic Exercises

  1. Naming Practice

    • Find code with poor names
    • Rename variables, functions, and classes
    • Ensure names reveal intent
    • Remove abbreviations and generic names
  2. Function Decomposition

    • Take a long function (50+ lines)
    • Break it into smaller functions (each < 10 lines)
    • Each function should do one thing
    • Give functions descriptive names
  3. Add Documentation

    • Take undocumented code
    • Add docstrings to all functions and classes
    • Follow Google or NumPy docstring style
    • Include examples where helpful

Intermediate Exercises

  1. SOLID Principles

    • Identify violations of SOLID in existing code
    • Refactor to follow each principle
    • Create before/after examples
    • Explain improvements
  2. Remove Code Smells

    • Identify code smells in a codebase
    • Categorize each smell
    • Refactor to eliminate smells
    • Measure improvement (lines of code, complexity)
  3. Refactoring Practice

    • Take legacy code
    • Apply refactoring techniques
    • Keep tests passing throughout
    • Document each refactoring step

Advanced Exercises

  1. Complete Cleanup

    • Take a messy codebase
    • Apply all clean code principles
    • Achieve 80%+ test coverage
    • Document the process
  2. Design Review

    • Review a real project
    • Create list of improvements
    • Prioritize by impact
    • Implement top 3 improvements
  3. Create Style Guide

    • Create a team style guide
    • Include naming conventions
    • Define code organization
    • Add examples and anti-examples
  4. Legacy Code Rescue

    • Take very messy legacy code
    • Add tests first (characterization tests)
    • Refactor incrementally
    • Modernize the codebase

Summary

Clean code principles lead to maintainable software:

  1. Meaningful Names: Reveal intent, be specific, avoid abbreviations
  2. Small Functions: Do one thing, have clear names, few parameters
  3. Comments: Explain WHY, not WHAT; prefer self-documenting code
  4. SOLID Principles:
    • Single Responsibility
    • Open/Closed
    • Liskov Substitution
    • Interface Segregation
    • Dependency Inversion
  5. Code Smells: Recognize and eliminate common problems
  6. Refactoring: Continuously improve code structure

Remember:

  • Code is read more than written
  • Clean code is professional code
  • Small improvements compound
  • Leave code better than you found it
  • Readability matters

Congratulations!

You've completed Module 10: Software Engineering! You've learned:

  • Version control with Git
  • Software development lifecycles (Waterfall, Agile, Scrum, Kanban)
  • Design patterns (Creational, Structural, Behavioral)
  • Testing strategies (Unit, Integration, TDD, Mocking)
  • Clean code principles (Naming, Functions, SOLID, Refactoring)

These software engineering practices will help you build better software throughout your career. Keep practicing, keep learning, and always strive to write clean, maintainable code!