G
Giovanni Azua
Hello,
This is a follow up to the thread "Where should I throw RuntimeException".
At some point the topic regarding the use of assertions took over that
thread discussion.
Before moving into the concrete arguments I wanted to present the following
definitions by Bertrand Meyer:
Defensive programming: "A technique of fighting potential errors by making
every module check for many possible consistency conditions, even if this
causes redundancy of checks performed by clients and suppliers. Contradicts
Design by Contract." [1, p1195]
Design by Contract: "A method of software construction that designs the
components of a system so that they will cooperate on the basis of precisely
defined contracts. See also: defensive programming." [1, p1195]
"Assertion Violation rule (1) A run-time assertion violation is the
manifestation of a bug in the software." [1, p346]
The key in the defensive programming definition is "check". It doesn't
really matter what is done after encountering the problem either throwing
exceptions or programming an alternate way e.g. Math.sqrt that handles the
special case of negative input by returning Double.NaN. Please note that
neither Eiffel "require" nor the Java "assert" for verification of pre
conditions would fall under the category "check", by definition Design by
Contract (DbC) is not a check, they are enabled only optionally and for
discovering programming errors. Granted that Java assert feature is a small
subset of what Eiffel DbC offers [3, p15 "So, is it like 'assert.h'"].
The point I was defending was that Sun's commended way to use assertions was
too categorical "Do not use assertions to check the parameters of a public
method" [2]. The reasoning for my discrepancy towards that rule is that
programs should not handle pre condition violations that are rooted in
programming errors by means of exceptions e.g. IllegalArgumentException.
However, the case of user input validation is a good use-case for using
IllegalArgumentException. In general, exceptions are meant to be handled
programmatically. You can't nor should recover from programming errors but
rather *fix* them e.g.
Defensive alternative A:
public void defensiveMethod(...) throws IllegalArgumentException {
// first checks
if (/* some input programming error condition */) {
throw IllegalArgumentException("Caught just another bug");
}
// some checks
// some more checks ...
// now we are "safe", do the real business
}
// a possible client
try {
defensiveMethod(...);
}
catch (IllegalArgumentException exception) {
// what can you possibly do here? maybe we could ...
//
// - retrieve class name of the caller
// - get the author name out of the java file
// - send her an email complaining to FIX her code!
}
vs.
DbC alternative B:
public void dbCMethod(...) {
// assert-based pre condition verification. Assertions
// will be disabled once all programming errors are
// corrected
// assert-based class invariant verification
// do the real business
// assert-based class invariant verification
// assert-based post-condition verification
}
Defensive programming as defined by Bertrand Meyer above yields the
following negative consequences in software engineering:
- degrades performance
- predates reliability
- increases complexity
- provides only a subjective sense of safety
- increases code duplication, redundancy
- scatters concerns across disparate layers
A few excerpts from Bertrand Meyer's book:
"Non-Redundancy principle: Under no circumstances shall the body of a
routine ever test for the routine's precondition." [1, p343] For the example
that depicts such case he writes "is not just unnecessary but unacceptable"
[1, p343]
"... complexity is the major enemy of quality. When we bring in this
concern, possibly redundant checks do not appear so harmless any more!
Extrapolated to the thousands of routines of a medium -size system (or the
tens or hundreds of thousands of routines of a larger one), the if x < 0
then ... of sqrt, innocuous at first sight, begins to look like a monster of
useless complexity. By adding possibly redundant checks, you add more
software; more software means more complexity, and in particular more
sources of conditions that could go wrong; hence the need for more checks,
meaning more software; and so on ad infinitum. If we start on this road only
one thing is certain: we will never obtain reliability. The more we write,
the more we will have to write." [1, p344]
"Aside from performance considerations, however, the principal reason to
distrust defensive programming is simply our goal of getting the best
possible reliability. For a system of any significant size the individual
quality of the various elements involved is not enough; what will count most
is the guarantee that for every interaction between two elements there is an
explicit roster of mutual obligations and benefits - the contract. Hence the
Zen-style paradox of our conclusion: that to get more reliability the best
policy is often to check less." [1, p345]
Meyer respond to you here ...
"Such a comment, however, comes from a microscopic understanding of
reliability, focused on individual software elements such as the sqrt
routine. If we restrict our view to the narrow world of sqrt, then the
routine seems more robust with the extra test than without it. But the world
of a system is not restricted to a routine; it contains a multitude of
routines in a multitude of classes. To obtain reliable systems we must go
from the microscopic view to a macroscopic view encompassing the entire
architecture." [1, p344]
the call does not satisfy the precondition, then the class is not bound by
the postcondition. In this case the routine may do what it pleases: return
any value; loop indefinitely without returning a value; or even crash the
execution in some wild way. This is the case in which (in reference to the
discussion at the beginning of this chapter) 'the customer is wrong'." [1,
p343]
Once there is a programming error that lead to pre condition violations in
production, it does not really matter what the preferred paradigm is, either
DbC or Defensive Programming, the result will be either a crash or any
behavior that differs to that stated in the software specification.
An excellent example on how the defensive programming approach i.e. check
and throw exception can not only give a very subjective sense of safety but
also become "the problem" is the 500 million loss case of Ariane 5 "The
exception was due to a floating-point error: a conversion from a 64-bit
integer to a 16-bit signed integer, which should only have been applied to a
number less than 2^15, was erroneously applied to a greater number,
representing the "horizontal bias" of the flight. There was no explicit
exception handler to catch the exception, so it followed the usual fate of
uncaught exceptions and crashed the entire software, hence the on-board
computers, hence the mission. " [4]
From my professional experience I have personally seen and worked with both
approaches. I have seen software developed for the Java platform that could
not afford bugs but neither compromise in reliability nor performance, a
real-time high-frequency trading arbitrage solution using the closest DbC
you can have in Java. I can't recall one single e.g.
IndexOutOfBoundsException in production and most pre condition verifications
were done via asserts and these were only enabled during testing.
I hope all the excerpts and explanations illustrate my point.
e.g.
- Formal methods [5]
- Contract-based automatic testing [6]
Soon there is this very very nice and interesting LASER Summer School where
Bertrand Meyer himself will present some of these concepts
[http://se.inf.ethz.ch/laser/2009/index.php]. I might join btw
For perfectjpattern I deliverately chose to stick to the way Sun commends
regarding the use of assertions, I would like my project perfectjpattern to
be accepted and not rejected by the Java community. I know the concepts of
DbC and how implementing those concepts in Java via assertions might be
outrageous for experienced developers (hey! at first it happened to me too)
but it can't hurt to have a wider picture and use this knowledge to make
more educated design decisions.
Related discussions:
"Where should I throw RuntimeException"
[http://groups.google.com/group/comp...n&lnk=gst&q=RuntimeException#30a419d1a0522e92]
"Assertions vs Exceptions" posted to this group in 2007
[http://groups.google.com/group/comp...hread/54c528bd8ca61f73/e4ebf460a43f9aa1?hl=en]
"Modular Protection vs Assertions" posted to the comp.lang.eiffel group in
2007
[http://groups.google.com/group/comp...6fbf9cd89c5/273571d8d33088a1#273571d8d33088a1]
Best regards,
Giovanni
[1] Programming with Assertions
[http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html]
[2] Object Oriented Software Construction 2nd Edition Bertrand Meyer
[http://www.amazon.com/Object-Orient...=sr_1_1?ie=UTF8&s=books&qid=1243672117&sr=8-1]
[3] OOSC course slides "Design by Contract"
[http://se.inf.ethz.ch/teaching/ss2005/0250/lectures/oosc_11_dbc_1up.pdf]
[4] The lessons of Ariane
[http://archive.eiffel.com/doc/manuals/technology/contract/ariane/page.html]
[5] Formal methods [http://en.wikipedia.org/wiki/Formal_methods]
[6] Automatic Testing based on Design by Contract
[http://www.mathematik.uni-ulm.de/sai/mayer/soqua05/slides/ciupa.pdf]
This is a follow up to the thread "Where should I throw RuntimeException".
At some point the topic regarding the use of assertions took over that
thread discussion.
Before moving into the concrete arguments I wanted to present the following
definitions by Bertrand Meyer:
Defensive programming: "A technique of fighting potential errors by making
every module check for many possible consistency conditions, even if this
causes redundancy of checks performed by clients and suppliers. Contradicts
Design by Contract." [1, p1195]
Design by Contract: "A method of software construction that designs the
components of a system so that they will cooperate on the basis of precisely
defined contracts. See also: defensive programming." [1, p1195]
"Assertion Violation rule (1) A run-time assertion violation is the
manifestation of a bug in the software." [1, p346]
The key in the defensive programming definition is "check". It doesn't
really matter what is done after encountering the problem either throwing
exceptions or programming an alternate way e.g. Math.sqrt that handles the
special case of negative input by returning Double.NaN. Please note that
neither Eiffel "require" nor the Java "assert" for verification of pre
conditions would fall under the category "check", by definition Design by
Contract (DbC) is not a check, they are enabled only optionally and for
discovering programming errors. Granted that Java assert feature is a small
subset of what Eiffel DbC offers [3, p15 "So, is it like 'assert.h'"].
The point I was defending was that Sun's commended way to use assertions was
too categorical "Do not use assertions to check the parameters of a public
method" [2]. The reasoning for my discrepancy towards that rule is that
programs should not handle pre condition violations that are rooted in
programming errors by means of exceptions e.g. IllegalArgumentException.
However, the case of user input validation is a good use-case for using
IllegalArgumentException. In general, exceptions are meant to be handled
programmatically. You can't nor should recover from programming errors but
rather *fix* them e.g.
Defensive alternative A:
public void defensiveMethod(...) throws IllegalArgumentException {
// first checks
if (/* some input programming error condition */) {
throw IllegalArgumentException("Caught just another bug");
}
// some checks
// some more checks ...
// now we are "safe", do the real business
}
// a possible client
try {
defensiveMethod(...);
}
catch (IllegalArgumentException exception) {
// what can you possibly do here? maybe we could ...
//
// - retrieve class name of the caller
// - get the author name out of the java file
// - send her an email complaining to FIX her code!
}
vs.
DbC alternative B:
public void dbCMethod(...) {
// assert-based pre condition verification. Assertions
// will be disabled once all programming errors are
// corrected
// assert-based class invariant verification
// do the real business
// assert-based class invariant verification
// assert-based post-condition verification
}
Defensive programming as defined by Bertrand Meyer above yields the
following negative consequences in software engineering:
- degrades performance
- predates reliability
- increases complexity
- provides only a subjective sense of safety
- increases code duplication, redundancy
- scatters concerns across disparate layers
A few excerpts from Bertrand Meyer's book:
"Non-Redundancy principle: Under no circumstances shall the body of a
routine ever test for the routine's precondition." [1, p343] For the example
that depicts such case he writes "is not just unnecessary but unacceptable"
[1, p343]
"... complexity is the major enemy of quality. When we bring in this
concern, possibly redundant checks do not appear so harmless any more!
Extrapolated to the thousands of routines of a medium -size system (or the
tens or hundreds of thousands of routines of a larger one), the if x < 0
then ... of sqrt, innocuous at first sight, begins to look like a monster of
useless complexity. By adding possibly redundant checks, you add more
software; more software means more complexity, and in particular more
sources of conditions that could go wrong; hence the need for more checks,
meaning more software; and so on ad infinitum. If we start on this road only
one thing is certain: we will never obtain reliability. The more we write,
the more we will have to write." [1, p344]
"Aside from performance considerations, however, the principal reason to
distrust defensive programming is simply our goal of getting the best
possible reliability. For a system of any significant size the individual
quality of the various elements involved is not enough; what will count most
is the guarantee that for every interaction between two elements there is an
explicit roster of mutual obligations and benefits - the contract. Hence the
Zen-style paradox of our conclusion: that to get more reliability the best
policy is often to check less." [1, p345]
wow it is even scary! like the perfect example, hey! I will let BertrandLew said:The method [Math.sqrt] must handle the illegal input. No amount
of concern for "performance" relieves it of responsibility to
handle a negative input.
Meyer respond to you here ...
"Such a comment, however, comes from a microscopic understanding of
reliability, focused on individual software elements such as the sqrt
routine. If we restrict our view to the narrow world of sqrt, then the
routine seems more robust with the extra test than without it. But the world
of a system is not restricted to a routine; it contains a multitude of
routines in a multitude of classes. To obtain reliable systems we must go
from the microscopic view to a macroscopic view encompassing the entire
architecture." [1, p344]
"if the client's part of the contract is not fulfilled, that is to say ifLew said:According to you, that would never happen.
How do you guarantee that? What would the
result be?
the call does not satisfy the precondition, then the class is not bound by
the postcondition. In this case the routine may do what it pleases: return
any value; loop indefinitely without returning a value; or even crash the
execution in some wild way. This is the case in which (in reference to the
discussion at the beginning of this chapter) 'the customer is wrong'." [1,
p343]
Once there is a programming error that lead to pre condition violations in
production, it does not really matter what the preferred paradigm is, either
DbC or Defensive Programming, the result will be either a crash or any
behavior that differs to that stated in the software specification.
An excellent example on how the defensive programming approach i.e. check
and throw exception can not only give a very subjective sense of safety but
also become "the problem" is the 500 million loss case of Ariane 5 "The
exception was due to a floating-point error: a conversion from a 64-bit
integer to a 16-bit signed integer, which should only have been applied to a
number less than 2^15, was erroneously applied to a greater number,
representing the "horizontal bias" of the flight. There was no explicit
exception handler to catch the exception, so it followed the usual fate of
uncaught exceptions and crashed the entire software, hence the on-board
computers, hence the mission. " [4]
From my professional experience I have personally seen and worked with both
approaches. I have seen software developed for the Java platform that could
not afford bugs but neither compromise in reliability nor performance, a
real-time high-frequency trading arbitrage solution using the closest DbC
you can have in Java. I can't recall one single e.g.
IndexOutOfBoundsException in production and most pre condition verifications
were done via asserts and these were only enabled during testing.
I hope all the excerpts and explanations illustrate my point.
The group of SE at ETH Zurich works on several answers to these questionsLew said:According to you, that would never happen.
How do you guarantee that? What would the
result be?
e.g.
- Formal methods [5]
- Contract-based automatic testing [6]
Soon there is this very very nice and interesting LASER Summer School where
Bertrand Meyer himself will present some of these concepts
[http://se.inf.ethz.ch/laser/2009/index.php]. I might join btw
For perfectjpattern I deliverately chose to stick to the way Sun commends
regarding the use of assertions, I would like my project perfectjpattern to
be accepted and not rejected by the Java community. I know the concepts of
DbC and how implementing those concepts in Java via assertions might be
outrageous for experienced developers (hey! at first it happened to me too)
but it can't hurt to have a wider picture and use this knowledge to make
more educated design decisions.
Related discussions:
"Where should I throw RuntimeException"
[http://groups.google.com/group/comp...n&lnk=gst&q=RuntimeException#30a419d1a0522e92]
"Assertions vs Exceptions" posted to this group in 2007
[http://groups.google.com/group/comp...hread/54c528bd8ca61f73/e4ebf460a43f9aa1?hl=en]
"Modular Protection vs Assertions" posted to the comp.lang.eiffel group in
2007
[http://groups.google.com/group/comp...6fbf9cd89c5/273571d8d33088a1#273571d8d33088a1]
Best regards,
Giovanni
[1] Programming with Assertions
[http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html]
[2] Object Oriented Software Construction 2nd Edition Bertrand Meyer
[http://www.amazon.com/Object-Orient...=sr_1_1?ie=UTF8&s=books&qid=1243672117&sr=8-1]
[3] OOSC course slides "Design by Contract"
[http://se.inf.ethz.ch/teaching/ss2005/0250/lectures/oosc_11_dbc_1up.pdf]
[4] The lessons of Ariane
[http://archive.eiffel.com/doc/manuals/technology/contract/ariane/page.html]
[5] Formal methods [http://en.wikipedia.org/wiki/Formal_methods]
[6] Automatic Testing based on Design by Contract
[http://www.mathematik.uni-ulm.de/sai/mayer/soqua05/slides/ciupa.pdf]