Innovation in legacy projects Aug 6 2018
Let me start by stating that all code is legacy code, the difference is in how we perceive it, and like with everything else, is only a matter of perception. There is more focus on the "new" but working on something new is not the only path to innovation. I’ve heard people wanting to work on the edge of the field bringing new development and improvements to the industry. Well, here is the trick, innovation is not tied only to new projects, innovation and improvements can occur on already existing projects, the only limitation to innovation is how we approach each problem.
Table of Contents
- Forgotten code
- Innovating
- Steps on how to approach an unmaintained project:
- 1. Assess the current state of the project.
- 2. Clearly define the characteristics you wish the project had:
- 3. Define the steps to get you there
- 1. Have a complete test suite that can validate that the application and engines work as intended:
- 2. Have conventions on how we want our developers to work
- 3. Make it easy to deploy:
- 4. Have a way to show the state of our project to validate that all of our metrics are OK
- 5. Have clear documentation
- Now what is next? How do we proceed from here?
Forgotten code
When we are given an old project we generally refer to it as legacy code, and some people even see this as a drawback or as something to avoid if you want to have fun. But the fun and the zest is only lost when we ourselves lose sight of the global scope of our project. Generally, legacy code has been running for some time, which in some sense shows the success it has had. With this change in perspective, we can approach a project that we would normally view as boring as a new challenge. Let's ask ourselves, how are we going to make this already successful project into something better? That can help us find the drive for improving an already running project.
Innovating
But what happens when what we are given is something unmaintained for years? What if we are assigned to a project that lacks documentation and probably with no tests to speak of? It might seem like a monumental task, almost impossible and probably full of boring little jobs to do. Let us see how can we approach this problem.
First, we need to recognize that all code is legacy code, the text editor you are using, the operating system you are using, the drivers for your keyboard, the file system, everything you are currently using is legacy code, every program built today is the legacy of tomorrow. Every code needs maintenance, and more important all code can be refactored, there is no one-time written never improved code. We might have seen code that lacks maintenance, some code that has not been touched for years, but that doesn’t mean that the code can’t be refactored in a different way. All code can be refactored and changed in different ways, a line of code can be expressed in different languages, with different styles, with different purposes, optimised for different hardware, etc. if it hasn’t change normally is not because it can’t be changed, it is most likely because the trade-off between changing it is not worth the changes, but this is a totally different matter.
Back to our problem, what to do when we are presented with a big unmaintained project. As we discussed we can change it from its current state and refactor it to evolve into something new and exciting, we are the ones that will decide how that evolution will come to be, and that on its own is a matter of excitement. Because we can decide how to change it, we can improve it in such a way that it will use all the new programming trends and even add artificial intelligence to it, if that is what we think will benefit the project. The crux is on how this changes will come to be, for example, if we want the changes to take place in 24hrs we have to be realistic that this is most likely impossible, the only project we can be sure will be using the new technologies and trends of today is a project we start today, so we have to be aware that this will take some time.
We can see now that we can add all the new features we want, we just need patience and time. The question is, how do we do this? How can we go about changing an unmaintained project into a project we are excited about. Well, here is how I would go about it:
Steps on how to approach an unmaintained project:
- Assess the current state of the project
- Clearly define the characteristics you wish the project had (where you want to take it).
- Define the steps to get you there.
That seems over simplistic, but it is straightforward, let me show you with an example. Let us say I’ve been put in charge of a project that we can safely define as legacy code in any sense of the word. I’ll use that project to show how each step would look like.
1. Assess the current state of the project.
Write down what is the current state of the project, for example:
Our project provides a repository for storing places of interest, we then provide tools to add metadata to this places in order to do queries and associations between places and metadata.
The characteristics of the project:
- One Ruby on Rails application
- 14 engines
- No documentation
- No test suite
- No defined workflows and conventions
- No continuous integration/ delivery
That wasn't that bad, plus it gives us a general idea of the state of the current project. I think that a simple approach like this will be easier to follow than trying to come up with a long and super rigid document that tries to capture all and every aspect of the project. If a task is too big to do in 30 minutes we will probably put it off for later or never.
2. Clearly define the characteristics you wish the project had:
When a project ends up in an unkempt state, like this project, everything takes longer than it should and the code becomes brittle, no one want’s to touch anything because it might break something else and many things have to be reinvented each time we need to make a change or add a new feature, so lets define the State we want the project to be.
- Have a complete test suite that can validate that the application and engines work as intended.
- Have conventions on how we want our developers to work
- Make it easy to deploy, and remove as many manual tasks as possible (Continuous delivery)
- Have a way to show the state of our project to validate that all of our metrics are OK
- Have clear documentation
3. Define the steps to get you there
We want to define steps that we can follow and will get us to where we want our project to be. For this to be useful and for us not to end up lost in overthinking, we should define a set of steps that are the minimum requirement to get us started. After we have the minimum requirement, we should define and commit to one last step, that last step is going to be to just comeback to this process and improve it. Let's define the steps for every goal we defined in the previous section:
1. Have a complete test suite that can validate that the application and engines work as intended:
First step:
Build the most basic suite of tests for each engine, just a general test suite that ensures that our tests can be run and that we can verify at least one method on each of our gems. We want to start somewhere if we were to define the perfect test suite we won’t be able to do it and more importantly we won't do it, but I think it is safe to say that we can complete this simple task in a short amount of time. And not forget to add the final step, that is to come back and iterate.
Next step:
Commit that every new code we will implement we will follow TDD and write our tests first and then write our code
Bonus step:
Commit to adding tests for any code we refactor. This way we can keep improving the code and one day we will end up having all of our code fully tested.
2. Have conventions on how we want our developers to work
First step:
Write down the git workflow we are going to use, this way it is clear how we are going to add new features and how we are going to fix bugs. For example, let us say we are going to start using pull requests, so write down:
Use git pull-requests and enforce it, even if it is just you opening and closing your own pull-requests, what we want is to build the habit.
Next Step:
Every time we find ourselves wanting to break away from the convention, review the current conventions to see if it is useful to change them, add a counter of how many times you have been confronted with this scenario when you find that this scenario repeats more than 5 times, then rewrite your conventions so you are sure this is something that really needs addressing and it is not just an edge case.
3. Make it easy to deploy:
First step:
Create a script to automate our deploy process. If you find yourself reaching for a set of commands you always use and you notice that it is a repeatable task just build a script to do it. Let's say we run the following commands all the time we deploy the application:
1
2
3
4
5
6
git pull origin master
bundle install --development
rake assets:clean RAILS_ENV=production
rake assets:precompile RAILS_ENV=production
rake tmp:clear RAILS_ENV=production
touch tmp/restart.txt
Imagine that we do this every single time! This can be automated to a simple bash script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash
skip_assets=0
if [ ! -z $1 ] ; then
skip_assets=1
fi
git pull
if [ $? -eq 0 ] ; then
bundle install --deployment
if [ $? -eq 0 ] ; then
if [ $skip_assets -eq 0 ] ; then
rake assets:clean RAILS_ENV=production && \
rake assets:precompile RAILS_ENV=production && \
rake tmp:clear RAILS_ENV=production
if [ ! $? -eq 0 ] ; then
echo "Error on Assets clean or Precompile"
fi
fi
echo "touching"
touch tmp/restart.txt
fi
fi
Next steps:
Create a setup script or container with our application, this will simplify the process of on boarding a new developer to the team. If we don't have any type of automated environment we would have to pair program with the new developer and go about installing the full stack on their computer doing all the configurations etc, if we for example create a Docker Image (or any other method for generating our application environments) we can easily just send this Image to the new developer and tell him to docker-compose up
or run this script source our_project_setup.sh
Bonus Steps:
Integrate our test suites with the deployment to have a CI, research Travis CI, Circle CI, or any other CI/CD provider.
4. Have a way to show the state of our project to validate that all of our metrics are OK
We need a way to know the state of our application, we need to know how fast or how slow our application is, we need to have benchmarks and profilers. But for now, let's start simple and go from there. It would be nice to have a dashboard where we can check the status of our application but that would take a lot of time, instead, lets again take little steps until we reach our goal.
First step:
Add google analytics to the application it is easy and at least will give us an idea of the traffic of our app
Next step:
Check our logging system, how are we logging our servers, if we don’t have a clear logging system implement one
Bonus step:
Integrate an analytics software and error monitoring software to our application.
5. Have clear documentation
This part is very important and there is not much to say about it, we need to document what we have, and try to keep that documentation up to date.
First step:
Create a document that describes what our application does and in general terms what are the parts that constitute it.
Next step:
Commit to creating documentation for any feature we add and add documentation of any changes we make.
Bonus step:
Make sure that all the commits for our version control system are meaningful and we can look at them and know why a change was introduced.
Now what is next? How do we proceed from here?
The work we have done is very useful, but, it will amount to nothing if we don't commit to taking action. How would I suggest we go about it?
All monumental tasks start with the first step, we will never have time to implement all this, but we will have time to do a small task every week. So I would recommend at least completing one of our first steps in each category at least one per week. In 5 weeks we will have a better system than what we would ever have if we had attempted to build the perfect system from the beginning. After those 5 weeks, I would suggest, go through this process again, find your next step and go from there, iterate! I think the best approach to anything is iteration, perfection is an illusion but improvement is something we can obtain.
This is just an example, but I hope it illustrates how by taking small steps to our goal we will reach it in due time, and probably faster than trying to come up with something perfect.
Let me know if you have any questions or tips on how you approach legacy code/projects.