The point doesn't follow from the headline. The argument isn't that unit tests are bad per se, it's that good code has fewer testable abstractions, and thus requires fewer unit tests.
Which is basically isomorphic to "good code is tight code". Duh.
The argument is that good code [...] requires fewer unit tests
Great point, my mistake. Title and headline updated (was: Good code has few unit tests). I really wish it were "duh", but measuring code quality by "unit tests per line" [1] is frighteningly common, and seductive in its simplicity and intuition.
1. Not necessarily directly. It might be in the form of "I'll add more unit tests to improve this code" (unit tests either cover an interface or they don't; going in to 'add more unit tests' to 'improve' code means you didn't define a clear enough interface to begin with). Or "our open source project has more unit tests than another" (but I won't point fingers).
But "unit tests per line" is a good metric. Why? Because programmers (well, at least I) hate writing unit tests. If you have a high unit-tests-per-line ratio, then writing one fewer line of code will let you avoid writing several lines of unit tests.
The easiest way to bump your unit-tests-per-line metric is to delete lines of code. That's a positive thing, in my book.
Well, if you abstract properly, you often wind up with fewer lines of code at the end, and certainly more predictable and easy-to-modify code. Am I really defending this? What's the opposing view, spaghetti code? :)
No, the opposing viewpoint to "more abstractions" is "better abstractions". ;-)
There are a surprisingly large number of programmers who learn "Oh yay, I can use abstractions to simplify this bit of code" and then turn that into "I think I'll make abstractions out of every bit of code". I was once one of them. In the process, they turn something that could've been a simple program, one that you could fit into your head, into an Enterprise Behemoth that you can't modify without implementing a half dozen interfaces and touching 30 files.
There's a cost to abstraction - it's (usually) extra code, it makes it harder to follow the logic of the program, and it makes your program less flexible in directions other than the intended use. So don't do it unless it actually gains you something in simpler code. Programs can become spaghetti-like through IFactoryFactories just as easily as they can become spaghetti-like through switch statements.
Also on Hacker News at the moment and fairly relevant:
I'm not forced to program at all. I do it because its most always held my interest. I've passed my 10,000 hour mark years ago. So for those that program well and do so by choice, why can't we spend our time focusing on defining good clean interfaces on the "real" code instead of the tests?
Well, don't get me wrong, I've seen my share of codebases with "Iface" and "Impl" classes (ew), and stupid, counterproductive unit tests that were done to check off boxes on a PM's checklist rather than improve stability. My favorite was a test to ensure that some static method somewhere returned the same hard coded string every time. The method was one line long, 'return "foo"; '.
But. If you've defined nice clean interfaces, and a particular module has complex behavior that will have to evolve over time, then adding tests to certify that that behavior is correct shouldn't be too much of a chore -- and they save your bacon when you need to change it and track any regressions afterwards.
Which is basically isomorphic to "good code is tight code". Duh.