Best Practices != Writing Good Code – Evaluating code quality

Writing good code is the dream of passionate developers.

However, it does not help us that the measuring bar for determining good code is quite relative. It is not like a test score, which states that you scored 89 out of 100. People read and write code differently, resulting in a variety of proven and tested solutions to common problems which are known as best practices.

Best practices such as ideal naming conventions differ for each programming language. For example, writing good code in Java and its best practices may not be in perfect alignment with writing good code in JavaScript.

The most common misconception that I want to tackle is that

Writing good code is all about following best practices

Following best practices is important, as they are proven solutions, ways or methodologies that help developers write value-adding code. However, blindly applying these practices without understanding the purposes and intent behind it will eventually lead to the developer shooting themselves in the foot.

The purpose of this post is for us (myself and those that read this article) to re-evaluate the way we write code by being mindful of what good code is.

This tutorial will examine

  • common criteria for determining code quality.
  • the importance of immediately refactoring poorly written code.
  • the pros and cons of the prevailing mindset towards best practices.

I want this to be informational, meaningful and of great help to developers in all stages of their journey. We will be working from the ground up starting from what is good code, and working out way up towards scrutinizing the initial mindset and view of modern developers when they hear the term “best practice“.

What is Code?

Code is simply a way of communicating to machines and human beings. The machine code (or byte code in the case of languages such as Java) that is generated after compiling is the set of instructions executed by the CPU. The source code (E.g. .java, .cpp, .py, .js, etc.) is what humans read.

In its purest form, it is an execution plan read by machines that process the code and perform the actions specified by the developer. To the developer, the code is a written documentation that outlines the steps taken to solve the problem.

Therefore, finding the right balance between optimizing the code for the machine and for the human eyes is the holy grail which all programmers are seeking.

If the steps are too vague or the execution plans do not run in accordance with how the code is documented, a great potential for needless confusion (in the form of side-effects or bugs) arises. Good code helps minimize the confusion and primes up the software to potentially thrive and provide maximum value to clients.

What is Good Code?

Good code reads like a well-written manual and runs like a well oiled machine.

This may seem like a tall order, but as professionals, I strongly believe that this the level we should be held accountable to. The following criteria are used universally by developers to determine the quality of the code.

  • Readability.
  • Robustness.
  • Reusability.
  • Performance.

I know that there are many sub-categories and other small factors that play into determining the quality of the code, but let’s first examine the big picture.

The greatest problem is that out of the four points, readability, reusability, and robustness are measured using a relative/subjective scale.

To measure performance, we can run the code in different environments with different data sets along with other code written for the same purposes and compare running times. In another word, there is an absolute measure of performance.

In contrast, each person has their own idea of readability, robustness, and performance.

One person might make the assertion that the font family Arial is more readable than

Trebuchet MS. However, depending on the external variables (E.g. background-color, page-layout, font-size, etc.), the answer may vary. 

For readability, robustness, and reusability, in some cases, we cannot make an absolute statement. E.g. Arial scores a readability point of 70/100 over Trebuchet MS’s 65/100.

The same can be said about writing good code.

The quality and worth of a piece of code must be interpreted through the lens of discernment and critical thinking, which requires an intimate understanding of the problem domain.

Marks of Bad Code

We have established a basic framework for assessing the quality of good code. How then, do we identify bad code?

Bad code is

  • Difficult to read and understand.
  • Brittle.
  • Difficult to reuse.
  • poorly performing.

In other words, it is an antithesis to everything that good code stands for.

Bad code is like cancer.

If you leave bad code unchecked, it will eventually spread to the point where you can’t even touch or remove that code without destroying the entire code-base. From the point of inception, bad code causes an onslaught of perpetual grief. Any attempts to remove the piece of code is costly, time-consuming, painful and may result in other side effects, just like chemotherapy.

Like cancer, the best way to deal with bad code is to identify and refactor it in the early stages.

Sometimes, when you are running short on time, it is inevitable to write bad code. We are human beings. We make mistakes.

One of the biggest mistake developers make is leaving bad code unattended. Like cancer, it becomes an absolute nightmare if the bad code is left in the code-base to grow for months and months. Eventually, it will reach a point where the code becomes unsalvageable.

If you look at a piece of code and it gives you a headache/stress, assuming that you know the problem domain and the source repository intimately, it is a reliable way to identify poorly written code.

The cost of Writing Bad Code

Bad code is responsible for hundreds of thousands of hours wasted. A computer programmer by the name of Ward Cunningham came up with a very vivid and clear universal portrayal of what bad code is. And that is the term technical debt.

“Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt. Entire engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation, object-oriented or otherwise.”

Ward Cunningham, 1992 (source: wikipedia article)

Most people know that when you borrow from a bank, you gain access to a sum of money that you wouldn’t normally have access to. The dark side, however, is that the money that you borrowed comes at a price. That interest continues to accrue (usually on an annual basis) until you have successfully managed to pay off the original amount that you borrowed, plus the interest owed.

In the same way, bad code will get you what you need. This could be a feature that your project manager agreed to provide to the client in an unreasonably short amount of time. But the most important thing, if you already have technical debt, is to pay it off quickly. If you continue to develop on top of that spaghetti code, it will continue to accrue interest and over time, it will develop into such a major mess that the code becomes impossible to refactor.

How to Deal With Bad Code

What else can we compare bad code to?

Bad code is like a crappy manual that is confusing to follow and oftentimes provides wrong instructions.

Nobody likes reading a poorly written manual, especially when they need to get something up and running ASAP. You follow step 1 and the result is supposed to be A, but you end up with A+B … WHAT THE?!?!

With code, nothing frustrates you more than nasty bugs and side-effects, especially when the code is put together in a way that makes it almost downright impossible to track where the error is coming from.

Generally, each module or method should have only a single purpose. Methods that perform more than one action or does things that it’s not supposed to is generally a code smell.

Whether you wrote it or somebody else on the team wrote it, bad code is a threat to the product, and to your mental/physical health. It must be dealt with right away. 

1. Convince the stakeholders

Generally, when working at a company, apart from your own unwillingness to drop everything and fix that mess, the only one stopping you from dealing with bad code is your boss or project manager.

Therefore, you need to persuade your project manager to give you time to focus on refactoring. Naturally, there are deadlines that need to be met. Therefore, it is important to convince your manager and let him know just how many hours of debugging and refactoring will be wasted if this problem is not addressed right away.

Project manager and bosses mostly care about meeting deadlines and the bottom line, so if needed, convince the project manager that unless the bad code is refactored right away, this is going to inhibit meeting deadlines in the future and, especially if tight deadlines are made (which happens most of the time from my personal experience).

Tell your boss that what would normally take three hours to change will end up taking two days if this cancer is not removed right away.

2. Take Action

Once you have convinced everybody that needs convincing (yourself, boss, project manager, etc.) the next step is to take action. Depending on how long the bad code has been left, the refactoring process can be relatively short and painless or a nightmare that will cause your hair to naturally fall out.

Before taking action, if you were the one who wrote that bad code, recall why and what made you write it like that in the first place.

If it was somebody else who wrote the code, try picking their brain but remember to be courteous when doing so. Pointing your finger accusingly won’t help anybody at this point. What is done is done. Chances are, they will have some insight into the problem, which will aid you greatly in the refactoring process. Alternatively, you guys can pair program and work on the problem together.

Case 1 – Bad Code Contained in a single class or method

Assuming that you understand the problem domain and have all the information required, remember that good code is

  • Readable
  • Robust
  • Reusable
  • Performs well

If it is contained in a single class, gather information on what you did wrong and rewrite the whole class/method. It should not take you too long and the software will be more robust and ready to stand the test of time.

Case 2 – Bad code has spread to multiple parts of the software

This is a more serious problem that requires an extremely accurate diagnosis. Regardless of the route that you and your team decide to take, the costs will be high. At this point, you will have to decide whether to re-write the entire portion that has been affected by bad code or to refactor portions of the application.

I would start off by asking: Are there any known side effects?

If there are known side effects, the next questions to ask would be

  1. Do you know what the root cause is and how to fix it?
  2. Are you certain that the fix will guarantee that it won’t cause side-effects in other parts of the application?

If you cannot say yes to the questions above, it might be better to rewrite the entire module. Whatever the root cause was, chances are, you learned what not to do if you had a second chance to build the program. Well, here is your second chance.

If you don’t know what the root cause is, well, you just learned a valuable lesson.

Good code is easy to test.

Keep your methods as pure as possible. Chances are, you now know why your current code base is difficult to debug. Perhaps some methods were doing way too many things. Whatever caused confusion in the previous iteration, write it down and don’t repeat it.

Readability

No developer is ever a one man show. Someday, somebody else will be maintaining your code. Readability is the foundation of good code. If the code is not readable, people will easily misuse and break your code. It will also be difficult to extend, modify and update. Eventually, that code will end up in the junkyard where it belongs.

The number one constant in programming is CHANGE.

One day, the user requirements will inevitably change. A developer will be sent to make the updates. If the code is difficult to read and understand, they will be forced to re-write it.

Good code reads like a simplified, easy to follow manual. Bad code reads like code.

This is where programming guidelines and best practices really help. For instance, nobody writes an English essay from right to left.

?ehcadaeh a uoy evig siht seod rO ?uoy ot elbadaer siht sI .siht gnidaer yrT

If you ask me, the three short sentences above are not very readable. Why? Because we are trained to read from left to right, not right to left.

In the same way, it is often good to follow best practices and coding conventions when available. Sometimes, you need to break these conventions, but when you do, you need to be able to express and communicate why you diverged from these conventions.

Code is a form of communication.

Remember that you are documenting the solution to a specific problem when you write code. If you can’t clearly communicate your solution in written form via code, you have something to work on.

Measuring Readability

For each of the criteria, the individuals that will be judging will be yourself and other developers. Therefore, a good way of measuring the readability of the code is to

  • Get your co-worker to read and provide feedback.
  • Come back to the code 3-4 weeks (after you have forgotten the business logic) and see if you can follow.
  • If possible, post your code online.
  • Show your code to other developers, whose opinion you trust.

In case that you did not pick this up, the common denominator is developers that have no prior knowledge or background information regarding the code that was written. Gather a sample of developers and ask them to read your code. When selecting your sample, be discerning. In the first round of tests, I would probably pick a sample of developers with similar background and skill level. For example, JavaScript developers with 3-5 years of experience. I know that this is not always possible, but if you are able to procure such a sample, that would be great for minimizing the impact of lurking variables.

For example, if you distributed your code to 10 developers with a similar background, if 8 out of the 10 developers deem your code extremely readable and easy to follow, chances are, you code is quite readable.

Robustness

Good code should account for errors during execution, as well as incorrect user inputs. At the very least, it should be well structured using assumptions that are easy to follow. Believe it or not, robustness ties in somewhat with readability.

Not all code is perfect.

One distinct, but often forgotten aspect of robust code is leaving clear error messages. Robust code should display unambiguous and clear error messages, with the assumption that users are ignorant of the system and will use its features out of line. Specific error messages that accurately point to the root cause will significantly reduce the time spent debugging, which empowers developers to deliver robust code in a shorter amount of time.

In order for the code to evolve, it needs to be tested in real world situations. Naturally, we can’t cover every single possible situation in one go. Software that is able to endure the wear and tear, over time, become what we call mature or robust software.

Measuring Robustness

Robust code does not break as easily as brittle code. Therefore, it is natural that robust code is born through the following means

  • Tests: both automated and manual (TDD, BDD, etc.).
    • Ship the code over to the QA. If the QA department can’t find anything, then great.
    • Try giving your software to a user who has absolutely no idea about your product. If they find it comfortable to use and don’t break anything in the process, then you are heading in the right direction.
    • Lastly, the live and ongoing “test” on the production server.
  • Feedback (from other developers)
    • If your co-worker finds it easy to understand, good!
    • Try and ask your coworker to build something with your code or extend it. If they can do so without breaking anything, then great job!
    • Once again, you are not the one who validates your code. Ask your friends, co-workers for their candid opinion of your code. Chances are, they will have some good and bad things to say. Eat the meat, throw out the bones and refactor!
  • Stress testing
    • Test your code again under worst case conditions. This is especially important for software that operates in mission critical environments, such as banks, factories, etc.

The results on the production server or real-world data and feedback are the best measurement of robustness. How well does your code cope with the data load? Does your end users find satisfaction in what you built? Does your system continue to run and gracefully handle unforeseen errors?

If your code cannot handle and meet your customers minimum requirements, your code is far from robust.

Robust code is not 100% error free, but one key aspect of robust code is consistency. It behaves as expected. If errors pop up, it should be easier to debug.

Reusability

Imagine if we had to continue to re-invent the map(), filter() and reduce() function each time we wanted to write some pure function that worked on transforming a list.

That is time wasted!

As you may already know, good code is testable on a unit level.

Code that solve a larger scale problem are called libraries. Code that provides an abstract solution that can be extended to solve a specific problem are called frameworks.

What do all these have in common?

You can reuse them. For Java developers, imagine if you had to re-invent the spring framework each time we wanted to use its MVC features. It will take us ages to write anything.

Good code isolates and solves a specific problem.

Generally, bad code is tightly coupled, meaning it is using/depending on stuff that it shouldn’t be touching. High-level abstractions should not depend on low-level implementation details. In order to achieve this in Java, we use inheritance and polymorphism (via abstract classes and interfaces).

In contrast, good code is loosely coupled. Each method and class only knows and does what it is supposed to do and on a higher level abstraction.

Methods should also generally perform a single task. Imagine if a method does two different task. That method can now only be used in situations where the two tasks occur. If we had separated the methods, each method can be used in cases where that task appears, as opposed to only being used if the two tasks appear together as a package.

Measuring Reusability

There is nothing better than a live test sample. If your co-workers can take your code and use it to build something comfortably without breaking anything, then your code is reusable.

Try open sourcing what you wrote if it is permissible. Having a larger population to test the reusability is generally beneficial. The data and use cases presented by each consumer will be invaluable in measuring and improving the usability of your code and any code that you write in the future.

Asking for opinions on a programming forum is also viable. As with all open source feedback. but be sure to take feedback with a grain of salt, because there are people who mean well, but provide mixed feedback that is sub-optimal. Not to mention that there are quite a lot of trolls out there.

What are Best Practices in Programming?

Now that we have processed and critically assessed some criteria for evaluating the quality of code, we can finally talk about best practices and how the common mindset and perception of best practices ties in with writing good code.

Best practices are a set of informal rules that the software development community have learned over time, which can help improve the quality of the software. (source: wikipedia)

Rules are there to keep you from doing stupid stuff. Like naming your methods a,b,c,d,e,f …, writing methods that are 200 lines long and so on.

Sounds great right? I totally agree with you. Best practices are a bright beacon, guiding ships to the shore during dark times of uncertainty.

However, best practices can lure the unwary developer into a false sense of security, thus creating an unhealthy dependence on existing solutions. As developers, we live and die by our ability to creatively come up with tailored solutions to problems that we face.

Best Practices are not Silver Bullets

Best practices are like precedents in the common law system. Simply put, it is a rule established to judge a certain case and is used hence-after to judge similar cases. But because not all problems are created equal, we cannot just apply some generic “best practice” without thinking about the underlying domain and expect great results.

Do we need the law? Yes! Without law, there will be chaos, just as there will be chaos and confusion without programming best practices to guide us. But at the same time, developers must realize that

Best practices are not silver bullets for solving problems.

Best practices are the byproduct of the experience and conclusions made by experienced developers in their past. Learning from their experiences prevents avoidable mistakes. But what is more important is your own experience and discernment, because the indirect and direct experience is different in quality.

Hearing about the juiciness of an apple and actually tasting it are two entirely different experiences.

Can you just blindly apply precedents to each case without examining background information?

You won’t become better by simply reading tons of books on design patterns and best practices.

One is able to write good code only after writing many lines of bad code.

Therefore, it is important to experience the horrors and hardships that terrible code brings firsthand. To be honest, I have experienced it not once or twice but many times on the job.

Each Problem has its own Optimal Solution

Now, if you read through all the previous sections, you may be thinking:

All the advice for writing good code and identifying poorly written code sounds like programming best practices.

That may be true. But let me ask you a question.

Once you heard the term “best practice”, did your creativity and critical thinking cap fly out the window?

Here is my next set of question to you.

  1. While reading about these “best practices”, did you think of any other possible methods or ways to write good code or improve your existing code-base?
  2. Did you think of a specific problem that you are facing and a list of possible ways to refactor your code to dig yourself out of technical debt?
  3. Did any ideas for improving your code occur to you?
  4. Are you motivated to re-examine and refactor the code that you write?

I am not telling you to refactor every single piece of code that you ever wrote. But please, get into the habit of routinely identifying bad code and making it a habit of refactoring it before it blows out of proportion.

The reason why I think developers should always examine best practices with a grain of salt is that it can potentially turn programmers into robots that pump out features routinely.

Blindly jumping onto the first solution that pops into your mind, and attempting to package it so that it is inline with best practices is not the right answer.

Design Patterns are a Double Edged Sword

First of all, I just want to say that I am not bashing design patterns. I have studied and used traditional design patterns suggested by the G.O.F. Design Patterns are great for studying thought processes, especially when you are just starting out in software design and system architecture. When you are inexperienced, examining solutions to common problems is an invaluable resource. It not only teaches you how to solve the problem, it also trains your mind to think differently to how you have thought in the past.

But here is (in my opinion), the dark side to design patterns. Feel free to disagree with me.

Design patterns can create an unhealthy dependence, subtly training the unwary developer to rely on cookie cutter solutions.

Auto-piloting is one of the greatest threats that inhibits a developer’s ability to solve problems effectively. Effective problem solving is about coming up with optimal, tailor made solutions that fit the current problem domain.

Design patterns are general reusable solutions. And general solutions are not specific. It is up to you as the person writing code, to come up with the solution. Design patterns can encourage people to copy and paste that design pattern in and think afterward. In most cases, developers should always write down and visualize a working solution before writing code.

How to Write Good Code

Okay, now that we have established the importance of writing good code, if you are still here, you probably want to know how to write good code. Writing good code is not just about following best practices and programming guidelines. Rather, it is evaluating what you know (proven solution, your experience, and other information at your disposal) and critically counting the costs of each viable options to reach a suitable conclusion that yields the most optimum output.

Writing good code is like publishing a manual on how to solve a puzzle. Each puzzle has its unique set of quirks. It is about understanding the problem and using code to effectively solve and communicate to readers how this problem was solved.

Ultimately, good code is measured and judged by three major stakeholders.

  • Developers.
  • The company paying the developers.
  • End users.

Good code can mostly be summarized in two words: clean code. Although the two are not 100% synonymous, clean code can easily be understood, which allows developers to be productive and not waste time. Writing clean code will result in benefits for both the programmer and the company that is employing the programmer.

To the business, the code is good if a developer can be productive, extend that code, and ship it with fewer errors in a shorter amount of time.

To the end user, the quality of the code is judged by their user experience and performance. How easily and quickly can I access what I want? How long do I have to wait to access the information upon request?

Clean code helps a team of developers satisfy the ultimate end goal of all three stakeholders.

Writing good code is not about just blindly applying best practices. It is about determining what the best practices is for the current situation. 

It all Begins with Being Intentional

As a programmer, it is your professional responsibility to deliver good code to your clients. And it all begins by taking pride in your work and being intentional with writing good code. At first, because you are changing your ways, there will be some resistence and inertia. As long as we persevere, in the right time, we will reap a harvest that will benefit not only yourself, but also your team, company and clients.

When you hear about best practices or proven solution, it is important to not just take it at face value because somebody told you so. Investigate for yourself, exactly why that approach is the best solution for that specific problem.

Best practices are useful. However, there are clear dangers when the term is elevated up to a point where it becomes synonymous with good code.

I hope that this post was thought provoking, convicting and also encouraging. Not everybody can write good code 100% of the time, so don’t beat yourself up when this happens. But at the same time, be firm about not incurring too much technical debt, as that will come to bite you in the butt later.

By bringing up my own opinion, my purpose is not only to impart knowledge, but also to provoke thoughts and discussions. Please feel free to comment and share your thoughts.

Please also note that I will not be wasting my time following up on baseless comments that have zero ounces of reasoning.

Thank you for reading and happy coding!

– Jay

About the Author Jay

I am a programmer currently living in Seoul, South Korea. I created this blog as an outlet to express what I know / have been learning in text form for retaining knowledge and also to hopefully help the wider community. I am passionate about data structures and algorithms. The back-end and databases is where my heart is at.

follow me on:
13 Shares