In the course of my time as a software developer I came to some realizations about how to write maintainable code in a professional setting. By this I mean: in long-term projects, usually for a customer, where maintainability is a key requirement. This in contrast to short-lived, personal, hobby projects where many of the requirements of a professional setting don't apply.
Layer-based Code Organization
Something that I see happening over and over again in codebases (mine included) is the tendency to "code solutions" instead of "modelling the problem".
By this I mean writing whatever code that solves the problem (makes the computer do what you intend it to do). Which is fine. Initially.
The problem is that meaning gets often lost in the process. Either partially or totally.
This makes it very difficult to understand what the code was about and adapt it to new requirements.
An alternative might be to use code to express the problem at hand. By coding and using the necessary "abstractions", a so-called "top-level" part of the codebase should be able to express the problem, while at the same instructing the computer to execute the solution.
Below that theoretical "top-level" would come the next level, which solves different problems (using the abstractions needed at that level). That level would in turn rely upon abstractions one level below it. And so on.
While this approach might need more effort and discipline it should pay long-term where projects are long-lived, involve many team members and maintainability is an issue.
De-Coupled Tests
When firstly introduced the idea of writing "unit-tests" or pieces of code that "test" your "production" code seemed alien and even a waste of time.
Decades later, practices like TDD are a well established standard. The professional projects that don't use some sort of automated testing are rather the exception.
However, in many projects (mine included) I discovered an aspect of automated testing that turned automated-tests into a liability rather than an asset.
The more tests a codebase had (usually but not necessarily resulting in more coverage) the bigger this problem presented itself: the design of the codebase became more rigid.
That is: code was increasingly more difficult to change, not less.
And that is because the way tests were written was by being tied directly to whatever design decisions happened to be present in the code being tested at that time.
This creates high-coupling not within the production code (which the experts usually advocate against and focus their energy on) but between production code and tests (which are supposed to be independent from production code and only be there to help you).
As a result, the code-design would be increasingly unable to improve. Overall the whole codebase would grow in complexity. By not being able to re-design to better abstractions the codebase would be stuck with unsuitable design decisions and abstractions.
The solution to this problem of course would be to de-couple automated-tests from implementation details. Abstractions used in production code would not appear in the test-code, but in a "bridge" / "adapter" layer.
Instead the tests would be written using an internal DSL (written only for in test-code; never in production). This would not only make tests easier to read, but allow production code to evolve 1) at all and 2) faster by being independent of test-code.
Updating the Documentation
One particular area which is usually a second class citizen in software projects is ... apart from security: documentation.
Often developers don't have time, are not given the time, or do not care (or are not told to care) about documentation.
Until it's too late and one either has no documentation or fragmented, incomplete, inconsistent or outdated one.
Because documentation does not "run" and is purely a "human" activity the solution in my opinion has to be a "human" one. But both technology and methodology can and should help.
My proposal here is to make writing or updating the corresponding documentation a requirement either at the pull-request level, or even better, at the issue level (reviewed by the customer).
That is: whenever a feature changes, whenever the code is modified, if applicable, a documentation change is also required. The issue or PR is not considered complete until the documentation is reviewed to have been changed satisfactorily.
Bonus: Security - Ownership approach
And because I mentioned it in the section before, let's also talk about security.
Security, maybe together with documentation, is an aspect that developers usually don't enjoy.
I might expand on this on a future post, but I think the approach is to appoint a security "owner" and have this person be a required reviewer of all PRs.
Final words
Hope these points here made motivated you to think about best-practices in a professional software-development setting.
I hope to be able to expand on these thoughts in the future.
Top comments (0)