Skip to main content

Command Palette

Search for a command to run...

Building Better Code as a Software Engineer Part 1

Understanding Code Quality, Abstraction, Collaboration, and Error Handling for Better Software Engineering

Updated
5 min read
Building Better Code as a Software Engineer Part 1
A

As a continuous learner, I’m always exploring new technologies and best practices to enhance my skills in software development. I enjoy tackling complex coding challenges, whether it's optimizing performance, implementing new features, or debugging intricate issues.

Writing code isn’t just about making it work; it’s about making it understandable, maintainable, and robust. A lot of this comes down to theory, which shapes the day-to-day practice of coding and collaboration. Here, we’ll look at Good Code, Bad Code's insights on code quality, layers of abstraction, thinking about other engineers, and error handling—and how they help you grow as a software engineer.


1. Code Quality: Building Blocks of Reliable Code

Code quality is essential for ensuring your work remains readable, maintainable, and efficient. This concept is fundamental: high-quality code is less likely to be buggy, easier to understand, and can be reused more effectively.

Example of Good vs. Bad Code Quality: Imagine a function designed to calculate the price after a discount.

Bad Code Example:

def calc(p, d):
    return p - (p * d/100)

This function works but lacks context. What does p or d mean here? And what if we need to add taxes or other adjustments later?

Good Code Example:

def calculate_discounted_price(original_price, discount_percentage):
    discount_amount = original_price * (discount_percentage / 100)
    return original_price - discount_amount

Now, this function is clear, descriptive, and makes sense at a glance. Quality is not about doing the minimum to make the code work—it’s about crafting code that will work for you and others over the long term. This makes future refactoring easier and bugs less likely, which saves significant time.

Tips for Ensuring Code Quality:

  • Name Variables and Functions Meaningfully: Don’t abbreviate excessively or use ambiguous terms.

  • Modularize: Write smaller functions that each serve a specific purpose.

  • Add Comments for Complex Logic: Avoid redundant comments but add context where it's needed.


2. Layers of Abstraction: Structuring for Scalability and Readability

Abstraction helps you manage complexity by organizing code in layers. In Good Code, Bad Code, the author emphasizes that organizing code in layers of abstraction allows you to focus on what matters without getting bogged down in unnecessary details.

Example: Suppose you’re building a basic web scraper to fetch data from a website.

Bad Code Example:

def fetch_data():
    import requests
    url = "https://example.com"
    response = requests.get(url)
    if response.status_code == 200:
        data = response.text
        # process HTML directly here
    # additional processing

This code not only mixes different concerns but also lacks a way to adapt if you wanted to change the data processing method or the data source itself.

Good Code with Layered Abstractions:

import requests

class WebScraper:
    def __init__(self, url):
        self.url = url

    def fetch_data(self):
        response = requests.get(self.url)
        if response.status_code == 200:
            return response.text
        else:
            return None

    def process_data(self, html_content):
        # Process HTML data
        return "processed data"

Here, fetch_data is solely responsible for making the request, and process_data for handling the HTML content. By abstracting these actions, each function becomes more maintainable and reusable.

Benefits of Layered Abstraction:

  • Isolation: Each layer has a clear purpose.

  • Reusability: Individual components can be reused across projects or modules.

  • Testability: It’s easier to write tests for isolated functionality than for monolithic code.


3. Thinking About Other Engineers: Code as a Collaborative Effort

Writing code with other engineers in mind is an art. Since other engineers will likely need to read, use, or modify your code, writing it in a way that communicates intent, clarity, and functionality can save time and reduce errors.

Empathy in Code

Empathy for future readers is key. Imagine you’ve written a complex sorting algorithm for specific data but didn’t document it. Six months later, another engineer might need to adapt or debug it. Without documentation or comments, it could take them hours to understand its purpose.

Bad Code Example without Documentation:

def sort_special(data):
    return sorted(data, key=lambda x: (x.type, -x.value))

This code sorts data but offers no explanation of why or how the logic was created. It's also not clear what type or value signify without further context.

Good Code with Documentation:

def sort_special(data):
    """
    Sorts data based on type and value in descending order.

    Parameters:
    data (list): List of objects with 'type' and 'value' attributes.

    Returns:
    list: Sorted list based on custom sorting rule.
    """
    return sorted(data, key=lambda x: (x.type, -x.value))

With a docstring, this function now tells us what it does, the expected input, and output. Small details like these make a massive difference to future engineers (including yourself) who may need to work on the code later.

Practical Tips for Collaborative Code:

  • Use Docstrings: Describe the purpose, parameters, and return types in functions.

  • Follow Style Guides: A consistent style improves readability and reduces friction.

  • Communicate Intent: Use naming conventions that reveal the purpose of variables, functions, and classes.


4. Errors: Preparing for the Unexpected

Errors are unavoidable, but how we handle them can determine whether they’re minor annoyances or critical blockers. Good Code, Bad Code advocates for error handling that is both proactive and instructive, enabling engineers to troubleshoot effectively.

Example of Poor Error Handling:

def divide(a, b):
    return a / b

If b is zero, this function will raise a ZeroDivisionError. This is functional but brittle—it doesn’t account for edge cases, which makes it risky in a production environment.

Improved Error Handling:

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("Cannot divide by zero. Please provide a valid divisor.")
        return None

This improved version of divide catches the error and provides a helpful message. Instead of crashing, it guides the user, helping them understand the issue.

Practical Tips for Error Handling:

  • Anticipate Common Errors: Plan for edge cases like empty inputs, None values, or invalid formats.

  • Use Informative Error Messages: This aids in debugging and improves the developer experience.

  • Log Errors Where Appropriate: Consider logging critical errors for postmortem analysis.


Final Thoughts: Bringing It All Together

The theory in Good Code, Bad Code offers actionable steps for writing better code. By focusing on code quality, structured abstractions, collaboration with future engineers, and mindful error handling, you’ll build systems that are easier to understand, debug, and expand. Embrace these ideas as part of your development philosophy, and your code will stand the test of time and team growth.