Live data from Hacker News

Push Ifs Up and Fors Down

matklad.github.io

81–90 of 209 posts

Re: Push Ifs Up and Fors Down

#81
post #66

Doesn't the second rule already imply some counterexamples for the first? When I work with batches of data, I often end up with functions like this: function process_batch(batch) { stuff = setUpNeededHelpers(batch); results = []; for (item in batch) { result = process_item(item, stuff); results.add(result); } return results; } Where "stuff" might be various objects, such as counters, lists or dictionaries to track ag…

> Wouldn't this violate the rule?

The article is offering a heuristic, not a hard rule (rule of thumb = heuristic, not dogma). It can't be applied universally without considering your circumstances.

Following his advice to the letter (and ignoring his hedging where he says "consider if"), you'd move the `if (batch.length == 0)` into the callers of `setUpNeededHelpers`. But now you have to make every caller aware that calling the function could be expensive even if there are no contents in `batch` so they have to include the guard, which means you have this scattered throughout your code:

  if (batch.length == 0) { return default }
  setup(batch)
Now it's a pair of things that always go together, which makes more sense to put into one function so you'd probably push it back down.

The advice really is contingent on the surrounding context (non-exhaustive):

1. Is the function with the condition called in only one place? Consider moving it up.

2. Is the function with the condition called in many places and the condition can't be removed (it's not known to be called safely)? Leave the condition in the function.

3. Is the function with the condition called only in places where the guard is redundant? In your example, `batch.length == 0` can be checked in `process_batch`. If all calls to `setup` are in similar functions, you can remove the condition from `setup` and move it up.

4. If it's causing performance concerns (measured), and in many but not all cases the check is unneeded then remove the guard from `setup` and add it back to only those call-sites where it cannot be safely removed. If this doesn't get you any performance improvements, you probably want to move it back down for legibility.

Basically, apply your judgment. But if you can, it's probably (but not always) a good idea to move the ifs up.

Re: Push Ifs Up and Fors Down

#82
My take on the if-statements example wasn't actually so much about if statements.

And this was obfuscated by author's use of global variables everywhere.

The key change was reducing functions' dependencies on outer parameters. Which is great.

Re: Push Ifs Up and Fors Down

#83
post #34

Earlier quoted context omitted.

Cyclomatic complexity may be a helpful warning to detect really big functions, but the people who worry about cyclomatic complexity also seem to be the sort of people who want to set the limit really low and get fiesty if a function has much more than a for loop with a single if clause in it. These settings produce those code bases where no function anywhere actually does anything, it just dispatches to three other f…

I call this "poltergeist code". Dozens of tiny functions that together clearly does something complex correctly, but it's very hard to find where and how it's actually done.

One incomplete but easy to state counter "rule" to fling back in such cases is just: If the function isn't generic and re-used for other unrelated things, then it probably shouldn't be a seperate function.

Yeah only probably, there can sure be large distinct sub-tasks that aren't used by any other function yet would improve understanding to encapsulate and replace with a single function call. You decide which by asking which way makes the overall ultimate intent clearer.

Which way is a closer match to the email where the boss or customer described the business logic they wanted? Did they say "make it look at the 3rd word to see if it has trailing spaces..."?

Or to find out which side of the fuzzy line a given situation is falling, just make them answer a question like, what is the purpose of this function? Is it to do the thing it's named after? Or is it to do some meaningless microscopic string manipulation or single math op? Why in the world do you want to give a name and identity to a single if() or memcpy() etc?

Re: Push Ifs Up and Fors Down

#84
post #43

Sometimes I like to put the conditional logic in the callee because it prevents the caller from doing things in the wrong order by accident. Like for example, if you want to make an idempotent operation, you might first check if the thing has been done already and if not, then do it. If you push that conditional out to the caller, now every caller of your function has to individually make sure they call it in the rig…

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.

Re: Push Ifs Up and Fors Down

#85
I strongly disagree with this ifs take. I want to validate data where it is used. I do not trust the caller (myself) to go read some comment about the assumptions on input data a function expects. I also don't want to duplicate that check in every caller.

Re: Push Ifs Up and Fors Down

#87
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)

The function printInvoice should print an invoice. What happens if an invoice cannot be printed due to one of the named conditionals being false? You might throw an exception, or return a sentinel or error type. What do to in that case is not immediately clear.

Especially in languages where exceptions are somewhat frowned upon for general purpose code flow, and monadic errors are not common (say Java or C++), it might be a better option to structure the code similar to the second style. (Except for the portrait format of course, which should be handled by the invoice printer unless it represents some error.)

> while code readability is extremely important, encapsulation is much more important

Encapsulation seems to primarily be a tool for long-term code readability, the ability to refactor and change code locally, and to reason about global behavior by only concerning oneself with local objects. To compare the two metrics and consider one more important appears to me as a form of category error.

Re: Push Ifs Up and Fors Down

#88
post #34

Earlier quoted context omitted.

Cyclomatic complexity may be a helpful warning to detect really big functions, but the people who worry about cyclomatic complexity also seem to be the sort of people who want to set the limit really low and get fiesty if a function has much more than a for loop with a single if clause in it. These settings produce those code bases where no function anywhere actually does anything, it just dispatches to three other f…

I call this "poltergeist code". Dozens of tiny functions that together clearly does something complex correctly, but it's very hard to find where and how it's actually done.

I love that name, and will definitely steal it!

Re: Push Ifs Up and Fors Down

#89
I'm not sure I buy the idea that this is a "good" rule to follow. Sometimes maybe? But it's so contextually dependent that I have a hard time drawing any conclusions about it.

Feels a lot like "i before e except after c" where there's so many exceptions to the rule that it may as well not exist.

Re: Push Ifs Up and Fors Down

#90
post #76

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?

Is that really achieving OP's goal though, if you're only raising it by creating a new intermediary level to contain the conditional? The conditional is still the same distance from the root of the code, so that seems like it's not in the spirit of what they are saying. Plus you're just introducing the possibility for confusion if people call the unwrapped function when they intended to call the wrapped function

But the checking and the writing really are 2 different things. The "rule" that you always want to do this check before write is really never absolute. Wrapper is exactly correct. You could have the single function and add a param that says skip the check this time, but that is messier and even more dangerous than the seperate wrapper.

Depends just how many things are checked by the check I guess. A single aspect, checking whether the resource is already claimed or is available, could be combined since it could be part of the very access mechanism itself where anything else is a race condition.

Post reply on HN