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
- Readability: Code is read far more often than it's written
- Maintainability: Clean code is easier to modify and extend
- Fewer Bugs: Simple, clear code has fewer places for bugs to hide
- Team Productivity: Developers spend less time understanding code
- 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
Naming Practice
- Find code with poor names
- Rename variables, functions, and classes
- Ensure names reveal intent
- Remove abbreviations and generic names
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
Add Documentation
- Take undocumented code
- Add docstrings to all functions and classes
- Follow Google or NumPy docstring style
- Include examples where helpful
Intermediate Exercises
SOLID Principles
- Identify violations of SOLID in existing code
- Refactor to follow each principle
- Create before/after examples
- Explain improvements
Remove Code Smells
- Identify code smells in a codebase
- Categorize each smell
- Refactor to eliminate smells
- Measure improvement (lines of code, complexity)
Refactoring Practice
- Take legacy code
- Apply refactoring techniques
- Keep tests passing throughout
- Document each refactoring step
Advanced Exercises
Complete Cleanup
- Take a messy codebase
- Apply all clean code principles
- Achieve 80%+ test coverage
- Document the process
Design Review
- Review a real project
- Create list of improvements
- Prioritize by impact
- Implement top 3 improvements
Create Style Guide
- Create a team style guide
- Include naming conventions
- Define code organization
- Add examples and anti-examples
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:
- Meaningful Names: Reveal intent, be specific, avoid abbreviations
- Small Functions: Do one thing, have clear names, few parameters
- Comments: Explain WHY, not WHAT; prefer self-documenting code
- SOLID Principles:
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
- Code Smells: Recognize and eliminate common problems
- 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!