I am a software engineer of 10 years and wondered what other people have found effective in stopping a codebase from degrading over time. For example, some of the issues I have noticed are:

  • large technical debt
  • loss of knowledge from companies due to personel changes
  • complex upgrade and migration strategies between product versions
  • multi site teams with different values
  • large requirement of knowledge to understand the product and component parts
  • difficult to understand design due to cumulative development over a number of years
  • large and diverse code paths
  • mixed technologies eg: oracle/sql server
  • different departments with differing levels and areas of code responsibility

My main area of interest is to learn what strategies a technical lead could employ in a new project to minimize degradation of the codebase over time?

| improve this question | | | | |
  • Ask questions in Internet? – Basilevs Dec 14 '16 at 13:12
  • 2
    That was a sarcasm. There is no way to answer this in QA format. You may ask what is the purpose of Universe with same success. – Basilevs Dec 14 '16 at 13:14
  • 1
  • 2
    This is more or less "What is software engineering?". Except for the thing about personnel changes; Peopleware discusses improving the work environment at length, so that you don't get so many personnel changes. – RemcoGerlich Dec 14 '16 at 15:02
  • 2
    To my experience, "multi site teams with different values" is the death of good code in a complex system - especially when the teams have different opinions about quality. Maintainable code in a larger code base can be most easily achieved within a small, ideally stable team where the seniors see the value in a maintainable code base. – Doc Brown Dec 14 '16 at 17:26

What are the most effective techniques to stop a codebase from becoming difficult to maintain as it grows

Obey the Law of Demeter. It teaches you that not only values in objects should be encapsulated but objects should be encapsulated by their friends.

large technical debt

Start by fixing whatever is making it larger. Killing crocs can be fun but draining the swamp is more effective.

loss of knowledge from companies due to personnel changes

Mentoring. Cross training. If you have only one go-to-guy for something it's time they trained another guy.

complex upgrade and migration strategies between product versions

Make developers do what they're asking users to do. Developers are good at automating what they care about. Get them to care about it.

multi site teams with different values

Having different values isn't a problem. Not understanding those different values could be. Visit the sites if need be.

large requirement of knowledge to understand the product and component parts difficult to understand design due to cumulative development over a number of years

Where are your requirements? If you're like most they're in the code. If you had requirements documents you trusted you could simplify around what you know you really need.

Since it's in the code the best thing to add now are regression tests that prove your application is still doing what you need it to do. With the tests in place you're now free to simplify, decouple, and modernize the design.

large and diverse code paths

Did I mention Demeter?

mixed technologies eg: oracle/sql server

If your design requires you to care who built your DB it's no wonder things have turned ugly. Kick your DB out of your domain and treat it like a plugin. Only a few things should talk to the DB directly. Did I mention Demeter?

different departments with differing levels and areas of code responsibility

...is a good thing. Do one thing and do it well. Don't interface with everyone. Talk only to your friends. Provide useful abstractions so your friends don't have to talk to your other friends. Did I mention Demeter?

| improve this answer | | | | |
  • 1
    +1 for "Killing crocs can be fun but draining the swamp is more effective." Don't kill the crocs man ;) – linuxunil Dec 14 '16 at 13:42
  • 3
    btw, did you mention Demeter? – linuxunil Dec 14 '16 at 13:43
  • it is starting to seem that the law of demeter is a big key in avoiding the mess – Dizzle Dec 14 '16 at 13:54
  • 3
    -1 for not mentioning the Law of Demeter :P – Radu Murzea Dec 14 '16 at 15:14
  • 1
    +1 for also mentioning the Law of Demeter :) – Dizzle Dec 14 '16 at 15:14
  • large technical debt

Technical debt is usually incurred in small increments. A lot of small tweaks are made to the code due to changing customer requirements, modules grow, features get added in places where they shouldn't. This happens because we don't see in the moment how the little shortcuts we take will weigh down the whole codebase after a while.

In my experience incurring technical debt is almost inevitable. Deadlines can put pressure on the project. New devs might not know how to best fit a piece of code into the codebase. The architects might not see how their original structure isn't ideal to support the new features.

What we can do, which is only possible with the right attitude from management, is to keep the technical debt manageable. Refactoring the code every once in a while to keep everything intact. We have to pay off the debt. For successful refactoring we should avoid breaking things, so this is only possible with a good unit testing environment. Which is why I would advocate doing TDD from the beginning. It gives you the safety to refactor when necessary.

  • loss of knowledge from companies due to personel changes

The only antidote to loss of knowledge is if you keep the knowledge alive. Documentation, even with the best intentions, goes stale. Often the experts don't know what someone needs to know that isn't already knee deep in their topic.

You need to make sure that your devs don't work in isolation, they need to work as a team, where every role could be taken over by another dev, should the main dev for a topic get into an accident.

To get your devs familiar with other topics, they need to actively work on them. Not just watch as the expert does his thing. Pair programming can work here. Reviews are not enough.

Documentation is of course also important. Your unit test cases mentioned above, can also serve as a less stale (because executable and always up to date) documentation. They should therefore be written in a style that you know what every test is doing.

  • complex upgrade and migration strategies between product versions

You need to plan ahead for migrations. If you introduce a new versioning tool, you need to already think how you can migrate out of it later.

  • difficult to understand design due to cumulative development over a number of years

For me this is the same as the technical debt described above.

  • different departments with differing levels and areas of code responsibility

Maybe rotations through the departments could help here.

| improve this answer | | | | |

I don't know about the "most effective", but I find the following helpful.

Abstraction is key, and comes in multiple dimensions and sizes. Abstractions should be useful and complete and allow the consumer to define and implement another abstraction in domain terms that it requires.

Technical debt means poor abstractions; abstractions that are hard to use, and, abstractions that are incomplete so as to necessitate the consuming client making up needed differences, and that by doing so ends up understanding and relying upon more of the underlying implementation than desirable, which at best results in tighter coupling and and at worst, in spaghetti and/or soup!

In increasing order of size, an abstraction can be: a single constant, a single method, a set of methods, an interface and/or class, a package of interfaces and/or classes, a layer.

Layering is about creating vertical abstractions that rest and build upon each other. When we create an abstraction that provides exactly the domain terms the next programmer up needs (to create their layer), it is effective at isolation. A layer is a set of interface and/or classes that provides a complete set of domain-oriented entities, relationships, and behaviors. Layering allows separation of concerns so that a middle layer can be redesigned without disturbing higher layers.

Creating and maintaining layering as code evolves requires constant awareness of the consuming client's domain requirements, recognition of a layer consumer's difficulties due to mismatch in domain requirements, and regular refactoring to create and keep layers useful and complete.

In the large, layering allows us to switch technologies, such as implementation languages.

We also need to be able to identify when we are crossing responsibility boundaries, such as organizational, departmental, or business boundaries. Responsibility boundaries merit special attention. We design appropriate domain-oriented abstractions at those boundaries. At boundaries we define horizontal abstractions that allow various organizations of both humans and automated systems to assemble themselves into a larger ecosystem.

At some point, as our endeavors increase in size and scope, we need to pop up from the code alone so we can speak effectively of our vertical layers and horizontal boundaries. This is architecture or architectural abstraction. Architecture helps us connect the dots between our requirements & intentions and design choices & implementations.

Centralization fights separation of concerns, which in turn fights good abstraction.

A single shared database for all persistence can be attractive for a small sized endeavor, but co-location of persistence makes it very easy for applications having different ownership and responsibilities to directly access each other's information instead of going thru an appropriate horizontal boundary.

A single shared rules engine similarly collects business logic from what are logically separate concerns and creates inter-dependencies of those rules that are difficult to tease apart later.

| improve this answer | | | | |

I think this questions the heart of our business. And many people thought and researched about it. But I dont think there is a trivial or comprehensive answer or strategy. If you target one of your issues and fix it, another one might rise up and overshadow your fix. And if you target all, in the very end, the issue that will rise is money since no one is able to pay for such a software development process.

| improve this answer | | | | |
  • Thank you for the response it seems to be a common problem I am seeing in the industry I thought if we started to discuss it perhaps some solutions and advice will surface – Dizzle Dec 14 '16 at 13:12

Not the answer you're looking for? Browse other questions tagged or ask your own question.