News for hackers. Stuff that matters.
Re:Tidy First? by aulin on Thursday January 11, 2024 @02:38AM (#38948983)
Guard clauses, something so simple yet so underrated, you never know how much you love them until you meet people from the single return per function church.

It still amazes me how they can believe that putting all the guards in tens of nested ifs can be saner than a few extra returns at the top.

And don't get me started on those that goto is bad but it's ok to put everything inside a do while (0) they can break out of and clean up at the end.

Sorry for the off-topic rambling, tidying is like "clean" code, everyone has their own definition and sometimes changing code for the sake of making it tidy is more dangerous than just changing the behaviour, especially if it was already tidy by some other people standard.

Re:Tidy First? by WesolyKubeczek on Thursday January 11, 2024 @03:00AM (#38949159)
Does this church still exist?
Re:Tidy First? by blowski on Thursday January 11, 2024 @03:14AM (#38949249)
I’ve definitely run into its members recently. They tend to have other strange beliefs, like “methods can’t have more than four lines” and “you can instantiate an object only in a factory service”.
Re:Tidy First? by WesolyKubeczek on Thursday January 11, 2024 @09:04AM (#38952283)
I didn't know the Clean Code church was also preaching the single point of return. Single point of return used to be big when Structured Programming was this "newfangled stuff". I'd rather expect some CS professor types smelling of mothballs and shambling about in empty hallways.
Re:Tidy First? by tirpen on Thursday January 11, 2024 @03:32AM (#38949400)
I think it's still required by by the MISRA-C standard, so if you write code for cars and some similar industries, you may get forcefully dragged into that church.
Re:Tidy First? by aulin on Thursday January 11, 2024 @04:09AM (#38949678)
Yep, it may come from that, or maybe as it often happens someone wrote a best practice in the coding style rule, something reasonable like "try to avoid returning from the middle of a function".

People didn't understand that, too vague, hard to enforce. Someone else decided to make it clearer, "try to avoid multiple returns inside a function".

Then someone else comes and decides to tidy up the coding style document, put it in imperative form: "All functions should have a single return statement".

Give it a couple of years, people forgot why the advice was there in the first place and now they just blindly apply an insane rule, teach that to new hires and enforce it in reviews. All functions now look like big arrows and every once in a while the max line length rule has to be relaxed a little to allow for all those levels of indent.

Maybe that's how it entered MISRA-C in the first place.

Re:Tidy First? by WesolyKubeczek on Thursday January 11, 2024 @09:18AM (#38952462)
I'd argue that in languages with manual resource management, you should be very careful how you return in the middle. Say you allocate something, or initialize communications with a device before a loop begins. In the middle of the loop, you find your result early and want to return it.

But your "return" should also have all deallocations performed! Also if you open communications to some external devices which tend to be notoriously stateful, you need to bring them back to a usable state by either completing commands sent so far, or resetting them, or whatever is needed.

If there is nothing analogous to "defer" statements or "finally" clauses, you will be in a world of pain if you sprinkle return statements without paying attention to all details.

Thus I can see a point of such a rule being in MISRA-C or something like it. Guess it's better to enforce an easily checkable rule (prone to birthing monsters) than educate all people properly.

Re:Tidy First? by aulin on Thursday January 11, 2024 @10:24AM (#38953429)
Notice I purposedly used "reasonable" and "in the middle of a function". I know where this rule comes from. It's how it evolved into a blind prescription that is a problem.
Re:Tidy First? by jddj on Thursday January 11, 2024 @04:16AM (#38949731)
Yep. When investigating a solution for a government-adjacent organisation I've been handed a standard which specified single return per function.

A recommendation likely lifted from one of these sources.

Re:Tidy First? by eesmith on Thursday January 11, 2024 @04:18AM (#38949747)
MISRA 2004, Rule 14.7: "A function shall have a single point of exit at the end of the function.", required - https://www.ibm.com/docs/en/rtr/9.0.0?topic=review-code-misr...

MISRA 2012, Rule 15.5: "Only one exit point should be defined in a function.", advisory - https://www.ibm.com/docs/en/rtr/9.0.0?topic=review-code-misr...

HN users cordenr and bfrog say it was dropped in MISRA 2023: https://news.ycombinator.com/item?id=38680587 and https://news.ycombinator.com/item?id=38704631 . Both in a recent thread on MISRA 2023 at https://news.ycombinator.com/item?id=38674158 .

FWIW, HN user FirmwareBurner in that thread links to the "Embedded System development Coding Reference guide" version 3.0 (2018) from the Software Reliability Enhancement Center, Japan at https://www.ipa.go.jp/publish/qv6pgp00000011mh-att/000065271... which says

  M3.1.5:
   A function shall end with one return statement.
   A return statement to return in the middle of processing shall
   be written only in case of recovery from abnormality.
Re:Tidy First? by tirpen on Thursday January 11, 2024 @07:37AM (#38951404)
I was not aware of the changes in MISRA-2023, thanks for informing me!

Also, that's great news to hear in case I ever want to go back to the automotive industry. :)

Re:Tidy First? by WesolyKubeczek on Thursday January 11, 2024 @09:06AM (#38952310)
Well, aren't early returns in guard clauses "recovery from abnormality"?

You could flex some language lawyering to define "abnormality"...

Re:Tidy First? by aulin on Thursday January 11, 2024 @10:25AM (#38953460)
I believe that's exactly it. An exception to the single return rule to allow for guard clauses. Wording is maybe a little too obscure though.
Re:Tidy First? by eesmith on Thursday January 11, 2024 @11:25AM (#38954382)
I don't know.

Here is the only relevant example I found:

  p = X_MALLOC(sizeof(*p) * NUM);
  if (p == NULL) {
    return (MEM_NOTHING);
  }
  ...
  X_FREE(p);
  return (OK);
It's clearly abnormal.

Something like a:

   if (get_size(obj) == 0) {
       return empty_case;
   }
   ... do more complicated code here ...
   return result;
does not feel "abnormal".
Re:Tidy First? by aulin on Thursday January 11, 2024 @03:23AM (#38949329)
Definitely. Let's wait if some adept shows up. I met plenty IRL.

One specific case I have in mind I think it came from the company mostly hiring fresh graduates and seniors indoctrinating them with their blind and badly aged structured programming rules.

Re:Tidy First? by mrkeen on Thursday January 11, 2024 @07:03AM (#38951113)
Sure, I'll play devil's advocate!

> Guard clause – exit a function early if certain conditions are not met. This makes the rest of the function easier to write (no nested if-statements).

If you have:

    if(!isUserPort(strPort)) return;
You're gaining knowledge about input, and then immediately throwing it away (assuming the 'happy' path.)

Later when you use strPort, do you check it again? If you pass it to another function, do you check it there too?

"Parse, don't validate" is the name that's been given to the following:

    case parsePort strPort of
        NotAPort -> ...
        RootPort port -> ...
        UserPort port -> ...
It's more nested, but now you can pass your UserPort around and not check it too many or too few times.
Re:Tidy First? by WesolyKubeczek on Thursday January 11, 2024 @09:34AM (#38952672)
Hm

Unless your parsePort returns a specialized type which then can be enforced by the compiler, and all your other APIs use the specialized representation, it's sort of "six of one, half a dozen of the other" situation because if you're throwing your strPort around, you'll be parsing it again and again and again.

And I can achieve what you are showing me just the same by putting my guard clause up the stack, closer to the main program or whatever.

Not all data is so clear cut, your guard clause may be guarding against a numeric struct member whose value is out of range for this one function to operate.

Which is why I usually have a few different wrenches for each size of nut I may encounter. Not dunking on your approach, let a hundred flowers bloom and all that.

Re:Tidy First? by DeathArrow on Thursday January 11, 2024 @04:43AM (#38949937)
>Sorry for the off-topic rambling, tidying is like "clean" code, everyone has their own definition

That's blasphemy. There is only one true clean code and that is what Uncle Bob is preaching himself.

Re:Tidy First? by xorcist on Thursday January 11, 2024 @04:53AM (#38950043)
"Goto is bad, throw an exception instead."

I meet them everywhere. It's just obvious how strong culture is.

Re:Tidy First? by kazinator on Thursday January 11, 2024 @04:01PM (#38959015)
You can mostly avoid running into the single return people, if you sneak into and out of the building by the rear entrance.
Re:Tidy First? by throwaway13623 on Thursday January 11, 2024 @05:37AM (#38950389)
I agree. Guard clauses and chunk statements make code much more maintainable. It has little cost of implementing, but a huge return on readability and reduced complexity. Nested if-statements are often hard to read and easy to break.
Re:Tidy First? by dspillett on Thursday January 11, 2024 @05:47AM (#38950473)
> It still amazes me how they can believe that putting all the guards in tens of nested ifs can be saner than a few extra returns at the top.

If I have a function that needs a group of guards like that but local style guides demand a single exit point, I start a boolean like “AllHunkyDory” that gets set false where a return would be. Then you can keep the less nested structure and just wrap the main body in “if(AllHunkyDory){}”.

It is probably a touch less efficient, that variable is going to take up a register at least throughout the checks, but a fiunction with piles of guard clauses is hopefully not in a tight loop anyway so that is unlikely to matter.

Re:Tidy First? by aulin on Thursday January 11, 2024 @07:00AM (#38951089)
Sure, there are ways, less readable and more error prone. But that's not what you see where this rule is enforced. You see nested ifs.
Re:Tidy First? by pass_the_salt22 on Thursday January 11, 2024 @07:46AM (#38951497)
I miss this in LISP. It might force me to better structure my programs so that a lot of this is dealt with in an up-front manner or simply can’t occur. I do it all the time in JS though and love it.
Re:Tidy First? by srcreigh on Thursday January 11, 2024 @08:22AM (#38951859)

    (let/cc ret
      (when (= y 0) (ret nil)
      (/ x y))
Re:Tidy First? by Bost on Thursday January 11, 2024 @10:40AM (#38953679)
Your snippet is not working.

  #lang racket

  (define (division x y)
    (let/cc return
      (when (= y 0) (return null))
      (/ x y)))
Re:Tidy First? by augustk on Thursday January 11, 2024 @07:54AM (#38951580)
> It still amazes me how they can believe that putting all the guards in tens of nested ifs can be saner than a few extra returns at the top.

On the other hand, if you have ten early returns your function should most likely be split into smaller functions anyway. The advantage of sticking to structured programming is that it becomes obvious when it's time to do so.

Another advantage of pure structured programming (with a single exit point) is that you can easily add post-conditions to your functions, i.e. assertions which must hold true before the execution returns to the caller.

Re:Tidy First? by kazinator on Thursday January 11, 2024 @04:00PM (#38958998)
I don't favor single return per function myself, but in C code bases where that was the norm, I liked being able to add a debug print in one place which logs the return value.

  DEBUG("%s (%s) -> %d", __func__, str_arg, ret);
  return ret;
Single return doesn't necessarily mean nested ifs; it can be linear.

  bool success = true;

  if (condition(arg1)) {
    success = try_that(arg2);
  }

  if (success && condition2(arg3)) {
    success = other_thing(arg2);
  }

  ...

  return success;

Resource acquisition:

  widget_t *w = widget_create(arg);
  gadget_t *g = w ? gadget_create(w, arg2) : 0;
  char *gncopy = g ? strdup(gadget_name(g));

  if (gncopy) {
    // we must have g and w */
  }
Early returns can cause bugs; they can be premature. The coder though that if a certain condition is true, none of the other cases matter. And may be that was true, but now some of the cases (perhaps new ones) do matter. Someone wrote code to handle them, but didn't notice it's not reached because of that early return.
Re:Tidy First? by aulin on Friday January 12, 2024 @12:22AM (#38964209)
You can define your guard clauses as macros and have them always log a warning and still have your debug log before the final return. See e.g. g_return_if_fail/reached and g_return_val_if_fail/reached in glib/gmessages.h.

Early returns can definitely cause bugs, but here we're talking about basic sanity checks and argument validation, not about program logic. Their purpose is if any of those checks fail we are in a very bad state and there's no point on entering the function at all.