In this context, Uncle Bob's style was compelling, it felt like a breath of fresh air. His opinionated attitude was also compelling. In general, associating a term like 'clean' to your methodology seems to be a good zealot recruitment strategy. 'Pure functions' is similarly brilliant marketing.
That said, a lot of his supposed solutions actually cause problems of their own if you let the pendulum swing too far. It creates its own kind of complexity. Books like these also tend to sort of get their own momentum, if you look back you see a lot of big names praising them. Surely they must be good, right? Well they were. Clean Code made sense when it was published. It's for the most part not good advice today.
*Edit: Updated: I had forgotten this was not from "Clean Code".
They don't have to (and probably shouldn't) be globally defined.
I'm thankful I'm not the only one with this critique. I see this so often and it makes navigating and/or debugging the code a nightmare, while I hop around these tiny single use methods. A long function I can read top to bottom is a lot easier to grok!
Tons of tiny functions, each doing almost nothing, frantically calling each other, and somehow useful functionality results.
Each function is very easy to understand, but how they together do useful tasks is near incomprehensible.
It's amazing to me how even smart people can be taken in by Harold Hill types. Can I get a job going around giving advice for $100k a pop? I promise to do better than that guy haha.
The team treated that as puzzle-solving, and it was part of their culture to tackle those kinds of challenges. There was light hazing of newbies whenever the CI didn't pass because methods were too big. So, that's why at least in this case. There was no technical argument, just inertia.
Code quality was atrocious. Things that could be 10 lines were 30 or 40, and extremely stateful.
Ousterhout is saying that we really want deep classes, i.e. the complexity of the implementation a construct hides should be considerably larger than the complexity it takes to use it. Which, at least to me, basically says "search for good abstractions". And what you're describing strikes me as a good way not to achieve that.
Of course, there's considerable wiggle room in that mental model, and one could still endlessly squabble about how (not) to break up the implementation.
[1]: https://www.amazon.com/Philosophy-Software-Design-John-Ouste...
I think the fallacy of the "Poltergeist Pattern" is to optimize solely on the - in itself valuable - metric of short simple methods, without consider anything else, like the complexity of using or reading the resulting code.
There were people who abhorred the sight of non-stateful code, there was little opportunity to fiddle with objects in it, not realizing that they just made their and everyone else's job harder with more moving parts, a concept that, despite being very easy to understand and with analogues to physical machines, was lost on many programmers.
It is a constant discussion. You can always find examples of long functions with lots of state which is hard to follow and then there are always examples of code where people went too short. And then there are always people taking any of those examples and showing why they were wrong.
I myself like going to the extremes for fun/toy/throwaway projects (not only on these but also with rules like "no `if`" or "no raw `for` loop" etc) to try out all the different alternatives and then resetting my default based on cases where it went well and where it went bad.
No such rule is always right, but most hmstem from being exposed to too much of one extreme.
Uncle Bob is a fraud by the way. Turning a perfectly readable medium length function into a class with dozens of tiny methods is a cardinal sin in my eyes. Then later someone stumbles upon this class and reuses some of these methods in one way or another, while working on a problem completely unrelated to the original, thus entangling the two implementations…
you may not be an adherent of judo and you may prefer and better understand karate, but that doesn’t make a judo teacher a fraud.
What's the programming analog of UFC/MMA?
The analogy also doesn't work because BJJ isn't some silver bullet. What people discovered is that the first M in MMA is actually the important part and if all you know is BJJ you're going to get starched by a boxer with a sprawl, or more likely a wrestler with a modicum of submission knowledge, who will never let you get to the floor in the first place, and instead just grind you out.
So just like the question "what is the best martial art" currently has no answer outside of "you need a mix of striking and grappling not just one thing", there is no answer to "What is the best programming style" outside of "think about the problem you have at hand and crib on examples and knowledge from other people who have solved a similar problem". This "unfortunately" points to boring industry standard tools, like Java, C/C++, Javascript, RDBMSs, IDEs, Linux etc, etc. Probably some newer stuff like Rust and React as well. And note that answer isn't one specific technology, like MMA its a bag of different tools you combine.
I fixed two bugs at once in a softsynth caused by this stupidity. In the envelope generator code, which loosely modelled the ADSR circuit in a synth which charges and discharges a capacitor, there was an integrator implemented like `env = ((target - env) * timeconstant) + env`. The `target` value was set to 1 to allow the "capacitor" to "charge" at a rate set by `timeconstant`, and when it reached the top it would be flipped to 0 and `timeconstant` changed to the decay rate.
It failed about one go in ten, with some fudges to set `timeconstant` in odd values. It turns out that they detected the "capacitor fully charged" state with something like `if (env == 0.999) { dostuff(); }` and for some values of `env` and `timeconstant` it might never hit *exactly* 0.999 if you got it exactly wrong.
It was logically wrong (detect a precise float value) and functionally wrong (envelopes switch over when the cap is about 2/3 charged). Changing the "flip over" condition to something more like `if (env > 0.999)` and changing the maximum value of `target` to 1.5 (mimicking a circuit running off 15V and trying to it a 10V output, like a real analogue synth) cured all its problems.
Well, all its *envelope* problems. There were more, but they get into scary discussions of poles and zeroes and negative frequencies, and you don't want that at this time of night.
Using it on the results of floating point arithmetic may be a bad idea, but that's a different matter. That should perhaps be a compiler or linter warning, rather than flat out be declared incorrect.
Here is some cursed C code to play with:
#include <stdio.h>
#include <float.h>
#include <math.h>
int main() {
int a = 0x1000002;
int b = 0x2000001;
float* fa = (float*)&a;
float* fb = (float*)&b;
printf("value-equals? %d\n", *fa == *fb);
printf("fabs-equals? %d\n", fabs(*fa-*fb)<FLT_EPSILON);
}
May print something like value-equals? 0
fabs-equals? 1I get the same output using the following (I switched to the portable memcpy solution.)
#include <stdio.h>
#include <float.h>
#include <math.h>
int main() {
int a = 0x361dc5df; // 2.351e-6f
int b = 0x361e3e22; // 2.358e-6f;
float fa, fb;
memcpy(&fa, &a, 4);
memcpy(&fb, &b, 4);
printf("value-equals? %d\n", fa == fb);
printf("fabs-equals? %d\n", fabs(fa-fb)<FLT_EPSILON);
}
Since FLT_EPSILON is 1.19209290E-07F , I would expect a lot of small, close numbers to give the same output, even without touching subnormal values.He says to turn the function into a class and local variables into private fields. So that you can split the function into small methods that operate on these private fields. I used it a few times in a company that had strict "no Sonar warnings" policy. But I hate the resulting code much more than the initial code.
assertEquals(expectedOutput, f(input));
When you split them in a way that makes the local state external - you now have to handle the state in unit tests which is much more hassle.BTW I'd also love to have a built in float type that fails when you assign 0 to it.
Anyway, I guess in Java operator == is a lost cause anyway. My favorite example:
// true
System.out.println(BigDecimal.valueOf(10) == BigDecimal.valueOf(10));
// false
System.out.println(BigDecimal.valueOf(11) == BigDecimal.valueOf(11));
// true
System.out.println(BigDecimal.valueOf(11).equals(BigDecimal.valueOf(11)));Yeah, but then you're having to learn all the special cases for when it silently gives wrong answers, and hope to hell that you didn't miss any.
Much better to have consistency and behave the same way all the time, than to optimise for 3 keystrokes and introduce all sorts of special exceptions that the programmer must memorise.
Then people would start writing `signum(a - b) == 0` (or some equivalent) instead of `a == b`, and/or factor that out into a helper function. Not sure that would be an improvement.
Instead if should return an error "Floating points should be compared using this library function that includes acceptable difference. If you want exact math use BigDecimal or similar. If you know what you're doing use library function with acceptable difference = 0.0"
And yes, Uncle Bob is giving some terrible advice. My least favorite is his advice to split "too long" pure functions into a stateful class with "short enough" methods that later can be called in the wrong order because now there's a right and wrong way to call them.