Cheating faster development by doing everything wrong

Most software development teams I’ve worked with purport to follow some kind of agile methodology. These methodologies typically focus on two things: quality assurance and planning. While these practices can lead to robust systems, they can consume a lot of time and resources — luxuries that small development teams and startups might not have. However, there’s an uncommon path that can work surprisingly well.

I worked with a small team that ignored most traditional methodologies and still managed to deliver successful products. They did virtually no testing, no release planning, no feature planning, and no collaboration. Each of these might sound unprofessional, but it allowed the team to deliver a product that was well received by their users and helped the business thrive.

Best practices ignored

Testing

This team spent hardly any resources on testing. There were some unit tests for the more abstract parts of the code, but there were no integration tests, no acceptance tests, and no end-to-end tests. There was not even a test environment other than the developer laptops. After a feature or bug fix worked on the developer’s machine, no further testing was done.

When developers skip testing, you users become your testers. This requires some way for users to provide quick and easy feedback. The developers need to be able to respond immediately to this feedback.

This works best when the users are not paying customers, as users need to dedicate time to provide feedback. In this team, the users were employees of the business, and they were expected to provide feedback on the software they used. They shared an office with their developers, so they could instantly provide feedback or ask for assistance. Users would typically get deeply involved in the development process, and had a sense of ownership over the product they relied on.

Release cycles

In scrum, multi-week cycles are the norm, which often dictates the minimum iteration time. This team focused on rapid iteration. They would release new features as soon as they were ready, and they would fix bugs as soon as they were reported. This sometimes meant that they could iterate multiple times in a single day.

To do this, the team agreed on working towards a minimum viable product as soon as possible, and then do a production release. This meant that they would release as soon as their work provided some value to the user. That way, subsequent polishing of said feature could take into account the user feedback.

Building what the user wants

A small team needs to be able to decline feature requests, otherwise they would get bogged down with major feature requests that might not bring as much value as several of smaller features combined. When a feature request was declined, the team would help the user to find a workaround. Often, the development team would help brainstorm ways to adjust business processes, acting as business consultants on top of their role as developer.

Maintaining a backlog

Many traditional methodologies use a backlog to do long term planning, but this team didn’t do that. They would just start working on a feature the business needed right now. The team lead would regularly discuss with the business owners what they felt would produce the most value. In this conversation, the team lead would estimate the amount of effort and risk involved. After that, the team lead and business owners would agree on a good balance between effort, risk and value.

Feature requests that were not actively worked on, were not tracked. The users would have some sort of wish list they communicated to the business owners.

Collaboration

This team didn’t collaborate much on features. They would just work on their own feature, merge their code, and then deploy. They didn’t pair program, they didn’t do code reviews, and they didn’t do sprint planning. While there was a weekly meeting to discuss progress, that meeting was mostly a social event, very little actual work was done in that meeting.

That’s not to say they didn’t communicate. They would often ask each other for help, and they would often discuss the best way to implement a feature. They would request features from each other according to their expertise, and they would often help each other out when they were stuck. But in the end, each developer took responsibility for their own work, and carried it from ticket to final deployment.

Prerequisites

Of course, this way of working does not come for free. To pull this off, a few prerequisites need to be in place.

A good team

The team needs to be able to communicate well, both internally and with users. Team members need to be able to work independently, but they also need to be able to ask for help when they need it. Most of all, the team members need to be able to handle criticism well, as this way of working will often lead to a lot of negative feedback.

This team was not a team of “rock stars”. Apart from the team lead, all of the developers started out as juniors, while they were still in university. Junior developers were assigned the same kind of tasks as more experienced developers (except during on-boarding), and they were expected to deliver the same quality of work. This gave them the opportunity to grow their skills quickly. While starting out as juniors, all of the developers grew beyond junior level in relatively little time.

The team lead was a very experienced developer, with over 20 years of experience in several IT roles. His role was mostly to facilitate the team, and to make sure that the team could keep moving forward. He also picked up particularly challenging, sensitive or urgent features, he would often refactor code to make it more maintainable, and he would make sure to remove as many hurdles as he could.

Paying off technical debt

This way of working will lead to a lot of technical debt. A startup may postpone the technical debt until VC money comes in, but other teams will need to address this on a regular basis. In this team, technical debt was mostly addressed by the team lead. He would often refactor code, and clean up code that was no longer in use. Common patterns were refactored into library functions, and those would receive unit tests. That way, there was a strong core library that the team could build upon.

Fast and reliable deployment

Iterating quickly requires rapid deployments. This team had a continuous integration pipeline in place. A merge request would take about 5 minutes to be in production. Actual deployment to production was manual, but it was easy and fast. This allowed developers to do several iterations in a single day, and gave users the confidence that any issues that blocked their workflow could be fixed quickly enough that they could wait for it.

The CI consisted mostly of static analysis tools, and the team member’s editors were typically configured to do most of the same static analysis. While static analysis is not an alternative to testing, it can help catch problems early.

Metrics and monitoring

Users were usually only the second source of feedback. By the time the user had figured out that the feature was not working as expected, the developer would probably already have seen this in the reporting. This team used Sentry and Prometheus to monitor their applications. They would get alerts when something went wrong, and they would be able to quickly respond to issues.

The code contained quite a bit of logging, metrics and assertions. This way, the developers could quickly see what was going on, and they could quickly fix issues.

A good relationship with the business and users

These fast iterations have a significant impact on the user’s schedule. They need to reserve a non-trivial amount of time to provide the feedback that drives the development cycle. Having users and developers share an office helped a lot. The users could just walk over to the developers and discuss a feature, and the developers could just walk over to the users and ask for feedback.

Backups

Frequent backups, and good tools to access those is crucial, due to the increased risks of data loss bugs. A backup procedure that requires length diff reconstruction is really not suitable. While backups weren’t consulted all that often, when they were, their convenient format (SQL dumps and plain file copies) would allow a developer to quickly fix any data losses that occurred due to bugs.

Downsides

Of course, breaking all the rules has its downsides. This way of working is not for everyone, and it has some significant drawbacks.

Developer stress

The fast release cycle meant that developers had to switch tasks quickly and frequently. Not everyone is comfortable handling the frequent task-switching that comes with this style of work. The business and the team lead made sure that other sources of stress (e.g. work environment, deadlines) are minimized.

Business impact

This way of working has some profound impact on the business. The business not only needs to be able to allocate user time to provide feedback, but they need to be able to handle downtime. The business owners were okay with this, as they felt that the benefits of the fast pace of development outweighed the other costs. The nature of the business allowed for under 99% uptime, far less than many other businesses would be OK with. This business did not have any special certifications they needed to adhere to, so they could afford to take this risk.

Short horizon

This way of working is very focused on the short term. The team was incapable of delivering features that would take more than a few months of work to deliver value. This meant that they had to be very careful with engaging big features, often looking for detours in the development cycle to get users involved earlier.

Security risks

The code produced with this development method is more likely to contain security issues, making this less suitable for internet-facing code. This risk can be reduced by using frameworks that prevent common security issues, a heuristic-based web application firewall, and “belt-and-suspenders” development attitude. In this team, the lead developer had significant cyber security expertise, which allowed him to embed defensive measures into the core of the application, so that a single vulnerability would typically not be enough to exploit the entire application.

Conclusion

This post just described lean development. However, reading this in a book is quite different from doing it. I’ve seen companies claim they are doing lean development, but in this team, I actually saw it done.

Lean software development is not for everyone. It only fits a business where the users and the developers are in close contact, and where the business can support the fast pace of development. But if you can make it work, it can be a very effective way of working, and will accelerate your development speed significantly.

Vanity RSA public key

If you want to give me SSH access to your server, use this public key (line breaks were added to not mess up the formatting):

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQ
+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/
+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/
+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/
+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/+Ondergetekende+/
K4CVktnyUNz5qrKzT3kJgXfktU9ufFqVLD/GqlDCV+5swUZohICigzegEE10B …

Read More

False test coverage

Code coverage is a tool used to make sure that the test suite actually tests the entire application. It does this by executing the test suite, and checking which parts of the code get used during the tests. Code that isn’t executed during the test, is marked as uncovered …

Read More

Lazy sorting in Python

Lazy sorting in python

A common need when working with data, is to find the top 10 in a larger list. An easy solution is to use the built-in sort function to sort the entire list, and then just take the first 10 items. While this works, it feels wasteful …

Read More