CSC/ECE 517 Fall 2010/ch6 6b HA
Assertions in Object Oriented Language
In most of the programming languages assertions are statements that enables developers to test assumptions about the code they write. For example, if a developer writes a function that checks if two strings are equal or not, he might assert that the lengths of the strings are equal. Assertions are always made using a Boolean expression which a developer assumes will be true when the assertion gets executed. If the assertion is false then error will be thrown at the runtime.
Introduction
By doing a verification using a boolean expression, assertion confirms developer’s assumptions about the program behavior and hence increase the confidence that an application is as per the requirement and free of errors. Research has shown that by writing assertions while a developer is programming is one of the efficient ways to detect and fix bugs. Also, assertions acts as a documentation to the inner working of the code and hence improves the maintainability of the code which is an important aspect of software engineering.
While debugging code, developers can write assertions in two forms, simplest being –
assert expression1 ;
where the expression1 is a Boolean expression which if true, verifies the correctness of your code and if false, throws an error during runtime showing you the assertion without giving you any details about the error.
Second form of assertion is
assert expression1 : expression2
where expression1 is again a Boolean expression and expression2 is an expression that has some value. The value can be the error message or may be related to the some value related to the current code execution. By doing this a developer can know much more details about the error which is causing that assertion failure and allow them to diagnose and fix it. This form of assertion is usually used when a developer is not testing the app as it gives some additional information to the 3rd party developers/testers that might help them diagnose and fix the problem.
As stated above assertions help in verifying the correctness of a program. However, assertions should be carefully used as in some cases it can be expensive to evaluate the Boolean expression of an assertion. For example, if a developer is writing a program for binary search, then he may need to verify that the list should be sorted. Now if he uses some method in assertions for doing that, it can affect the performance of the app. For this reasons, almost every compiler supports enabling and disabling of assertions so that the developers don’t need to remove their code for assertions everywhere and to increase the performance and quality of the software.
Usage
Assertions play a vital role in software development. From Requirements to shipping of the final product, writing assertions does help in each of the stages. Assertions verify the requirements, help developers to refactor their code, they assist in testing and verify the correctness of the final product.
Assertions should only be used for debugging purposes and they should not be considered as a part of normal program flow and should never be replaced with exceptions. For example, if developer is validating user input he should use standard conditional processing. If the input is invalid he should either recover gracefully or throw an exception. This validation should not be done using assertions. However, if this validated information is passed into other methods, you may assert that these values are acceptable within the other methods.
Types of assertions
There are primarily three types of assertions in programming languages namely -
1) Preconditions: In such type of assertion, certain conditions should have been met before calling a method. These conditions are usually checked at the entry point of the calling function.
2) Postconditions: It defines the functionality of the method. By asserting post condtions at the end of the method developers check the output with the desired requirements
3) Invariants: These assertions define conditions over a specified region of the code and verifies state-space consistency for a class. It can be evaluated at the entry or exit points of the method.
Design by contract
Design by contract or Programming by Contract is a design approach for client and suppliers developing a computer software. According to this approach, software designers (or clients) define verifiable interface specifications for software modules with preconditions, post conditions and invariants[9]. These specifications for software modules are known as contracts. A software designer should specify the pre and post conditions i.e before the execution what conditions should be true and after the execution what things should be true. In object oriented architecture, preconditions can be weakened by subclasses and post conditions can be strengthened.
As an example, if a function has some specified pre condition then client would held responsible if the condition fails inside the function. It also means that the function should not have to test for that precondition and similarly client should not have to write try/catch while calling that function. Client or a software designer should ensure that they will meet the pre-condition by doing runtime tests or may be by assuming some condition which will be satisfied based on the function requirements and specifications.
This explicit definition of pre and post conditions forces software designers to think about the requirements of the specification and their implementation. Also for developers, these assertions may also influence declaration and throwing of runtime exceptions and try/catch block. The design by contract approach eliminates unnecessary redundant checks. Pre and post conditions reduce the need for checking the need for verifying the same condition both at client as well as developer’s side.
For example if a method has specified some pre-condition then the failure of that condition is the responsibility of the client of the method. The method should not have to declare a checked exception for that condition, it should not have to test and throw for that condition, and likewise the client should not have to try/catch for that condition.
Assertions in Object Oriented Languages
Assertions play special role in object oriented software construction. Almost all object oriented languages supports assertions natively and for those who don't they have third party library available which wraps the assertion in the form of modules. Following are the popular object oriented languages and types of assertion they support.
C++
Unlike other languages, assertions in C++ are implemented using assert macro. The argument provided to the assert statement should be true when macro is executed or else the program aborts and displays an error message. By default the assert macro in C++ comes in the standard library [2] and is usually defined in assert.h. However a developer can define their own macros to have a better functionality.
Assertions at the run-time
#ifndef DEBUG #define ASSERT(x) #else #define ASSERT(x) \ if (! (x)) \ { \ cout << "ERROR!! Assert " << #x << " failed\n"; \ cout << " on line " << __LINE__ << "\n"; \ cout << " in file " << __FILE__ << "\n"; \ } #endif
Sample program which uses an assert macro -
/*using the assert macro defined in assert.h */ #include <stdio.h> #include <assert.h> main() { int num; printf("\nInput an integer value"); scanf("%d", &num); assert(num >= 0); printf("Value entered is %d.\n", num); return 1; }
Design by Contract
C++ supports design by contract assertions using third party tools like DBC, C² for Visual Studio , [ http://www.digitalmars.com/ Digital mars] or [ http://www.nano-editor.org/ GNU Nano]. Many compiler these days comes with their own unit testing framework which supports design by contract.
The purpose of proposed ANSI C++ assertion is to provide a default assertion behavior similar to C. But since C++ is object oriented, these assertions rely on extra C++ features like templates and exceptions to provide more generic and powerful support than just simple macros of C.
Ruby
By default Ruby doesn't have native support for assertions[6], however we can include Test::Unit framework if we want to work with assertions. Assertions are part of Test::Unit::Assertion module. Test::Unit::Assertions contains the standard Test::Unit assertions. Assertions is included in Test::Unit::TestCase.
In Ruby, the message given to each assertion is shown at the failure. Also a developer can customize his assertions based on assert_block.
Assertions at the run-time
Ruby has support for well defined assert functions. These are defined as public instance methods in the Assertions module. Few of the examples are given below.
require "test/unit" # Assertion of boolean condition a=32 assert a>16 # true #Assertion on Equality result=(palindrome(str))?"yes":"no" assert_equal(result,"yes", "String is a palindrome") #Assertion to check if the object is nil assert_nil [1, 2].uniq! #Assertion to match regex. assert_match(/\d+/, 'five, 6, seven')
Design by Contract
Design by contract assertions are supported in Ruby using third party libraries like Ruby DBC (Ruby contract) and
Python
Unlike Ruby, assertions in python are built into the language. However, syntax for the assertion is very similar to Ruby. As per the Python documentation generic syntax for writing assertions is
assert_stmt ::= "assert" expression ["," expression]
If the assertion fails, then an exception Assertion error is thrown that results in termination of the running program.
Assertions at the Runtime
#!/usr/bin/python def Max(a,b) assert(a!=b), "Both the values are equal" if a>b return a if b>a return b print Max(5,6) print Max(4,4)
This would print the following result -
6 Traceback (most recent call last): File "test.py", line 13, in <module> print Max(4,4) File "test.py", line 5, in Max assert(a!=b), "Both the values are equal" AssertionError: Both the values are equal
Design by Contract
Python doesn't support assertions using Design by Contract approach [5]. However, there are few 3rd party libraries are available which enables python to support design by contract assertions. Among them, PyDBC orContracts for Python are most popular.
PyUnit
PyUnit is a standard Unit Testing framework for Python. PyUnit comes with the standard Python Library. It is based on famous JUnit framework and uses the built in Python's assert statements which are built natively into the Python language. An example for PyUnit which checks for the default size of the widget when it is created.
import unittest class DefaultWidgetSizeTestCase(unittest.TestCase): def runTest(self): widget = Widget("The widget") assert widget.size() == (50,50), 'incorrect default size'
C#
C# is modern, general purpose object oriented language. In C# one can use assertions either using the Debug Class or the Trace Class which are defined in System.Diagnostics namespace. Since Debug class methods are not included in a release version of an application, the assertions do not inflate the code for or increase the size of the binary[4].
Assertions during Runtime
The following example verifies that the divisor should not be equal to zero. So whoever is calling the IntDivide function it needs to check for that.
//Function to divide an integer int IntDivide ( int a , int b ) { Debug.Assert ( b != 0 ); return ( a / b ); }
When the code is build in release mode, the Debug.Assert method disappear. If a developer wants to check his assertions then instead of writing Debug.Assert, he can use Trace.Assert which stays there in the Release version too. Please note that, as explained above, this may inflate the size of the binary executable and may also lead to performance issues.
// Assertions remain in the release version too //Function to divide an integer int IntDivide ( int a , int b ) { Trace.Assert ( b != 0 ); return ( a / b ); }
The above example is same as the previous one but the code for this would be included in the shippable version too. Hence it may also show assertion failures to the end users if the code doesn't have any recovery mechanism.
Customized assertions
Like in some other languages it's also possible in C# to display the custom message with the failed assertions or to customize the assertion block. For the above example, for better maintainability, we can display a message with the assertion as shown below -
//Function to divide an integer
int IntDivide ( int a , int b ) { Debug.Assert ( (b != 0), "Divide by zero", "failed in function IntDivide" ); return ( a / b ); }
Instead of showing a dialog boxes on assertions, in C# one can also write these assertions failures into an event file. For doing this we can override the Fail method of existing Tracelistener to make it behave in a different manner. The program must contain a listener and inherit from the Tracelistener class. For more information see Tracelistener Class , Debug.Fail and Trace.Fail methods.
Java
Before J2SE 1.3, Java did not have support for assertions, however the exception handling was very well supported. The designers of Java thought it to be a superior feature allowing to try/catch/finally and thrown an exception. After the release of J2SE 1.4, assertions came built in. Assertions in Java also contains Boolean expressions which define the correct state of the program at specific locations in the program code.
Runtime assertions
Since Java is a statically typed language, it supports runtime assertions[1]. It supports all the types of assertions described above, i.e. preconditions, postconditions and internal invariants. Assertions can be shown with the help of the following example taken from Java's internal documentation site.
import java.util.Scanner; import java.io.IOException; public class AssertionTest1 { public static void main(String argv[]) throws IOException { Scanner reader = new Scanner(System.in); System.out.print("Enter your age: "); int age = reader.nextInt(); //Assertion.NDEBUG=false; Assertion.assert(age>=18, "You are too young to vote"); // use age System.out.println("You are eligible to vote"); } }
In the above example, a programmer assumes that the age of the individual should be equal to or more than 18 in order to vote. So whoever is calling that function should verify it before (Programming by contract).
Advantages/Disadvantages of Assertion based Programming
Assertions are great way to help developer's to maintain the code in the long run. However, they should be extremely careful about when and where to use assertions in their code. It may add an expensive cost to the production code[10].
Advantages of using assertions
- Assertions allow developers to verify their assumptions and terminates execution as soon as the assumption is violated.
- Assertions replaces the ad hoc use of conditional tests which degrades the performance of the software.
- Using assertions is beneficial as they provide run time check of developer's assumption that he may have otherwise put in the form of code comments. Comments may get out of sync with the code base but the assertions can't, if they do then developer is forced to update them unlike comments.
- When developer is using assert statement, he's more confident when the execution of the program reaches a certain point as the precondition or postconditions are guaranteed to be met without writing any conditional checks.
- Assert statements help developer's to refactor and optimize their code with better confidence as the are sure about preserving the code correctness.
Disadvantages of using assertions
- Assertions have certain overhead associated with it. If they are enabled in the release code, it can lead to performance issues.
- If the assertion invariants fail in the code, it is assumed that they might have been treated as a coding errors which are without any recovery mechanism.
- Developers need to be careful while writing assertions. Sometimes assertions are written in place of conditional checks which unnecessarily decrease the performance and increase the development time.
- Sometimes developers evaluate expressions inside the assertions while debugging. When the software is shipped, those assert statements are removed, hence it impacts other variables which are dependent on that expression.
Conclusion
Assertions are an important part of writing code. It maintains the integrity and maintainability of the software. Almost all the object oriented languages today support assertion based programming. By writing assertions developers can actually save lot of time and money that are spend on testing a software[10,11]
Also, most importantly assertions help software developers to verify the requirements of the client in the form of programming by contract. Designing software by contract is crucial to verify the software correctness as it facilitate code reuse since the contract for every set of method is fully documented.
References
[2] UC Berkeley, How to use Assertions in C
[3] Programming with Assertions
[4] Assertions in managed code
[8] Python Unit Testing Framework
Contract enforcement with AOP, IBM
Research Papers -
[10] Satpathy, Siebel, Rodriguez, Assertions in Object oriented software maintenance: Analysis and case study
[11] Richard N. Taylor, Assertions in programming languages, ACM SIGPLAN Notices, v.15 n.1, p.105-114, January 1980