… OK the title is click bait. I’m fascinated by the idea that writing titles in this way is more effective than just something more mundane. Apparently including a number makes it more interesting than just “Some techniques for writing more maintainable code”. Hey, you clicked, it clearly works 😛
It’s pretty easy when you write software to write code that is hard to maintain. In this post we’ll discuss a few techniques to help. Writing maintainable code is an art form. It takes time to make headway. Just add techniques one at a time and over time your personal style will evolve. Don’t rush it.
Why do we need to write maintainable code anyway?
This might seem like an odd question. But think about it. Once your code is working it… works. Who cares what it looks like under the hood? Well, the only person who cares is someone who’s gonna have to fix it.
- Requirements of the software may change due to a variety of factors. Not least of which is that the world changes and your user base changes. If you’ve written code that is very rigid and difficult to adapt to the changed requirements, you or your successors on the project are not gonna be happy. They or you will be sad. There may be blood, sweat, toil and tears.
- Bugs in the software will crop up as your users put it to work. You need to be able to identify the problem quickly and accurately, and get a fix out as fast as possible. A well-structured code base can help with that. Writing well structured code helps to prevent bugs in the first place.
- It’s not always gonna be you maintaining the software. Sooner or later you’ll move on to other things. Your client’s whole business may be based around the software you wrote though, they don’t have the option to move on. Code that you wrote may last 20 years. Writing well structured code makes it easy for someone else to pick up where you left off.
What are the characteristics of maintainable code?
Well, it’s pretty much what we just talked about.
- The code is flexible and easy to change. This is one of the hardest things to get right when writing software.
- The code is easy to understand for anyone who’s reading it. Getting this right is also quite hard.
Both of these requirements can be met by structuring the code appropriately. There are many books about how to do this. preço do ivermectina na ultrafarma One of the best (but also densely written and difficult to understand and apply) is Domain Driven Design 2nd Edition by Eric Evans.
The basic principles are not so hard to understand, the devil is always in the detail.
Rule 0: Do not prematurely optimize, continuously refactor.
Basically, your main focus should always be getting the software written and working. Once you’ve created some butt-ugly monstrosity that works then you can go looking for ways to make it more beautiful.
The issue is that it’s easy to make incorrect choices when you’re writing code. Especially architecture choices – if you go and impose some inappropriate structure on the code it is absolutely much worse than writing a bowl of spaghetti. Instead of some all-tied-together bowl of mush that’s difficult to change and understand, you’ll have an AbstractGizmoFactory sending FindNewParrot commands to the Wakalixer, which is impossible to understand.
Instead of dreaming up the perfect architecture for your program before you write it, the correct approach is to make a basic skeleton that works and then refactor as you go along. Don’t be afraid to edit, throw away and completely restructure code. You have version control, if you mess it up you can always throw away the refactor and start over.
The other thing to bear in mind is refactor continuously. Don’t make spaghetti and then add more spaghetti. You take stuff apart and leave bits all over the place but once you’ve fixed whatever you were working on, you must tidy up. Otherwise by the end of the project you will have a giant ball of string and you won’t have any idea where to start unravelling it.
It’s more important to learn and apply refactoring techniques than stringently follow good programming practices at the time of writing the code. Note that I said “more important” – the bathwater does in fact contain a baby.
YAGNI and DRY
YAGNI stands for “You aren’t gonna need it”. Basically it means, don’t over-engineer stuff. It’s easy as engineers to want to make things as shiny and beautiful as possible, and to cater for all kinds of situations. Don’t do it. Make the code do what it needs to do and nothing more – unless you can get an easy win, then do that. But don’t be tempted to spend lots of time making things perfect or making the code extensible in ways that may never be used in real life. Make the code flexible so that it’s easy to extend it if you need to, but don’t engineer it so that it’s littered with already-in-place extension points which may never be used. It adds time to development and makes the code harder for others to understand. Especially don’t add unneeded functionality. You don’t need to make phone calls with a microwave. Adding functionality just because it seems easy to do and like something the users will like is what we in the industry call a very bad idea.
DRY stands for “Don’t repeat yourself”. If you are doing something twice in the code, think about making one function and calling it from two different places. The reason for this is simple. If when you change it in one place it has to change in the other, then changing one function called from multiple places is way less error prone than finding all the places where you use the functionality and changing them individually.
These principles sound so dumb and basic that you’d think they wouldn’t need to be stated. Just… laugh at yourself the next time you violate them, because we all do. Then fix it.
Break the software into a hierarchical structure of layers / units / modules that have a single responsibility each.
The model-view-controller architecture pattern splits the code into data access (model), display (view) and modification commands (controller). Within each layer you can break things down further into more specific classes. There are other ways to break down the layers but MVC works for me and is easy to understand, so it’s the one I personally keep coming back to. Incidentally do be aware that some of these terms (like Controller) are used in specific contexts with specific meanings (especially in web development). I’m talking about this from an architecture point of view.
Segregating the parts of your application into clearly defined layers helps a lot with debugging and understanding the code. If your code is all tangled together then figuring out why it’s not doing what it is supposed to can be difficult.
For example your Employee data structure lives in your data access layer, it can be displayed using an EmployeeView object that lives in your display layer. If for some reason you want to change the way you display the employee, the only class you touch is EmployeeView. Or you could create an EmployeeSummaryView that displays only a few fields of Employee, or you could create different EmployeeViews that display the information graphically and as text. The underlying data is the same, only the view changes.
You might want to update the employee’s salary – this is where you’d call an EmployeeController which would then modify the Employee data object. This sounds like overkill and for simple programs it can be, but consider what you gain by following the controller route – you can add validation easily – you can check that the salary was not accidentally updated to be negative or more than the GDP of China. If for some reason you want the capability to roll back changes, then if you’re asking the controller to make changes rather than updating the employee record directly, you can easily add some mechanism to keep track of the most recent changes and revert them if necessary. You can add history tracking. It’s pretty nifty.
Use interfaces to segregate the layers and also to segregate third party dependencies. Program to the interface and not the implementation.
Let’s say you’re writing a game. The game accepts input via keyboard and mouse, changes the game data in accordance with the input and game logic, displays stuff on the screen in response to data changes and saves stuff to a file for when the player wants to quit and resume later. where to buy ivermectin uk reddit It’s pretty clear that we’re gonna have an input layer, a data layer (model), a game logic layer (controller), a persistence layer (saving game state to file) and a display layer (view).
Let’s consider what we should do when the player presses a key to make the game character jump. Common sense would suggest that we send the key press value to the game logic and let it decide what to do in response. However that means that we’re hard coding that it’s a key press that makes the character jump. Instead we should, in the input layer, translate the key into a jump command, and that is what should be passed to the game logic. This way the game logic is independent of the specific input method. If we hook up a different input method like a screen tap on a phone to make the character jump, then we don’t have to extensively rewrite the software. If we make the input layer conform to an interface, then in order to make a new input method available all we have to do is write an interface implementation, and we can then have our game both with touch screen and keyboard input.
Now let’s say that we’re implementing the game using a specific graphics library – say OpenGL. We’ve isolated the display logic to the display layer and we’ve segregated the display layer from the rest of the code. We still can go one step further and isolate our display logic from the specific technology used to implement it. Again, this may be overkill but if we do display logic in a two step process where we have a display interface which contains commands like “draw sprite here” and a display adapter which then adapts each display command to the specific library that we’re using, then we can easily change the display library to something different without impacting the rest of the code.
Note the way we do things here. Layers interact via Interfaces, Implementations implement the interfaces for specific systems like a touch screen. Adapters make an external third party interface conform to our internal interface. Anything that is not an interface implementation only makes use of the functionality through the interface. This way we can easily swap out interface implementations, mock them for testing and so on. Interfaces are contracts. Anything that satisfies the contract works with our code. This is a very powerful, tricky idea. I’m only really coming to grips with it myself and I’ve spent years as a coder.
Understand and use dependency injection and inversion of control.
If you don’t know what these buzzwords mean then the time has come to educate yourself. They are not especially difficult concepts once you understand them but they generate a lot of hype and for some reason people think they are hard, so it can be difficult to sort out what you should pay attention to.
An object has dependencies. For example, your GameLogic controller needs to access the DataStorage to get data and to access the View to send stuff for display. Dependency Injection simply means that somewhere in the GameLogic class you declare that you have a dependency on DataStorage and View and the Inversion of Control Container takes care of supplying those dependencies.
Dependency injection – the dependencies are injected into the object from somewhere else meaning we don’t have to care where they came from.
Inversion of Control – the object no longer controls the creation of the things it depends on, the control is held by something else – the inversion of control container.
Usually Inversion of Control containers come as a library for your specific language. For example Java has Spring as an IoC container. Find out which containers are available for your language. Seriously, if you feel shaky on this go read up on it a bit, it will change your life as a coder. Start here.
Use software design patterns in your code.
Design patterns are strategies for solving particular coding problems. Examples are Publish/Subscribe, AbstractFactory and the like. If you understand and use design patterns in your code you increase maintainability because people who’ve never seen the code before can understand what it’s doing by knowing how the design pattern works. If you’ve never come across design patterns before (unlikely but possible) then do a bit of googling. It’s well worth being aware of these things.
It’s probably worth documenting via a comment or comments when you do use a design pattern, so that people can go look it up if they’re not familiar with it. Also if you use a design pattern and modify it in any way, make sure you document that.
The codebase and the problem domain should use a common language.
This is basically the entire message of Domain Driven Design. There are several nuances to this but the idea is that if you are for example writing accounting software then you should come across objects with names like “Account”, “Balance”, “Withdrawal” and “CashFlowStatement” in the code base. It’s quite easy to get this wrong and you have to be prepared to refactor mercilessly.
The issue is that as coders we are not domain experts. That means that identifying the important aspects of the problem domain which we are modelling with our code requires us to spend a lot of time with domain experts. ivermectin dosage scabies 200 micrograms 9mg Both domain experts and coders need to be able to talk about programming objects without confusion, hence the need for a common language. Another problem is that frequently domain experts don’t think like coders. Solving a problem via a program is a very specific requirement. If your industry is not super automated then you’re used to solving the problem using human means which can be fuzzy and imprecise.
The common language is a technical specification that grows out of the collaboration between domain experts and coders, and mutates as the project progresses – it’s not a static, prescribed thing. It allows both coders and domain experts to communicate precisely and clearly.
The important thing here is that the language is common. We don’t want domain experts talking about one thing and programmers talking about something different. As much as possible, it should be possible for even a non-coder to look at the code and say things like “Oh, I see, you’re adding the Parts to the Order and then sending it for Approval” rather than “What’s an AbstractOrderFactory?”
This is probably the hardest thing to master out of all these techniques and I really suggest that investing some reading time into Domain Driven Design will pay off. If not in your code then at least in interviews where you can demonstrate that you know buzzwords.
Hopefully this was useful. As always this post is purely my opinion on stuff and you’re free to disregard anything that seems dodgy 🙂