Earlier quoted context omitted.
(Opinions are my own) > Also, their "no exceptions" rule only applied to work involving their legacy code. The reasons for not using exceptions are outlined in this post: https://abseil.io/tips/76 absl::Status is basically exceptions without language sugar. Or, another way of looking at it, golang err in C++. Basically, non-local control flow is dangerous as it can't be evident to the programmer when something deep i…
> Basically, non-local control flow is dangerous (...) This is the crux of the error you're making. Exceptions are not about control flow. Exceptions are a transparent and clean way to handle exceptional events. Exceptions are not intended to, say, handle the status code of a HTTP requests. Exceptions are intended to handle exceptional and potentially unrecoverable errors in a safe and controlled manner, such as fail…
I'd sum up this argument as: Exceptions are syntax sugar that allow callers of a function to ignore the error cases of something they are calling and depend on something that calls into them to handle the error case.
An example:
void HandleRequest(const Request &request, KV &kv) {
...
kv.set("a", 10);
...
}
In this example KV.set will raise an exception. The programmer implementing HandleRequest isn't directly exposed to this fact and so to them, and the reviewer, and future onlookers this code looks correct. Now lets say kv.set() throws an exception in production under a specific case. Maybe there were two people attempting to set the same value at the same time or a networking issue. Doing this in the context of a webserver might make sense as the webserver might handle exceptions as error codes but that's not the end-all-be-all. Suppose we refactored to something like this Something CreateSomething(....);
How do you know if this function will throw an exception? How do you find all of the possible exceptions that can be thrown? Statically you can't really. If instead you see absl::StatusOr CreateSomething(...);
You can tell for sure that the result has some error that needs to be handled. Your original code: kv.set("a", 10);
This code no longer compiles in an absl::Status world. Instead you'd need to do something like: CHECK_OK(kv.set("a", 10)) // this will panic
RETURN_IF_ERROR(kv.set("a", 10)) // Bubble up
From this we get:1. a stack trace since RETURN_IF_ERROR adds metadata about the call site.
2. Guarantee that code will not compile if errors are not handled.
3. Guarantee that future readers know that some very high level function could probably call into code that can produce an error you need to handle.
This matters much more if things like this are happening:
kv.beginTransaction();
kv.set(...);
kv.endTransaction();
This could be handled by destructors in this case but in other cases: otherService.startingWork();
kv.set(...);
otherService.doNextStep();
There are cases where destructors do not make sense. You do not always want to call `doNextStep()` as it would be 100% wrong in the case where we cannot set our value in our kv store. Contrived but I've run into these in real life services. If a developer sends me the above code snippet I might LGTM. If the developer instead sends me: otherService.startingWork();
if (!kv.set(...).ok()) { log("something went wrong"); }
otherService.doNextStep();
I'll be able to point to the exact problem with this code much more easily. Also if there's an outage and I need to read this code I can clearly see why this `something went wrong` in the logs correlates to incorrectly called doNextStep().I'm not saying that Status is perfect (I'm not 100% sold) but exceptions are a type of control flow in an abstract sense. The problem some people have with it is it's control flow you can't audit.