Experts, True Believers and Test-Driven Development: how advice becomes a religion
codewithoutrules.com
codewithoutrules.com
But someone posted some actual code, it had a switch statement, only 3 case options, about 7 lines and if the switch didn't hit, throw exception after
"I would be careful with that switch, it looks like a code smell, this is how I would do it: 2+ interfaces, 3+ classes, abstract this, factory that and over 100 lines of code.
I just refuse to believe a simple switch that everyone can understand in about 1 second is not the better solution to some complex code, even if it does use a "pattern"
However, I think GP's point is valid. If during a code review I have a switch and it makes no sense to abstract it, I'd smack the reviewer on the head if he/she wants me to "fix" it.
I think objectively the code is better for it but sometimes it takes more social sophistication than I possess. I end up spending too much time talking in post mortems and it all sounds like 'I told you so'
You mean visualize with a tool, or in your head? If the former, I'd love to know of a tool for visualizing code flow.
If the latter, then I think a switch - with entry and exit points, and all code paths clearly visible together on one screen - is much easier to visualize than an implicit branching hidden behind interfaces, requiring you to jump around definitions of several objects.
When I said visualize, I meant primarily in the programmer's mind, but there are other tools that help. Code coverage tools ensure that all branches of code are tested, but fail to ensure that all permutations of all branches get tested. This is something where fuzzing tools can attempt to ensure that all branches are taken, but will quickly run into the limitations of the underlying machine if the cyclomatic complexity is too high.
Beginning programmers learn flow control very early in their development. Most will also learn fairly quickly that creating a rat's nest of ifs and switches can become difficult to reason about. It's for that reason that we learn abstractions that help us increase the complexity of the tasks we can solve without increasing the complexity of the code we have to understand.
That is certainly true, but it is equally true of any mechanism for making those decisions. With some mechanisms, like if-else and switch-case, the decisions are explicit. With others, like virtual function dispatch, the decisions are implicit. Either way, the decisions are still being made and the combinatorial properties of multiple decisions are still the same.
From my perspective all three tried to solve maintainability. OOP via encapsulation and standardisation, functional via uncompromising standards, and TDD via validating each small subsection of the solution.
They all "work" if you commit to them enough. But none of them work when you half arse them. So instead of people working hard they move on to the next "solution" because the last solution required too much effort (TDD is definitely a victim of this, considering how much work it requires).
PS - People being "anti" something are just as religious as the people being "pro" something, except the anti people often have a smug sense of superiority. See the people arguing against GOTO in all and any circumstances in spit of how little sense their arguments make.
This sounds dangerous. It gives rise to the attitude that if X doesn't work you are just not doing it enough. Instead of accepting that maybe it's not the right approach to this particular problem.
1) Unit testing/TDD, specifically setting things up where I am able to run a unit test in less than a second on any code I'm working on has been a huge productivity boost vs. running a whole suite periodically
2) Immutable variables (or generally avoiding mutation)
3) SRP and cyclomatic complexity reduction
4) Pattern-matching (Elixir/Erlang) which also reduces the amount of branching in code and thus its depth (reducing cyclomatic complexity further)
5) FP (which incorporates (2) and (4) plus generally constraining side effects and I/O to the smallest part of the code possible, and leaving the rest non-side-effecting)
I have done scrum, but I haven't seen it as directly effective as these other things, it's more for effective PM IMHO.
"My pragmatic summary: A large fraction of the flaws in software development are due to programmers not fully understanding all the possible states their code may execute in. In a multithreaded environment, the lack of understanding and the resulting problems are greatly amplified, almost to the point of panic if you are paying attention. Programming in a functional style makes the state presented to your code explicit, which makes it much easier to reason about, and, in a completely pure system, makes thread race conditions impossible."
http://www.gamasutra.com/view/news/169296/Indepth_Functional...
Re-thinking everything for every project is wasteful. Sometimes you already know a way to do a project that works for you. Sometimes that way is mirrored by a lot of other people.
Other times you are selecting these things because it's a common thing to talk about and when you're teaching people the project, you can prime their learning by telling them to expect these models and methodologies in this project.
The mistake IMO is making the religion industry-wide when really you'll be lucky to implement it consistently just in your company/project.
Joel Spolsky covered this fairly well in his piece on McDonald's-style software. The best way to write a single program is to master programming, and develop alone or in a small group of masters, and build a beautiful and enduring tool with whatever practices suit you best.
But none of that can be packaged up and passed around to new programmers on demand. It takes time to master, and it certainly can't be smoothly overseen by someone who never learned it. The best way to get 100,000 programmers writing a billion lines of code is to develop simple routines that can be followed and overseen by anyone.
The results won't be as good as mastery, but in many cases they don't need to be. There's a lot of simple software out there which benefits more from being done now than being done right.
Having said all that, I do think rigidity is becoming a problem for the software industry. It's not being employed at the levels needed to make software work, it's being employed at the levels needed to make development legible. Adding procedures that worsen development but make it easier to watch is a serious issue.
https://www.joelonsoftware.com/2001/01/18/big-macs-vs-the-na...
The worst of this group often become indispensable, because they've designed something nobody else can understand. It's politically difficult to push back on this sort of problem, which is why I am job hunting now. Of the six or so really smart guys that remain on my current project, I trust maybe three of them, but would only work with two again (the third is so happy to be here that he doesn't resist any of the chaos)
Software is a team sport. All of my decisions affect how long it takes for you to fix a simple bug. All of yours affect how often I have to stay late to fix a production issue.
I think these things become a religion because there is a large group of developers who simply won't do something unless management makes a rule about it. So now to get them to participate, dogma has to be involved.
In general these are the same folks who attribute all adversity to bad luck instead of lack of forethought. They won't see the consequences of their shortcuts because that's just the way things are.
At that point, you're basically in permanent damage limitation mode anyway, though.
Software development is a skilled and creative activity. Certainly not everyone has to be a "rockstar" or a "ninja" or whatever we're calling those people this week. I've worked with plenty of developers who do a decent, competent job without spending their lives thinking about code 24/7 and staying up to speed on every little development in the industry. However, there is a baseline level of giving a $#!% that is necessary to do a decent, competent job.
I have the feeling that, while TDD is not an absolute, this article doesn't put forward good case examples for skipping it, or at least doesn't try to discuss these examples and find out why they aren't fit. Instead; it takes a shot at stereotypes and builds a strawman on it.
Moderation comes from practice, in fact. It comes from trying and failing, not from extra-prodding the experts, or from studying the matter. And should one be confronted with a zealot, their experimental rebuttal should be convincing enough that zealotry isn't a problem.
On the other hand, OP's assertion that, e.g. a pseudo random generator shouldn't be tested comes without explanations, and frankly is unconvincing : there are acceptance tests (they may not be enough, but passing them is the least you can do) https://en.wikipedia.org/wiki/Randomness_tests, and you can also split the seed and the function you implement, and test thoroughly that the function performs what the algorithm describes.
Likewise, I would totally accept that someone that writes zero tests but has an inexpensive and permanently available source of validation (e.g. from prod) practices TDD.
I'm not sure how you can do validation without writing tests, but let's clarify our terms. I work in the embedded field. We rarely, if ever, write unit tests in the typical sense. Most tests are based on black box testing of the system (related to integration tests) sometimes of a system component if we can plugin a simulator of other components (closest we get to unit tests). Most often, these tests are not written in the same language as the system or even running on the same platform. They talk over serial or something to the device and record the responses back.
These tests still have to be written, even if it's just:
Send messages X, Y, Z
Receive messages A, B, C as responses
A, B, C should match *data* modulo time tag
A test has been written. And if this testing is run frequently against the code changes, and failures treated typically as blockers on working on other features or issues, then this is definitely TDD. Often, however, these tests are run infrequently, ergo not TDD, despite the comprehensiveness of the test suite. The tests do not drive the development (though perhaps they should more often than not).I was skeptical of this post to begin with but this line sums it up for me. The author is railing against a perceived threat. Of course you can't unit test _awesome_. That's ridiculous.
So what's the claim here?
I think more developers need to be introduced to the concept of the, "state of the art," and be led to resources like SWEBOK[0]. It has a section on software testing and it's good to know what the state of the art is across the industry.
I don't have the link in my db, but I've come across studies that have shown no significant difference in productivity when utilizing test-first vs. test-after approaches to software development. That's the only real religious sell I still hear these days and it has been rather well debunked at this point. All that matters is that software is tested... how you approach it seems to just be a matter personal preference.
You'd be surprised. When I advocate unit-tests, I often get the question back "But how do we unit-test this GUI-code?". Which, as you point out, is ridiculous. But they still ask the question.
The truth is that these people have inter-twined their GUI-code and business-logic (and maybe even DB-access code) and they don't even realize that this is a code-smell.
They don't see how they can apply unit-testing to their code, because they haven't realized that their code should be divided into smaller, independent units.
To "convert" these people (to further reiterate the religious analogy) you first need to convince them that you're not actually crazy. Towards that end, this might be a start.
As someone who reviews code for a good portion of my day it makes little difference to me how you, the programmer, managed to write the tests. We share the same goals and the end result seems to matter more than how it was accomplished.
If you write the tests afterwards, it's hard(er) to know if you've managed to retro-actively cover all the formal requirements. And that makes it harder to later on trust that the tests will cover your ass entirely (as far as formalized business-requirements go).
I can definitely see the appeal, but I'll be the first to admit I don't go the full mile myself, always. And that's I guess what this blog-post is all about. Be critical about how you apply well-meant advice.
In fact the SWEBOK 3.0 section on test-driven design mentions this. It was first proposed in extreme programming as an alternative to more formal specification methods.
> I can definitely see the appeal, but I'll be the first to admit I don't go the full mile myself, always. And that's I guess what this blog-post is all about. Be critical about how you apply well-meant advice.
As banal as such advice is if that is as far as the article went I'd be fine with that. However the argument was weakened when the author eschewed unit tests out of, what appears to be, ignorance.
I don't always write tests first either... when I'm prototyping I'll usually try a few approaches first. However I generally throw out that code once I find a tack I want to take and begin specifying the interfaces, constrains, and invariants through tests.
In higher-level languages with sound type systems I generally rely on properties of the type system and compiler in lieu of tests I'd write in a more dynamic environment.
It's just a matter of preference that I tend to think more before I write code and write tests first.
In my case, I've found I write more readable, easier to understand code when I use tests to drive out my design. It may actually take more time to write the code itself, but the product ends up better, imo.
(Granted, I think I take the approach this article advocates and only do so when I can help inform the design.)
In my case, I've found the opposite—when I use tests to drive my design, I end up with lots of tiny moving parts; each one individually is understandable, but the design as a whole is not, and figuring out how to do anything different is an exercise in frustration. I end up jumping between 2 or 3 files, trying to figure out where anything happens, because it's all been unit tested into perfect isolated pieces.
With TDD a large part of the religion thing comes from people like Uncle Bob. It's not that his style is bad, but the biggest problem I've seen with his style is when people who are already passionate with their craft and maybe practicing TDD go crazy with his advice while missing the context of his message.
That context is his focus towards systematically sloppy corporate shops that have picked up really bad habits and won't touch the tool.....often the same ones that still use a waterfall development process (now with a thin scrum veneer). When you have these kinds of shops it is probably better to to a bit to the other extreme first, and to then find a happy medium after you develop better habits and understand the tool .
I honestly don't know what Bob should do. I know he occasionally says stuff that gets people upset. However, the majority of the stuff that is attributed to him is just outright false. It doesn't matter how many times he publicly corrects the record.
Uncle Bob does not believe in unit testing every function.
Uncle Bob does not believe in a 1:1 mapping of function with unit test (or even close). Uncle Bob is fine if a unit test spans/covers multiple functions.
Uncle Bob is not against unit tests that touch the database or disk.
The list goes on and on.
I watched many of his Clean Coder videos, and was surprised to see how nuanced he is. He does not speak in absolutes. His blog posts are much less polished, and often get him into trouble. But even there I see a fair amount of nuance.
Not as often as some of his critics might suggest, perhaps, but he really does speak in absolutes at times and some of those absolutes really are as foolish as his critics say.
He also frequently adopts a style that presents his personal opinions and experiences as if they were more than that, despite a lack of other evidence to support his positions or even the existence of other evidence that seems to undermine those positions. While he may hide a small disclaimer somewhere, many of his readers aren't going to see it, and I don't believe for a moment that he isn't fully aware of that when he chooses to present his material as he does.
In one of his articles he mentioned that there are very few cases where he deems it acceptable to not write unit tests immediately, but one such scenario is where you develop your code interactively using a REPL. Of course it's still better to then create the unit tests for regression testing, refactoring etc.
But the point is that the most important thing is simply that the code is tested somehow. Basically what agentultra said above.
They're also very useful for other "pure" operations. A factory should always produce an object with such and such properties. A user input validation method should always fail given some classification of invalid input, etc.
Hardcore TDD is definitely not The Way for me but it's very good to use at times. You'd be amazed how much it speeds up testing these kinds of processes when you can just watch your list of tests change from pass to fail or vice versa. As with anything, your mileage may vary.
The fact is that both are true. You don't need TDD to use unit testing — you can implement first and test afterwards. In some cases, this may actually be preferable.
However, although TDD isn't a silver bullet solution, it can be very helpful in a lot of cases. Like with a lot of things, the usefulness of TDD depends on the situation at hand, and a good software engineer chooses the right development techniques for the job.
Unit testing of some sort, however, is considered good practice regardless of whether you're using TDD or not. (And of course, integration and acceptance testing is a good idea, too.)
That sounds like a test-first strategy, which I would categorize as TDD. Yes, you can write unit tests without it being TDD, but I'll second the notion that the sorts of situations the parent pointed out benefit from a test driven approach.
For some people, mind maps make zero sense and they learn far slower. Some concepts just don't fit into mind maps at all and even for very visual people it just slows them down. There's often a more specific visual learning method for many problems than a simple mind map too.
TDD is mind maps. Give it a try, but don't feel bad just because mind maps don't gel with your brain.
You're basically wasting your time not doing it. If you want maximal productivity, learn TDD. Writing unit tests upfront is most definitely a case of "one step backward, two steps forward." Your code under test will be written better (which is to say, more modular/less cyclomatically-complex/easier to maintain and refactor), you'll break it far less often (from the same body of code or other code that relies on it), and you'll feel far more confident about your code. You won't be afraid to refactor it (afraid that you'll break something) because the tests validate it. It's also invaluable on teams, where not everyone is sharing the same mental model.
Speaking of which, remember that your mental model of the code is the true limitation. Unit tests allow you to offload some of that, so that you no longer have to have a perfect representation of all possible states your code can produce, inside your head (which if even possible would allow you to write perfect bug-free code). This is incredibly liberating. You can literally feel the difference. I know it sounds all touchy-feely to describe it this way, but so be it. But hey... If you can imagine every possible state all your code will ever produce at all times, you don't need TDD nor unit-testing, because you already know the "fails". ;)
I didn't do TDD most of my career, product I delivered worked, where delivered on time and reached the goals set out by the business. Why bother? Because it's good practice? Hardly a valid argument in a business perspective.
I think got into TDD for its aesthetic or philosophical appeal. The idea of being able to make reliable software by first writing down what its supposed to do just struck me a poetic and tractable. On the one hand, I spent a lot of time and money going through a dip in productivity while learning all the things to make testing work for me. On the other hand, I eventually emerged from that dip with skills and a mentality that allow me to be productive and relaxed in a way that I wouldn't have thought was possible.
All that being said, determining the right amount and granularity of tests is certainly much more of an art than a science and friends on the developer(s), what the system is doing, framework and the language you are using. I definitely write many more tests when I'm writing Ruby code than when I'm writing Rust. I'm also very new to Rust so we will see if that lasts.
TDD is meant to help inform your design thinking, but there's no rule that says you can't change your mind as you go.
One of the problem is not treating tests also as a design problem as in what to test and what the code for tests should be like? I never see the rule of simplicity applied to the test code as well. Eventually it all becomes like a big city that conceal fugitive bugs.
EDIT: to end the quotation from pg book.
And I'm not moving to a village. :)
However I can imagine having that logic built up "abstractly" by imagining user input and database output objects and fully tested is something that would really help a project.
Having only 20% code coverage or less is nothing to be ashamed of as long as that code is the code that matters and it's better, more abstract and more readable because you started writing it TDD. Before things got ugly with user input and database results.
This. It is almost obvious. But you still see lots of cases where advice is followed where it doesn't make sense.
A particular contrarian thing that I do in Java. When I need objects that are pure Data Transfer Objects (DTOs) with no logic whatsoever inside I just declare all members public and do not bother writing (or generating) getters and setters. I know we were told NEVER to do this. But in this case there is not attempt to encapsulate anything as there is nothing to encapsulate.
Always think for yourself.
The trick is having enough introspective capability to identify when you have made a mistake, and not fall back on dogma to say "I followed The Path, so it can't be a mistake".
The worst ones are the ones that infect every aspect of the business like ITIL. The scope and complexity of the methodology is so broad, you need to hire priests (ie. consultants) and build shrines (ServiceNow, Remedy, etc) to the religion and you end up in a state where nobody in the business understands WTF is going on.
That answers the question frequently asked here re: why horrific and dysfunctional companies like IBM get a tight grip on companies.
The first is devotional understanding. Martin Fowler is a wise and learned programmer and says that TDD is the best way, so One believes that.
The second is intellectual understanding. TDD is good because it codifies the programmer's intentions for what the code should do, makes refactoring easier, prevents regressions and all the other logical reasons that TDD proponents talk about.
The third kind, and the only one that's true understanding, is experiential understanding. TDD is good because you've experienced development with and without it and feel how different they are. Once you get to this point, there's no dogma about writing tests first or anything of that nature. The practice becomes natural and you're free to deviate when it feels wrong.
See also: Shu Ha Ri
In this context, experiential understanding (when you're an expert) is the point where you start suffering from Expert Blind Spot.
And there's a bunch of research that suggests the best teachers (by default) are the people at middle point, intellectual understanding, precisely because the understanding is less automatic and integrated.
The book "How Learning Works" (which I review at https://codewithoutrules.com/2016/03/19/how-learning-works/) talks about this at length.
For those who want a good example, I strongly recommend Julia Evans, aka @b04k:
She writes a lot of novice-focused material on technology. She does a much better job at explaining things than I would. And she does it with an excitement for the material that is infectious. Even for those who know the technical details, I recommend following her; I always walk away saying, "Wow, she's right! I had forgotten how cool that is!"
(I know nothing about Buddhism or learning research, just making a point about chronology.)
Let's say that I spent some time brewing your favorite tea and that I did everything right so that the tea was ~perfect~. And then I took a pin and dipped it in feces, and then dipped the tip of that pin into your tea. Would that in any way have any effect on your enjoyment of the tea?
Could programming, and perhaps many other things, work the same way? Maybe we would all be creating better software if we were a little more religious with our code?
The growth of programming as an abstract, professional business has seen a lot of growth in cargo cult practices. There's a lot to be said for respecting the system you describe, where no one is a master of a system unless they can clearly understand when to disregard that system.
See Also
Health Advice Including but not limited to (Diets, GMO, Types of Exersize/Timings)
People want something to latch onto and feel superior about.
If I am eating 2000 calories a day now, and start eating 2500 calories a day, I can measure the impact of that change on my weight as well as my lifting performance.
I really doubt the impact of TDD is directly measurable like this. I may be wrong though.
Nope.
"Oh, you have this problem? You probably don't take enough Vitamin D!"
"Oh, you have this problem? It's probably due to lack of exercise."
"Oh, you have this problem? You're probably not drinking enough water."