The Morality of Error Checking
So, I'm working on this set of classes that generate Java code. In the process, I discover that I have a bunch of private methods on a class and I choose to factor them out into a new class that looks like this:
package glaze.tool;
public class Formatter {
private String text = "";
private String indentation = "";
public void newLine() {
newLine("");
}
public void newLine(String lineText) {
newText("\n" + indentation + lineText);
}
public void newText(String newText) {
text += newText;
}
public void indent() {
indentation += "\t";
}
public void outdent() {
assert indentation.length() > 0;
indentation = indentation.substring(1);
}
public String getText() {
return text;
}
}
The thing that irks me is the outdent() method. I know that the "right" thing is to drop the assertion and add a throw for something like an IllegalStateException and catch it up higher. But, the thing is, I know all of the callers of Formatter, and they all have tests. The outdent() method will never be called when we haven't indented.
In the end, I decided to do this:
public void outdent() {
if(indentation.length() > 0)
indentation = indentation.substring(1);
}
And the behavior is documented with a test:
public void testOutdentWithNoIndentIsaNoOp() {
formatter.newLine("one");
formatter.outdent();
formatter.newLine("two");
assertEquals("\none\ntwo", formatter.getText());
}
I changed the meaning of outdent so that the expected behavior is to do nothing when there is no indentation. Was it a good choice? I think so. It worked well in context. But, I think that it is something that people could debate about for days.
Error detection and recovery is something that we like to pontificate upon the industry, but I don't think there is any one-size fits all solution. In some situations, you are better off failing fast, and in others silent acceptance is okay.
!commentForm
What I'd like to do in this situation is write a contract assertion and have a theorem prover tell me that it can't be called incorrectly. Unfortunately, that isn't in the state of the art for most of us today. Hence the high noise level associated with discussions.
Yes. It wouldn't even need a full blown theorem prover in a case like this. There are only a couple of callers and the use is pretty clear. -- MichaelFeathers
Yes. It wouldn't even need a full blown theorem prover in a case like this. There are only a couple of callers and the use is pretty clear. -- MichaelFeathers
"The outdent() method will never be called when we haven't indented."
Why are you then worried about throwing an IllegalStateException, if the "method will never be called" in this state?
It just clutters the code for no reason. It's a bit of a lie. -- MichaelFeathers
Why are you then worried about throwing an IllegalStateException, if the "method will never be called" in this state?
It just clutters the code for no reason. It's a bit of a lie. -- MichaelFeathers
That's fine, but I'm ticked off that you're using tabs instead of expanding to the nearest multiple of 4.
I didn't think any of us old-timers would fall into that pit again. :-)
But I sympathize. I have the same trouble deciding whether to require good behavior, survive bad behavior, or both. I'd hate to have the revised outdent hide a coding bug later on. Maybe the code generator should die more easily than any "production" application code.
Yeah, I'm going to change to spaces eventually. Tabs were just easier to test-drive in the beginning. I agree: tabs are a tool of the devil. Re dying gracefully, the thing about this code is that it generates Java code.. and the spacing is just a nicety. If, somehow, it ends up wrong, the code will still compile. I'd hate to stop anyone in their tracks when it's released just because the indents fell out of sync. -- MichaelFeathers
I didn't think any of us old-timers would fall into that pit again. :-)
But I sympathize. I have the same trouble deciding whether to require good behavior, survive bad behavior, or both. I'd hate to have the revised outdent hide a coding bug later on. Maybe the code generator should die more easily than any "production" application code.
Yeah, I'm going to change to spaces eventually. Tabs were just easier to test-drive in the beginning. I agree: tabs are a tool of the devil. Re dying gracefully, the thing about this code is that it generates Java code.. and the spacing is just a nicety. If, somehow, it ends up wrong, the code will still compile. I'd hate to stop anyone in their tracks when it's released just because the indents fell out of sync. -- MichaelFeathers
I have mixed feelings about that. In normal case I would introduce some custom subclass of IllegalStateException[?] or make the Formatter class non-public (package-scope for example) to hide some of ugliness from external world. However, this code deals mostly with String manipulation and such libraries are by tradition more tolerant to errors (see Velocity). I don't know if it's a good approach, but it just works in many places and I haven't tried to imagine consequences of adhering to fail-fast rule in this area.
I'm not sure what the effect would be either. I do know that I'm noticing that there are some very significant differences between application and library code but people don't talk about them much. Unfortunately, most people who write about coding conventions seem to write from a library developer's background. I see this example as application code. I know who uses Formatter. It's public and other people can use Formatter, but it's not really useful outside the context of the tool.
One thing that I think is worth talking about in this example is whether there really is a problem. The outdent() method has new documented behavior. Did I finesse away an error condition? -- MichaelFeathers
I'm not sure what the effect would be either. I do know that I'm noticing that there are some very significant differences between application and library code but people don't talk about them much. Unfortunately, most people who write about coding conventions seem to write from a library developer's background. I see this example as application code. I know who uses Formatter. It's public and other people can use Formatter, but it's not really useful outside the context of the tool.
One thing that I think is worth talking about in this example is whether there really is a problem. The outdent() method has new documented behavior. Did I finesse away an error condition? -- MichaelFeathers
Thinking about it in terms of text or text editing, one would not expect an outdent operation in a word processor to crash if the operation was invoked too many times.
And you are very right, these types of discussions occur frequently for API developers, and in my experiences, it is usually a matter of preference. On occasion, we opt for adding a boolean parameter to these methods that indicate if they should fail.
-Brian
And you are very right, these types of discussions occur frequently for API developers, and in my experiences, it is usually a matter of preference. On occasion, we opt for adding a boolean parameter to these methods that indicate if they should fail.
-Brian
Why not keep the assert in your final version? It would help you track down erroneous outdent calls when debugging.
I think your making a good compromise, though obviously it's situational. You've got several dilemmas here. One is balancing at production runtime. That's the fail fast vs silent acceptance.
Another dilemma is balancing production vs. maintainability. Luckily you have some tools, such as assertions for this. It would be much nicer as the first poster pointed out to have contract assertions validated by a theorem prover, than a plain old assertion, but it works ok.
Silent acceptance can be okay for the end-user, but for the developer, it's always the wrong path. You don't always need to choose failure however, since you could instead choose noisy acceptance. We don't think about that option too often because noisy acceptance is almost always the wrong choice for the end-user. There are rare exceptions, but in general letting the end-user know that the widget popped isn't much use.
I think your making a good compromise, though obviously it's situational. You've got several dilemmas here. One is balancing at production runtime. That's the fail fast vs silent acceptance.
Another dilemma is balancing production vs. maintainability. Luckily you have some tools, such as assertions for this. It would be much nicer as the first poster pointed out to have contract assertions validated by a theorem prover, than a plain old assertion, but it works ok.
Silent acceptance can be okay for the end-user, but for the developer, it's always the wrong path. You don't always need to choose failure however, since you could instead choose noisy acceptance. We don't think about that option too often because noisy acceptance is almost always the wrong choice for the end-user. There are rare exceptions, but in general letting the end-user know that the widget popped isn't much use.
Add Child Page to TheMoralityOfErrorChecking