# A Concise, Opinionated Guide to Writing Good Code (with Python examples) This guide summarizes core principles for writing clean, maintainable, and effective code. It's opinionated and rule-based, designed to provide clear direction for junior developers. Adhering to these rules will help you build better software and become a more valuable team member. Python examples are provided for clarity. ## 1. Naming Matters Immensely * **Rule:** Use intention-revealing names. * **Don't:** `d = (datetime.now() - start_date).days` * **Do:** `elapsed_time_in_days = (datetime.now() - start_date).days` * **Rule:** Avoid disinformation. * **Don't:** `account_list = {"id": 1, "name": "Alice"}` (It's a dictionary, not a list) * **Do:** `account_data = {"id": 1, "name": "Alice"}` or `account_dict = ...` * **Rule:** Use pronounceable and searchable names. * **Don't:** `genymdhms = datetime.now().strftime('%Y%m%d%H%M%S')` * **Do:** `generation_timestamp = datetime.now().strftime('%Y%m%d%H%M%S')` * **Rule:** Be consistent. * **Don't:** Using `fetch_user_data`, `getUserInfo`, `retrieve_client_details` in the same project. * **Do:** Consistently use one style, e.g., `get_user_data`, `get_order_info`, `get_product_details`. ## 2. Functions Should Be Small and Focused * **Rule:** Functions must do **one thing**. * **Don't:** ```python def process_user_data(user_id): # Fetches data response = requests.get(f"/api/users/{user_id}") user_data = response.json() # Validates data if not user_data.get("email"): raise ValueError("Email missing") # Saves data db.save(user_data) # Sends notification send_email(user_data["email"], "Welcome!") return user_data ``` * **Do:** Break it down: ```python def fetch_user_data(user_id): response = requests.get(f"/api/users/{user_id}") response.raise_for_status() # Raise HTTP errors return response.json() def validate_user_data(user_data): if not user_data.get("email"): raise ValueError("Email missing") # ... other validations def save_user_data(user_data): db.save(user_data) def send_welcome_email(email_address): send_email(email_address, "Welcome!") def register_user(user_id): user_data = fetch_user_data(user_id) validate_user_data(user_data) save_user_data(user_data) send_welcome_email(user_data["email"]) return user_data ``` * **Rule:** Functions must be **small**. (The "Do" example above also illustrates this). * **Rule:** Minimize function arguments. * **Don't:** `def create_user(name, email, password, dob, address, phone, role, is_active): ...` * **Do:** ```python class UserProfile: def __init__(self, name, email, dob, address, phone): # ... initialization ... def create_user(profile: UserProfile, password: str, role: str, is_active: bool = True): # ... use profile attributes ... ``` Or pass a dictionary: ```python def create_user(user_details: dict): # Access details via user_details['name'], user_details['email'] etc. # Consider using TypedDict for better structure if using Python 3.8+ ... ``` * **Rule:** Avoid side effects where possible. * **Don't (Hidden Side Effect):** ```python user_list = [] def add_user_if_valid(name, email): if "@" in email: user_list.append({"name": name, "email": email}) # Modifies global state return True return False ``` * **Do (Explicit):** ```python def create_user_record(name, email): if "@" not in email: raise ValueError("Invalid email") return {"name": name, "email": email} # Usage try: new_user = create_user_record("Bob", "bob@example.com") user_list.append(new_user) # State change happens outside the function except ValueError as e: print(f"Error: {e}") ``` ## 3. Comments Are for "Why," Not "What" * **Rule:** Comment the "Why," not the "What." * **Don't:** ```python # Check if user is eligible if age >= 18 and country == "US": # This just repeats the code is_eligible = True ``` * **Do:** ```python # User must be a legal adult in the US to qualify for this specific offer. if age >= 18 and country == "US": is_eligible = True ``` * **Rule:** Do **not** leave commented-out code. * **Don't:** ```python def calculate_total(items): total = 0 for item in items: total += item['price'] # tax = total * 0.10 # Old tax calculation # total += tax total *= 1.10 # Apply 10% tax return total ``` * **Do:** Remove the commented lines. Use Git history if you need to see the old calculation. ```python def calculate_total(items): total = sum(item['price'] for item in items) total *= 1.10 # Apply 10% tax return total ``` * **Rule:** Keep comments up-to-date. (Self-explanatory - if the logic changes, update or remove the comment). * **Rule:** Avoid redundant comments. * **Don't:** ```python count = 0 # Initialize count count += 1 # Increment count ``` * **Do:** Just the code is enough. ```python count = 0 count += 1 ``` ## 4. Formatting and Structure Enhance Readability * **Rule:** Use a consistent style guide (e.g., PEP 8 for Python). Use tools like `Black`, `Flake8`, `isort`. * **Don't:** Inconsistent spacing, line lengths, import orders. * **Do:** Code automatically formatted by tools like `Black`. * **Rule:** Top-down narrative. * **Don't:** Define helper functions *before* the main function that uses them, forcing the reader to jump around. * **Do:** ```python def main_process(): data = _fetch_data() result = _process_data(data) _save_result(result) # --- Helper functions defined below --- def _fetch_data(): ... def _process_data(data): ... def _save_result(result): ... ``` *(Note: Leading underscore `_` often indicates internal/helper functions)* * **Rule:** Keep related concepts vertically close. (The example above also shows this). * **Rule:** Use whitespace. * **Don't:** ```python def process(a,b,c): x=a+b y=x*c if y>10: print("Large") else: print("Small") z=y-a return z ``` * **Do:** ```python def process(a, b, c): intermediate_value = a + b final_value = intermediate_value * c if final_value > 10: print("Large") else: print("Small") adjusted_value = final_value - a return adjusted_value ``` ## 5. Keep It Simple (KISS & YAGNI) * **Rule:** KISS (Keep It Simple, Stupid). * **Don't:** Using complex metaprogramming or obscure language features when a simple loop or conditional would suffice. * **Do:** Prefer straightforward, readable solutions. * **Rule:** YAGNI (You Ain't Gonna Need It). * **Don't:** Adding configuration options, database fields, or API endpoints for features that *might* be needed in the future but aren't required now. * **Do:** Implement only what's necessary for the current requirements. * **Rule:** Avoid premature optimization. * **Don't:** Spending hours micro-optimizing a function with string concatenations before profiling to see if it's even a bottleneck. * **Do:** Write clean code first. If performance is an issue (measure it!), profile and optimize the specific hotspots. Often, a better algorithm beats micro-optimization. ## 6. Don't Repeat Yourself (DRY) * **Rule:** Avoid duplication. * **Don't:** ```python def process_file_a(path): # 10 lines of validation logic if not valid: return None # Process file A specific logic ... def process_file_b(path): # Same 10 lines of validation logic copied here if not valid: return None # Process file B specific logic ... ``` * **Do:** ```python def _validate_input(path): # 10 lines of validation logic return is_valid def process_file_a(path): if not _validate_input(path): return None # Process file A specific logic ... def process_file_b(path): if not _validate_input(path): return None # Process file B specific logic ... ``` ## 7. Handle Errors Gracefully * **Rule:** Use exceptions over error codes. * **Don't:** ```python def divide(a, b): if b == 0: return -1 # Error code return a / b result = divide(10, 0) if result == -1: print("Error: Division by zero") ``` * **Do:** ```python def divide(a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b try: result = divide(10, 0) except ValueError as e: print(f"Error: {e}") ``` * **Rule:** Provide context with errors. * **Don't:** `raise Exception("Error!")` * **Do:** `raise ValueError(f"Invalid user ID format: '{user_id_str}'")` ## 8. Test Your Code * **Rule:** Write unit tests (using frameworks like `pytest` or `unittest`). * **Don't:** Skipping tests because the code "looks simple." * **Do:** ```python # Example using pytest from my_module import add def test_add_positive_numbers(): assert add(2, 3) == 5 def test_add_negative_numbers(): assert add(-1, -1) == -2 def test_add_mixed_numbers(): assert add(5, -3) == 2 ``` * **Rule:** Test behavior, not implementation. * **Don't:** Writing a test that checks if a specific private helper method (`_helper`) was called. * **Do:** Writing a test that checks if the public method produces the correct output or state change, regardless of which internal helpers were used. * **Rule:** Keep tests clean, readable, and fast. (Apply the same principles from this guide to your test code). ## 9. Practice Continuous Refactoring * **Rule:** Follow the Boy Scout Rule. * **Don't:** Seeing a poorly named variable or a slightly complex block of code and leaving it because "it works." * **Do:** Taking a few moments to rename the variable or extract a small function to improve clarity before committing your primary change. * **Rule:** Refactoring is part of development. (This is a mindset, less about specific code examples). ## 10. Optimize for Readability * **Rule:** Code is read more than written. * **Don't:** Using overly clever one-liners or complex list comprehensions that are hard to decipher. ```python # Clever but potentially hard to read result = [x**2 for x in range(10) if x % 2 == 0 and x > 3] ``` * **Do:** Prioritize clarity, even if it means slightly more verbose code. ```python result = [] for x in range(10): is_even = x % 2 == 0 is_greater_than_3 = x > 3 if is_even and is_greater_than_3: result.append(x**2) # Or a more readable comprehension if appropriate result = [x**2 for x in range(4, 10, 2)] # Clearer range ``` ## 11. Python-Specific Best Practices * **Rule:** Embrace Pythonic idioms. * **Use List Comprehensions (when clear):** Prefer `squares = [x*x for x in numbers]` over manual `for` loop appends for simple transformations. * **Use Context Managers (`with` statement):** Ensure resources like files or network connections are properly closed. ```python # Don't f = open("myfile.txt", "w") try: f.write("Hello") finally: f.close() # Do with open("myfile.txt", "w") as f: f.write("Hello") # File is automatically closed here, even if errors occur ``` * **Iterate Directly:** Iterate over sequences directly instead of using index manipulation. ```python # Don't for i in range(len(my_list)): print(my_list[i]) # Do for item in my_list: print(item) # Do (if index is needed) for i, item in enumerate(my_list): print(f"Index {i}: {item}") ``` * **Rule:** Use Type Hinting (Python 3.5+). Improves readability, enables static analysis tools (`mypy`), and clarifies intent. ```python # Don't def greet(name): print("Hello " + name) # Do def greet(name: str) -> None: print("Hello " + name) def add(a: int, b: int) -> int: return a + b ``` * **Rule:** Use Virtual Environments (`venv`). Isolate project dependencies to avoid conflicts between projects. Always create and activate a virtual environment before installing packages (`pip install ...`). * **Rule:** Prefer f-strings (Python 3.6+) for string formatting. They are generally more readable and often faster than `.format()` or `%` formatting. ```python name = "Alice" age = 30 # Don't (older styles) print("Name: %s, Age: %d" % (name, age)) print("Name: {}, Age: {}".format(name, age)) # Do print(f"Name: {name}, Age: {age}") ``` * **Rule:** Understand Mutable Default Arguments. Be wary of using mutable types (like lists or dicts) as default function arguments, as they are shared across calls. ```python # Don't (potential bug) def add_item(item, my_list=[]): my_list.append(item) return my_list list1 = add_item(1) # [1] list2 = add_item(2) # [1, 2] - Unexpected! # Do def add_item(item, my_list=None): if my_list is None: my_list = [] my_list.append(item) return my_list list1 = add_item(1) # [1] list2 = add_item(2) # [2] - Correct