Previously on Locally Sourced: About a year ago, I started this newsletter with a bunch of posts that I originally called XP 2020 and later called Entropy Essays – you can find all the posts here. I got bogged down and never got to the punchline…
There’s a common thread in the story of how testing, object design, and pair programming have been adapted since XP and Agile became buzzwords. I think estimating fits here as well.
Broadly speaking, these practices are hard, and because they are hard, easier versions of them have proliferated. Although the easier versions seem similar, they require much less team effort but have, I think, much higher long term costs.
For a lot of us, building web applications in 2021, we work in a world that has been defined by terms and practices that come from the Extreme Programming and Agile movements that started in the early 2000s. Your concept of “testing”, “pair programming”, “velocity”, “standup”, “iteration”, and “continuous integration”, all come out of that movement. Object design doesn’t exactly come out of those writings – the XP term was “system metaphor” – but the same people were also writing about object design, so I’m including that.
There’s a general theme to agile practices:
- Take something that is hard, but infrequent
- Do it much more frequently, in much smaller steps
- Incrementally improve using the feedback from your faster feedback loop
Continuous integration is a clear version of this story. The process that XP was arguing against was a situation where teams worked on separate features for weeks or months and then integrated back into the main branch rarely, and with a significant amount of pain. The integrations were hard, doing them more frequently had the counter-intuitive effect of making integrations easier because they were smaller and the code changed less between them.
You can tell a similar story with testing. XP testing is a contrast to an older style team where all tests were performed by a QA team and only performed after the developers were done.
Pair programming is continuous code review. In OO design, Agile and XP practitioners argued against BDUF (Big Design Up Front), and in favor of refactoring and incrementally letting the design emerge from the tests and the code.
These practices also have the following properties (with the arguable exception of continuous integration, which is much more amenable to being fixed with tooling):
- The practice is hard and success doing the practice on one project doesn’t mean you’ll be successful the next time.
- The practice adds an in-the-moment cost to all code, with the promise of a cost savings later. (So, there’s a cost to writing tests up front, but the hope is that later change is less expensive)
- The practice is not resilient – once you slide away from doing the practice 100% of the time, it’s very hard to make up lost ground.
It’s the last point that really confounds me. We have all these potentially great practices and they are all pencils balanced on the pointy end and the slightest nudge sends them tumbling.
I can tell you how to add test coverage to a code base that doesn’t have any. I’m not sure I can tell you how to take a code base that has a one hour test suite and get it back down to ten minutes, let alone under a minute.
My best advice for legacy code bases– stop making things worse, and write all new code with faster, better tests – is a) probably naive, b) doesn’t improve things quickly, and c) just puts the pencil back on the point to fall down again.
This, I guess, assumes that you see a one hour test suite as a problem. Which, I suppose, not everybody does. I feel pretty confident, though, that a faster test suite that covers the same ground as a slower one would be preferable.
Anyway, the point: for all of these hard, not very resilient practices, a simplified version has emerged. The simplified version seems more resilient, but I strongly suspect it also has less long-term value.
This is why I started using the term “entropy”, where a low-entropy state is hard to maintain, but a high-entropy state is more stable. Good testing is high-energy and low entropy, like the pencil balanced on a point, but easily falls into the more stable state of high entropy.
For testing, the high entropy version includes mostly end-to-end tests that don’t depend on the structure of the code (as opposed to the TDD version, which has some, but not many, end-to-end tests). This is high entropy and less effort because the end-to-end tests, while they can be challenging to write, don’t also require you to engage or design the code API while you are writing them, they don’t require you to think about units, or dependencies, or your API. They do, however, still tell you if the code does what you expect. Usually.
There are similar high entropy versions for OO design (don’t do fancy OO stuff, just use the OO syntax of your language). And pairing (pair occasionally, but not so much that you can actually do knowledge transfer or such that it reduces the amount of code review you need to do).
Here’s the thing – the high entropy version of any of these could be better, for whatever “better” means. I don’t really know, and neither do you. I don’t think it is. I think writing a lot of big slow tests gets you into long term trouble. But a lot of people have looked at TDD and unit-test-based processes and said, “eh, not for me”. And some of them have wound up with hour long test-suites plagued with random failures.
Agile processes, including the ones I’m talking about here, require a very high degree of reciprocal trust in your team. Teams with power imbalances or D&I issues or just teams with jerks on them will struggle to succeed with these practices.
All the hard, low entropy processes demand trust. You’ve got to trust your team for pair programming to replace code review, and they all have to trust each other. You’ve got to trust your team to assume that time spent writing tests up-front will pay off in a more adaptable design. That trust is hard to build. Many teams will not – can not – build that trust. A large enough team may not be able to do it at all. It’s legitimately hard for me to imagine an organization with 100 developers not having some kind of high-structure, high-ceremony code review process.
It requires far less trust in your team to sort of handwave the idea that they should write some integration tests, or that they should pair program and still do a full PR review. It even looks like you are fixing the problem, at least for a while.
These high entropy solutions are attractive, by which I mean not just that people find them pretty, but they are attractors. As a team finds it unable to keep up trust, as a team can’t quite drive design from tests, the higher entropy solutions are there, and they are more resilient. They are so resilient, that it’s nearly impossible to break out of the gravitational pull of one of these solutions once you’ve started along them.
I wish I had a snappy conclusion here. I think these are hard problems, and I think there’s not enough discussion of why these processes aren’t resilient and why teams so easily drop them. (I mean, it’s possible that, for example, TDD just doesn’t work, but I’ve seen it work enough times that I find that conclusion unsatisfying as well.)
You can find the final version of this post at http://noelrappin.com/blog/2021/05/entropy-essays-8-why-entropy. Come by and add a comment – tell me what I’ve gotten wrong.