back

by vardump·12y ago·view on hn ↗
Whenever you do forget, it's immediately obvious by looking at the function in question. Or by grepping things that "open" something to see if there's corresponding defer nearby.

In practise, it's not a big problem, because you learn fairly soon when doing something reversible that a defer is needed to reverse it when leaving scope/function. Like:

  func DoSomethingInSomeDir() error
  {
    err := os.Chdir("somedir")
    if err != nil { return err }
    defer os.Chdir("..") // always done even if there's panic
    ... do the something that can fail
    return nil
  }
Granted, using chdir in the first place is rather evil in anything but in a simple utility. And yes, chdir("..") doesn't necessarily bring us back to the starting position. And it itself can have error, which should be handled in real code. But this is a quick example. :-)

But I didn't need to write a ChdirToSomewhereAndBack wrapper class either. Control flow is completely obvious. All potential bugs are in plain sight, not hidden in some wrapper.

If you saw this code for the first time ever, you'd have absolutely no surprises or need to browse code in some class.

If you needed to do this chdir thing often, you could simply wrap it in a function:

  func DoSomethingInANamedDir(dirName string, fn() error) error
  {
    err := os.Chdir(dirName)
    if err != nil { return err }
    defer os.Chdir("..") // always done even if there's panic
    return fn(); // do the something that can fail
  }
then just:

  DoSomethingingInANamedDir("something", 
    func() err { ... something that can fail ... })
Of course, as someone who sees this for the first time needs to look inside DoSomethingingInANamedDir. Such is the price of wrappers.
1 comments
'Whenever you do forget, it's immediately obvious by looking at the function in question. Or by grepping things that "open" something to see if there's corresponding defer nearby.'

That's not great. Static guarantees are worlds better than "I can manually look at it, and manually grep for places I need to look".

"But I didn't need to write a ChdirToSomewhereAndBack wrapper class either."

That's not exactly hard, and if you're doing something frequently then writing something to abstract it away is the right approach. It's true that many languages leave defining that wrapper class ugly and push it far away from the site of use in one-offs.

"All potential bugs are in plain sight, not hidden in some wrapper."

That would be one of the upsides, yes. The related downsides are that there is more room for bugs because you have to get it right every time instead of just once, and bugs might be hidden in plain sight when there is too much clutter.

"If you needed to do this chdir thing often, you could simply wrap it in a function:"

That seems isomorphic to the RAII wrapper class, with slightly more syntactic cruft.

"That's not great. Static guarantees are worlds better than "I can manually look at it, and manually grep for places I need to look"."

Grepping or looking is pretty much what you need to do also in C++, if you happen to have the bug in the wrapper. There's no static guarantee that can figure out if there's no chdir back to original location.

If you have a bug in the wrapper, you fix it once in the wrapper, and it's more likely to have shown up in a test if that wrapper is used multiple places. There is no static guarantee that you wrote the wrapper right, but hopefully there is some guarantee that you used the wrapper right. Definitions can't outnumber uses (or errors in some of those definitions don't matter...).