I like this a lot. At first, putting ifs inside the fors makes things more concise. But it seems like there's always an edge case or requirement change that eventually requires an if outside the for too. Now you've got ifs on both sides of the for, and you've got to look in multiple places to see what's happening. Or worse, subsequent changes will require updating both places. So yeah, I agree, pulling conditions up…
Push Ifs Up and Fors Down
11–20 of 209 posts
Re: Push Ifs Up and Fors Down
#12Re: Push Ifs Up and Fors Down
#13The author's main concern seems to be optimising performance critical code.
Re: Push Ifs Up and Fors Down
#14Performance of an if-statement and for-loop are negligent. That's not the bottleneck of your app. If you're building something that needs to be highly performant, sure. But that's not the majority.
Re: Push Ifs Up and Fors Down
#15And the last example looks like a poor advice and contradicts previous advice: there's rarely a global condition that is enough to check once at the top: the condition usually is inside the walrus. And why do for walrus in pack {walrus.throbnicate()} instead of making throbnicate a function accepting the whole pack?
Re: Push Ifs Up and Fors Down
#16 printInvoice(invoice, options) // is much better than
if(printerReady){
if(printerHasInk){
if(printerHasPaper){
if(invoiceFormatIsPortrait){
:
The same can be said of loops printInvoices(invoices) // much better than
for(invoice of invoices){
printInvoice(invoice)
}
At the end, while code readability is extremely important, encapsulation is much more important, so mix both accordingly.Re: Push Ifs Up and Fors Down
#17The author's main concern seems to be optimising performance critical code.
Re: Push Ifs Up and Fors Down
#18I agree, except for this example, where the author effectively (after a substitution) prefers the former: fn f() -> E { if condition { E::Foo(x) } else { E::Bar(y) } } fn g(e: E) { match e { E::Foo(x) => foo(x), E::Bar(y) => bar(y) } } The latter is not only more readable, but it is safer, because a match statement can ensure all possibilities are covered.
That's not quite right, it's a substitution AND ablation of 2 functions and an enum from the code base.
There's quite a reduction in complexity he's advocating for.
Further, the enum and the additional boilerplate is not adding type safety in this example. Presumably the parameters to foo and bar are enforced in all cases so the only difference between the two examples is the additional boilerplate of a 2-armed enum.
I strongly suspect in this case (but i haven't godbolted it to be sure) that both examples compile to the same machine code. If my hunch is correct, then the remaining question is, does introduction of double-entry book keeping on the if condition add safety for future changes?
Maybe. But at what cost? This is one of those scenarios where you bank the easy win of reduced complexity.
Re: Push Ifs Up and Fors Down
#19Code complexity scanners⁰ eventually force pushing ifs down. The article recommends the opposite: By pushing ifs up, you often end up centralizing control flow in a single function, which has a complex branching logic, but all the actual work is delegated to straight line subroutines. ⁰ https://docs.sonarsource.com/sonarqube-server/latest/user-gu...
Re: Push Ifs Up and Fors Down
#20I agree, except for this example, where the author effectively (after a substitution) prefers the former: fn f() -> E { if condition { E::Foo(x) } else { E::Bar(y) } } fn g(e: E) { match e { E::Foo(x) => foo(x), E::Bar(y) => bar(y) } } The latter is not only more readable, but it is safer, because a match statement can ensure all possibilities are covered.
Whether or not this matters depends on what, exactly, is in those match arms. Sometimes there's some symmetry to the arms of an if statement. And in that case, being exhaustive is important. But there's plenty of times where I really just have a bit of bookkeeping to do, or an early return or something. And I only want to do it in certain cases. Eg if condition { break; } else { stuff(); }
Also, if-else is exhaustive already. Its still exhaustive even if you add more "else if" clauses, like if {} else if {} else {}.
Match makes sense when the arms of the conditional are more symmetrical. Or when you're dealing with an enum. Or when you want to avoid repeating conditions. (Eg match a.cmp(b) { Greater / Equal / Less } ).
The best way to structure your code in general really comes down to what you're trying to do. Sometimes if statements are cleaner. Sometimes match expressions. It just depends on the situation.