What my team and I learned when improving code quality in an existing codebase

July 11, 2019 in Code Quality, Teams

In this article, I present a list of learnings and a general process for you to achieve a better code quality. The ideas I described here are based on my experience and don’t represent the only way to achieve a good and healthy code. In case you don’t know where to start from, I hope this blog post helps you.

Learnings from code quality improvement

I’ve worked along with many companies running in different business domains. However, I’ve noticed that some premises worked just like the same in all of them. Here I show them as learnings. I take these points very seriously when defining an improvement process; otherwise, the chances of succeeding in improving and maintain the quality of your code are meager.

  • It’s not a single person job. For real. Everyone in your team must be committed to improving existing code, and more importantly, writing quality code for new features.
  • Freezing new features while refactoring won’t work. When your team doesn’t deliver anything for a while, business or product people tend to make sure refactoring turns into a curse word, just like Abracadabra or Hocus pocus.
  • It’s not a proper time for “move fast, break things” mindset. Big refactoring and architectural changes already have a more significant potential of breaking things; going fast only increases your chances. Focus on improvement at its pace and be sure that the more automated tests you get, the more comfortable with changes you get. Changing the production code without unit tests, acceptance tests, and integration tests are very risky. Remember, inserting too many bugs during refactoring may get this word cursed as well.
  • Trace a strategy and keep your team aligned. That’s the best way to improve your code-base quality continually. From time to time, stop what you’re doing, check the metrics, change strategy (if needed), align your team, and keep working. Learn with your mistakes and improve this process as well.
  • Make quality improvements something usual. It is a never-ending job. If you want to create this culture and make it last, I strongly recommend integrating practices into your development flow.
  • Each team has its problems and pace. It is a bad practice to compare “improvement scores.” The environment gets much tension, and the efforts of quality improvement soon turn into efforts for lessening a metric to be on top of a leaderboard or whatever. Different teams have a different view of quality and may be in different contexts. Compare your team with the very same team. “Is the team having progress?” that’s the question that matter.
  • Look for the short and the long run. When choosing a strategy, don’t pick only big working items. It kills your motivation as showing its progress may take some time. Think in the short-run (low hanging fruits and small fixes) and the long run.

With them in mind, here’s a suggestion of how to deal with messy code and how to take it to a better level.

Dealing with poor quality code

1. Measuring your current code quality

You’re telling me your code quality is poor. However, is it? You won’t know until you measure it. Also, it allows you to see the progress of your changes. Are they improving code quality or just adding more complexity and code smells?

If you don’t know what measure, I’ve suggested in the last blog post 3 metrics that you can automatically collect by linters. You may run them by your own or by using a product like SourceLevel to do it for you. It’s up to you, as long as you measure it.

2. Tracing an improvement strategy

When each member of your team works by their own, you lose track of the improvement progress and stick with the feeling of getting nowhere. That’s why you need a good strategy because it gives focus to the team.

A good strategy may vary depending on the business and on the phase of the product. If you work in a startup and hasn’t launched your product yet, you need to keep developing new features and fixing bugs to achieve that time-to-market date. Usually, this phase of the product introduces a lot of smells and design problems. However, that’s ok, because you don’t know your product very much. You’re learning the domain, and it takes time to get the big picture of your business. In this phase, I think the best strategy is introducing the least issues as possible and keeping track of the existing ones.

If your product is already launched and running, you can spot all those codes smells, calculate methods and classes cyclomatic complexity, duplicated code, etc. Then, you need to merge those improvements to the product evolution plan.

As I said, don’t stop delivering is something I’ve learned. Refactor what really matters, what impacts for your near future work and business demands. It works just like any other technical debt and must be included in or diluted into the roadmap.

When I say you should dilute into the roadmap, I mean don’t feed more than one board. Prioritize your board according to your business needs. Insert low hanging fruits and small changes through the selected cards for your sprint. Refactoring and significant changes may lead to an independent card as long as it makes sense for your business.

Be transparent to your team and prioritize issues that make future work easier and faster to change. If solving an issue has no significant gain for the next couples of weeks, let it there. Remember, you don’t want to decrease the number of quality issues merely, you want to be improving your quality continually.

Talk to your team and show them the value of improvements. Keep your product and business people engaged and aware of the successes and failures. Show how it impacts them by using their language: delivering faster, fewer bugs, better performance, and money saved or gained.

3. From time to time, share the results with your team

As I said, this is not a single person job. So, sharing the progress of your code-base improvement has a positive impact on both keeping your team engaged and motivated. Visibility is also essential for other stakeholders, like product or business people, or even the CTO.

It doesn’t matter if you’re a manager, a tech lead, or a developer. Gather everyone and celebrate small wins and, most importantly, the big ones. Make sure everyone is involved with the process.

You may ask, when and how should I share the progress?

Each team may find a proper opportunity. However, here go suggestions: you can have a 10 minutes walkthrough of the metrics during a Review Meetings (for those who run Scrum), a weekly 3 minutes follow up during a stand-up meeting, or even a separate monthly meeting only for communicating the numbers and aligning next steps. The time between the meetings depends on how fast the changes are going on. The faster you can see progress, the shorter the time between them.

Answering how to share the progress, I suggest plotting charts with metrics results and they’re history. They’re the most effective way to tell stories with numbers. Just by seeing a chart, you can tell whether your team has done an excellent job or not. Also, they help you a lot asking questions about your process.

4. From time to time, review your strategy

As your team members keep working on the source base, they add new issues and solve old ones. It is essential to keep your metrics aligned with a good strategy. If metrics change too much, it’s time to review your strategy.

Your business may change too. If your competitor launches a new feature and your product must to catch them up, you’ll probably have to work harder on this. You may have to suspend a significant refactoring (in case the feature can’t wait for it), or even debate the trade-offs of inserting more poor quality code versus having more time for improving it. That’s the perfect time to stop and draw a new plan. Don’t abandon improvements, even though you may not work on them for a short time.

5. From time to time, review your metrics

Another essential aspect of a health code is to define what quality is. If you know this, then measuring does the job of telling you how good it is. To achieve this, you need to measure the right things.

The problem is that we won’t know for sure what’s the right things to measure before we start measuring it. That’s why collecting new metrics and stop collecting unnecessary ones is a job to do regularly.

Conclusion

Having a health code-base takes time, and the development flow must integrate as part of the process. Here I presented some learnings (so you don’t have to fail for those reasons) and a general process you can implement at your company or team.

Share your thoughts in the comments below.

If you don’t know what to measure or don’t want to collect the metrics manually, check SourceLevel’s supported linters and Sign up for a 14-day trial.

Measuring Code Quality: 3 Suggestions On How To Start And Why They Are Important

June 17, 2019 in Code Review

If I ask you to tell me how much quality your code has, how would you answer it? There’s not a correct answer, and responses vary depending on seniority or past experiences of each one. As there is no consensus of what’s quality, here’s a list of aspects I think your code must have to be considered good (not necessarily in this order):

  • It must work. Whatever the feature is, it should only do what it was designed to do. No more, no less.
  • There must be automated tests to ensure it is working and to describe how it works.
  • Code is well documented (at least as much as it needs).
  • The code is clear and shows its intention by having well-chosen variables, methods, and class names.
  • It is easy to maintain, or in other words, it is easy to change.
  • It has no security breaches.
  • It is fast (at least fast enough for its usage).

Different features may be added depending on the kind of software you’re building. You may need your code to meet some legal issues if you work in a bank or an insurance firm, for instance.

OK, we got attributes of a good code. Why should I measure it?

In my opinion, measuring is the best way of looking at the situation as it is. It is essential to listen to the feelings of developers and your senior developers. They may have a very accurate sense of codebase health status. However, you can’t rely just on it.

Developers are humans, and as humans, they are influenced by emotions, by egos (have you ever heard lines like “my code is beautiful, I’m an artist”?) and also by their experience.

This last aspect, the experience is the most crucial one. Depending on how much messy code has a developer dealt with, its definition of quality changes. That’s why measuring is so important.

You get a standardized view of your code health.

Another great thing about metrics is looking at its history to make questions. For instance, if your number of code smells in September is 33 and October it goes down to 22, you could tell your team “what have we done? Let’s keep doing this because it improved the overall code quality”! Alternatively, in the worst scenario, asking your team, “why are code smells growing so much this month?” may raise important issues, like your team being pressured by the product team to deliver faster and faster without looking to quality.

These numbers allow you to ask questions, understand your process and the impacts of your changes.

OK, I agree that measuring is essential. What should I measure?

Many companies look to the number of bugs as an indicator of quality, but the fact is that it relates more to the quality of the process than to the health of your codebase. Of course, bad code leads to more bugs, however not every bug is due to bad code.

Well, for me, it is. Moreover, it tends to happen when there’s no one looking for quality in the team. We don’t usually think about quality before writing a new feature, nor we think about quality when done. We only think about quality during the development of a feature where we find a lot of… Let’s say, “what the fuck.”

That’s the reason the most common metric for software development is “WTF/minute,” as illustrated in the cartoon below by Thom Holwerda.

WTF/minutes metric

Despite being funny, it raises the question how should I measure quality then? Well, there isn’t a right answer to that. However, we’re going to present some ideas.

Here is a list of useful metrics that you may measure. They are simple to calculate and there are automated tools to calculate all of them (there are plenty more if you google for it).

Measuring code complexity

How easy it is to change is directly related to its quality. So, the first health metrics should be related to its complexity. Calculating complexity can be hard, but there are a lot of open-source tools available for this purpose.

It consists of calculating the number of possible lines executed in all possible arrangements for a given piece of code (or throughout your entire code base).

The formula is Cyclomatic Complexy = Edges – Nodes + (Nodes with exit point * 2). Let’s see it in practice. Given the following code, written in Ruby using ActiveModel API:

post_changes = post.changes || {}
if post_changes.present?
  post.save!
  ChangeHistory.create!(entity: 'Post', entity_id: post.id, changes: post_changes)
  if post_changes[:slug].present?
    Redirect.create!(
      path: post_changes[:slug].first,
      redirect_to: post_changes[:slug].last
    )
  end
end

The following image shows the code as nodes and edges of a graph that represents the path of its execution. Notice that it ignores the fact that bang methods (methods ended with an exclamation mark) may raise an exception and it would stop the execution.

In this case, the complexity would be:

Number of edges (E) = 9
Number of nodes (N) = 8
Nodes with exit points (P) = 3

Applying the formula: N - E + (P * 2)
Cyclomatic Complexity = 9 - 8 + (3 * 2)
Cyclomatic Complexity = 7

OK. Now, let’s try to reduce this number by refactoring the code. A good strategy is to reduce the number of nested ifs, and we could achieve this by the following piece of code:

post_changes = post.changes || {}
post.save!

if post_changes.present?
  ChangeHistory.create!(entity: 'Post', entity_id: post.id, changes: post_changes)
end
if post_changes[:slug].present?
  Redirect.create!(
    path: post_changes[:slug].first,
    redirect_to: post_changes[:slug].last
  )
end

Just by looking at this code, one could say it is easier to understand and more comfortable to change. Its intention is way more straightforward: save the post, create a ChangeHistory record if post attributes changed and then, if slug has changed, create a redirect rule. Here’s the graph:

Now let’s do the math:

Number of edges (E) = 8
Number of nodes (N) = 8
Nodes with exit points (P) = 2

Applying the formula: N - E + (P * 2)
Cyclomatic Complexity = 8 - 8 + (2 * 2)
Cyclomatic Complexity = 4

As we see, the second piece of code, although it does the same job, is less complicated than the first one. That’s how measurement can help your code to be easier to understand: by knowing where are the most complicated points. So you and your team can work on it until it is acceptable for your rules. Yeah, acceptable is the word, since there is not a magic number. To determine it, you need first to understand your context.

A good practice is: doesn’t matter which number, keep the job on lessening it.

Measuring code smells

Back in history, when we would go to the woods to collect food, and there was no expiration date impressed on it, we had to use our senses. If you didn’t want to get food poisoned, you would smell it first.

That’s the same analogy to the code. It doesn’t necessarily point to a bug or a rotten piece of code. It just raises the smell that something may not be good. It can be an anti-pattern, duplicated code, bad practices, anything that could be an alert for developers.

Static Analysis can find those smells in an automated way. There are open-source linters that do an excellent job looking for them. The metric here is the absolute number of them found in your code. Very easy to calculate.

Measuring security issues

Good code is secure. That’s why it is crucial running an automated tool that looks for SQL injection, data leaks, and other potential security issues that are common in the daily development routine.

Automated tools are not perfect, and some times, they trigger false-positive alerts. However, it is no excuse for not running them. If your codebase has too many false-positive alerts, you totally should start taking these alerts more seriously.

Static analysis software looks for known patterns (like using eval or not sanitizing your data before inserting in the database) and sums them up. That’s the metric. As simple as that.

Conclusion

The concept of quality may vary depending on the company, on the product stage or even by each personal view. To keep or improve the quality of your code, you need to define what is quality for your team and measure it. There are a lot of other metrics available. Keep your quality definition in mind and look for metrics that ensure your code matches it. There already are many tools designed to maximize and engage discussions about quality. Look for them!

In this blog post, I suggested three metrics, all of them (and much more) are present in our solution. SourceLevel runs automatically for every pull request open and keeps the history of the master branch so you can raise questions about your process looking to data, not opinions.

Sign up for free and enjoy our trial for 14 days!