In-Depth Notes on Programming by Contract and Error Management

Objectives

  • Common Programming Errors: Understand the types of errors encountered in programming.
  • Programming by Contract: Define this concept and its implications for software development.
  • Precondition vs Postcondition & Class Invariants: Differentiate these vital components in contract-based programming.

Writing Robust Code

  • Classes of Problems in Running Programs:
    • System Failures: CPU failures, memory errors, full disk, network crash, etc.
    • Invalid Data: Errors due to user input or bad data (e.g., out of range).
    • Programming Errors: Lead to unknown or invalid states in the software.

Assertions and Exceptions

  • Assertions:
    • Manually added to enhance robustness in code.
    • Used as part of the programming contract to validate conditions.

Dependability and Object-Orientation

  • Object-Oriented Design:
    • Quality and dependability are critical for module reusability.
    • Design by contract principles can be applied across different object-oriented languages to ensure robustness in software.

Understanding Contracts

  • Parties Involved:
    • Client: Requests a service.
    • Supplier: Provides the service.
  • Contract Characteristics:
    • Each party has expectations of benefits and obligations.
    • Obligations define what is to be done for benefits to be granted.

Contract Document

  • Clarifies responsibilities and expectations:
    • Protects client by specifying service results.
    • Protects supplier from non-specified obligations.
    • No Hidden Clauses: Only documented obligations apply.

How Contracts and Software Design Interact

  • Contractual Analogy in Software:
    • Pre and post conditions act as obligations and benefits in code.
    • Encourages clear definitions of expected conditions before and after a function execution.

Precondition and Postcondition

  • Precondition: Requirements that must be met before a function is called.
  • Postcondition: Conditions guaranteed to be true after executing the function.
Examples:
  1. Square Root Function:
   void write_sqrt(double x) {
       // Precondition: x >= 0.
       // Postcondition: The square root of x is printed.
   }
  1. Vowel Checking Function:
   bool is_vowel(char letter) {
       // Precondition: letter is a letter (A-Z or a-z).
       // Postcondition: Returns true if letter is a vowel, false otherwise.
   }
  1. Division Function:
   int divide(int a, int b) {
       // Precondition: b != 0;
       // Postcondition: Returns a / b.
   }
  • Note: Violating the precondition (e.g., dividing by zero) breaks the contract.

Defensive Programming vs Design by Contract

  • Defensive Programming:
    • Checks for unexpected conditions everywhere.
    • Leads to complex and redundant checks.
  • Design by Contract:
    • Clearly defined responsibilities reduce redundancy.
    • Encourages simpler and more maintainable codes.

Sample Class: BankAccount

  • Class Design:
public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        assert initialBalance >= 0 : "Initial balance must be non-negative";
        this.balance = initialBalance;
        checkInvariant();
    }

    public void deposit(double amount) {
        assert amount > 0 : "Deposit amount must be positive";
        double oldBalance = balance;
        balance += amount;
        assert balance == oldBalance + amount : "Balance must increase by deposit amount";
        checkInvariant();
    }

    public void withdraw(double amount) {
        assert amount > 0 : "Withdraw amount must be positive";
        assert amount <= balance : "Cannot withdraw more than the balance";
        double oldBalance = balance;
        balance -= amount;
        assert balance == oldBalance - amount : "Balance must decrease by withdrawal amount";
        checkInvariant();
    }

    public double getBalance() {
        return balance;
    }

    private void checkInvariant() {
        assert balance >= 0 : "Invariant violated: balance is negative";
    }
}
Main Method Example:
public static void main(String[] args) {
    BankAccount account = new BankAccount(100.0);
    account.deposit(50.0);
    account.withdraw(30.0);
    System.out.println("Final balance: " + account.getBalance());
}

Lab Activities and Exercises

  1. Square Root Writer: Create a method to print the square root of a non-negative number.
  2. Vowel Checker: Check for vowel characters (case-insensitive).
  3. Safe Division: Implement division with non-zero denominator check.
  4. Evaluate the purpose of the assert keyword in Java and the consequences of unmet preconditions.
  5. Discuss the benefits of contracts in larger software projects for team functionality.