Hacker News new | ask | show | jobs
by cjfd 2184 days ago
You should immediately stop designing for future use cases.

Use TDD and only write only just enough code for the currently known required functionality. When you get to know more required functionality the tests protect you from breaking existing functionality and you can extend your code to support the new use cases as well. At this point you should make your code just generic enough to support all known use cases without code duplication or too much boilerplate code. If you can support functionality in more than one way you can decide what way to choose based on what you expect in the future. But choosing the most simple solution trumps attempting to future proof your code. It turns out that predicting the future is quite hard and there will be new feature requests that nobody had foreseen and code that has been made as generic as possible will not handle this well.

Spiderman says that with great power comes great responsibility. The converse also holds true. With great responsibility comes great power. You cannot just pick the easy part of TDD, be irresponsible and expect to have any power. The less easy parts of writing a test for every use case and of refactoring all the time make the practice of not attempting to guess the future possible. If you leave out the prerequisites the end result will not be so very pleasant.

1 comments

TDD has costs. It's expensive and only works on certain types of systems. It also makes exploration of the problem domain extremely costly and makes refactoring a nightmare. I dislike TDD full stop but there are some domains where it's unambiguously a bad idea.

It implies:

- Behaviour can and should be compartmentalised

- Certain types of efficiency are ignorable

- Data structures are better off being relatively simple

- Behaviour is able to be understood before the system emerges

- The system doesn't fundamentally need a lot of mutable state

- Dispersing functionality across small, atomic functions (that obscure sequential flow and state mutation) is good for the code

- It is easy to extract the functionality of this system into pure functions

- A high-level veiw of the system is unnecessary (!)

- In this system, most bugs will come from small units, not interactions between units

- Behaviour of units is likely to be relatively unchanging

- Refactoring primarily happens between interfaces, not to them

- Test rigging is cheap and easy at every level of abstraction, or at least that it's better to contort your system into a structure where that's true

None of these things are a given.

The only thing that somewhat makes sense is that during exploration TDD may not always be the most practical. It immediately starts to be extremely practical once the exploratory phase is over.

I kind of feel you live in some kind of alternate universe. None of these sound true to me, at all. The most important misunderstanding that seems to be going on here is the assumption that in TDD it is a given that one is testing single classes and/or methods. This is not the case. In fact, in most cases it is much more beneficial to test a set of classes/methods at the same time in a way that is representative of something that the customer values.

Honestly, I have seen TDD work so well in so many different circumstances that I have started to consider people who do not do not write test for most possible scenarios as being on the not-so-very professional side of things.