7 ms·
This... is not Kent's finest moment. I get what he's saying, but the snark doesn't really help, and doesn't actually rebut any of the points that DHH was making
by revscat 13y ago
This... is not Kent's finest moment. I get what he's saying, but the snark doesn't really help, and doesn't actually rebut any of the points that DHH was making against TDD.
Testing does tend to have a negative impact on API design. If we were to make a list of positives and negatives, this one would go in the "negative" column. Listing out TDD's positives does not invalidate that negative.
- kasey_junk 13y ago"Testing does tend to have a negative impact on API design" I think this sentiment is the single root cause of the current back in forth. In my experience this is completely untrue. For DHH and others there appears to be some sort of culture of dogmatic polluting of APIs for testing which I'm not familiar with. Everything else seems to be talking around the real issue and finger pointing.
- rhizome 13y agoTo be sure, "negative" is the weak point in the statement/sentiment. It's a value judgement subject to where the person is drawing a line between the goods and the bads (methodology always unstated), so the argument wallows in indeterminacy. As long as people throw out a simple "the real issue" without explaining what they mean by it, it's inevitable. Pretty soon, every player is talking past one another, someone writes a blog post purporting to lay out the landscape of all sides, someone counters rap-battle style, someone else posts to HN "Ask HN: your favorite reasons to TDD," which provides the foundation for an ebook, after which three people post "Show HN: site to plan an optimal TDD strategy." After that, we wait for someone to posit a successor and the cycle starts over again.
- barrkel 13y ago"Testability" tends to add unnecessary (by KISS) configurability and indirection to code composed out of sub-units. Because a testable unit of code must have its inputs and collaborators controlled, all the inputs and collaborators need to be configurable or replaceable, even when such configuration is unnecessary to the business value aimed at. In statically typed languages like Java, this tends to exhibit as a proliferation of interfaces that only have a single implementation, namespaces with too many identifiers publicly exposed, and lots of open extension points - which hurts versioning. It tends towards requiring complex IoC controllers with associated config. Code ends up filled with boilerplate glue and over-abstraction (factory-factories etc.), and much harder to read because IDEs can no longer follow the link between a method call and its implementation - because the method call is all too often dynamically resolved via an interface implementation.
- briantakita 13y ago> "Testability" tends to add unnecessary (by KISS) configurability and indirection to code composed out of sub-units. You mean "White Box Testability". Don't forget Black Box testing is agnostic to the implementation.
- revscat 13y agoLet's take as an example dependency injection. The genesis for DI was to facilitate the testing of objects that are dependent on objects that are not easily constructed, such as DAOs. And the whole reason this was done was so that these classes, the ones that have a DAO dependency, could be more easily and quickly tested. So, in order to use (constructor) DI you would have a constructor that looks like this: public InvoiceController(InvoiceDao invoiceDao) { this.invoiceDao = invoiceDao; } The key point to remember here is that this has been done so that in our unit tests we can inject a fake InvoiceDao. Now, let's say that our InvoiceDao class has one and only one constructor: public InvoiceDao() { // sets up connection parameters, etc. } Then the constructor for InvoiceController could be simplified to: public InvoiceController() { this.invoiceDao = new InvoiceDao(); } This is quite a bit cleaner from an API perspective, and that is the entire point. This is only a simple example. For more complex classes, with multiple dependencies, it really becomes cumbersome. What if InvoiceController also needed access ReporterDao? Well, then you need to add that as a parameter to the constructor as well. Your API is made more complicated, all in an effort to make testing possible. This does not, of course, invalidate the benefits of unit testing, which are many. But it does expose a negative that is not frequently acknowledged, and that's what DHH is talking about, and what Kent has failed to address.
- kasey_junk 13y ago"The genesis for DI was to facilitate the testing of objects that are dependent on objects that are not easily constructed, such as DAOs. And the whole reason this was done was so that these classes, the ones that have a DAO dependency, could be more easily and quickly tested." I disagree with this central premise. If you have introduced DI simply for testing reasons than you have missed the point. The reason for DI is that a very common point of change with software is at the interaction between clients and services. Tight coupling at this point of change is usually a design smell. That DI enables automated testing is a by product of it being loosely coupled not the point of the loose coupling. That TDD drives you to this sort of solution, all other things being equal, is one of the advantages of TDD. If DI has become cumbersome and is making your code hard to reason about/read/maintain etc then blame DI, not the testing. You also might want to consider the higher level architecture of your solution if you are finding yourself with lots of complicated dependency chains.
- DrJokepu 13y agoIf you don't think that the AngularJS API is Vietnam then I don't think I'd be able to change your mind regarding this.
- kasey_junk 13y agoI don't know the first thing about AngularJS.
- ghayes 13y agoYou're implying that AngularJS's API is Vietnam? I get that the DI system is complicated, but in my mind, it's a very well thought out approach which relies minimally on global objects. I'd take Angular's approach over jQuery's any day.
- DrJokepu 13y agoYes, I am. It might be clever but it's unintuitive and as a consequence developers (even otherwise experienced ones) who have not yet mastered AngularJS will write the kind of mess that is painful to clean up later. This is more or less true for any technology, but as a general rule the more complicated or different the concepts are, the less likely it is that people will get things right the first few times. And this gets worse as the application gets larger and / or more people get added to the team. A simpler approach with less cleverness would help.
- tunesmith 13y agoThis is exactly an argument I am having at work right now, from someone that hates dependency injection. If I have a controller that calls a service, and a service that calls a dao, then the service needs the dao injected: public Service(Dao dao) { this.dao = dao } Which means (in the absence of autowiring) that the controller needs to be "aware" of the dao: ... service = new Service(new Dao()); result = service.serviceMethod(param1, param2); ... And that is objectionable because of putting too much of a burden on people writing controller logic, because of separation of concerns, etc. I am finding it difficult to argue against that point. I get that this goes away if using a proper inversion of control container so one can autowire, but that is not always practical.
- dragonwriter 13y ago> And that is objectionable because of putting too much of a burden on people writing controller logic, because of separation of concerns, etc. No, the controller needs to be aware of a method of creating new services. Depending on the specific language and other constraints, that might be a factory function passed into the controller's constructor, a factory object passed into the controller's constructor, a refernce to a service locator object, or some other mechanism. Specific direct knowledge of the DAO class is generally neither necessary nor desirable.
- revscat 13y agoHow is that a burden? The controller has to know about the methods of the DAO no matter what. The debate you're having seems to be "what is the best way to get a concrete instance of the DAO into the controller?" The argument being made for constructor/accessor injection is that it makes testing easier. The argument against it is that it makes the API more complicated than it would otherwise be.
- doktrin 13y ago> This... is not Kent's finest moment. It's really not. Objectively, this wouldn't even be within artillery range of the front page if it hadn't been written by a Big Name(tm). I even have what borders on a fundamental disagreement with his last point : > Anxiety. Perhaps what I'll miss most is the way TDD gives me an instantaneous "Is Everything Okay?" button. This point of Kent's feels particularly weird given that DHH had specifically criticized the TDD artifact that is unit test worship & mock abuse. That workflow practically embodies the notion of a "false sense of security". It might do wonders for personal anxiety, but I have doubts about its ability to rigorously test a system :/
- jonahx 13y ago> It's really not. Objectively, this wouldn't even be within artillery range of the front page if it hadn't been written by a Big Name(tm). This is doubly true of the DHH article Kent is rebutting.
- eloff 13y agoNo, it was a well written article that made its point articulately. This post is snarky and only briefly touches on some positives of TDD without addressing the points raised by DHH. I don't know either of these guys by name, but I upvoted DHH and not this post because it is simply better written.
- briantakita 13y ago> Testing does tend to have a negative impact on API design. Not all test has a negative impact. IMO, certain forms of testing have a positive impact because it provides feedback and allows fearless refactoring. White Box testing with many mocks and other affordances has a negative impact. Black box testing does not require many affordances.