Finding bugs in programs is not an easy task, especially when they are subtle bugs. The process of finding bugs is not exciting, and you may have gone through your program wasting your time trying to find bugs that should not have existed in the first place. Such bugs may exist because you did not understand the specifications correctly. And as Barry Boehm, the father of software economics, put it: If it costs $1 to find and fix a requirement-based problem during the requirements definition process, it can cost $5 to repair it during design, $10 during coding, $20 during unit testing, and as much as $200 after delivery of the system. Thus, finding bugs early on in the software life cycle pays off. You can use assertions to detect errors that may otherwise go unnoticed. Assertions contain Boolean expressions that define the correct state of your program at specific points in the program source code. The designers of the Java platform, however, didn't include support for assertions. Perhaps they viewed exceptions as a superior feature, allowing you to use
Design by Contract
We can view program correctness as proof that the computation, given correct input, terminated with correct output. The user invoking the computation has the responsibility of providing the correct input, which is a precondition. If the computation is successful, we say that the computation has satisfied the postcondition. Some programming languages (such as Eiffel) encourage developers to provide formal proof of correctness by writing assertions that may appear in the following roles:
These three roles collectively support what is called the design-by-contract model of programming, a model that is well supported by the Eiffel programming language. Java technology, on the other hand, doesn't have built-in support for the design-by-contract model of programming. In fact, the Java platform did not have built-in support for assertions until the release of J2SE 1.4. Assertions
An assertion has a Boolean expression that, if evaluated as BankAccount acct = null; // ... // Get a BankAccount object // ... // Check to ensure we have one assert acct != null;
This asserts that Using assertions helps developers write code that is more correct, more readable, and easier to maintain. Thus, assertions improve the odds that the behavior of a class matches the expectations of its clients.
Note that assertions can be compiled out. In languages such as C/C++, this means using the preprocessor. In C/C++, you can use assertions through the
The program will be aborted if the
is inserted at the beginning of the program. This causes the C/C++ preprocessor to ignore all assertions, instead of deleting them manually. In other words, this is a requirement for performance reasons. You should write assertions into software in a form that can be optionally compiled. Thus, assertions should be executed with the code only when you are debugging your program -- that is, when assertions will really help flush out errors. You can think of assertions as a uniform mechanism that replaces the use of ad hoc conditional tests. Implementing Assertions in Java Technology
J2SE 1.3 and earlier versions have no built-in support for assertions. They can, however, be provided as an ad hoc solution. Here is an example of how you would roll your own assertion class.
Here we have an
Note that in the Code Sample 1: Assertion.java
Note: In order for Code Sample 1 to compile, use
Code Sample 2 demonstrates how to use the Note: It is important to note that in this example assertions are used to validate user input and that no invariant is being tested or verified. This is merely to demonstrate the use of assertions. Code Sample 2: AssertionTest1.java
Figure 1 shows a couple of sample runs of
![]() Figure 1: Sample Run of AssertionTest1 Let's look at another example in which assertions can be very helpful. If you have a Code Sample 3: AssertionTest2.java
If you run
![]() Figure 2: Sample Run of AssertionTest2 As I mentioned earlier, developers and users should have an option for turning assertions on and off. In the three examples I've provided, if you do not want assertions to be executed as part of the code, uncomment the line
Note: In this example, you cannot simply turn assertions on or off at runtime. You need to recompile the code in order to turn assertions off. As you have probably realized, adding an ad hoc assertions solution to Java technology is not a totally efficient approach. This is mainly because Java technology does not have a preprocessor. It is possible to think about other means of turning assertions off at runtime. Assertion Facility in J2SE 1.4 and Later
As you have seen, the
The important thing to note about the assertion facility is that it is not provided as a user-level class library. Rather, it is built into the language by introducing a new keyword statement to Java technology: The assertion facility in J2SE 1.4 and later versions is not a full-blown design-by-contract facility. Adding such a facility would require substantial changes to the language and might subvert the simplicity of Java technology. However, the simple assertion facility provides a limited form of design-by-contract style programming. Using Assertions
Use the
The
As an example, I will modify Code Sample 3 to use the Note: The example here relies on the user's input. Assertions, however, should be used to check for cases that should never happen, check assumptions about data structures (such as ensuring that an array is of the correct length), or enforcing constraints on arguments of private methods. Code Sample 4: AssertionTest3.java
Note: if you are using J2SE 1.4.x (or later versions) to compile If you now run the program using the command
and you enter a valid character, it will work fine. However, if you enter an invalid character, nothing will happen. This is because, by default, assertions are disabled at runtime. To enable assertions, use the switch
Following is a sample run:
C:\CLASSES>java -ea AssertionTest3
Enter your marital status: w
Exception in thread "main" java.lang.AssertionError: Invalid Option
at AssertionTest3.main(AssertionTest3.java:15)
When an assertion fails, it means that the application has entered an incorrect state. Possible behaviors may include suspending the program or allowing it to continue to run. A good behavior, however, might be to terminate the application, because it may start functioning inappropriately after a failure. In this case, when an assertion fails, an Note: By default, assertions are disabled, so you must not assume that the Boolean expression contained in an assertion will be evaluated. Therefore, your expressions must be free of side effects.
The switch
And to disable assertions in a specific package and any subpackages it may have, you can use the following command:
Note that the three-dot ellipsis (
Switches can be combined. For example, to run a program with assertions enabled in the
Note that when switches are combined and applied to packages, they are applied to all classes, including system classes (which do not have class loaders). But if you use them with no arguments (
then assertions are enabled in all classes except system classes. If you wish to turn assertions on or off in system classes, use the switches Using Assertions for Design by Contract
The assertion facility can help you in supporting an informal design-by-contract style of programming. We will now see examples of using assertions for preconditions, postconditions, and class invariants. The examples are snippets of code from an integer stack, which provides operations such as Preconditions In order to retrieve an item from the stack, the stack must not be empty. The condition that the stack must not be empty is a precondition. This precondition can be programmed using assertions as follows:
public int pop() {
// precondition
assert !isEmpty() : "Stack is empty";
return stack[--num];
}
Note: Because assertions might be disabled in some cases, precondition checking can still be performed by checks inside methods that result in exceptions such as Postconditions In order to push an item on the stack, the stack must not be full. This is a precondition. To add an item on the stack, we assign the element to be added to the next index in the stack as follows:
However, if you make a mistake and you write this statement as
then you have a bug. In this case, we need to ensure that invoking the
public void push(int element) {
// precondition
assert num<capacity : "stack is full";
int oldNum = num;
stack[num] = element;
// postcondition
assert num == oldNum+1 && stack[num-1] == element : "problem with counter";
}
Note that if a method has multiple return statements, then postconditions should be evaluated before each of these return statements. Class Invariants A class invariant is a predicate that must be true before and after any method completes. In the stack example, the invariant would be that the number of elements in the stack is greater than or equal to zero and the number of elements should not exceed the maximum capacity of the class. These conditions, for example, can be coded as follows:
private boolean inv() {
return (num >= 0 && num < capacity);
}
To check that the stack should satisfy this predicate at all times, each public method and constructor should contain the assertion
right before each return. Source Compatibility
The new Conclusion
When writing programs, it is a good idea to put checks at strategic places for violations of basic assumptions. These checks help in debugging code. The new assertion facility in J2SE 1.4 (and later versions) provides a unified support for assertions in Java technology as well as a convenient way for developers both to turn assertions on and off as needed and to abort Java programs while printing a message that states where in the program the error was detected. Remember that these messages are for us -- developers -- and not users. Although the use of assertions replaces the ad hoc use of conditional tests with a uniform methodology, it does not allow for a repair strategy to continue program execution. This means that when an exception is detected, the program aborts with no recovery mechanism. Nevertheless, assertions play an important role in debugging and designing code with testability in mind. The assertion facility can be used to support an informal design-by-contract style of programming. For More Information
Acknowledgments
Special thanks to Jonathan Gibbons of Sun Microsystems, whose feedback helped me improve this article. | |||||||||||||||||||||||
Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.
|
| ||||||||||||