On Thu, 26 May 2016 16:54:11 +0200
Torsten Bronger <
bro...@physik.rwth-aachen.de> wrote:
> >> My program is only a few hundred lines long yet, but I have already
> >> three cases where I can only abort:
> >>
> >> - The DB behaves inconsistently.
> >> - json.Marshal() fails.
> >> - http.ResponseWriter.Write() fails.
> >>
> >> And all of this occurs in multiple places in the code. I could
> >> wrap the three cases in own functions that also contain the
> >> panic, but I would do this only to avoid my ubiquitous
> >> PanicIfError. Should I do this wrapping nevertheless, or keep
> >> the PanicIfError's?
> >
> > I prefer to use log.Fatal for these "too hard to handle now"
> > errors;
>
> PanicIfError also writes to the log. In which respect does
> log.Fatal do more than that? Besides, I find a stack trace quite
> informative.
Well, the original intent of panic() is to handle truly exceptional
cases, and "exceptional" here also means you did not actually _expect_
such condition to happen. The best example is dereferencing a nil
pointer: when you write your program you do not consciously attempt to
write code which explicitly dereferences nil pointers. Hence when such
dereferencing happens, you have no idea what could have gone wrong, and
that's why panic() helps you by sort-of snapshotting the executing
state for you (printing out stack traces is really just a more modern
way of dumping core files).
Conversely, when your program flow _explicitly_ detects a grave
("no way to handle") error, that's not an unexpected condition: at the
point the error is detected, the program flow has full awareness about
the context of the error. Hence there's little sense in snapshotting
the execution state because you can just log an error message decorated
with the description of its context and bring the program down.
That's what log.Fatal*() do.
If you'll think about this a bit more, you'll notice that well-known
ubiquitous programs (think of, say, GNU coreutils) do not blow up with
stack traces on errors -- they output an error and quit. Quite often
they quit with different exit code for different classes of errors
(say, configuration errors, input data errors, data output errors etc),
though in my practice the only occurence where custom exit codes were
useful was writing a program run by Sendmail for the final delivery --
because Sendmail is actually able to interpret a dozen of specific exit
codes of its delivery agents to make a decision on how to report the
failure to the client and what to do with the message.
log.Fatal*() always exit with code 1 but providing a custom "log and
exit" function is trivial. Conversely, blowing up with exception stack
trace dumping on innocent errors like the user passing a non-existing
command-line option is an approach popularized by sloppy coding in
languages/runtimes with exceptions.
Having said that, I admit that using panic() when log.Fatal*() appears
to be a good fit is not actually bad. Given your previous concrete
example, IMO panicking on json.Marshal() erroring out or detecting the
database state inconsistency is OK. Still, you should understand that
conceptually the stack trace won't give you any more clue to the case
of that error than you already have when it has happened.