Hacker News new | ask | show | jobs
by jamieb 4389 days ago
"TDD creates bad architecture".

What is good architecture? I subscribe to the idea that we are doing software engineering and as such there are some "generally" understood principles such as SOLID, the Law of Demeter, Cyclomatic Complexity, etc that provide objective measures of "good" architecture (I apply SOLID all the way up the architecture hierarchy not just on classes).

What I've noticed is that TDD results in code that scores well against these measures, while code that scores well is easy to test (i.e. after writing the code).

Therefore, I think the argument that TDD creates bad architecture is false.

About 25 minutes into the talk we get to the crux of DHH's complaint and it is that Hexagon is an alternative to the Active Record (which he created) and the only reason Hexagon exists is to allow TDD. Hexagon requires throwing away the really useful code that is Active Record.

Hexagon appears to be an attempt to introduce sound software engineering practices (SOLID etc) into the Ruby world (with what success I do not know). Active Record and Rails in general is really useful if what you want is what it does, but sometimes its not. The implied claim that Hexagon is a bad architecture is false. The claim that Hexagon only exists to facilitate TDD is false.

"Mocks returning mocks returning mocks"

I use mocks. Fowler and Beck said on the whole they don't use them which genuinely surprised me. They cited examples of code where the test actually enforced implementation rather than purpose. I think that's probably how I wrote tests for the first few years. Code that results in mocks returning mocks returning mocks is code that is violating the Law of Demeter. Its bad code. It happens to be really hard to test, and it happens to be really hard to write tests first that way. Universally I've only ever seen tests like that when the tests were written after the code. TDD doesn't produce code like that because its easier to refactor it rather than keep digging that hole.

Mocks returning mocks returning mocks is a symptom of not doing TDD.

"My mind works differently... I have to write code first"

Spike. Problems that I don't know how to solve I spike first (I write code with no tests, or with tests only as drivers of execution). That's easy. The hard question is, "Now I have all this code, I have to throw it away and TDD it?" That's pretty hard to stick to in a business environment. I choose to write tests-after for all those pieces of code that already meet SOLID metrics, and rewrite the code (using TDD) for the pieces that don't. The pieces that don't are very difficult to write tests for after, and they also happen to be the pieces where I find bugs (for example, I'll cut and paste a bit of logic and find its wrong for one set of inputs).

"All code should have full coverage of automated tests"

All three agreed that this is the case. Fowler: "If you have a full suite of tests I don't care how you got it [TDD or not]". I don't know about you but I'm still fighting this battle. I also have to deal with teams that have a "full suite of tests" and 80% test coverage, but where every single one of those tests simply executes code. No actual "test" occurs. Indeed, in particularly memorable test, I managed to delete 70% of the lines of code and all the tests passed (including deleting the one line that was the main purpose of the method). Approximately 90% of all the tests were complete garbage: they reported success as long as the code didn't throw an exception.

4 comments

> I subscribe to the idea that we are doing software engineering and as such there are some "generally" understood principles such as SOLID, the Law of Demeter, Cyclomatic Complexity, etc that provide objective measures of "good" architecture

Cyclomatic Complexity is not a principle, it's a specific measurement of potential complexity. Law of Demeter is a guideline SOLID is an object oriented-centric set of principles.

These are 3 completely different things.

You then go on to state using these makes software more testable. Since TDD can only exist in "testable" code, TDD is good design because these measurements/guidelines/principles are good design.

It's a non-sequitur of epic proportions.

I guaran-goddamned-tee you I can write a piece of software with less cyclomatic complexity than the Linux kernel, that is not nearly as well designed. Conflating those two things is what it means to be PHB.

CC is simply a measurement of a specific type of complexity, and like all measurements, it means jack shit without context. For example. the measurement of 8 inches. Is that number high, low, or normal? Or put another way, are we measuring a mans penis, a mans leg, or a mans hand?

this is why so much software turns to shit. This right here. The process by which you make decisions. If it's a good process, you'll have a tendency to make good decisions. If not, as illustrated by the post above, you will have a tendency to make bad decisions. A series of mostly good decisions will result in acceptable to good software. And the opposite results in unacceptable to bad software.

You want to learn how to write good, stable software? Learn to examine your thought process, and be explicit in your attempt to make good decisions. Be willing, and able, to identify bad decisions, why they're bad, and what you should have done instead.

What you did was believe the conclusion (TDD is good design) and then constructed the argument for it. DO NOT DO THIS. This is the stuff of bad decisions and software design failures, and this will happen regardless of which process you subscribe to.

I'm kind of shocked by how hostile your reply is! Not even 'passionate' - it's condescending, presumptuous, and isn't especially constructive!

The only part of this post that I think really deserves a good response is

You then go on to state using these makes software more testable. Since TDD can only exist in "testable" code, TDD is good design because these measurements/guidelines/principles are good design. It's a non-sequitur of epic proportions.

That's not really what he said. He was saying that essentially there is a sort of design isomorphism - like a mathematical dual - between code test-ability and other desirable properties like compose-ability, low coupling, re-usability, and maintainability. The argument in favor of TDD says that building code out of a TDD process that requires testing means making design decisions that have this nice property of building highly compose-able code.

hostile? or blunt with a bit of dramatic flair. You'll take your pick based upon whether or not you agree with my position, that has nothing to do with me.

> That's not really what he said.

It is what he said, here's his wording, verbatim

_What I've noticed is that TDD results in code that scores well against these measures, while code that scores well is easy to test (i.e. after writing the code). Therefore, I think the argument that TDD creates bad architecture is false._

You're interpreting things that were not said.

The entire argument is a non-sequitur. It doesn't even matter if you agree with him, an honest evaluation of what he stated would result in the conclusion that the conclusion absolutely does not follow from the argument.

The only thing that can consistently produce good design is good decision making. CC doesn't tell you anything without context, SOLID can be misapplied and is very OO-centric, and the Law of Demeter isn't even a principle, it's a guideline. It's like saying 'prefer composition over inheritance unless it's a clear win'. Ok great, but that doesn't actually result in a good design, it's cautionary guidance on what tends to be the better decision.

none of these things necessarily result in good design, and none of these things are required for good design. Hell, even the idea of 'good design' is nebulous and changes from 1 project to the next, and over time w/i the same project. Good design in a mobile app where energy is of the utmost importance is not the same as good design in a scientific application where correctness and verifiability are of the utmost importance.

The problem here is that you have yet another person coming to a conclusion and then working backwards in order to justify the conclusion. This is the sort of thing that consistently results in bad design __regardless of how many acronyms you follow_.

There is absolutely nothing in those ideas that intrinsically results in good design, or even intrinsically avoids bad design. That too was a part of DHH's point, one that a lot of people seemed to miss.

The conclusion does not follow because the conclusion came first. You mischaracterizing me as angry doesn't change that, but it is another indication of a flawed thought process (that the validity of the argument somehow stems from me being angry or not). Which brings us full circle back to the sort of thought process required for good decisions.

jamieb: here's a sequence of thought that explains why I think TDD creates good architecture.

mreiland: angry rant that seems only tangentially related to jamieb's comment

me: I'm going to say jamieb has a sensible opinion on the subject, though I don't entirely agree with him. And I'm going to assume mreiland has had a bad day, but he hasn't said anything that contributes anything to my understanding of the subject, jamieb's comment or discussion in general.

It's only sensible if you don't think too hard on it.

The point is similar to DHH's point, in that a process (in this case TDD) doesn't magically give you good design. Neither does following SOLID, understanding the Law of Demeter, or being aware of the Cyclomatic Complexity of your project.

jamieb thinks TDD results in great design (the conclusion). jamieb then decides to cast around finding a reason why. jamieb comes across a series of completely unrelated things that "makes things more testable", and since they're generally associated with positiveness, concludes that TDD isn't negative.

The conclusion does not follow the rationale. The point is that this is indicative of the sort of thought process that tends to create bad designs.

Rather than trying to push for more developers to be pro/anti-TDD, we need more developers who are simply aware of the decisions they make, and the process they use to come to those decisions. That will have a far larger effect than any amount of TDD ever will.

Welcome aboard the Black Pearl Miss Turner! [1]

1. https://www.youtube.com/watch?v=b6kgS_AwuH0

Just replying to your first paragraph.

There are many generally good principles of design, the key is "generally", because when taking to extremes, they eventually conflict with each other.

Same applies to TDD, you have to know what design decisions it lead to are good, what design decisions it lead to are bad.

Cyclomatic Complexity as a measure of good architecture? Not from what I've seen. If you add internal consistency checks your CC score gets worse. (Add a check to make sure that your fast exponential always is going to return a positive number? You just supposedly made things worse.)
We have a guy on our team, let's call him Kent. He writes lots of tests, so many tests, mostly before he coded anything. Oopps. If he even prototyped a bit, he would have seen he was barking up the wrong tree. So he writes a whole battery of new tests. Now all his time is gone. He has to spend even more time. Let's call this the Martin syndrome.

We also have another colleague, he wants to have his cake and eat it. His name is David. He is now doing septagon testing. Does it benefit the project? Probably not, but it gets attention, and that's the important thing. For him.

Then finally, we have a colleague, who as always remains nameless and faceless. We never rwally notice these colleagues. They produce working code, with a few sensible tests as appropriate. They deliver for the client. They are not arching for book sales, conferences, blog spam clicks or anything else. They are the real professionals.