I doubt it was easier to reason about at all. The code set an error var on a number of if / else conditions. They then used a separate flag, so that a double write wouldn't be necessary. ie. int flag = 0;
int err; // notice no initializer
if(stuff) { flag = 1; err = 1; } else {}
if(other_stuff) { flag = 1; err = 2; } else {}
if(flag) { LogError(err); }
I follow the "avoid double write" convention too, but in some cases, it can become very difficult to reason whether a local is uninitialized; this is a pretty terrible class of bug, because you work with garbage data off of the stack.
Setting to zero at declaration makes it much clearer that it will never be uninitialized, but it's essentially meaningless and arguably a form of dead code.
Maybe someone will suggest a redesign? More functions, smaller functions? It seems that there's a strong (though unpopular) argument that you should use medium sized functions, and some teams insist on doing this. In some cases, it can be easier to find the content, verify specifications.
Edit: On second thought I can see we didn't avoid the double write, just did it in a different variable. So I don't understand what the author's point is, lol.
If you are doing a lot of work inside those branches it may be worth a refactor to simplify the branching logic.
The fact that it set off the static analyzer is very annoying too. Now I have to somehow debug the static analyzer, or just accept the risks. I've had some uncomfortable experiences assuming the analyzer is wrong and ignoring or suppressing it... theoretically, the static analysis will do a much better job than a human, so I can't help but doubt, and feel I missed something.
Just as an aside, some teams try to avoid multiple return paths. I believe there's some studies indicating a higher incidence of bugs... for a solid code quality reason you could theoretically deviate.
I don't really see how it could increase bugs over setting a flag. With a function that sets a flag there's always a risk that you change the flag accidentally after you already hit the value you want. Which is the same risk as returning earlier than you want, I suppose.
I just find the multiple return paths easier to read, debug and understand, rather than stepping through to trace the value of the flag, you just figure out which return is firing.
The more I write, the more I lean into immutability though. Bugs happen when values can change that shouldn't.
From experience, I have never once heard anyone from higher-level languages (above C & C++) complain about "multiple return paths". That said, for C, I definitely understand the need for reduced return paths due to lack of auto-free / destructor mechanics.
bool go = true;
if (go) doA(&go);
if (go) doB(&go);
if (go) doC(&go);
As a functional thinker, this is "just" an encoding of the Maybe monad, so I actually quite like it. If you care about small efficiency wins, though, it's not great -- an early bail means checking all the remaining conditions. It's cute though!I'm generally pretty okay with similar patterns over accumulators, as long as it's clear that the purpose of that variable is to be an accumulator. If we're just overwriting a variable because we don't need the old value anymore, or something like that, I'm much less charitable.
A big benefit of functional programming, for me, is to learn the safe roads as explicitly as possible, so that you can then identify (and use) them when they aren't so well signposted.
I actually prefer pcollections: https://github.com/hrldcpr/pcollections
AtomicReference + immutable data types is a really nice way to program in Java, and is basically the way most Clojure programs are written.
Surely this would cause some strange performance outcomes with, say, a gradually built up immutable list.
I'd rather focus my energy on adding unit testing, and refactoring to be testable. If you have easy-to-read unit tests that cover all possible edge cases, I stop caring (as much) about how the actual method is written. Be as optimized/clever/concise as you want. I don't care if you reassign your local variables.
The only excuse is when I am doing something algorithmic and that would improve the performance.
By extension I prefer local variables over class state and static state and other “self managed lifetime” stuff like that.
And I am a GC blub programmer, I don’t use Rust or C.
Stuff like this is the reason why functional programming is synonymous with inefficiency and why Real Programming™ is still done in languages like C.
Neat rule, but, it won't work. Firstly, as an example, Scala which allows you to declare a read only val, you still see vars used. There is no way you're going to get any traction enforcing this across the developer spectrum.
The benefit of these concepts are realized when they are the only option, hence FP and why mixed paradigm languages are half-assed. Java isn't even a mixed-paradgim. You're wishing.
You'd get the benefit of easier to reason about code everywhere you used it, even if others within the codebase don't. Using that argument, we would argue that it's never worth trying to find a cleaner way to implement something because maybe some intern some day will do something weird in a different part of the code base. We don't have to drop down to the lowest common denominator for code quality and we benefit every time we simplify things, even if not everyone does.
Use the best tool for the job.
You see them very rarely, and usually with a narrow scope.
I agree that you probably can't enforce an absolute rule of no mutable variables. But making it the exception rather than the rule (e.g. require it to be justified in code review) makes a huge difference.