Open chipbarnaby opened 10 months ago
I'd like to lay out some of the pretty extensive discussion that led us to where we are with the logger re: bullet point 2. We actually moved away from the suggested method, which is essentially what we had before.
The main point is the simplest: loggers log. They neither detect, propagate, or handle errors. Client code that detects an error should issue the error, and begin its propagation, whether it's logged or not (C++ Coding Standards #69). After all, only the client function knows its postconditions or invariants have been violated - a logger bears no responsibility for the client's problems. The throwing constructors in btwxt are an example. And there's plenty of flexibility for btwxt's client:
try
{
auto grid_axis = new GridAxis(std::vector<double>{(1.0)}); // Can't create a GridAxis with only 1 value: throw
}
catch (BtwxtException &e)
{
// Do some extra work, extra reporting, whatever...
throw (std::exception(e.what());
}
It's not different from try-catch around a std::vector resize()
.
Throwing is the preferred mechanism in C++ (C++ Coding Standards #72) because, among other things, it separates error handling from the control flow and program logic. If the logger were responsible for generating and propagating the exception, you're making a non-enforceable agreement with the client that just logging error() => fatal. Now any implementation of the logging class interface that doesn't throw, is going to violate the method's postconditions (e.g. creating a viable GridAxis).
Lesson: Even if you can override the error()
function to throw, don't. If a user of error()
needs to log and throw, it can indeed log, then throw, as two separate calls, no need for a derived exception class at all. (That was just one possible shortcut.) In fact, I'd go so far as to label the Courierr methods as nothrow
.
As an aside, we made the decision to allow btwxt to throw across the API boundary, even though that's also not advised (C++ Coding Standards #62) due to there being no universal binary interface for C++ exceptions. We almost exclusively link statically, and even if not, most open source projects that link to it will be using the same build system to build client and libraries at once. So, it's likely to be 99.9% safe.
My response is that Courierr is not a logging class, it is an error-condition-routing class. It allows notifications of various events in a library to be conveyed (dare I say couriered) to the application using that library. Whether the application logs or whatever is none of the base class's business.
My argument for defining error() as "fatal -- no return possible" and using that instead of throw is to consolidate the handling schemes. The application may throw within its derived error(), but routing the event that way allows the application first dibs on what happens. Getting rid of throws in btwxt is not really the point, because std library functions used by btwxt may throw, so the application try/catch is probably needed anyway. (Although std library exceptions could be deemed rare enough to allow them to be caught at some outer level.)
What I want in the application side code is --
All of the above reduces clutter on the application side. The design as it stands is workable, but I think it requires a bunch of code that if I ran the world, I would make unnecessary.
The other suggestions (name in RGI, use const string& for messages, eliminate the context pointer in the courrier base class, maybe more) are independent and I'm not hearing any controversy.
Eliminate exceptions or make them optional. One idea is to have the return value of write_message indicate how to proceed. One response could be "Throw".
Replace error() / warning() etc. with messageType
Message format: Often useful to use form: GridAxis 'MyAxis': Linear extrapolation bogus
Messages should be returned as const char* or const string& (not string_view).
Additional thoughts
write_message
)