4 ms·
My question was "what does it mean to to you (or to Joel Spolsky, since it's his words originally)" to "deal with an error immediately"? Do you see some third o
by simiones 4y ago
My question was "what does it mean to to you (or to Joel Spolsky, since it's his words originally)" to "deal with an error immediately"? Do you see some third option in addition to my 1 and 2?
Otherwise, whether you write:
{
auto x = new int[100];
int err = foo(x);
if(err) {
delete[] x;
return err;
}
//do something with x
delete[] x;
return 0;
}
or you write
{
auto x = std::unique_ptr<int>(new int[100]);
foo(x);
//do something with x
}
I don't see any significant difference. The exception case is shorter (less code to review), while the manual error return code is more explicit (easier to review the code).
- jstimpfle 4y ago> I don't see any significant difference. Because this is a toy example. In a real system, you avoid allocating / deallocating the buffers right before and after you work on them, to begin with.
- simiones 4y agoYou are not answering my question. The fact that x is a buffer is irrelevant. Here, I'll rephrase: Otherwise, whether you write: { auto x = fopen("someFile"); int err = foo(x); if(err) { fclose(x); return err; } //do something with x fclose(x); return 0; } or you write { auto x = std::ifstream("someFile.txt"); foo(x); //do something with x } What is the fundamental difference?
- jstimpfle 4y agoI'm not sure what obvious or not-so-obvious observation you want me to state, but in my point of view there is no difference relevant to this discussion; both is uninteresting and bad code that you want to avoid writing in a larger system. It doesn't change much whether this is a buffer or any other kind of resource. As your system grows more structure, the handling of resources (including errors) is moving more and more towards centralized subsystems. You want to avoid complicated fallible and _repetitive_ logic inside random "application" code. In application code, you don't want to just open files, incurring all sorts of non-deterministic latencies, blocking the current thread of execution. What you do instead is, you move to an event based system, where your "application modules" are completely contained in a shell of "platform code". Note that both your examples contain really boring _repetitive_ code, whether visibly in the source, or only in the assembly generated by RAII mechanism. This is _bad_ as soon as you have multiple of these chunks of code. Think web applications. Do you accept() a socket from within any random request handler? Does any request handler read() from the socket to parse the request data? No, all that happens in a driver that just notifies the individual request handlers when more work is coming. Consequently, the only places where errors from accept() or read() need to be handled are at central locations within the framework. This requires some structure, for example your web request handler has to be registered with the web framework, like framework.register_handle("POST", "/foo", handler). Or similar, I'm actually not a web person. In the same way, you don't fopen() files in any random place in a 100 KLOC codebase. Instead you have abstractions for asset loading, media playback, savefile storage, ... whatever and _ideally_ all those come back to probably some kind of caching layer, maybe an async I/O worker pool, and finally a single line of fopen() (or open()) in your entire codebase. Note that for this to work, you have to change your general approach to things. You can't expect to write script-like code anymore that does "save file now" or similar. What you do instead is, you push a job to some subsystem, and when the job is done you will be notified later so you can complete the necessary tasks. It's all event handling.
- ryandrake 4y agoMy philosophy has always been: Clearly separate the input from the result from the error, using different features of the language for each. Pass input variables only as arguments, return only the "result" of the function, and throw errors. Take the classic (and simplistic) divide function, in pseudocode. There are a number of ways you can do it, and I'm not listing them all: divide(numerator, denominator) returns quotient, throws error divide(in numerator, in denominator, out quotient) throws error divide(in numerator, in denominator, out quotient) returns error divide(in numerator, in denominator, out quotient, out error) divide(numerator, denominator) returns tuple(quotient, error) I've always considered the first one more expressive, more usable by the caller (e.g. you can pass the result of the function directly to the next function), and the least confusing. The second one is -meh-.. I'm just not a big fan of input and output arguments. You have a return value, why not use it? The last three are particularly bad, because the caller must remember to check the error value manually (unless your compiler can remind you).