Live data from Hacker News

Push Ifs Up and Fors Down

matklad.github.io

131–140 of 209 posts

Re: Push Ifs Up and Fors Down

#131
post #16

Push everything down for better code readability 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…

> printInvoice(invoice, options) // is much better than > ...

in Elixirland, we'd name that function maybe_print_invoice which I like much better.

Re: Push Ifs Up and Fors Down

#132
post #130

Earlier quoted context omitted.

If the function is called from 37 places, you need to refactor your code, but to answer your question on that point: it depends. DRY feels like the right answer, but I think we'd have to review an actual code example to decide. On examples where you're talking about a library function, I think you have to accept that as a library you're in a special place: you're on an ownership boundary. Data is moving across domain…

> If the function is called from 37 places, you need to refactor your code, Really? I do not have to think hard before I have a counter exampl: authentication I call authenticate() is some form from every API All 37 of them

If you are explicitly calling authenticate() for each api, you’re doing it “wrong”. At that point you want implied authentication not explicit authentication. Why not move it to some middleware that gets called in every api call?

Re: Push Ifs Up and Fors Down

#133
post #29
post #20

Earlier quoted context omitted.

> 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 s…

It's only exhaustive in this toy case. Add another one and the burden of checking for exhaustiveness with ifs falls on your shoulders.

So long as you have an else block on your if statement, it’s exhaustive. I think I can keep track of that.

Re: Push Ifs Up and Fors Down

#134
post #105

My weird mental model: You have a tree of possible states/program flow. Conditions prune the tree. Prune the tree as early as possible so that you have to do work on fewer branches. Don’t meticulously evaluate and potentially prune every single branch, only to find you have to prune the whole limb anyways. Or even weirder: conditionals are about figuring out what work doesn’t need to be done. Loops are the “work.” Ul…

Can I float an adjacent model? Classes are nouns, functions are verbs.

And then at some point someone shows you how Classes can be verbs, and functions can be nouns, and your brain hurts for a while. You overuse that paradigm for a while, and eventually learn to find the appropriate balance of ideas.

Re: Push Ifs Up and Fors Down

#135
post #93
post #9

Earlier quoted context omitted.

The way to solve this is to split decisions from execution and that’s a notion I got from our old pal Bertrand Meyer. if (weShouldDoThis()) { doThis(); } It complements or is part of functional core imperative shell. All those checks being separate makes them easy to test, and if you care about complexity you can break out a function per clause in the check.

To add to this, a pattern that's really helpful here is: findThingWeShouldDoThisTo can both satisfy a condition and greatly simplify doThis if you can pass it the thing in question. It's read-only, testable, and readable. Highly recommend.

This is not obvious to me. The whole point was to separate the conditionals from the actions.

In your example, it’s not clear if/how much “should we do this” logic is in your function. If none, then great; you’ve implemented a find or lookup function and I agree those can be helpful.

If there’s some logic, eg you have to iterate through a set or query a database to find all the things that meet the criteria for “should do this”, then that’s different than what the original commenter was saying.

maybe: doThis( findAllMatchingThings( determineCriteriaForThingsToDoThisTo()))

would be a good separation of concerns

Re: Push Ifs Up and Fors Down

#136
post #77

Earlier quoted context omitted.

Consider a simple example where you're checking if a file exists, or a database object exists, and creating it if not. Imagine your filesystem or database library either doesn't have an upsert function to do this for you, or else you can't use it because you want some special behaviour for new records (like writing the current timestamp or a running total, or adding an entry to a log file, or something). I think this…

> a database object exists, and creating it if not. Imagine your filesystem or database library either doesn't have an upsert function to do this for you, or else you can't use it because you want some special behaviour for new records (like writing the current timestamp or a running total, or adding an entry to a log file, or something). This is why databases have transactions. > simple example where you're checking…

Sure, I agree that a transaction should be used here (in the database example at least). But that's orthogonal to my point, or maybe even in favour of it: doesn't a transaction necessitate keeping the conditional close to the effect? It's a perfect example of what I'm trying to say, how do you make sure the conditional happens in the same transaction as the effect, while simultaneously trying to push the conditional towards the root of the code and away from the effect? Transaction boundaries are exactly the kind of thing that makes pushing up the conditionals difficult.

Re: Push Ifs Up and Fors Down

#137
post #130

Earlier quoted context omitted.

If the function is called from 37 places, you need to refactor your code, but to answer your question on that point: it depends. DRY feels like the right answer, but I think we'd have to review an actual code example to decide. On examples where you're talking about a library function, I think you have to accept that as a library you're in a special place: you're on an ownership boundary. Data is moving across domain…

> If the function is called from 37 places, you need to refactor your code, Really? I do not have to think hard before I have a counter exampl: authentication I call authenticate() is some form from every API All 37 of them

The strongest interpretation of the remark is not that you need to refactor because you have a function called 37 times (which is likely a good thing) but rather that if you think you need to move an if statement into or out of it, you face refactoring.

Re: Push Ifs Up and Fors Down

#138
post #134
post #105

Earlier quoted context omitted.

Can I float an adjacent model? Classes are nouns, functions are verbs.

And then at some point someone shows you how Classes can be verbs, and functions can be nouns, and your brain hurts for a while. You overuse that paradigm for a while, and eventually learn to find the appropriate balance of ideas.

Haven’t seen that yet after 25 years. It just always seems like lazy naming when this isn’t followed. Maybe I missed something.

Re: Push Ifs Up and Fors Down

#139

Earlier quoted context omitted.

Maybe write the functions without the checks, then have wrapper functions that just do the checks and then call the internal function?

It sounds like self-inflicted boilerplate to me.

If you were going to write the tests anyway, the additional boilerplate for splitting it up and doing a wrapper isn’t so bad (in C at least, maybe it is worse for some language).

Re: Push Ifs Up and Fors Down

#140
post #138
post #134

Earlier quoted context omitted.

And then at some point someone shows you how Classes can be verbs, and functions can be nouns, and your brain hurts for a while. You overuse that paradigm for a while, and eventually learn to find the appropriate balance of ideas.

Haven’t seen that yet after 25 years. It just always seems like lazy naming when this isn’t followed. Maybe I missed something.

I have to agree, particularly if you look at functions as pipelines: data/events go in, other data/events go out.

If I had to hazard some kind of heuristic with 99% applicability, it'd be to always strive to have code with as few indentations (branches) as possible. If your code is getting too indented, those deep Vs are either a sign that your implementation has a strong mismatch with the underlying problem or you need to break things up into smaller functions.

Post reply on HN