6 ms·
There's definitely some good points here, but I have a huge issue with this point: > Something that is hard to test is badly designed. In my experience, TDD h
by ryanbrunner 11y ago
There's definitely some good points here, but I have a huge issue with this point:
> Something that is hard to test is badly designed.
In my experience, TDD has a natural tendency to favour decoupling at all costs, and TDD zealots will push de-coupling units as an unqualified positive. And if your metric is "good design = easily testable and low coupling" it certainly looks that way.
This mindset fails to take into consideration the fact that tightly coupled things are often simpler to understand and reason about. They're more closed to extension, sure, and harder to test in isolation, but a straightforward process that acts like a "black box" and performs a job simply in a few lines of code is often better than the class explosion that rigid TDD often encourages. And what's the value of keeping things open to extension if the only thing that is going to reasonably extend them are tests?
A good illustration made by DHH as part of one of his anti-TDD tirades is here: https://gist.github.com/dhh/4849a20d2ba89b34b201 https://gist.github.com/dhh/4849a20d2ba89b34b201
Anyone who can honestly say the latter example has a better design has a very different opinion on "good design" than I do.
- ktRolster 11y agoHis point, as phrased, is correct though, something that is hard to test is badly designed. You need to be able to test your software in some way or another. Even if you've proven its correctness, you still want to put it through some basic sanity tests (as Donald Knuth pointed out).
- sago 11y agoYes, but it misses the point of the criticism. The issue isn't whether the final product is difficult to test. It should be roughly as easy to test the final product no matter the implementation strategy. The problem is that TDD requires a royal-road of testing. No code can be written without tests for that specific piece of code. So you can only build the whole out of components that are testable in isolation. The claim is that this excludes architectures that don't provide such a step-by-step path of testing. This interacts badly with the 'make everything an API' idea. You end up with these functionally small units with over-engineered APIs rather than something more complex, more efficient, and easier to refactor as a whole.
- jskulski 11y ago> This mindset fails to take into consideration the fact that tightly coupled things are often simpler to understand and reason about You're right on a small scale. With teams or feature changes, tightly coupled things quickly breed incidental complexity. I like what you said about decoupling not being unqualified positive. It's not, it's very good, but it's costly (with a high return). It's all about trade offs. Testing, and specifically testing-first, is expensive compared. If I'm working on a critical piece of my system? Yep, worth it. TDD. Do I need to refactor something so I can reduce complexity with this change? TDD. Am I writing a quick workaround for better UX that will be removed after two days? TDD isn't appropriate. It's a essential skill to have, but not the only way to practice.
- Silhouette 11y agoWith teams or feature changes, tightly coupled things quickly breed incidental complexity. That's a big assumption. However, even if it's correct and tighter coupling does introduce some additional complexity in any given case, if the code is also significantly easier to understand as a result then it may still be quicker and more reliable to maintain it. After all, very loose coupling also breeds a kind of incidental complexity, because something still has to connect all the components.
- davidjnelson 11y agoInteresting point. How about dependency injection for oo code and composition for functional code?
- Ace17 11y agoWould you have an issue with "Something that is hard to prove correct is badly designed"?
- m_fayer 11y agoJust the frequency of certain memes... "interfaces", "design this Design that DESIGN", "composition", "too coupled", "too decoupled", all points me in the direction of excessive use of DI, indirection, and as you say - explosion of tiny "single purpose" types. I've worked on many such code bases. I don't deny the benefits in testability and extensibility, but I think the benefits have always been outweighed by the difficulty of onboarding new developers, the fact that few people manage to develop a big-picture understanding of how the whole thing fits together, and the proliferation of YAGNI-violating code that ends up being written.
- davidjnelson 11y agoIt's a great point, and a hard problem. Too early and over abstraction leads to inefficient maintenance and no abstraction leads to inefficient and error prone maintenance. I guess the trick is finding the right balance.
- aikah 11y ago> In my experience, TDD has a natural tendency to favour decoupling at all costs, and TDD zealots will push de-coupling units as an unqualified positive. And if your metric is "good design = easily testable and low coupling" it certainly looks that way. Depends on the kind of TDD. I feel like TDD as Unit Testing driven design IS a waste of time (I'm talking about unit tests first here). However integration testing first is useful. Let's say I'm writing a library(a http router). The library will obviously have an API that will be consumed by the user, well for me testing will be like writing first how I think the API should look like. For a http router it would be like : var router = new Router(); router.get("/{id}",someController); response = httpClientMock.get("/34943"); expect(response.body).toBe("Hello 34943"); It costs me very little to write that , provided the language has all the facilities to test http servers[1], and check that the result of the operation is what I expect. I don't need to test the code that will parse the path for route variables directly, yet I did write the test first. 1 : that's often the problem , languages care about syntax but don't care about developer experience and the quality of tools around the language. It's not just "a compiler and the rest is your problem",no testing should be entirely part of the language or its standard library. Testing SHOULD be made as easy as possible.
- ryanbrunner 11y agoI agree insofar as writing that sort of test prior to writing code that makes your tests pass is a good thing. I don't think that matches the popular definition of TDD though. Most TDD advocates will insist that a key facet of TDD is that NO code exists in your application without first writing a test first. Given that, you're forced either into doing the highly detailed unit tests, or having an integration suite that needs to accomodate for every potential edge case of the entire system underneath it, which for anything non-trivial will be extremely time-intensive to run.