DEV Community

Liz Acosta
Liz Acosta

Posted on • Originally published at Medium

2

Code That Won’t Cost You: Coding best practices for any language to increase code quality and reduce developer toil

Bad code is not only hard to read, it can cause expensive incidents. According to the Uptime Institute’s 2022 Outage Analysis, “nearly 40% of organizations have suffered a major outage caused by human error over the past three years” with over 60% of all failures resulting in at least $100,000 in total losses.

With more and more AI-generated code creeping into software, the potential for bad code increases. While LLMs are just the latest innovation altering the software development lifecycle, regardless of whatever disruption comes next, code will always benefit from consistently applied best practices.

In this article, we’ll talk about tech debt, good versus bad code, best practices to follow, and other ways you can fortify your software. We’ll also discuss practical strategies for implementing these best practices in your development workflow.

💡 This article is designed to accommodate different learning and reading styles, so feel free to jump ahead.

When tech debt comes to collect: Understanding the cost of neglecting code quality

“Oh, we’ll add that to the backlog and when we have a chance, we’ll get around to it.”

Say these words two more times and you’ll invoke a curse known as “technical debt.” For those of you new enough in your engineering careers to be blissfully unaware of this curse, technical debt (often shortened to “tech debt”) refers to the extra work required in the future to correct the outcomes of faster or more expedient solutions, including architectural designs and development shortcuts, taken in the present. Like financial debt, tech debt incurs interest the longer it is put off. And like financial debt, tech debt can be expensive.

In January 2023, tech debt came to collect when more than 1,300 flights in the United States had to be cancelled and 10,000 more delayed. Why? Because files accidentally deleted while trying to update a database caused the Notice to Air Missions system (NOTAM) — which alerts pilots to potential hazards along their routes — to fail. It wasn’t the first time something like this had happened; they had been kicking the tech debt can down the road for years.

In essence, tech debt is the result of unaddressed technical decisions, including poorly written code, that have remained unchanged and propagated throughout a software system, leading to future complications. When these brittle systems finally collapse, it costs more to fix them than it would have cost to avoid the debt altogether. Moreover, on average, developers waste 23% of their development time on tech debt, and the cognitive drain can lead to dissatisfaction, burnout, and even more debt.

The solution seems simple: Don’t push bad code.

However, the daily pressures of deadlines, the complexities of existing systems, and the need for quick fixes often make this a significant challenge. Consistently writing clear, maintainable code requires discipline, awareness, and the implementation of sound development practices.

The good, the bad, and the ugly code: What “good code” is and why it is important

What is considered “bad code”?

“Bad code” is a lot of things: Hardcoded secrets, overly complex functions, missing unit tests, unnecessary repetition, unclear documentation, etc. Go look at some of your earliest code — it’s probably not as good as the code you write now. At a high level, while some bad code takes longer than it should to understand, other forms of bad code might be simple to grasp but still exhibit issues like inefficiency, lack of maintainability, or poor design. Code that is harder to understand is more susceptible to bugs and vulnerabilities because its complexity makes it difficult for developers to correctly interpret its logic, leading to errors during modification, debugging, or when integrating with other parts of the system.

Similar to poorly written prose, if you find yourself having to stop and re-read a block of code more than a few times, it’s probably code that should be refactored. Difficulty in code comprehension isn’t just a minor inconvenience; bad code significantly complicates debugging — especially in the chaos of a 3 AM critical outage incident. That’s when things get really ugly.

So what is considered “good code”?

While one aspect of bad code can be its difficulty to read, defining it solely by this characteristic is an oversimplification. Good code, therefore, involves more than just being “easy to read”; it also encompasses factors like maintainability, efficiency, clarity of intent, and adherence to best practices. So what specific attributes contribute to this readability? Just as standards for good writing vary across genres, the characteristics of good code can differ based on programming languages, system architectures, and frameworks.

However, at a fundamental level, high quality code exhibits several key traits: it is maintainable, reliable, and secure. Now that we have a sense of what makes code easy to understand, change, operate, and debug, what are some best coding practices we can keep in mind to help guide our software development efforts?

Suggested best practices for code quality in any language

Never hardcode secrets

Hardcoding sensitive information or arbitrary values directly into your code is a significant source of technical debt and security risks. In October 2022, Toyota revealed that a hardcoded secret had been exposed in a public repo for nearly five years. The issue was remedied, but it would be naive to think that no breach of data had already occurred.

Example of a hardcoded secret:

    REPORTING_API_TOKEN = "A_HARDCODED_TOKEN_1234"

    def report_quality_gate_status(gate_status, check_type):    
        headers = {"Authorization": f"Bearer {REPORTING_API_TOKEN}"}
        data = {"status": gate_status, "check_type": check_type}
        try:
            requests.post("http://localhost:8081/api/status",
                          headers=headers,
                          json=data)
            print("Status reported successfully.")
        except requests.exceptions.RequestException as e:
            print(f"Error reporting status: {e}")
Enter fullscreen mode Exit fullscreen mode

In the code above, a request is made to the http://localhost:8081/api/status endpoint. The request contains headers with authorization — a very familiar API pattern. Unfortunately, the token has been hardcoded, leaving this code vulnerable to a security breach.

Improved version without a hardcoded secret:

    def report_quality_gate_status(gate_status, check_type):
        """Reports the status to an external service."""
        # API token for reporting retrieved from environment.
        api_token = os.environ.get("REPORTING_API_TOKEN")  

        if not api_token:
            print("""Warning: REPORTING_API_TOKEN environment        
                  variable not set (reporting skipped).""")
            return
        headers = {"Authorization": f"Bearer {api_token}"}
        data = {"status": gate_status, "check_type": check_type}
        try:
            response = requests.post("http://localhost:8081/api/status",
                                    headers=headers, json=data)
            response.raise_for_status()  # Ensure successful request.
            print(f"""
                 Status '{gate_status}' for '{check_type}'
                 reported successfully.
                 """)
        except requests.exceptions.RequestException as e:
            print(f"Error reporting status: {e}")
Enter fullscreen mode Exit fullscreen mode

In the improved version, the value for REPORTING_API_TOKEN is retrieved from the operating system environment and never exposed in the code.

💡 Click here to learn more about handling secrets.

Reduce redundancy by embracing the DRY principle

One of the core purposes of programming is to automate repetitive tasks, but that’s not the only reason to reduce redundancy. The more often a task is replicated throughout a system, the greater the vector of vulnerabilities. Redundancy is also harder to maintain.

Example of redundancy:

    class QualityGateCheck:
        def __init__(self, error_threshold, warning_threshold):
            self.error_threshold = error_threshold
            self.warning_threshold = warning_threshold
            self.status = "Pending" 
            self.check_type = "Combined Threshold Check" 

        def run_check(self, code_errors):
            self.status = "Pending"
            if code_errors >= self.error_threshold:
                self.status = "Failed: Exceeds error threshold"
                result = self.status
                return result
            elif code_errors > self.warning_threshold:
                self.status = "Passed with errors"
                output = self.status
                return output
            else:
                self.status = "Passed"
                final_status = self.status
                return final_status
            return final_status

        def get_status(self):
            current_status = self.status 
            return current_status

        def describe_check(self): 
            description = f"""
                          Checking for errors above 
                          {self.error_threshold}
                          and warnings above
                          {self.warning_threshold}.
                          """
            return description
Enter fullscreen mode Exit fullscreen mode

The code above creates a class called QualityGateCheck initialized with thresholds for errors and warnings, a status, and what kind of check it is. The default status is Pending and the default check type is Combined Threshold Check. The class also contains three different functions: one to run the check, one to get the status of the check and one to describe the check.

The redundancy occurs in a few different places:

  • In run_check, status is again set to Pending — something that is handled already in the instantiation of the class.

  • In the same function, two unnecessary intermediate variables are created and returned: output and final_status.

  • As the function is currently written, the last return final_status will never be reached.

  • Similar unnecessary intermediate variables occur in the functions get_status and describe_check.

Improved version with reduced redundancy:

    class QualityGateCheck:
        def __init__(self, error_threshold, warning_threshold):
            self.error_threshold = error_threshold
            self.warning_threshold = warning_threshold  
            self.status = "Pending"
            self.check_type = "Code Error Threshold Check"

        def get_status(self):
            # Returns status without intermediate variable.
            return self.status

        def describe_check(self):
            # Returns description without intermediate variable.
            return f"""
                   Checking for errors above {self.error_threshold}
                   and warnings above {self.warning_threshold}.
                   """

        def process_code_errors(self, code_errors):
            # Unnecessary status setting and
            # intermediate variables removed.
            if code_errors >= self.error_threshold:
                return "Check failed: Exceeds error threshold"
            elif code_errors > self.warning_threshold:
                return "Check passed with errors"
            else:
                return "Check passed"
            # Unreachable return removed.
Enter fullscreen mode Exit fullscreen mode

💡 How does this improved version further reduce redundancy in the conditional logic?

You can extend this concept to deeply nested functions, application configurations, integrated systems, and any other opportunity to DRY — Don’t Repeat Yourself. Strive to identify and eliminate duplication wherever possible.

Define, divide, and decouple responsibilities for maintainability

Speaking of deeply nested functions, the Single Responsibility Principle (SRP) advocates for reducing complexity where possible by narrowing down classes and modules to a single task. We can expand on this principle to include how we define, divide, and decouple services and applications and architecture, ultimately mitigating technical debt.

In April 2022, Atlassian experienced a 14 day outage while trying to delete data related to a deprecated legacy standalone application. In the incident, the API used to perform deletions of accepted IDs related to two entirely different entities, resulting in the removal of instances of the inactive app and active cloud sites using the application. Customers lost access to their Atlassian products (very likely including their backlogs of tech debt).

Decoupling the data deletion process for the legacy application from the active cloud sites could have prevented this significant disruption.

Example of a function with multiple responsibilities:

    def main_quality_check(errors):
        error_threshold = 10
        warning_threshold =  5
        gate = QualityGateCheck(error_threshold, warning_threshold)
        final_status = None

        if not errors:
            final_status = "No code errors provided"
        elif not isinstance(errors, int):
            final_status = "Invalid code errors"
        else:
            if errors >= gate.error_threshold:
                final_status = "Check failed: Exceeds error threshold"
            elif errors > gate.warning_threshold:
                final_status = "Check passed with errors"
            else:
                final_status = "Check passed"
            gate.status = final_status

            api_token = "A_HARDCODED_TOKEN_1234"
            if not api_token:
                print("""Warning: REPORTING_API_TOKEN
                     environment variable not 
                     set (reporting skipped).""")
            else:
                headers = {"Authorization": f"Bearer {api_token}"}
                data = {"status": gate.get_status(),
                       "check_type": gate.check_type,
                       "check_description": gate.describe_check()}
                try:
                    response = requests.post("http://localhost:8081/api/status",
                                            headers=headers, json=data)
                    print(f"""
                         Status '{gate.get_status()}' for '{gate.check_type}'
                         reported successfully.
                         """
                         )
                except requests.exceptions.RequestException as e:
                    print(f"Error reporting status: {e}")
        return final_status
Enter fullscreen mode Exit fullscreen mode

There is a lot going on in the code above. Not only is the code validating whether or not errors is an integer, it is also determining the status of the quality gate check as well as posting the resulting gate check status to an endpoint. In other words, there are many different tasks happening in this function — it is overcomplicated, which makes it not only harder to read, but harder to test.

Improved version with separated responsibilities:

    # Handles just the operations pertaining to evaluating the code errors
    # against thresholds.
    class QualityGateCheck:
        def __init__(self, error_threshold, warning_threshold):
            self.error_threshold = error_threshold
            self.warning_threshold = warning_threshold
            self.status = "Pending"
            self.check_type = "Code Error Threshold Check"

        def get_status(self):
            return self.status

        def describe_check(self):
            return f"""
                   Checking for errors above {self.error_threshold} and warnings
                   above {self.warning_threshold}.
                   """

        def process_code_errors(self, code_errors):
            if code_errors >= self.error_threshold:
                return "Check failed: Exceeds error threshold"
            elif code_errors > self.warning_threshold:
                return "Check passed with errors"
            else:
                return "Check passed"

    # Handles just the operations pertaining to validating the value
    # of code errors and can be used elsewhere in the code.
    def validate_code_errors(code_errors):
        if not code_errors:
            return "No code errors provided"
        if not isinstance(code_errors, int):
            return "Invalid code errors: Expected an integer."
        return None

    # Handles just the operations pertaining to reporting the gate check status
    # and can be used elsewhere in the code.
    def report_quality_gate_status(gate_status, check_type, check_description):
        api_token = os.environ.get("REPORTING_API_TOKEN")
        if not api_token:
            print("""Warning: REPORTING_API_TOKEN environment variable not set
                 (reporting skipped).""")
            return
        headers = {"Authorization": f"Bearer {api_token}"}
        data = {"status": gate.get_status(),
                "check_type": gate.check_type,
                "check_description": gate.describe_check()}
        try:
            response = requests.post("http://localhost:8081/api/status",
                                    headers=headers, json=data)
            response.raise_for_status()
            print(f"""
                 Status '{gate_status}' for '{check_type}' reported successfully.
                 """)
        except requests.exceptions.RequestException as e:
            print(f"Error reporting status: {e}")

    # Ties everything together.
    def main_quality_check(errors):
        error_threshold = 10
        warning_threshold = 5
        gate = QualityGateCheck(error_threshold, warning_threshold)

        validation_result = validate_code_errors(errors)
        if validation_result:
            return validation_result

        final_status = gate.process_code_errors(errors)
        gate.status = final_status

        report_quality_gate_status(gate.get_status(),
                                   gate.check_type,
                                   gate.check_description())
        return gate.get_status()
Enter fullscreen mode Exit fullscreen mode

In the improved version, the tasks of validating the value for errors, processing the errors, and then reporting the errors status have been broken into separate functions that are called in main_quality_check.

By dividing responsibilities, we define each task and decouple them from each other, resulting in code that is easier to read, easier to test, easier to maintain, and easier to extend.

Write effective code comments and documentation

Meaningful comments enhance code readability, but excessive or poorly written ones hinder it. The issue isn’t the length of necessary documentation, especially for public APIs and SDKs which often require multi-line explanations. Rather, a red flag for potential refactoring is when a small block of core logic needs significantly more comment lines to explain what it does than the code itself. In such instances, refactoring using best practices to create clearer, more self-explanatory code is preferable to relying on verbose comments to decipher complexity. Well-structured and clearly named code often minimizes the need for extensive explanatory comments on its basic operation.

Example of verbose comments:

    # The main function to orchestrate the quality check process.
    def main_quality_check(errors):
        error_threshold = 10
        warning_threshold = 5
        # Create an instance of the QualityGateCheck class with the configured
        # thresholds.
        gate = QualityGateCheck(error_threshold, warning_threshold)

        # Validate the input 'errors' using the validate_code_errors function.
        validation_result = validate_code_errors(errors)
        # If the validation returns an error message (not None),
        # return that message and stop further processing.
        if validation_result:
            return validation_result

        # If the input is valid, process the 'errors' using the
        # process_code_errors function to get the final status.
        final_status = process_code_errors(errors, 
                                           gate.error_threshold, 
                                           gate.warning_threshold)
        # Update the status of the QualityGateCheck instance
        # with the determined final status.
        gate.status = final_status

        # Report the final status using the report_quality_gate_status function, 
        # passing the gate's status and check type.
        report_quality_gate_status(gate.get_status(), 
                                   gate.check_type,
                                   gate.describe_check())
        # Finally, return the determined final status of the quality check.
        return gate.get_status()

Even at a glance, this code probably gives you a headache. The comments arent adding anything helpful.
Enter fullscreen mode Exit fullscreen mode

Improved version with more useful comments:

    def main_quality_check(errors):
        """Orchestrates the quality check process."""

        error_threshold = 10
        warning_threshold = 5
       # Create a QualityGateCheck with the above values. 
       gate = QualityGateCheck(error_threshold_config, warning_threshold_config)

        validation_result = validate_code_errors(errors)
        if validation_result:
            return validation_result

        final_status = process_code_errors(errors,
                                           gate.error_threshold,
                                           gate.warning_threshold)
        gate.status = final_status # Update the gate’s status.

        # Post the status of the gate check and the type. 
        report_quality_gate_status(gate.get_status(), 
                                   gate.check_type,
                                   gate.describe_check())
        return gate.get_status()
Enter fullscreen mode Exit fullscreen mode

This version makes use of docstrings and comments to help understand the code without detracting from the code.

💡 IDEs and other tools can utilize these docstrings for improved navigation, debugging, and API documentation generation.

Use comments judiciously to explain the why behind the code, not just the what. Meaningful names for variables and functions often reduce the need for extensive commenting. Additionally, leverage docstrings to document the purpose, arguments, and return values of functions and classes.

Maintain consistency and build a lasting code legacy

Regardless of the specific best practices you choose to implement, consistency is paramount. The easiest way to keep code consistent is to enforce a unified style throughout. There are different styles for different programming languages, and even within those coding guidelines there are variations. For instance, indenting with tabs versus spaces. Choose whatever style best suits your use case.

Maintaining a consistent code style streamlines the adoption of best practices across a project. This stylistic uniformity extends its benefits to several crucial areas:

  • Consistent unit test coverage: When code style is consistent, it becomes easier to establish and maintain uniform standards for unit testing. This includes consistent naming conventions for test cases, a consistent structure for test setup and teardown, and a predictable approach to mocking dependencies. This uniformity makes it simpler to identify areas lacking adequate test coverage and ensures that tests are written and executed in a standardized way, leading to more reliable and maintainable tests.

  • Consistent refactoring: A consistent code style facilitates safer and more efficient refactoring. When the codebase adheres to a predictable structure and set of conventions, developers can more easily understand the impact of changes and apply refactoring patterns consistently. For example, consistently named variables and methods, along with a uniform code layout, make it easier to identify and extract common logic, rename elements, or restructure code without introducing unintended side effects.

  • Consistent documentation: Consistency in code style naturally promotes consistency in documentation. When code follows a predictable structure and naming scheme, it becomes easier to establish conventions for inline comments, docstrings, and higher-level documentation. This includes a consistent tone, structure, and level of detail. Uniformity in documentation makes it easier for developers to find the information they need, understand the codebase, and contribute effectively.

Furthermore, a consistently styled codebase, coupled with the consistent application of best practices in testing, refactoring, and documentation, significantly eases the onboarding process for new team members. The predictable patterns and structures within the code and its related artifacts enable faster comprehension and integration into the development workflow.”

In essence, consistent code reflects a commitment to care, attention to detail, and future maintainability. Most software development is a collaborative endeavor, and someone else will likely need to read or modify your code in the future. By prioritizing consistency and writing high quality code, you contribute to a positive and sustainable code legacy. The choices you make in naming variables, refactoring functions, adding comments, and addressing technical debt shape this legacy.

What kind of legacy you leave is up to you.

From theory to practice: Best practices for implementing coding standards effectively

Now that you understand best practices for writing good code, how do you actually implement them?

Remember that best practices are guidelines that enable developers to do their best work collaboratively and efficiently; they are not meant to control anyone. They should inspire and empower you, not limit you.

  • Learn from others: Actively read code from reputable open-source projects to help you learn from other styles, perspectives, and use cases.

  • Leverage automation and shift left: Integrate tools for static code analysis, linters, and code reviews into your development workflow as early as possible (shifting left). These tools can automatically identify potential issues and enforce coding standards, minimizing the impact of bad code and freeing you up to think about more complex problems.

  • Cultivate curiosity and embrace exceptions: Understand the rationale behind coding style enforcements. Occasionally, there might be valid reasons to deviate from a standard, but these exceptions should be conscious and well-documented. The justification for a certain coding style enforcement may seem obvious to you, but can you explain why it’s so obvious? Occasionally refactoring our brains is just as essential as refactoring our code. Every now and then you might have to break your own rules.

Key takeaways: Building a foundation for sustainable software development

Avoiding technical debt is not just a matter of writing “good” code — it is a continuous commitment to clarity, maintainability, and collaboration. By embracing the best practices outlined in this article you can build more robust, scalable, and sustainable software systems.

Remember that the effort invested in writing good code today pays significant dividends in reduced maintenance costs, fewer outages, and a more productive and satisfied development team tomorrow.

Try it yourself: Tools and resources for enforcing code quality

Understanding these core best practices for writing good code helps lay the foundation for software development with minimal tech debt. As software applications and the technology that supports them become more and more powerful — and therefore more complex — enforcing best practices becomes even more critical.

When it comes to enforcing code quality at scale, tools like SonarQube integrate seamlessly with existing development pipelines.

  • Shifting code quality all the way left, SonarQube’s IDE plugin annotates your code as you write it with reasoning and remediation suggestions that ensure issues don’t even get committed. It’s free and you can get started with it right away.

  • Pull request analysis helps automate the more basic aspects of code reviews so you can focus on larger issues and catch security vulnerabilities before they’re merged to prod. Check out this repo for hands-on experience using SonarQube.

  • Quality gates and profiles mean that you set the standards you want to hold yourself to, adjusting and adapting as necessary.

💡 The code for the examples in this post can be found here.

Tutorial image

Next.js Tutorial 2025 - Build a Full Stack Social App

In this 4-hour hands-on tutorial, Codesistency walks you through the process of building a social platform from scratch with Next.js (App Router), React, Prisma ORM, Clerk for authentication, Neon for PostgreSQL hosting, Tailwind CSS, Shadcn UI, and UploadThing for image uploads.

Watch the full video ➡

Top comments (0)