CSC/ECE 517 Fall 2010/ch6 6b HA

From Expertiza_Wiki
Jump to navigation Jump to search

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 this verification on that 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

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. 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 Language

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 and is usually defined in assert.h. However a developer can define their own macros to have a better functionality.

#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;
}

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, 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')

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. 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'


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.

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. 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.

Advantages of using assertions

  • Assertions allow developer's to express in code what they assume to be always true 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

References

[1] Using Assertions in Java

[2] UC Berkeley, How to use Assertions in C

[3] Programming with Assertions

[4] Assertions in managed code

[5] Assertions in Python

[6] Assertion module in Ruby

[7] Assertions, Wikipedia

Research Papers -

[8] Satpathy, Siebel, Rodriguez, Assertions in Object oriented software maintenance: Analysis and case study

[9] Richard N. Taylor, Assertions in programming languages, ACM SIGPLAN Notices, v.15 n.1, p.105-114, January 1980