Broken Windows in Software Engineering

Broken Windows in Software Engineering
Photo by Pawel Czerwinski / Unsplash

The Broken Windows Effect

The broken window theory was popularised in the 1982 article by social scientists James Q. Wilson and George Kelling. The theory states that a building with a broken window left unpaired sends a message to passersby that no one cares. As a result, there will be a tendency for vandals to break a few more windows. Afterwards, graffiti and other damage appear, eventually leading to more serious crimes such as robbery and murder.

Broken Windows in Software Engineering

While the theory was used to explain decay in neighbourhoods, it can apply to anything where neglect can attract other damaging behaviour, which eventually deteriorates into something not easily repairable.

One noticeable area is software engineering. You might have seen "broken windows" in the project's source code. Poorly maintained code that doesn't follow best practices, lousy naming conventions, and fragile tests. There might be some 'hacks' comments or lots of "todo" that no one plans to address.

Poorly maintained code sends a message to other engineers that no one here cares about the code or is in charge of it. If the engineers who regularly work on it don't make an effort to keep the code clean and maintainable, why should you also?

This creates a negative feedback loop where another engineer will tend to add more lousy code to the current mess, attracting even more crappy code.

This can ultimately lead to the software becoming so messy and unmaintainable that no one can make any enhancements without requiring a big decision or risking breaking something.

Another negative side effect is good Engineers might leave as working in such an environment can be frustrating and technically unrewarding. So, in the end, the only engineers remaining are those who feel the code is "fine as is" and have no desire to improve it.

Broken windows can stem from poorly written code, shortcuts, or wrong management decisions. And it's never any single code commit that got it into this state, but a series of smaller changes over a long time.

One broken window — a badly designed piece of code, a poor management decision that the team must live with for the duration of the project — is all it takes to start the decline. If you find yourself working on a project with quite a few broken windows, it’s all too easy to slip into the mindset of “All the rest of this code is crap, I’ll just follow suit.” It doesn’t matter if the project has been fine up to this point. - The Pragmatic Programmer by Dave Thomas and Andy Hunt

But like broken windows in neighbourhoods, we can take action to prevent the codebase from becoming a software disaster. These can be done with the same approach of 'policing' the minor bad code to avoid the more severe issues.

Preventing Broken Windows

The first step is to stop continuing to commit lousy code. When making changes to the code base, always take the extra time to do things right and make sure you leave the code in a better state than you found it.

Stakeholder buy-in

Getting stakeholders and product owners on board with the direction is vital, as features can take longer to complete. One of the causes of broken windows is poor management decisions and focusing on short-term gains. You might need to explain the importance of doing things properly now can save time later and reduce technical debt.

Reasons for not fixing broken windows:

  • Cost and time to make changes increases
  • Fear of making changes due to the risk of breaking something
  • Not easy to extend or add new features
  • Risk of Engineers leaving due to frustration working with messy code
  • Codebase being unmaintainable and even the death of the software

Keep it Simple

Whilst it's good to write efficient code, don't do it to just make the code smaller, do it so that the next person can more easily maintain it. Making code logic complex or over-engineering can negatively impact the team's productivity because the code is now harder to understand and maintain.

Well named Methods and Variables

It can be easy to overlook the importance of having a good variable or method name. After all, it doesn't affect performance as the compiler will convert it to machine code. And it can sometimes be challenging to think of a good name. But without a good naming convention, it can be even harder for another engineer (or yourself in a few months) to read and understand what the code is doing. Self-explanatory methods and variables make code more intuitive as developers can generally understand the code without going through it in detail.

// Example of Bad naming. What does this process do? Does 'enrol' mean if someone wants to enrol? 

var s = new Entity() s.process(); 
if (enrol) { 
... 
}

// Better naming. Easier to read and understand 
var student= new Student() 
student.Register() 
if (isEnrolled) { 
... 
}
Good variable names

But on the other hand, don’t be obsessed with self-explanatory names as it can lead to long methods.

// Example of name that's too long void GenerateLocalStudentAttendanceReportForMiamiDevConConvention() 
{ 
... 
}
Long method names

Classes that are too large or too small

Generally, a class that is too large signals that it's doing too many things. The more responsibilities your class has, the more often you need to change it and any other code that references that class. This makes changes more complex and time-consuming and can introduce unrelated bugs.

SOLID principles are an excellent guideline for building software that is easy to maintain and extend. In particular, the 'S' in SOLID is the 'Single-responsibility Principle' and aims to reduce dependencies. If a bug is introduced with your change, it shouldn't affect other unrelated areas.

“A class should have one and only one reason to change, meaning that a class should have only one job.”  - Single responsibility principle, Robert Martin

Those who don't follow it tend to create bloated classes that can contain thousands of lines of code and make it difficult to maintain. On the other hand, those who strictly stick to it create too many small classes, making it hard to keep track of and maintain.

Too many Comments

On the surface, comments sound helpful and the more, the better. It shows that an Engineer is thinking about the next Engineer maintaining it and taking pride in documenting their work.

But while intentions are good, comments can be lies waiting to happen. For example, will the next Engineer remember to update the comment when they change the code?

A better approach is self-documenting code. Try to write the code so it's easier to read and understand, removing the need for comments. However, even though developers can understand how a particular code works, they might not understand why it was implemented in that manner. Comments can be helpful here to explain why something was done a certain way.

Duplicate Code

Because every line of code that goes into the codebase needs to be maintained, duplicated code decreases productivity. In addition, when Engineers update one part of the code, they must make the exact change to multiple locations. Then there's the risk that someone will miss one section and introduce a bug.

“it’s not a question of if you’ll forget about the multiple places to maintain, it’s when.” - The Pragmatic Programmer by Andrew Hunt, David Thomas

Duplicated code stems from programmers copying and pasting code or having a poor understanding of how to apply abstraction. Fixing duplication starts by recognising where the duplication exists. Then, you can follow the DRY (Don't Repeat Yourself) software engineering principle and extract similar code into a method, service or class.

For example, a "sending email" code snippet found in multiple locations can be moved to a single method or class called "SendEmail(address, subtitle, text)". Any future email protocol or logic changes are then isolated to one section.

Avoid Premature Optimisation

A common programmer mistake is spending time improving something that isn't needed. This results in wasted effort and extra complexity in the code and distracts the programmer from delivering value to the user faster. Furthermore, most software doesn't benefit from highly optimised code as modern CPU and RAM are powerful enough for general user usage. So instead, better use of time is to write 'OK performant code', focusing on clean and easy-to-enhance code. Then when you identify performance issues, fine-tune them.

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimisation is the root of all evil (or at least most of it) in programming.” Donald Knuth, The Art of Computer Programming

Methods too long

Long methods can be hard to read, understand and maintain. Large methods stem from having too many parameters, local variables, if statements or nested loops. The more things you need to follow, the more cognitive load you have to go through to understand the code. Long methods might also be doing more than one thing, which goes against the "Single responsibility principle" best practices. And they can be problematic for test coverage.

You can reduce methods size by using the Extract Method. Find chunks of codes or lines that work closely together, and pull them out into their own method.

If there are many parameters or variables, you might need to consider abstracting them into their own class and properties.

Code Smells & Static Code Analysis

Broken windows in code indicate a deeper problem in the application. The software itself could work fine and be bug-free, but it's difficult to read and maintain because of poor quality code.

“Smells are certain structures in the code that indicate violation of fundamental design principles and negatively impact design quality.” - Suryanarayana, Girish (November 2014). Refactoring for Software Design Smells

Static Code Analysis tools are a great way to automatically inspect the codebase for code smells, bugs and security concerns. It can also generate a report of all the issues and suggestions for addressing them. These can be the 'police' for broken windows and should be incorporated into your CI/CD to set quality standards.

Iterate and Refactor Often

If you are working on an existing application with many broken windows, don't try to fix them all immediately. It can be a waste of time, and you'll also probably make it worst. For example, there's no benefit in cleaning up a code section that works fine but is rarely updated, even though it has many broken windows. Instead, identify the most significant pain points or commonly updated code and start there.

Like doctors who promote the importance of maintaining good health rather than just treating the illness, Engineers should be looking at keeping a clean code base without broken windows. Not only do they help prevent large technical debt building, but more importantly, they make programming more enjoyable.

The Broken Window Effect in Software Engineering