Categories
Software Engineering

Experimenting in a legacy code base

I work on what could be called a “legacy code base”. We’ve just crossed the 10 year anniversary of the first commit. Between then and now over 40 developers have contributed. Many features have come and gone, and the platform we develop for has changed beyond recognition and so have our ways of writing code.

Because of these reasons, we have a vibrant, frustrating, yet interesting code base. Over the past three or so years we have systematically refactored and improved it, but we have a lot further to go. We’re in a place where we can start to think about adopting new technologies to modernise our code base.

Using the newest technologies available to us has a lot of benefits the biggest for me is that developers get the satisfaction of using the newest and greatest tools.

But before we can adopt new technologies we have to make sure that developers have a shared understanding of how to use the new technologies and what we want to achieve by adopting it.

The best way to do this is to experiment.

When we experiment with code we learn new ways to write code, and more often than not, we learn why our previous ways writing code aren’t as good as we thought!

Legacy code bases come with a cost. You are surrounded with code full of history and reasons why you just can’t change it, and often there aren’t a lot of tests to make you feel safe! To make matters worse, you can’t just add a new technology for the sake of it – that’s the kind of thing that gives you a legacy code base in the first place. This combination makes experimenting a tricky thing in legacy code bases.

So how do we experiement in a legacy code base? Here are some ideas that I have been trying to adopt over the last six months:

Create disposable or small applications to demo your ideas. You aren’t constrained by your legacy code base and you can move quicker. Don’t forget that you will ultimately be integrating into a larger code base. If you can reuse these applications it is even better, keep them stored in a separate repository so you can use code reviews to explain your experiments to colleagues.

Create a “bleeding edge” application (better name pending), this is an app that can be used to incubate new technology before folding it into your main code base. Think Google Inbox and Google Gmail. If you can roll these changes out to users you get a better idea of what works and what doesn’t.

Design your code base with small modules and strong boundaries defined by abstract types. You can peacefully change your implementations of one module without impacting the rest of your application.

Each of the above options are just different ways of saying that you need to find somewhere outside, or inside, of your code base that allows your to make changes without those reprecussions being felt elsewhere.

When you’ve finished experimenting, you will know how best to propagate your changes safely into the rest of the code base without creating more legacy code.

Categories
Software Engineering

To abstract or not to abstract

The longer I’ve written software the more I debate with myself about whether I should be adding an abstraction or not adding an abstraction.


Let us define an abstraction, it could be an interface, a trait, a protocol, or an abstract class. It is a structure that defines how a piece of code should interact with the outside. But not how that interaction is handled.


Abstractions are a powerful tool, but they should be used appropriately. They are powerful at the boundaries of your code but introduce too much indirection when used overzealously.


A good abstraction lets a developer switch out an implementation without any effort. A bad abstraction finds a developer clicking through many files trying to a lot of information in their head.


I have a few rules of thumb that I try to follow:

  1. An abstraction is useful when there is a genuine reason to swap out the implementation.
  1. If you are at a boundary of a key separation of concern use an interface to define that boundary.
  1. If you are designing a library use an abstraction to define a public API that can be added to or sensibly deprecated
  1. If you know your class isn’t going to be swapped out don’t use an abstraction

These are not a definitive set of rules but I find they rein me in from creating an abstraction for everything under the sun!

Categories
Software Engineering

Some thoughts on testing

Photo by Oğuzhan Akdoğan on Unsplash

I associate a number of things with writing test code.

The first is finding peace of mind. In years gone by I have written some dodgy code that has gone to production, I still think about some of this code to this day. I still write dodgy code, but I’m able to stop it from going to production with a superpower I have gained. That superpower is to write tests for my code and mostly stop that code from being released (crashlytics will sometimes disagree). A good set of tests should be enough to give me confidence that what I have written actually works.

Testing is the quickest way to validate your code. As an app developer running a suite of tests from your IDE, in a matter of seconds, is far quicker than navigating to the relevant screen in your app and then doing a sequence of actions to find out your code doesn’t work! 

The code you write to test code is a good indicator of the complexity of the code you have written; large test functions, repeated test code or long lists of dependencies all indicate that your test code is a bit complex. I try to let this guide me when I am writing code. 

I’m not going to tell you I know how to write proper tests, because I don’t and have a long way to go before I start to write good tests. However, over the past few years I’ve started to pick up a few things and form some – hopefully useful – opinions. 

Concise test naming

Don’t be too descriptive, get to the point quickly, and make sure the name matches what is in the body of the test. You and a colleague will need: to review this code or refer back to tests in the future. Make your tests easy to understand now and avoid regret in the future. 

I like to imagine non-technical colleagues might want to read a report on test coverage and then share it to other teams. If you think your tests are easy to understand you are going in the right direction. If you aren’t sure, why not ask someone else?

I think testing code with a small public API helps keep your test names concise. The more API you have to test, the more words you need to describe what you are testing. If you really can’t avoid a large public API, split your tests into a number of different files focusing on a particular method or function of that API to help reduce potential confusion. 

Consistent structure for tests

If you are working on code in the same project you will want to see consistency across in the code that is written. If every test has a familiar structure your or a colleague won’t have to spend time getting up to speed with the general shape of the code, you can just get on with the testing. 

I think this fits nicely alongside the idea that your test code will inform you of the complexity of the code you are testing; if your tests are consistently different or hard to understand you should probably change the code you have written. 

White box or black box?

For the longest time I was an advocate for white box testing; I wanted to know that my tests rigorously tested internals of the code I have written, this is great for my peace of mind. However, changing the tiniest implementation detail would cause a butterfly effect of failing tests through the entire test code base. This is alarming and stressful for whoever is making a change, not a good developer experience! This has led me to becoming a fan of black box testing.

I do think white box testing is helpful, I think it is a great way to help understand difficult to understand code. Writing tests verifying the behaviour of different parts of this code can help you to understand what is happening. I like to think of it as writing notes “this function does this.. And causes this to happen..” the best thing is those notes will tell you if you are right or wrong as soon as you execute them!

Nowadays, I like to just test and output for a given set of inputs. It is a much nicer developer experience and your test code is less intimidating to look at. I also think it has helped to inform how I write code. I try to ensure any function returns something that can easily be used in a test and ensure there are no hidden side effects.

The three points above are things I regularly think of when I write tests. They certainly aren’t a recipe for cooking up the perfect tests but they do help me write better tests bit by bit. 

Categories
Software Engineering

Slice don’t Splice

Photo by Juja Han on Unsplash

This weekend I’ve spent some time working on a side project written TypeScript, I’ve never used it before so I’ve spent a lot of time referring to documentation and learning a lot. One thing stood out.

I had an array of data that I wanted to create a sub-list of elements starting from and index, i, to a range. You can do this by calling array.slice:

“Extracts a section of the array and returns the new array”

https://www.tutorialsteacher.com/typescript/typescript-array

Typescript, or the underlying Javascript also has a function that adds or removes elements from an array. This is called array.splice:

Adds or removes elements from the array

https://www.tutorialsteacher.com/typescript/typescript-array

I suppose I don’t need to go into detail about what caused me to write this blog post, but I have some lessons:

  • Pay close detail to the functions you are writing or selecting from autocomplete.
  • Test every single piece of code you change, even if you think you are making a small change
  • Unit testing isn’t always enough to catch issues, especially when your unit is manipulating data being passed into it

I’d also like to call out the concept of immutability. This would have saved a stupid developer from a stupid mistake.