Working Code vs Maintainable Code

You write code that works. Wow!

Developed according to the requirements, unit tests passed (you wrote it, so they are bound to pass, right? :D ) , QA verified, operational in production environment and your happy PM brings pizza for you. As a software engineer, is it sufficient to write code that just works? How prepared do you think your code is to meet upcoming changes?

Software cannot stay as it is, because the requirements that defined the software has an firm attribute called “CHANGE”.

The working code that you wrote will definitely be facing these changes sooner or later. Among the many changes, you may have to remove an existing logic completely and introduce new flows, integrate a new client library because the current one is having performance issues in production, database model changes and many more. It will be you (fortunately or unfortunately) or another poor developer who will have to do this. As Charles Darwin quotes above, in order for your software project to survive, your software needs to be more responsive to the changes coming through, without being a hassle to whoever is developing it later.

Generally, in any field there are many techniques, patterns, concepts, best practices which may look like boring academic stuff to you, but have you ever wondered why they exist? They are all born to this world to solve specific problems. They were invented by people who had gone through the pain of doing things without them. Now you already have the solutions. Learn them and structure your code appropriately and you will see the benefits when you have to make changes to your code.

In this post I will present a few tips which I hope will act as eye openers that helps you to convert your working code into a maintainable (smart) code.

Think Before You Code

Most developers just start coding right after they had the user requirements made available to them. Don’t try to be that, “I just get the job done, its either my way or the highway” kind of guy. Start typing and committing a whole bunch of classes is not going to show your productivity. A good Software Engineer always thinks about the after effects before he writes the code. He asks “what if, why this way” kind of questions from himself before confirming the approach. Some developers just start coding without a plan, and then when the changes come, end up with a lot of spaghetti if-else flows, lengthy methods, strange illogical dependencies and stuff which are far worse than that.

Good engineers always looks at the solutions from many design aspects, be it abstraction, re-usability, modularization, configuration isolation, integration and many more. They pose question to themselves in order to find the best design for their code. For example:

  • Can this method/entity be used commonly by other classes? Then take it to an abstract layer.
  • What are the areas that will be frequently changed in the design? Then encapsulate those so that changes will not affect other areas.
  • Does this sound like a design pattern I learned before?
  • What if the client wants something that way in the future?

If you don’t take time to think and design your code, you are missing the most thrilling part in Software Engineering. Trust me. Its an Art!

SOLID Principles

Almost every developer knows OOP but where do they use them? Mostly in their interviews. Using an OOP language like Java, C# will not make your design Object Oriented. If the project is well organized using the right OOP concepts in the right manner, developers will love to code.

SOLID principles are a subset of many principles promoted by Robert C. Martin. All of them are equally important but I would say every software engineer MUST know at least the first two principles, which are the roots for many design patterns out there.

SRP — Single Responsibility Principle

A class should have one and only one reason to change, meaning that a class should only have one job.

This concept is bound with the terms, Coupling and Cohesion.

Cohesion is a measure that defines the degree of intra-dependability within elements of a module. Higher the cohesion, better the program design.

Coupling is a measure that defines the level of inter-dependability among modules of a program; the level that one entity needs to interact with the other modules to do it’s job. Lower the coupling, better the program design.

Look at the responsibility of a class as a reason for change. Do you have to change your class for many reasons? Then your class sounds like it has more than one thing to take care of. Think about redesigning and refactoring them to smaller, highly cohesive classes before its too late.

Also, name your classes with care to shout the single responsibility it has. If you can’t just tell what a class does by looking at it’s name, then that smells of bad code. Rename and refactor.

OCP — Open/Closed Principle

Objects or entities should be open for extension, but closed for modification.

This states that if you have to add new features to your code in maintenance phases, it should be done without touching existing code. New features should be added as extensions. (Implementing an existing interface for new entity etc.) If your existing code is not touched, then you have a guarantee that existing behaviors will not change due to this new integration of code.

Before applying this principle you should carefully look at the design to identify the areas that will change frequently and the areas that will not. There are good design patterns which helps you to achieve OCP, like the Strategy pattern.

LSP — Liskov Substitution Principle

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.


Well yeah, sometimes definitions make simple things more complicated. Let’s see what LSP actually is.

When we do the static designs, we come up with inheritance trees, with IS-A relationships entities. What this principle states is that a derived class should always be a perfect substitute for it’s super type. Someone should not be surprised by the results when they use a sub type and expect super type behavior.

Rectangle — Square problem is a classic example for a violation of this principle.

public class Rectangle {
    private int height;
    private int width;
    public void setWidth(int widthVal){
	width= widthVal;
    public void setHeight(int heightVal){
	height = heightVal;

    public int getWidth(){
	return width;

    public int getHeight(){
	return height;
    public int calculateArea {
        return height * width;
public class Square extends Rectangle {
    //Setters overrides to keep the promise for square.
    public void setWidth(int widthVal){
	width= widthVal;
        height = widthVal;    
    public void setHeight(int heightVal){
	height = heightVal;
        width= heightVal;	

Now what will happen if someone had a Square object referred by its super type (substitute) Rectangle, and set the width/height set as 5/10? When he calls the calculateArea() method on Square… BOOM!!!

ISP — Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

This principle states that clients should not be forced to implement interface methods they don’t intend to use. Instead of one fat interface, many small interfaces are preferred based on groups of methods, each one serving one sub module.

If your design already has such fat interfaces, consider using a design pattern like Adapter pattern for Segregation.

DIP — Dependency Inversion Principle

Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.

Let’s understand this with a classic example that Robert Martins used in his article.

Think about a simple copying program which copies characters from keyboard to printer. The copy entity directly used the low level functions of the keyboard and printer to read and write characters. What if we want to add more readers and writers, and change them in runtime? And suppose the Copy entity is a critical class which has a lot of business logic in it. Changing Copy is more error prone too.

A good design would have separated the keyboard and printer with an abstraction layer (interface) with appropriate high level methods. When introducing this layer of abstraction, since the high level modules contain the complex logic they should not depend on the low level modules. So the new abstraction layer should not be created based on low level module functions but low level modules are to be created based on the abstraction layer.

(As I see, most projects start to violate these SOLID principles around the time when they expand heavily. May be after the first few live releases. You throw away your class diagrams, ER diagrams and do quick fixes just to get the job done. Finally you mess your precious design. So be careful on what you do. Bad designs are big pains!)

Refactor Phobia

Stop following bad practices you see in code bases. Be brave and refactor those into better code. That’s why developers code in IDEs and not in a notepad. Find references and refactor safely and document it so others also know of the change. Let’s take a simple scenario to explain this.

Your company has a messy project and suppose you are now allocated to that project. It has the below method to read all “Jobs” which are in progress in its Job repository (Dao).

Collection<Job> readAllJobsInProgress();

This method nicely returns all the jobs in progress. Now you need another method to get completed jobs. You go through the code, see the above method, follow the pattern and you add the below crappy method.

Collection<Job> readAllJobsCompleted();

If you have to get cancelled jobs, go create another crappy method readAllJobsCancelled(). What if you were asked to sort jobs by updated time, descending order.? Do you change all methods? And what if more job statuses were introduced later? Are you going to add more methods like this?

Don’t ever do this. The first time you see this code, what you should do is refactor the method to accept job status as a parameter as given below. That way that next developer won’t repeat the bad practice. Plus it’s worth it because reading jobs by status is centralized now.

Collection<Job> readAllJobsByStatus(JobStatus status);

I have seen most developers add the second reusable method but they don’t refactor and remove above junk methods because they are afraid to do so. Change this attitude and refactor safely. It is less likely that you will be given time to “Refactor the code” by your managers. So do it in parallel, then and there, whenever you see wrong code patterns.

Learn Anti patterns

As a software engineer you learn lot of design patterns. That’s good. But are you aware of Anti patterns as well? The patterns that may go wrong in long ride, without even noticing?

Lot of projects build anti patterns without even knowing and removing them later is almost impossible. Then developers have to go through all sorts of difficulties during the maintenance phases. I’m not going into detail about anti patterns here, but I wanted to list it here as food for thought.

Read Other Codes

Reading other developers code is a great way to learn new patterns and tools very fast. There are tons of open source codes available out there. Well written by experienced high end engineers and architects. I personally have learned a lot of Java shortcuts, new features and techniques by reading open source code bases like Spring framework. You also will learn the tricks and tips that others have done to protect the maintainability of their code.

Great professionals are always inspired by others!


Try to write maintainable code. Not just working code. It takes additional time and effort. But your code will be more flexible to changes, in return. This way there won’t be any maintenance nightmares and your happy PM will keep on bringing more pizza for you and the team!

Priyan Perera Associate Tech Lead September 15, 2017