johnmcfarlane / papers

9 stars 1 forks source link

Contractual Disappointment in C++ #51

Closed johnmcfarlane closed 2 years ago

akrzemi1 commented 3 years ago

I would like to complain about the usage of the word "error". My complaint applies not only to this paper, but also to any paper that uses word "error" to mean "any disappointment that is not a bug".

Term "bug" is quite intuitive and unambiguous: we know that this is something that the programmer did wrong and has to fix. The same cannot be said about the word "error". For instance, "a bug is a programmer error". Therefore, when you start with

illustrates how errors and bugs are treated very differently

I am immediately confused, even though I have encountered documents that use term "error" in the way you do. If there is no better name for it, I would at minimum suggest to define the term "error" before it is used the first time.

Similarly, the statement

An error is the violation of a End User Contract.

requires some clarification, because it is in conflict with the alternative model for contracts. The alternative model, in short, is this.

Apart from other things a contract has a precondition and a postcondition. The user's obligation is to satisfy the precondition. If (and only if) the precondition is satisfied, the provider's obligation is to (1) maintain the class invariant (if any), and (2) do one of the two:

In this model, throwing an exception and not satisfying the postcondition is not considered a program violation. One model is not better than the other, I guess, but I think the paper should acknowledge the existence of the second (and quite popular) model.

johnmcfarlane commented 3 years ago

@akrzemi1

I would at minimum suggest to define the term "error" before it is used the first time.

Unfortunately a lot of these definitions and introductions are scrambling to be at the top of the document. I agree that the use of error is problematic for the reasons you give. However, I'm being lazy and deferring to Herb's definition from P0709 and I lay that out in the very first sub-section, "Bugs and Errors".

johnmcfarlane commented 3 years ago

@akrzemi1

... it is in conflict with the alternative model for contracts. The alternative model, in short, is this. ... One model is not better than the other, I guess, but I think the paper should acknowledge the existence of the second (and quite popular) model.

I don't yet see the alternative use of error here. If, say, a file is not found with a C++ API (a function) and the function throws an exception. This is within contract. This is what is termed a 'wide contract'. This is not an error. It is an exceptional circumstance. Can you provide an example of something that:

Even if it's something that users might refer to it as an error, but isn't accurately an error, I'd be keen to clarify this. But I need to know exactly what it is. Error is indeed a difficult word to use well to the reader's satisfaction!

akrzemi1 commented 3 years ago

I don't yet see the alternative use of error here. If, say, a file is not found with a C++ API (a function) and the function throws an exception. This is within contract. This is what is termed a 'wide contract'. This is not an error. It is an exceptional circumstance. Can you provide an example of something that:

* can be (accurately) described as an error,

* isn't a bug, and

* would not be considered an error within the document?

Even if it's something that users might refer to it as an error, but isn't accurately an error, I'd be keen to clarify this. But I need to know exactly what it is. Error is indeed a difficult word to use well to the reader's satisfaction!

I mean the situation in a "End User Contract". Suppose I (contract provider) offer a function:

void Stack::push(int val)
  [[post: this->size() == OLD(this->size()) + 1 ]]
  // trows upon exceeding max_size()
;

The function does not require of the user to check if size() == max_size(). If this is the case -- as per the contract -- it throws an exception. Obviously, the postcondition (that the size is incremented) cannot be satisfied.

It is my reading of section "Bugs and Errors" that you consider this to be an "error".

johnmcfarlane commented 3 years ago

I mean the situation in a "End User Contract".

The End User Contract is concerned with use of the program and what the program user can expect it to get out of it. It's still not 100% clear in the doc, but functions should not be directly related to End User Contract, rather to C++ API Contract.

Obviously, the postcondition (that the size is incremented) cannot be satisfied.

Regarding whether a postcondition can be met if an exception is thrown, I would question that. It needs clarity, yes, but if a contract states that the function might throw, I would not automatically expect the stated postconditions to be met.

E.g.

vector<byte> load(string filename)
  [[post v: v.data() != nullptr ]]
  // throws if file couldn't be opened, e.g. file not found
;

If the file isn't found, I think all bets are off. The postcondition is something the caller might assert on the line following the call. The program won't get to that line if an exception is called. If that's the point we're not in agreement on, I think that's a matter for specific discussion in SG21.

It is my reading of section "Bugs and Errors" that you consider this to be an "error".

I am eager to fix this section but still am missing what I need to do that.

If a function throws an exception, that's neither a bug nor an error. It is an exceptional occurrence -- a different control flow to follow when special things happen. It can/should be entirely described as part of the contract.

akrzemi1 commented 3 years ago

The End User Contract is concerned with use of the program and what the program user can expect it to get out of it. It's still not 100% clear in the doc, but functions should not be directly related to End User Contract, rather to C++ API Contract.

Ok, so you are saying that word "error" is only in the context of the interface between the running program and the user. It would work for me, but at the same time the article, in the same section (Bugs and Errors), refers to P0709R4, and P0709R4 provides a different definition of "error": all those things that you throw.

Second. Even if we are talking only about the interface between the program and the user, is it not still worthwhile to distinguish the things like "user, but you have no memory left on the device to run the program", form "this is a bug in the program". That is, the contract of the program is not "I will open your file" but "I will check if there is a filesystem in your environment, if you have enough privileges, if you have enough memory, if the file you want exists, and only then will I give you the contents of the file".

johnmcfarlane commented 3 years ago

P0709R4 provides a different definition of "error": all those things that you throw.

My interpretation of P0709R4 is that throwing an exception is something the program developer can chose to do in response to an error. Does that sound right? Can you point to something specific in P0709 which takes a different view?

...is it not still worthwhile to distinguish the things like "user, but you have no memory left on the device to run the program", form "this is a bug in the program"...

I believe that is referred to in P0709R4 as "abstract machine corruption". I've added an explanation to Bugs and Errors of why it isn't discussed.

akrzemi1 commented 3 years ago

P0709R4 provides a different definition of "error": all those things that you throw.

My interpretation of P0709R4 is that throwing an exception is something the program developer can chose to do in response to an error. Does that sound right?

It might be wrong or right, depending on what you mean by "error" is. I still do not know what your definition of "error" is. Your paper says "An error is the violation of an End User Contract", which I read as "unless we are talking about End user Contract, there is no such thing as error". Now I hear you quote P0709R4, "throwing an exception is something the program developer can chose to do in response to an error": this clearly implies that an "error" is something in C++ API. I see it as a contradiction: one sentence says "error" is only in End User Contract, the other sentence says that "error" is in C++ API.

Can you point to something specific in P0709 which takes a different view?

P0709 in 1.1 has: "In this paper, 'error' means exactly and only 'a function couldn’t do what it advertised'—its preconditions were met, but it could not achieve its successful-return postconditions, and the calling code can recover."

It seems it represents the same view as when you say "throwing an exception is something the program developer can chose to do in response to an error", but it is different view than "An error is the violation of an End User Contract" (taken from your paper).

johnmcfarlane commented 3 years ago

Now I hear you quote P0709R4, "throwing an exception is something the program developer can chose to do in response to an error": this clearly implies that an "error" is something in C++ API.

The C++ API is involved with handling the error. If an error occurs and the C++ API detects it and throws an exception, there is no error in the C++ API. The C++ API is correct.

I see it as a contradiction: one sentence says "error" is only in End User Contract, the other sentence says that "error" is in C++ API.

An "error" is a violation in the End User Contract. That error may be dealt with as part of a C++ API, which might be perfectly implemented and bug-free.

It seems it represents the same view as when you say "throwing an exception is something the program developer can chose to do in response to an error", but it is different view than "An error is the violation of an End User Contract" (taken from your paper).

I guess I'm laying down some ground rules in this document. And I'm sure there is plenty of code out there which break those rules. (There's a ton of code which throws exceptions in response to bugs, including in the Standard Library!) But ask yourself when an exception might ever be thrown that observed P0709's and my advice. My bet is that in every case, the developer(s) could not have done anything to avoid that event. There was no bug they could have fixed. Either something largely unavoidable happened (e.g. out-of-memory) or something happened which the user must fix (missing file, closed port, bad command-line parameter, semantically-incorrect data in a YAML file, etc.)

What is an error for which you might throw, but which isn't something that's the responsibility of the user?

akrzemi1 commented 3 years ago

I guess I'm laying down some ground rules in this document. And I'm sure there is plenty of code out there which break those rules. (There's a ton of code which throws exceptions in response to bugs, including in the Standard Library!) But ask yourself when an exception might ever be thrown that observed P0709's and my advice. My bet is that in every case, the developer(s) could not have done anything to avoid that event. There was no bug they could have fixed. Either something largely unavoidable happened (e.g. out-of-memory) or something happened which the user must fix (missing file, closed port, bad command-line parameter, semantically-incorrect data in a YAML file, etc.)

Thanks. This actually gives me the intuitive understanding of what you consider an "error". If I were to phrase, it would be "a disappointing situation in the program's environment (OS, input files, ports) that has to be reflected in a program and propagated through the program layers, and ultimately communicated to the end user, because it likely conflicts with the user's task". Does this capture your view?

What is an error for which you might throw, but which isn't something that's the responsibility of the user?

Let me try. Consider a program doing mathematical computations, e.g. computing the roots of a user-provided polynomial. Te user doesn't know it, but the program -- apart from computing the desired values -- also maintains an internal log in a file. This log can be used in case the application needs to be debugged. Of course, such logging requires file manipulation, so the logging procedure may get an information from the filesystem that file does not exist or cannot be opened. This will be reflected as a throw in the program, but this information should never surface to the user interface.

Similarly, a different program may be tasked with removing all files in the folder. The way it is implemented, is that it first lists all files in the folder and then calls some delete on every element in this list. It may happen that upon such individual deletion we will get an exception because the file does not exist (maybe another program deleted it in the meantime). We shouldn't bother the user (or anyone else) about this because the goal was not to have the file there: if someone else deleted it for us, it is ok. This qualifies as an exception at the level of delete call, but not at the level of our program logic.

Does this make sense?

johnmcfarlane commented 3 years ago

"a disappointing situation in the program's environment (OS, input files, ports) that has to be reflected in a program and propagated through the program layers, and ultimately communicated to the end user, because it likely conflicts with the user's task". Does this capture your view?

Pretty-much, yes.

Consider a program doing mathematical computations, e.g. computing the roots of a user-provided polynomial. Te user doesn't know it, but the program -- apart from computing the desired values -- also maintains an internal log in a file. This log can be used in case the application needs to be debugged. Of course, such logging requires file manipulation, so the logging procedure may get an information from the filesystem that file does not exist or cannot be opened. This will be reflected as a throw in the program, but this information should never surface to the user interface.

This really helps clarify things.

The whole paper is written from the point of view of the program developer. They are part of a kind of 'supply chain'. They are the user of interfaces such as system APIs (the filesystem being one of them). And they are providers of an interface to the End User. What is reported as an error from the filesystem is not necessarily propagated as an error back to the End User. In between, the program can eat that error, i.e. by catching the exception and continuing. That's what happening in this case. The program doesn't stop running; it continues without logging. So what you're describing does involve an error, but it's in the C++ API Contract (between filesystem and program) and not in the End User Contract. It's elsewhere in the supply chain.

Similarly, a different program may be tasked with removing all files in the folder. The way it is implemented, is that it first lists all files in the folder and then calls some delete on every element in this list. It may happen that upon such individual deletion we will get an exception because the file does not exist (maybe another program deleted it in the meantime). We shouldn't bother the user (or anyone else) about this because the goal was not to have the file there: if someone else deleted it for us, it is ok. This qualifies as an exception at the level of delete call, but not at the level of our program logic.

Yes, you're right. Again this is an error. But it's not 'our' error. It's not an error that the developer has domain over.

I'm wondering now what -- if anything -- needs clarifying in the document.

akrzemi1 commented 3 years ago

I'm wondering now what -- if anything -- needs clarifying in the document.

What would help me at least, would be to modify the definitions of "bug" and "error". It is my understanding that the difference between the two is in what is the source of the disappointment: in the former case it is the divergence between program specification and what it actually does (or change "program" to "component"); in the latter case it is the environment that behaves differently than assumed. Both bugs and errors can surface at any level, including C++API and the program interface.

johnmcfarlane commented 3 years ago

Any better?

johnmcfarlane commented 3 years ago

I'm at the point where I cannot think of much more to add. Next, I will cut out as much as possible and then polish.

johnmcfarlane commented 3 years ago

@nicoliberato here is the essay

johnmcfarlane commented 3 years ago

switch examples are wrong. Assertion should be a case.