Software development is exciting and dynamic, the range of different specifications you can get is extremely wide. Also, there might be a lot of repetitive work such as application monitoring, bug fixing, refactoring – which affects our productivity and takes up time that could be spent doing more interesting things.
That’s why you should focus on reusability and maintainability of each piece of code you produce. As a final product, it will reduce your software development time, you will reuse blocks that you have already tested and used while building other applications, and it will be a great tool for expanding your software development knowledge. Let’s discuss the five concepts that might be helpful in increasing component reuse as well as their maintenance.
1. Test your code – properly
Quality is never an accident; it is always the result of intelligent effort.John Ruskin
Automated testing your code will force you to improve its quality and make you a better developer. To write a clean test suite your code should be testable – it should allow for somewhat effortless testability of each of your system’s parts, independently. This fact alone will naturally force you to use elements of a clean code and you will be getting more familiar with standard industry patterns. Your test suite will save you a lot of time and increase reliability, extensibility and, consequently, the maintainability of your code – all amazing stuff!
You should never test your code manually or enter debug mode – it takes a lot time and it’s usually commonly repeatable. Sometimes you need to make a big change in your code and manually retest the majority, if not the whole application. It might take days, or even months. Automated tests run fast and they continuously prove your software is doing what it’s supposed to do.
When it comes to maintainability, you want your code to be reliable. You want to know that it’s working properly and that it won’t brake. As a developer, you want to develop various applications and tackle interesting problems, not checking logs each morning, constantly bug-fixing things, or having late night wake up calls regarding some production issues. Mastering the writing of a clean test suite is challenging and it requires time, persistence and practice, but you will get rid of lot of the usual repetitiveness – leaving you with more time to do interesting things!
In order to write a reliable test suite, you should start writing your tests before, or in the early stages of development. If you do it after you developed a certain functionality, it usually tends to be biased or not thorough enough. If you write your tests first, you should also benefit from having exactly as much code as needed- as your tests specified. This is a great mechanism to keep your code simple, easy to read, and maintainable for everyone.
Modern testing frameworks are heavily used and they are sufficient to test pretty much any application you need to develop. You can easily mock integration points which increase your development speed – you don’t need to deploy application to some server to test any change in code you did, you can do it locally, usually without starting service itself, and very fast.
You should consider your test suite as a functional specification of your application. If you have each functionality covered, your code is reliable and you can focus on tackling your next challenge. It can also be common language between you and the solution architect, production manager or some other business person you are working with. You can always show them your test results to double check if there is something you didn’t consider.
Be careful when naming your tests. Usually your test display names don’t need to be the same as the names in code, so use instructive, human readable test display names. Tests should not be dependant, they should be short, simple and runnable anywhere.
2. Modularise your applications
You can increase reusability of your software by including modules into software design. This encourages you to keep related code close together and decoupled by rest of the functionalities. You can also think of them as building blocks for your application. Each block provides a specific functionality/layer, and when they are combined together you get complete the software component. This approach is called Modular software design and it provides low coupling and high cohesion principles.
If one module requires changes, we would like those changes not to have any impact on other modules, in order to keep the change as small and encapsulated as possible – this means that modules between themselves should be independent – Low coupling. We also like to keep related code close together. It makes it easier to read, to find it when needed, and it just makes logical sense. This applies to module as well- it should contain code that provides certain functionality. This is called High cohesion and is a common feature of reusable component.
You can easily reuse modules in other applications when same functionality is needed. Modularised applications are usually more adaptable to requirement changes than monoliths, because they tend to group related code together and are decoupled from the rest of the codebase. A change in one part of the code doesn’t affect the rest of the code, so changes are usually much easier when designing modular.
Let’s consider a modularly designed application as shown on image bellow:
Api module usually doesn’t consist of business logic, it contains request and response pairs and also service interfaces your application exposes. In other words, it contains all messages that are needed by another service to integrate with your service. When other component wants to integrate with your application, it can depend only on your api module, no need to show business core logic. Keep in mind that api module should be properly documented, since other developers will use it to integrate with your service!
Core module encapsulates all the business logic your application is responsible for. It implements services exposed in api module and provides logic behind them. Server module’s responsibility is to start your application, to expose web controllers if needed, and to hold web resources that your application might have.
If your application is modular, as suggested in the example, you get some out-of-the-box features. First of all, you have a segregated api and when other component integrates with your service, the business logic of your application is hidden and only the interface to integrate with is exposed. Memory footprint of your application dependency to integrate with is much smaller, since the other component depends only on your api module.
It also decouples your service from the exact protocol it’s using to expose the web controller. Why would you couple closely to REST? You should not. You can expose the same interface over REST, SOAP, JBR or any other protocol. Concrete protocol might even be outside of your application, exposed on some facade service your company is using.
3. Introduce new layer – Peer
It’s common for a web application to follow Repository-Service pattern and expose some controller so that it is accessible. Your controller is responsible for receiving external request and start processing it. It usually depends on service or even better, interface service implements. Service is doing some business logic, usually checking constraints, mapping data to representations different than request your controller receives. It usually depends on the interface that the repository implements.
The side effect, however, cpould be polluting your service with too much logic, conditional branching and code duplications. Introduce one more layer- let’s call it Peer – and use it to segregate validation logic from your business processing logic. Its logical place is between controller and repository.
Check all preconditions that need to be checked in Peer itself, and don’t even start processing it if any of the required conditions are not met. This wa, your service layer is cleaner, more maintainable and contains preferably only business logic. Your Peer layer should be responsible for only one task- validation of all necessary conditions for processing to be successful.
Maybe it includes some type checks, mapping between different data representations or checking data consistency- do all this in Peer. It will provide you with a clear segregation between service business logic and custom backend data validation.
4. Develop your common library – use generics
When you are developing an application, take a look at its functional specification. If one is not provided for you, write one yourself and get the client’s or product manager’s approval before writing a single line of code. Requirements should be clear if you want to develop high quality, maintainable software.
Take a close look at your specification and consider what features your application should provide. Generalise each feature you can, to make it reusable. Don’t repeat yourself and don’t ‘reinvent the wheel’. If you have already implemented such or a similar feature, it’s usually a clear sign that it is a feature you might repeatedly need in future applications. If you haven’t implemented such a feature yet, try to generalise your implementation and place it in your common library.
Enrich your library with new components and further generalise each of the components with every subsequent project it will be be reused in!
Generics are a powerful tool when generalising, so you should use them heavily in your common library. You are creating building blocks that you can use whenever you need some previously implemented functionality. It speeds up not only the development process, but also the testing phase, since the same components are being reused between projects, they have already been tested, are reliable and working.
Service integration is a common case in enterprise applications. In one application it was required to integrate with some other application via SOAP interface. In another application, you might need to integrate with different one via REST and it additionally requires authorisation. In case you don’t want to start all over again, or duplicate code you have already written, you should consider implementing your IntegrationClient and put it in your common library.
“I wouldn’t like to take way all the happiness from your implementation of IntegrationClient, by giving you the implementation details, but let’s at least talk high level about it. So our client needs to be able do communicate over different protocols and have the ability to turn on authorisation feature. Separate configuration from implementation so we can place our IntegrationClient in common library and reuse it when needed.
Whenever a new application that you are developing needs to integrate with other components you will have it already implemented and ready to reuse. Reliability, development speed and certainty that the component will work, are some of the great benefits of reusing. You won’t always be able to see in advance all of the functional requirements your IntegrationClient needs to have, but it shouldn’t discourage you from implementing one in common library and expanding it with certain functionality when needed.
Another common example is working with files. If that is a common feature your applications need, why not implement your common file reader, which supports parsing header, body and footer of your file, transforming rows into your entities? It can also provide configuration for batch processing, or working with zip files, or any other functionality your domain requires.
5. Read literature
Reading enables us to see through the eyes of others and trains the mind to be flexible. You should always strive to become a better developer by learning more about specific technologies, and books are great way to further acknowledge other developers’ ideas and learn from their experiences. There is a vast amount of software development- related literature, but let’s have a look at three titles which will give you further insight into the development of higher quality and more maintainable applications.
Clean Code A Handbook of Agile Software Craftsmanship-Prentice Hall – Robert C. Martin (2008)
Code is just a representation of developer’s thoughts, and it is often hard to read other person’s thoughts. That’s why you should take extra care when it comes to code readability. Reading this book, you will find out more about code readability, common practices and conventions, technical debt and how to reduce it.
Head First Design Patterns – Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra(2014, O’Reilly)
From the first computers that appeared around 180 years ago, the first programming languages (some 80 years ago), up to today, many applications were developed by different developers. Those applications share some common functionalities and provide multiple solutions to the same problems. Giving multiple implementations for the same issue, this book collects the best solutions for common problems and proposes design patterns. It focuses on providing solutions that are easily maintainable and extendable.
Clean Architecture – A Craftsman’s Guide to Software Structure and Design – Prentice Hall Robert C. Martin (2017)
Software design is one of the most important skills developer should have. It comes mainly with experience, but this book can provide an interesting playground for brainstorming about software architecture. It describes software architectural principles and will open new perspective looking into software development.