Are your applications future-proof?

Code is a liability, not an asset. Often written with tight deadlines, it is expected to run for years. Time to think about the aspects of longevity.

Everything drifts, everything decays.

(/u/_someotherstufforhmm)

Aging projects can be hairy.

Cryptic code everywhere. Quirky concepts combined into a convoluted mess of software architecture. The programmers who wrote the code have long left the company.

You are the new dev on the team. Your first task is to update an obscure library that seems to be used all over the code in very… um, creative ways. Of course, the library has no unit tests, so you have to expect to break half of the library clients by just looking sharply at the code.

You silently wish for a magic wand to clean up the mess. Or for another job at another company far, far away.

Tech debt

Maybe you witnessed such a scenario already. A project team made some easy decisions in the early stages of the project, and years (or maybe only months) later, substantial technical debt has accumulated.
Tech debt, or code debt, is the implied cost of additional rework caused by choosing an easy (limited) solution now instead of using a better approach that would take longer. (Wikipedia)

A certain level of tech debt is inevitable, but how can tech debt be kept minimal from the start?

How can you make your software future-proof?

And can the choice of a programming language make a difference?

To clarify: Future-proofing of software needs to happen at multiple levels.

The organization level

A healthy software maintenance concept starts at the organizational level. If the organization as a whole does not commit to maintaining a long-living and evolving product, stop here.

Software modifications have various causes, like changing user behavior and user demands, regulations, or market demand. An organization should be prepared to react to such changes at high speed. This implies having a sufficient budget for changes.

Ensure to have all requirements documented. Have a functional specification and a technical specification. Most importantly, have processes in place that ensure these documents are kept up to date, all the time.

The architecture level

There are a lot of architectural concepts and methodologies out there, and I refrain from proposing any particular model here. All good architecture models have one aspect in common:

The separation of concerns.

Separation of concerns keeps the changing parts separate from the stable parts. In a layered architecture, for example, there is typically a domain layer that is self-containing and changes only slowly, and other layers connect the domain model to the "real world" and keep the side effects outside the domain model.

Whichever architectural model you choose, it should be tested-and-proven. Ideally, your development teams are already familiar with it.

Granted, I just wrote I do not want to propose anything in particular, but I keep hearing good things about Domain-Driven Design (DDD). For example, hear out Redditor /u/_AxBxCequalsX:

Last 7 years in Go professionally and 15 years in software in total, DDD with domain events is the answer. The layering and separation of concerns has lead my teams to the most maintainable projects over time. Where I have started a project and not invested upfront in layering or a DDD approach, 2-3 years later, we have had to invest in heavy refactoring towards DDD.

The software development level

When the point arrives of actually hacking code into the keyboard, try to be aware of the footguns.

Especially, do not try to predict the future. In no time, you will have loads of abstractions in your code that try to anticipate any possible use cases that probably never come.

Instead, focus on writing solid code for the current requirement. Avoid premature abstractions and framework-building because YAGNI—You Ain't Gonna Need It.

Write code to be thrown away.

This advice applies especially to the early stages of a project. Early code has prototype quality. Instead of investing energy in refactoring, adjusting, and bug fixing, write the next version from scratch, incorporating all the lessons learned from the prototype. 
As Fred Brooks famously points out in "The Mythical Man-Month":

Plan to throw one away; you will anyhow.

And finally, don't try to be clever. Code is much, much more often read than it is written. So focus on writing readable code, even when this means you have to write three lines instead of packing the same logic into a one-liner full of fancy shortcuts and tricks. Gimme a KISS and Keep It Simple, Stupid.

Ok, enough acronyms for now.

How Go helps create maintainable code

This is a Go blog, and I cannot end this post without massaging your brain with some Go advocacy.

Joking aside, I think Go has several properties that make it an ideal language for writing future-proof code. Here is a partial list:

Go is readable

Go is a highly readable language, mainly due to three aspects.

  1. Go has a small spec. The Go authors were as serious about what features to keep out of the language as about what features to include. The language core is kept small, which makes the language not only compile fast but also rather easy to learn, compared to other, more feature-rich languages.
  2. Go has a clear syntax. Go code is not only pleasant to look at, but it also requires little brain energy to decipher Other People's Code™, keeping the brain free to do creative work.
  3. Go ends all style discussions. A tool named gofmt is the single source of truth for styling Go code. gofmt does not only reduce white noise in Git commits but also, and more importantly, makes the code of other people easier to read.
Go is boring

Yes, Go's second name is The Boring Language.

For one part, it's because the core language is small and leaves the programmer few options to write "clever" but incomprehensible code.

For the other part, the core language is remarkably stable. Additions to the language happen rarely. This way, Go avoids the feature bloat that plagues other programming languages.

If you understand Go code written today, you'll understand Go code written five years from now.

Go takes backward compatibility seriously

Not only does the Go team keep the language core stable, but they also go to great lengths to ensure that the latest Go 1.x toolchain is backward-compatible with older code.

In other words, code written with Go 1.0 is supposed to still compile with the latest Go 1.x toolchain. (With very few exceptions. Security fixes, for example, might have to break older code, but for good reasons.)

Go uses a deterministic dependency system

Dependency management is hell. Or, more formally, it is NP-complete. This means that it is not known whether an algorithm exists that can resolve library dependencies within polynomial time. For this reason, classic dependency management systems use heuristics to calculate valid versions of dependencies.

Heuristics, however, do not guarantee deterministic results. Heuristic algorithms are doing guesswork. This leads to non-deterministic builds with all negative consequences.

Go takes a different approach. Go's Minimum Version Selection algorithm is based on a stricter set of rules and constraints than heuristic algorithms. The results are therefore predictable and consistent, helping to maintain stable builds.

Go is an ideal long-term language

All these properties make Go an ideal choice for projects that do not only need short development time and fast executables, but also stability in the long run.

Conclusion and further reading

Maintaining software over a long time requires concerted action at all levels, from the organization as a whole down to the single developer.

An aspect often neglected is that the chosen programming language can influence long-term stability and project maintenance costs considerably.

A programming language is selected for various reasons, from developer preference to performance aspects. The ability to produce long-lived, maintainable code should be high on that list.

I have written previously about the many good reasons to choose Go:

Why Go?

7 reasons for choosing Go

The Go Proverbs cover the KISS principle, clarity over cleverness, and the advantage of gofmt, among other things. Here is the original page, and here is a version with every proverb turned into a Limerick.

About the importance of paying back tech debt: We invested 10% to pay back tech debt; Here's what happened

Domain-Driven Design in Go: Too modern Go application? Building a serverless application with Google Cloud Run and Firebase (This is the first article in a long series about DDD and related concepts.)

YAGNI can even trump the DRY principle: DRY is a footgun, remember to YAGNI | Swizec Teller

Code with passion, live with passion.

Christoph


Applied Go Courses helps you get up to speed with Go without friction. Our flagship product, Master Go, is an online course with concise and intuitive lectures and many practices and exercises. Rich graphic visualizations make complex topics easy to understand and remember. Lectures are short and numerous to save your precious time and serve as quick reference material after the course. Learn more at https://appliedgo.com.

Photo by Markus Spiske on Unsplash



Categories: Maintenance