4 ms·
tl;dr: TDD is not, on its own, effective. If code is highly coupled, tests become highly coupled and a nightmare. The author advocates that learning to properly
by dyarosla 9y ago
tl;dr: TDD is not, on its own, effective. If code is highly coupled, tests become highly coupled and a nightmare. The author advocates that learning to properly refactor and create low-coupled code should be a first priority ahead of following TDD blindly.
IMO, nothing really profound here.
- tonyedgecombe 9y agoIt's interesting where this leads you though, deep into dependency injection, inversion of control containers and patterns like MVVM. I'm not saying these things are bad but they do have a cost.
- eropple 9y agoIt doesn't necessarily lead you there at all. "Dependency injection" is a fancy phrase for "pass stuff a function needs into the function", only it does so by adding state to objects where that state probably shouldn't exist. You don't have to faff around with IoC or MVVM to properly isolate dependencies; indeed, it's the core idea behind something like Gary Bernhardt's "Functional Core, Imperative Shell" (which, if you're not understanding this instinctively, makes me fear for your code): https://www.destroyallsoftware.com/talks/boundaries https://www.destroyallsoftware.com/talks/boundaries https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell https://www.destroyallsoftware.com/screencasts/catalog/funct...
- AstralStorm 9y agoSometimes state is an actual desirable thing and strategy can be a dependency. Heck, higher order functions (functions taking functions as argument) are dependency injection personified.
- eropple 9y agoState is a desirable thing in the code that wraps your business logic, for sure! Otherwise you're not actually doing anything. That's distinct from state in your business logic, though.
- tonyedgecombe 9y agoThat's interesting although after a brief look it just looks like he is splitting the code into two parts, one part he tests and the other part he doesn't. I don't really disagree with this, my UI code has very few tests. "which, if you're not understanding this instinctively, makes me fear for your code" Thanks for the insult.
- eropple 9y agoSorry--that was a generic "you" there, not you-specifically. I'm saying that this is something everybody needs to understand because writing state-munging code is intrinsically harder to do correctly. He's not just "splitting the code into two parts", though. He is consciously and specifically defining a core based on functional principles that avoid mutating state (which is harder to measure and reason about). That UI layer, his "imperative shell", is where state is managed, rather than embedding side effects into his business logic. (If you watch the Boundaries talk you'll see he frames larger applications as sets of functional cores surrounded by a layer of imperative routing between them.)
- luord 9y ago"Functional core, imperative shell" sounds quite dogmatic and not "getting it" is no reason to fear for anyone's code. Just like the OP article argued that TDD can become an all-solving hammer in the eyes of people, so can functional programming. In this particular case I can think of a whole host of applications where the core should definitely not be functional (and, for what it's worth, immutable state is not inherent to functional programming, or at least it didn't use to be). If it were so, ORMs wouldn't be a thing.
- eropple 9y agoI can't think of a single case outside of a systems or game development context (and a number even there) where anything I have written was improved by tightly coupling state mutation to business logic. Not one. Rather than trying to invoke "dogma" so very seriously, feel free to suggest one. But it's a really long row to hoe: if you are conflating state mutation and business logic, you have entire classes of errors that clean separation doesn't and it's harder for you to meaningfully test for correctness. Not firing that gun into your foot seems very obvious. And functional programming has never encouraged mutation of data model objects. Not at any point. Mutation of data model objects is and can be nothing but a side effect of a function. There are cases where you can't not mutate a model, such as when that refers to a system resource (a console, a socket, etc.); those are not your data model. Those are dependencies that your application uses to create instances of your data model and feed them through a set of functional processes. Almost like there's an imperative shell that deals with and controls side effects, wrapped around a core of pure functions that manage stateless transforms based on your business logic...? ORMs (in the mutating, own-your-object sense like an Active Record model) shouldn't be a thing because they're awful creations that aggressively they encourage bad, muddy code that's harder to test and trace and debug. Once more for emphasis: the second you mutate a data object you have made testing an order of magnitude more difficult and now you have to live with that forever. Active Record requires this. There are places where you may choose to embrace this, but it's a good way to shoot yourself in the foot. On the other hand, data mappers (Anorm, DataMapper, Slick) live outside of the cleanly tested, no-dependency core (and its data models) and are used to feed objects into your business logic and record the results back to an external data store. If you aren't using plain old objects to encapsulate your data, you're asking for it.
- lgunsch 9y agoVery true. Even Robert Martin himself says that TDD without refactoring is worse than TDD (in one of his books). He mentions that if refactoring is not done you will be worse off than had you not done tests at all. He even recommends simply deleting all the tests.
- AnimalMuppet 9y ago> If code is highly coupled, tests become highly coupled and a nightmare. TDD is supposed to prevent that. What's supposed to happen is, if the test is hard to write, it's telling you something. You're supposed to pay attention to that pain. It's supposed to show you that your code is highly coupled, and make you fix that. If you just go ahead and write the test, difficult as it is, instead of finding a way to refactor to clean up the coupling, then yes, TDD produces a nightmare of a test. That's because you're not doing it right, though. (Yes, I recognize that this is a lot like a No True Scotsman. But in fact any methodology, practiced badly, will produce sub-optimal results...)