5 ms·
To be honest I didn't bother reading the article, I used to think tdd was a good idea, I don't anymore. To paraphrase the book a philosophy of software design,
by bennysomething 5y ago
To be honest I didn't bother reading the article, I used to think tdd was a good idea, I don't anymore. To paraphrase the book a philosophy of software design, tdd leads to tactical programming instead of strategic design.
The premise of doing the simplest thing to make a test go green feels a bit ridiculous to me.
- Huggernaut 5y agoCan you talk a little bit more about tactic vs strategic development? What do you find ridiculous about doing the simplest thing to make a test go green first?
- js8 5y agoNot OP, but I find TDD ridiculous as well. Thinking strategically is understanding the trade-offs of your code, and coming up with good abstractions, that take these chosen trade-offs into account. You want to express the business problem in a language that is close to the domain, and then write a translation (and sometimes the translator) to the language closer to the metal. Coming up with the correct language how to express it (the language can be as simple as recognizing that your problem is a general one on which you can apply a known algorithm) is precisely the strategic thinking. Applied top-down, it also means exhaustively understanding states of the application and major data structures (for example, database schema) and designing the functions around that. It can also be applied bottom-up, by building reusable tools and building blocks, which are independent on the global state, and then connecting them as simply as possible (this is also sometimes called hexagonal design). Both top-down and bottom-up have pros and cons. I am much in favor (instead of too much unit testing or TDD) of building better abstractions that make humans less prone to errors (these can even be DSLs), building assertions into your code that warn you something is amiss (and can also be used in property tests), and always testing against something (i.e. the test code doesn't use the same assumptions as the program it is supposed to test, which is a big issue I have with unit testing). The best parts of the code are the ones that you don't have to test - if the compiler (or your abstraction) can guarantee you an assumption about your code, then you don't need to worry about it being wrong in the first place. And in particular, the problem with "x+y in C++ can launch nuclear missiles" is not syntax, it is a weak type system (ideally you would use something like Haskell that controls the side effects). Again, using checks in a stronger language is a strategic, not a tactical decision.
- victorNicollet 5y agoI share their opinion. I understand "Do the simplest thing" to mean "ignore any additional insights you may have". If I know that a class will need to be accessed from multiple threads, I am not allowed to use locks, concurrent data structures or lock-free patterns until I add the first test that checks for multiple thread access. When that happens, I need to throw away almost all of the previous implementation. The same happens for any special data structure or algorithm that I know in advance will be necessary to solve the high-level problem, but it takes tens of tests to reach a point where the "simplest solution" no longer works. As an exercise, consider implementing a Union-Find data structure while constraining yourself to never writing a line of code that is not the simplest thing to solve the current test. My experience has been that it's better in those cases to construct a unit testing roadmap so that: - you incrementally build the correct solution - you throw away as little code as possible - throughout the process, you minimize the amount of code not covered by a test
- pjc50 5y ago> If I know that a class will need to be accessed from multiple threads, I am not allowed to use locks, concurrent data structures or lock-free patterns until I add the first test that checks for multiple thread access. When that happens, I need to throw away almost all of the previous implementation. The same happens for any special data structure or algorithm that I know in advance will be necessary to solve the high-level problem, but it takes tens of tests to reach a point where the "simplest solution" no longer works. As an exercise, consider implementing a Union-Find data structure while constraining yourself to never writing a line of code that is not the simplest thing to solve the current test. Who's imposing that constraint on you? Why? Can't you negotiate your way out of that?
- rolisz 5y agoTDD. That's the point of TDD and that's what people are arguing against. If you relax this constraint, you get something different.
- ncann 5y agoI share the same sentiment. TDD is one of those things that are really nice on paper for trivial toy code like Cat extends Animal, but quickly becomes impractical and falls apart against real world problems.
- lmm 5y agoTDD intentionally encourages a bottom-up programming style where you allow the design to emerge instead of consciously designing up-front. The argument is that this results in better solutions, which in my experience is true - https://wiki.c2.com/?WhatIsAnAdvancer https://wiki.c2.com/?WhatIsAnAdvancer kind of describes the feeling.
- cjfd 5y agoYes, I think this is true. While there have to be some big picture strategic design decisions like 'do we use a database or not' and 'is this a server/client application or not' below that very high level it quickly deteriorates into making the big decisions when you have the least information and this leads to decisions that one is probably going to regret.
- thibauts 5y agoNo. TDD is as top-down as it can be. « Test-driven development (TDD) is a software development process relying on software requirements being converted to test cases before software is fully developed, and tracking all software development by repeatedly testing the software against all test cases. This is as opposed to software being developed first and test cases created later. » [1] [1] https://en.m.wikipedia.org/wiki/Test-driven_development https://en.m.wikipedia.org/wiki/Test-driven_development
- jasfi 5y agoPerhaps it can be both bottom-up and top-down depending on your mindset.
- thibauts 5y agoThen it’s not test « driven ».
- lmm 5y agoThen what do you call it where you write programs bottom-up but still write test cases before implementation? Because that's the most common approach that I've seen being done and called TDD.
- 4140tm 5y agoYou are skipping a step there - it's red -> green -> refactor As for "tactical programming instead of strategic design" - that's entirely up to the developer and how they approach the problem. if you are prone to neglecting the design you'll do it any way. If anything, (actually) practicing TDD would force you to come up with better design.
- thibauts 5y agoHow does TDD force you to a better design ?
- afarrell 5y agoIt doesn't. It signals a particular point in time to improve the design. Good design is a creative activity and cannot really be forced, only given space to grow.
- tremon 5y agoBy forcing you to think about your code's interface before thinking about its implementation. That means you get larger components that fit together based on the problem requirements, rather than components with interfaces that are only locally optimal. You can see this same thing in action when watching functional programming live coding sessions: most live coding is bottom-up, but they still write the function signatures before writing any code.
- duped 5y agoThis strikes me as tautological. If you're using anything statically typed you have to write the function signature before it's body.
- tremon 5y agoStatic typing is not synonymous with manifest typing (always require an explicit type declaration). Many functional languages can infer the type of an expression from its definition, so explicit type signatures are optional. These languages are still statically typed though.
- thraxil 5y agoA nice clarification that I've heard from Dave Farley is that TDD doesn't necessarily imply not designing your system or architecture ahead of time. You still do that, it's just that with a more traditional approach, you take that design/architecture and begin implementing it assuming that while you may end up making some changes, it's basically correct. With TDD, you begin implementing your design/architecture just the same except that from the very beginning you assume that your naive design is probably wrong and and leave yourself open to making those large corrections as you encounter the need. He likens it to the scientific method where you start with a hypothesis and then design experiments that would disprove your hypothesis. In my experience, when you approach TDD this way, it remains extremely strategic.
- js8 5y ago> With TDD, you begin implementing your design/architecture just the same except that from the very beginning you assume that your naive design is probably wrong and and leave yourself open to making those large corrections as you encounter the need. I think the assumption that you can test your way out of bad design is false. Design is about assumptions you make, and you cannot test these assumptions, because your tests have to make the same assumptions. In other words, the problem with the scientific analogy is that in science, the model and the world are never going to be the same thing. But in TDD, the model (specification) and code are ideally the same thing. You should make sure they are the same thing and try NOT to deviate from that in the first place. What you can do, however, when refactoring, is to compare old implementation (non-efficient one, for instance) and the new implementation. That is possible, but I rarely see that in TDD approaches (modern testing focus is on one instance of code only).
- AnimalMuppet 5y ago> I think the assumption that you can test your way out of bad design is false. Design is about assumptions you make, and you cannot test these assumptions, because your tests have to make the same assumptions. I think that's right. But what TDD buys you is that, when the design changes (and it should, if it's a bad design), and you have to modify your component/class/module/whatever, you can run the tests and see if you broke anything. You can then, for each failing test, think through whether the change should have broken that (and therefore the test is now wrong), or whether your change actually broke something that had been working. This can make it far easier to make changes safely, and lets you make them with far more courage. It means you're not stuck at "we really should change that, but it might break something, so it's not worth it."
- dgb23 5y agoI agree with the book on TDD if it is the _driver_. It’s not, you are. But if you’re doing a lightweight version where you do actually design, or explore a design, and use tests to simply run your code in isolation, document usage variations and failure modes, then it’s a very useful and pragmatic tool. This doesn’t necessarily mean you should hack your code into small bits and all that. Example tests and a REPL are just a nice combination of tools for interactive programming.