I started a new job and we've been instructed to use Ubers Go coding standards. I'm not sure about one of their guidelines entitled "Exit Once":
If possible, prefer to call
os.Exitorlog.Fatalat most once in yourmain(). If there are multiple error scenarios that halt program execution, put that logic under a separate function and return errors from it.
Wouldn't this just mean offloading main() into another function (run())? This seems a little superfluous to me. What benefits does Uber's approach have?
I'm not familiar with Uber's entire Go coding standards, but that particular piece of advice is sound. One issue with
os.Exitis that it puts an end to the programme very brutally, without honouring any deferred function calls pending:(my emphasis)
However, those deferred function calls may be responsible for important cleanup tasks. Consider Uber's example code snippet:
If
ioutil.ReadAllreturns a non-nil error,log.Fatalis called; and becauselog.Fatalcallsos.Exitunder the hood, the deferred call tof.Closewill not be run. In this particular case, it's not that serious, but imagine a situation where deferred calls involved some cleanup, like removing files; you'd leave your disk in an unclean state. For more on that topic, see episode #112 of the Go Time podcast, in which these considerations were discussed.Therefore, it's a good idea to eschew
os.Exit,log.Fatal, etc. "deep" in your programme. Arunfunction as described in Uber's Go coding standards allows deferred calls to be run as they should before programme execution ends (potentially with a non-zero status code):An additional benefit of this approach is that, although the
mainfunction itself isn't readily testable, you can design such arunfunction with testability in mind; see Mat Ryer's blog post on that topic.