In the eight years that I’ve been a software developer, I’ve had my share of difficult projects.
Early on, the difficulty might have been because I was new to development and new to the code base. I had to learn my team’s conventions and how to use our internal tools.
Recently, however, I had my most stressful project so far. The initial development seemed to go well enough, but getting the quality to where it needed to be took much, much longer than anticipated. I’m now taking the opportunity to reflect on what went wrong, so I can avoid making the same mistakes in the future.
In this project, I was taking some legacy code and rewriting it to be web-based. I’ve probably done this ten times before, but this was one of our most complicated activities. It was also one of my first projects since we switched to a quarterly release cycle, which meant more pressure from deadlines.
I won’t go into more detail than that, except to say that due to the nature of my job, the quality of released code has to be very, very high. The possibility of a data integrity error – either from a programming mistake or because the user is confused – is simply not acceptable.
Part of our process is to have a design for each new piece of development, describing what we’re going to do and why. This is partially so that we can discover issues early on – much better for someone to realize that what we’re going to do won’t work before we start coding than after we’re finished – and partially so we have a record we can refer back to later if we’re not sure why we did something the way we did.
For this type of project – migrating legacy code to web – the assumption is that the functionality of the new activity will be the same as that of the old one, with the exception of changes that are explicitly called out. Usually, just listing out the changes is sufficient. In this case, however, because the activity is so complex, nobody on the team was familiar with all of it. This led to two issues:
- The quality assurance (QA) people reviewing the design weren’t fully familiar with the functionality, so they didn’t realize that there were changes they would like until they started testing the completed development. At that point, it was a lot more difficult to make the changes than it would have been if they had been brought up during the design. If the existing behavior had been fully spelled out in the design it would have been easier to catch changes we needed to make.
- Part of functional testing involved performing the same workflows in the old and new activities and ensuring that the functionality matched except where called out in the design. Since we didn’t have a complete list of functionality, testing was more difficult.
The original project plan called for 300 hours of development.
However, this project was not the only thing on my plate, and I was assigned a lot of other work that kept pushing it back. At the same time, I was under pressure to get it done so we could keep making progress towards getting rid of our legacy code.
As a result, I pushed harder than I should have, finishing the initial development in just over 100 hours. That put me way ahead of schedule, development-time-wise (if not by the calendar) but with three big problems:
- I barely had any unit tests. What I did have covered only a small fraction of the code.
- I hadn’t done enough manual testing to ensure everything worked as anticipated.
- I didn’t have a complete description of how the activity was supposed to work, so the new activity had less than 100% functionality.
As I mentioned above, we have very high quality standards, and as such every piece of new development has to undergo inspection by multiple people. This would normally mean that two other developers would review my code and two QAers would test it. Due to the complexity involved, we actually ended up with three QAers assigned to test and two unscripted testing sessions when additional QAers tried out the activity. We also had usability sessions when nontechnical people came in and tried out the new functionality.
The lack of unit tests hit me again at this stage. When doing a fix, it was difficult to ensure that I wasn’t causing any new issues – and we were still pushing hard to meet (new) deadlines. As a result, in the first round of code review and QA, we ended up with a fair number of new issues created by fixing the old ones.
Towards the end of the first round of testing, we were still finding differences between the old and new activities – an indicator that we really needed to have a detailed written description of exactly how everything worked.
This was a project in which it seemed that everything which could go wrong, did. Requirements kept changing – both technological and functional. We were constantly brushing up against (and shooting past) deadlines. Getting the quality up to expectations took much longer than anticipated.
We did have some wins. From a usability perspective, the new activity is much better than the old one, and the users who participated in usability testing were excited to see the improvements. Accessibility – which presented its own challenges – is also much improved. The process worked – while it took a long time to make the code stable, it was never in danger of being released to end users before the quality was where it needed to be.
For future projects, my takeaways were:
- Take the time to make the design as detailed as it needs to be – even if that means (as it would have in this case) spending an extra week on it. It’ll pay off in the long run.
- Test, test, test. An extra two-four weeks up front writing unit tests and doing manual testing would have easily paid for itself in the long run. I’ve been pushing unit tests for a while and I’m now getting buy-in for spending the extra time required.
- Given unrealistic deadlines, push back. I have a reputation for fixing problems quickly, especially when it’s a high-priority bug, but in this case I was simply being assigned too much work and I should have said so instead of trying to get everything done. Not pushing back enough left me stressed out as I struggled to finish everything, and quality suffered – which meant taking more time to ensure that the code was in the state it needed to be in.
I have many more projects like this to take care of over the next year (although this was the most complex). I’ll make things a lot less stressful on myself by insisting on following our best practices, even if it takes more time up front. And now I have an updated list of possible bugs to watch for in the future.
This was definitely a learning opportunity. Here’s to being better.