Introduction
As a software engineer, I often need to work on an existing solution that was developed previously. And I always notice the difference between a well done Job where all details are considered and the botched work where the focus were only on that specific instant without taking into consideration the evolutions in the future.
So I'm writing this article to talk about the elements that make a solution clean and that make the ramp up of a new ressource easy.
In fact, having a clean solution is not only the responsibility of the developer. Yes the developer is a key role in that goal, but all the team should have that in mind: The validator, the integrator, the devops; the team leader ... all of them are responsible for the stability of the system. Here in this article, every section will focus on a concept that can be made by one or several roles in the team
Please feel free to suggest other points in the comments if you consider that they are important in the quality of the deliverable.
Developers should respect patterns
One mistake that several developers make, is that when they have a requirement, they directly jump into the IDE and start coding. The most part of the job of the developer is thinking, then comes coding to translate your idea into a code. Certainly, you need to think about how your deliverable will be compliant with the requirement. But also you need to ensure that you will deliver a clean and maintainable source code.
It should not only work, but it should also work and repsect the best practices. Always tell to yourself that someone else may work on your code, and you need to help him by providing a self documented and clean code.
You may also work on it again after several months or even years later, and here you will help yourself in the future and avoid blaming your memory for forgetting things. Even if you implement workarounds for any reason, comment them clearly and mention the reason for their existence.
Having a well structured and organized solution relies on several points, some of which are the following (if you have other points in mind, feel free to put them in comment).
Modularity and Reusability
Separation of concerns is one of the most important concept in programming. In fact, it means that you need to organize the components of your solution in a way that one module is responsible on one activity. For example, you need to put your logger in a separate module, your access to the database in a separate layer, etc. And it's also valid for classes, one class should have only one and single responsibility.
This separation will give your solution a certain level of modularity and it will allow you to have good visibility of your solution. You will be able also to reuse your module when needed and you won't have a duplication of code.
Extensibility
Your solution should be extensible, it means that later on time if another person wants to add features, he needs to do it without impacting the existing ones. At the level of the class, you should ensure that your class is open for extension (inheritance or implementation) and closed for updates.
For more details about the previous concepts, please refer to SOLID which is an acronym that refers to five development principles. I just tried to explain some of them in my words without going deeper as this article gives the main lines.
Logging and exception management
Generally, when I work on an existing solution, I try to run it from a functional point of view. This generally helps a better understanding of the requirement.
In an application when an exception occurs or if I don't find log files to find the traces, I will know that I will have some tasks other than building new features: which is the stabilization of the application. So while developing ensure that you have handled all the possible exceptions (even the native one when possible) and also ensure that you have a logger in a separate module.
As the logs contains a huge amount of data, they should be configurable to ensure that the information is accessible and easy to read.
Some of the elements to configure are the following: The level of logging (debug, info, error ...), the log rotation, the file's sizes, the log retention ...
Unit Tests are important
Let's be honest, most of the developers think that the unit tests are annoying and a waste of time because they need to recall the methods that they already called and used in the application. Unit tests might not be the funniest part of the development but they are very important.
The unit tests allow you to have a very good visibility on the stability of your code, they don't only help you to find the issues in your code before providing the solution to the QA team, but they also help you to see if you have some anti-patterns in your code. The more your coverage percentage increases, the more your code is stable. For C#, there are several tools that help you to have nice reports like Open Cover with ReportGenerator. To be able to test the methods or the constructors where you use abstract classes or interfaces you can Mock them using Moq or any other mocking library.
Do Code Reviews between each other
Sometimes it's called a Peer Review. When the developer does an implementation, he does it according to the elements that were provided to him. A fresh eye on the work can confirm the implementation or suggest updates as it might have new elements. The goal is not to audit each other but to share the experiences and enrich the content: Several brains think better than only one brain.
Generally, it's the team leader who coordinates between the team members to have this kind of meeting, but also it's the role of the developer to ask to have this peer review.
Configuration Management is a MUST
Actually, I don't think that there is a team who wants to deliver a project successfully without using a versioning tool like git, mercurial, svn ... But the way of using them is very important.
Some conventions between the team members should exist while working on the same repository. Some of them can be as follows:
- Every member create a separate branch to work on a new feature or a complex task. Once finished, he merges it with a common branch and tests it very carefully before pushing his changes.
- The messages of the commits should have enough details and should be relevant for the person who wants to see the commit logs of the repository
- Versions between deliveries should be incremented following a standard that is known and respected by all the team members.
- Plan sessions for complex merges between the owners of the branches to avoid a bad resolve of conflicts.
- Put Tags for every delivered version
- have a separate branch that contains only the delivered versions can be very helpful.
Continuous Integration
Continuous integration is very important to avoid surprises in the final delivery of the project. The idea is that in every iteration, a version should be delivered by the development team to the QA team.
I remember in some projects where we didn't use any automation server, we installed the development tools ( visual studio + packages ...) in the machine of the QA and we provided him the procedure to rebuild the solution. Which is (I think) a very bad Idea. The staging environment should be neutral and independent from the local environments of the team members. Here comes automation servers like Jenkins, Gitlab ...
These tools are generally deployed in a virtual machine where we install all the prerequisites to build the solution. Then a job is configured to get automatically the code from the repository and then builds a centralized artifact. This output is the reference for everyone.
Code Quality check
Let's suppose that YES you respected all the best practices and the patterns during the development, and YES as a team you synchronized and reviewed all your code.
You still need to perform a check on the code quality using an automated tool. There are tools like SonarQube that are able to verify the quality of your code and suggest fixes (to avoid eventual semantic errors)
At the first level, the tools perform analysis to ensure the stability and the maintainability of your code. But it's also possible to check if your code is secure enough or not and doesn't contain a threat of intrusion. These concepts are called SAST and DAST and they will help you harden your solution.
Document EVERY LITTLE DETAIL!
As a team, you need to have a reference document for all the steps of your project.
The specification document should be very clear and detailed, it's the reference between all the team members, the only source for the developer should be the Specification document: Not emails, Not a phone call. ONLY SPECIFICATION DOCUMENT. Why? because later when the QA will validate the solution, or when other stakeholders want to understand the features, they will not go through all your mail exchanges or phone calls. They should have only one source as a reference.
The technical documents such as the architecture, the developer guides, the build procedure, the integration guides ... also should be centralized and updated during the lifecycle of the solution.
All the details should be written. Things can be obvious to you because you were inside the project. But they may need an explanation for a newcomer. So don't hesitate to write every little thing.
Another important point in the documentation is that a section should be there to track the logs, with the author and the reason for the updates. You can find templates for every type of document that can help you to get a good structure.
So to summarize this section, you need to put in detail all the information about all the work you've done. No matter the tools you use: Confluence, SharePoint, a word document in google drive... The INFORMATION SHOULD BE THERE!
Conclusion
A successful solution is not just a delivered one. It's delivered, maintained, and long-lasting. As a team, you need to do your part well, and prepare all the elements in order that other teams do their part well also. For this, technical parts are important, but also respecting the processes is very important