Jonathan Boccara's blog

6 Tips to Make Your Development Project More Likely to Succeed

Published February 12, 2021 - 0 Comments

With my team we finished a large project that consisted in replacing an old technology by standard C++ patterns across our codebase.

This was the largest project I’ve ever worked on. We were the four of us and it took us several weeks to root out all the usages of this old technology. In the end, we managed to decommission it in the estimated time, and our code is now much more expressive.

Although the project was a resounding success, several things could have made it fail.

After the completion of the project we made a retrospective session, where we reflected on what we did that helped us achieve this challenging project, but also on what had set us back and could have made the project a failure.

You’ll find those reflections synthesised into the following 6 takeaways. They’re not specific to this project and I think they’re good practices for any development project to follow.

1. Don’t let the same error happen twice

Since our code changes spanned widely across our codebase, many tests came out red. Making a test red is not a problem in itself. After all, if we weren’t able to break them, tests would be useless.

What matters more is how quickly we can analyse red tests and fix them. When several hundreds of tests are red, you want to spend as much time as possible to analyse and fix each of them.

One way to do that is to make sure not to analyse the same type of root cause twice. Indeed, one given root cause could be repeated in the code, and be responsible for several tests being red. Saving the repeated analysis then saves some precious time.

Whenever you fix a bug, try and think how to make sure the same problem hasn’t happened somewhere else. And if possible, detected this automatically.

Compile time detection

The most efficient automatic detection is at compile time.

For example, at one intermediary step of the development, we had some void* pointers coming from the old technology that we had to pass to a function:

void functionExpectingVoidStar(void* pointer)
{
    // ...
}

We discovered once by analysing a broken test that a call site passed by mistake a wrong pointer, that happened to be typed. And since in C++ all pointers convert implicitly to a void*, the function accepted it the typed pointer we shouldn’t have passed it.

After fixing the bug by passing the void* in this context, we changed to interface to this:

template<typename T>
void functionExpectingVoidStar(T* pointer) = delete;

void functionExpectingVoidStar(void* pointer)
{
    // ...
}

This way, the interface no longer accepts typed pointers (in general you want to use typed pointers rather than void*, but in this case we had void* objects to work with in the first place).

When rebuilding the code, all the other call sites that passed wrong pointers bubbled up in compilation errors. Fixing them at this point fixed the corresponding broken tests with no extra analysis time required.

Runtime detection

One great way to detect errors at runtime is to use asserts. A broken assert is a blessing: it gives you the stack where the problem is happening on a silver plate.

If you can’t detect a bug at compile time, use an assert to detect it as early as possible as runtime.

2. Don’t expect too much of your dev plan

Large projects require preparation. In this particular project we had spent hours brainstorming about the best way to tackle it, how to break it down into small deliverables, and we had tried to anticipate issue and to anticipate how to solve those issues.

This analysis helped us craft our dev plan: the successive steps that were to lead the code from where it was to where we wanted it to be.

And then we started the project.

During the first steps, the dev plan guided us fairly well. But at some point, the reality of the code didn’t match what we had planned.

In this case, the thing to do immediately (I wish I did it more quickly) is to consider changing the dev plan. Or throw it away altogether if the reality is too far away from it.

As Sir Arthur Conan Doyle makes it say to Sherlock Holmes: “It is a capital mistake to theorize before one has data. Insensibly one begins to twist facts to suit theories, instead of theories to suit facts.”

There is psychological resistance against abandoning a dev plan we spent so much time crafting, and that we based on to provide estimates to the management. But if the dev plan was based on our vision of the code at the time, and that the code is in fact more complex, then the best course of action is to change the plan.

And with legacy code, the code is always more complex that you think it is. So don’t expect your dev plan to be a guide taking you by the hand to a destination. See it more like a general direction into unmapped territory.

3. Verify your hypotheses

Like we just saw, we shouldn’t expect the dev plan to guide us smoothly to the end of the project. But some things can increase the life expectancy of a dev plan. One of them is to check hypotheses.

A dev plan is a thought experiment: you plan the changes in code you will execute to reach an objective. To do this you make hypotheses.

There are at least two types of hypotheses: how the code is now, and how changes will affect it.

If you’re familiar with the codebase, you may be tempted to consider those hypotheses as facts. The dev plan builds on those hypotheses and if they turn out to be wrong, the dev plan crumbles.

So to give the best shot to your dev plan and to your project, check your hypotheses as much as possible.

To a large extent, you can check how the code is now. However, checking how changes will affect it is more challenging: if you start changing the code to check, then you’re starting the project itself.

One compromise is to make some targeted changes in a branch that you’ll throw away. This way you can poke at the code and see how it reacts, as well as the effect of your experiments on the tests.

Two of my structuring hypotheses turned out to be wrong in our development. I’m lucky we adapted quickly when we realised that. Since then, I’m much more careful to check my hypotheses when planning.

4. Don’t let stuff build up

We programmers love to code. And it’s easy to get carried away into coding, and focus on improving the code for days in a row.

But there are other things than code to monitor. For example:

  • code reviews of the changes made by other developers on the project,
  • tests,
  • CI errors,

Be sure to treat those at least on a daily basis. Letting code reviews accumulate leads to frustration for everyone: the authors are waiting for feedback on their changes, and the reviewers end up facing a mountain of code to review.

For other reasons, letting code accumulate without making sure the tests are green makes everything more complicated. Changes checked in recently over code that had failing tests are blocked because of those tests. And the wider the span of commits with red tests, the harder it is to pinpoint the root causes of the regressions.

Have a good hygiene for tests, code reviews, CI errors, and the like. Treat them on a regular basis, and don’t let them build up.

5. Communicate intensely and asynchronously

One of the things that made our project move forward quickly is the intense communication between the members of our team.

An interesting note is that we never saw each other during the project: it started after the coronavirus outbreak and we were working from home.

We’re using Microsoft Teams, and we created a channel dedicated to the project. You can do the same type of channel with Slack too.

The advantage a channel over email is to have all the information located at the same place. Microsoft Teams also allows to have one or more spreadsheets in a Microsoft Excel embedded in the channel itself, in the form of a tab, which is quite convenient.

And the advantage of a channel over sitting next to each other and talking is that we interrupt each other less: you can always finish what you’re doing before checking the notifications of the channel.

6. Check in and rebase often

Having several developers working on the same codebase is challenging. Indeed, if two people modify the same line of code, there is a merge conflict. And if one changes the code that the other calls, then there could be passing tests locally for every one, and overall a break in the tests.

There is no magic to solve those problems, but one way to mitigate them is to avoid as much as possible to work on stale versions of the code.

Check in your code often, so that others can work on top of your modifications, and not concurrently. And rebase your code often, so that you have in local the latest changes of your fellow developers.

Share your tips now

Those are the 6 tips that made the most difference for my team, on this project. I hope they’ll be useful to you too.

Could you share the tips that made a difference for your team, on a recent project? Leave them a comment below.

You will also like

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin