Skip to content
Snippets Groups Projects
Commit eb219b4c authored by Juuso Rytilahti's avatar Juuso Rytilahti
Browse files

Part 2 material published

parent e2b5b57f
No related branches found
No related tags found
No related merge requests found
# Contract-Based Programming / Design
**Methodology** aims for correctness.
**Component** examines routines and classes. Component is a concept that describes a self-contained piece of software in which the components are strongly interconnected and the links between the components are looser, allowing the software to be assembled from the components, and also allowing the components to be interchangeable. Component can be small, but in practise components are bigger than a couple of methods e.g. they might contain multiple classes. In java a typical component could be e.g. a package that contains multiple classes.
Components can be examined in two different ways: client relationship and inheritance relationship.
**Specification** forms a **contract** between the (software) component's client and **implementor** (**interface**). In this context interface could be described as a communication layer between two different entities. Later in this course the term interface will also be used in a slightly different context.
If the component is not defined, can it even function incorrectly? On the other hand, if code follows the specification exactly (code === specification), then the code cannot function incorrectly! This might not matter so much on smaller projects (e.g. solo hobby projects), but when the scale (and the team size) grows, the need for the code to follow the specification becomes more important as well! As a more concrete example, think if the library you used to e.g. sort an array would cause an undisclosed side-effect?
**redundancy**: e.g. verbal definition of a program code (e.g. method), and unit tests represent redundancy. – similar to strong typing for variables (e.g. `int num`). For example if we first define a method with comments and then implement the method. If the method follows the code exactly, there is redundancy on the comment and the implementation. Redundancy is not a bad thing in this context, as it increases the reliability of the code.
## Using Contracts in the Course
At first we will have a a brief introduction to specification, correctness, and contracts (this tutorial). The objective is to provide a new, correctness-emphasizing perspective on the subject matter.
Contract is a mean to implement the *divide-and-conquer*-approach and break down (complex) design tasks.
This course focuses on understanding the idea, using non-formal specification. (this will be discussed later during the course).
In the context of object-oriented programming theory, rule descriptions will be used to explain the theory in practice. In this context the objective of the rule description is to document and explain the operation of object concepts in a more formal way.
At the end of the course, there will be a review of the use of contract-based programming. Its objective is to give you the ability to design your own contract terms and link them to implementation.
Using contract-based programming can be taken even further:
- Formal specifications can even be verified, e.g., at the compilation stage (~meta-level).
- Specifications enable writing (unit) tests for the component.
- Specifications can also assist in designing tests.
- In the future they could maybe be utilized using Large Language models. NOTE: there are risks based on this approach (mainly hallucination).
## Example: Contract Specifications
Let's look at couple an example:
```java
public class InsertionSort {
/** Method 'sort'
Meaning: Sort integers in ascending order using insertion sort.
Parameter numbers: a vector containing the numbers to be sorted.
Precondition: 'numbers' is not null; 'numbers' contains only
Integer-type objects.
Postcondition: Returns a new vector where 'numbers'' integer objects are sorted in ascending order. Does not
modify the 'numbers' vector. No side effects.
Stable (with respect to equal elements).
Exceptions: none.
*/
public static Vector<Integer> sort(Vector<Integer> numbers)
```
The method names in the contract specification (e.g.`Method sort`) is in this case an unnecessary redundancy, as java defines the method name in the method's signature. It is included here as an example, as what is included in the specification depends also on the information the programming language itself conveys.
The contract-based programming adds to the comment the `precondition` and `postcondition` and `exception`.
The `precondition` is a list of conditions that must be fulfilled in order to ensure that the program executes correctly.
The `postcondition` is important, as it can convey information e.g. if the method returns a new array, or mutates e.g. an array given as a parameter. This information might be useful, if e.g. one wants also to preserve the original array for some other purposes, e.g. displaying it based on multiple different sorting options (e.g. name, profession, etc.).
Additional note: Stable means that if the elements are equal, their original order remains.
## Example: Contract Specifications #2
```java
/** Method 'findPosition'
Meaning: Returns the position in 'vec' where inserting 'elem' would
keep 'vec' sorted.
Parameter vec: An integer vector to search for 'elem''s position.
Parameter elem: The integer for which a position is sought in 'vec'.
Precondition: 'vec' and 'elem' must not be null. All elements of 'vec'
and 'elem' are Integer-type objects.
'vec''s elements are in ascending order.
Postcondition: Returns the integer position of the first element in 'vec'
that is greater than 'elem'. If none are greater, returns the size of 'vec'.
No side effects; does not alter 'vec' or 'elem'.
Exceptions: none.
*/
public static int findPosition(Vector<Integer> vec, Integer elem)
```
Please note that the specification might also be not dependent only on the code it describes, but also the writer of the specification. Exact wording might differ between specifications of the same code from different writers.
As an additional side note: In java, that elements can be null requires that they are based on objects. More specifically, if the `elem` would be `int` instead of `Integer` it could not be null in any situation.
## Contract Implementation
Roots trace back to the 70s to the works of E.W. Dijkstra, who created a kind of foundation for the Science of Programming direction. Background: *Bertrand Meyer / Eiffel Software 1986: Design by Contract*.
Examples of contract implementation in languages
- Directly in the language (e.g., programming language `D`)
- Separate library (e.g., Java)
- Separate precompiler
- Comments (= not officially supported by the language). The comments are only instructions to the people reading the code. This is also the way contracts are mainly used in this course. Java takes this into consideration using signatures, which will be covered later on the course.
## What do routine contracts address?
- Input
- Allowed input values and meaning of the input values.
- **Preconditions**
- Result/Output
- Allowed result values and the meaning of the result.
- **Postconditions**
- Other Aspects that need to be considered when creating the specification
- How complete is the specification?
- All possible scenarios are impossible to cover!
- Error and exception situations
- Invariants
- Side effects
- Efficiency: time, and space complexity
- Methods of objects: the object itself is also an input and a result!
It should also be noted that in this course we probably go further than is necessary in real-life scenarios. However using (at least partially) contracts can and probably will be a beneficial tool for programming, especially on larger projects. Need of good documentation of the code can also be seen if you e.g. want to re-start developing a hobby project after taking a longer break of its development.
# Obligations as Contracts
| | Obligations | Benefits |
|------------|------------------------------------------------------|------------------------------------------------------------|
| Customer | The precondition must be valid before the call | After the call, the postcondition is valid |
| Implementer| The implementation must bring the postcondition into effect | Situations pruned by the precondition do not need to be addressed |
defining the postcondition is often easier, because the precondition can often exclude complex scenarios.
Another way to formulate:
- What does the contract expect as input?
- What does the contract guarantee as output?
- What does the contract maintain?
Now continue to:
[Part 2.2 - Constructing Routines](Part%202.2%20-%20Constructing%20Routines.md)
\ No newline at end of file
# Constructing routines
Today’s topics
- Constructing routines
- Routine specification
- The significance of routine definition
## The desired outcome
Each routine should have one clear purpose. The routine represents some functional concept. The name of the routine should create and reflect the method's meaning. The rules on how methods should be written differs.
The implementer should not (for the most part) need to know how the routine is used. This leads us to a principle called **Encapsulation**. The benefit of encapsulation is that the method's caller does not need to know how the routine is implemented.
For a more concrete example, if you are sorting an array using a predefined library, you do not need to know the specific implementation, only what are the preconditions and what are the postconditions (e.g. does the sorting method return a new array or mutate the original).
Additionally, often it is preferable to have many 'moderately small' clearly stated routines rather than one complex routine that implements many things (divide-and-conquer). If the routine is overly complex, and contains multiple different functionalities, it can often be used only in very specific scenarios, leading to duplicate and hard-to-maintain code.
**Signature** is the interface between the routine’s caller and implementer. It defines what functionality is the responsibility of the routine, what is excluded, and what is handled as a special case. An example of a routine is presented later on this lecture.
an example of a signature in Java:
```java
public float sqrt(float x) throws IllegalArgumentException
```
Side effect management is a significant challenge in defining definition and correctness.
## Routine specification
Let's look at the following example:
```java
/**
* Returns the square root of x.
* @.pre true (= precondition is always true)
* @.post Math.abs(RESULT * RESULT - x) < 1.0e-10
* & RESULT >= 0.0, or x < 0 -> raise exception
*/
public float sqrt(float x) throws NegValueException
```
**Definition** of the above example is on the lines 1-6 (the comment). It should be noted that this definition is partly unformal (e.g. the postcondition '*, or x < 0 -> raise exception*'). If the pre and postconditions would result in boolean values, the contract would be formally defined. However, as this method is aimed to be readable by a human, and not by a machine, this approach is fine.
If we look at the example more closely, The specification above constructs from the following parts:
Verbal description:
`Returns the square root of x.`
Pre-condition:
`@.pre true (= precondition is always true)`
Post-condition:
`@.post Math.abs(RESULT * RESULT - x) < 1.0e-10`
The method's signature is the following
```java
public float sqrt(float x) throws NegValueException
```
We can also look more closely to the method's signature:
access modifier: `public`
result type: `float`
method name: `sqrt`
parameters: `float x`
throwable exceptions: `throws NegValueException`
In Java, a method's signature is part of the programming language itself. It specifies:
- The name of the method
- The data types of input and output values
- The method's visibility (in a class context)
- Exceptions raised by the method (only in error situations)
- Generic types (not shown above, will be discussed later)
As an additional side-note, the names of the parameters are not particularly significant in Java's signature!
All methods have some sort of definition, which is **clarified** by the preconditions, postconditions and by the verbal description of the routine.
- Precondition: when it can be called
- Postcondition: what happens after the method has been called (e.g. the method returns an new array of students sorted by surname.)
## The Significance of Routine Specification
Routine specification can be seen as a communication between the routine and the client. Additionally, the implementation is freely modifiable, as long as the specification of the routine remains unchanged.
The routine's caller does not need to know the implementation (sometimes they don't even have access to it)
- Planning and Documentation
- Planning is possible with just routine specifications
- The same specification formalism throughout the project
- Expectations
- What does the implementer commit to?
- Well defined routine specifications leads to general usability and reusability.
Functionality of a routine can be verified with the routine specifications. One relevant question is does the implementation match the specification? If the specification is good, it can, for example, make writing unit tests easier. Additionally, in some languages and/or plugins, the specification can be automatically tested to make sure that the implementation follows the specification, thus making the code more robust.
On the other hand, if the specification is missing or it is inadequate, the specification cannot be objectively verified. This leads to problems, especially in large and/or old projects. As a more concrete example, imagine joining into a 10-year-old project as a software developer. On your first day, you would notice that there is very little to no comments on the code. Maintaining or extending the code would be a nightmare!
Routine specification also aims to give means to locate possible errors. The specification tries to provide "boundaries" for correction. If everything worked before the method was called and the precondition was fulfilled, if the method does not follow the postcondition, the error is most likely withing the routine specified by the specification.
As an additional side note, older code is often more reliable than new. Of course there are exceptions on this, as e.g. dependencies get major updates and require changes to the old code that uses them.
In object-oriented programming, the routine specification in the context of classes, tries to affect the selection of characteristics and roles. There is also the additional challenge of ensuring class encapsulation. In the case of inheritance, there's a separate agreement between the inheritor and the inherited class.
Now continue to:
[Part 2.3 - Side-Effects and Exceptions](Part%202.3%20-%20Side-Effects%20and%20Exceptions.md)
\ No newline at end of file
# Side-effects
## What are side-effects?
The concept of a side-effect is related to execution, and thus to methods. A side-effect means that something changes as a result of execution, typically a variable’s value changes.
For example, a method that simply calculates a return value based on input values is free of side-effects.
As an another example, a method that updates the value of some instance variable of an object has side-effects.
Essentially, the prerequisite for a side-effect is that the programming language has an *assignment statement*. Assignment statement is a programming language structure that sets the value of the state indicated by a variable, but returns nothing (the setting is merely a side effect).
## Are side-effects unnatural? Undesirable?
One way to look at side-effects is to use mathematics as a starting point. In mathematics, there are no side-effects; the 'variables' in mathematics are not like variables in imperative programming languages (such as Java). In mathematics, variables are set once to represent a value - in programming languages, a variable's value can change during execution.
Side-effects are a natural way in imperative languages to change state. Computation/execution in imperative languages essentially consists of a series of state changes. Often, methods of objects are intended to have effects on the object’s state, thus having side-effects.
In terms of method descriptions (and therefore correctness), the description of side-effects can be overlooked, especially if the effect extends outside the OO-program.
## Manifestation of side-effects
Let's look at side-effects more broadly. The following table lists manifestation of side-effects from an individual object towards larger entities:
| Context | Description |
|--------------------------------|--------------------------------------------------------------------------------------------------|
| Internal state of the object | Variables of the object during the execution of an OO-program. |
| Global state | Static variables of classes related to the execution of an OO-program. |
| Operating system level | Information in the execution environment of an OO-program that can be programmatically changed. |
| External world | Effects on hardrives, networks, physical hardware, etc. |
## Example: Side-effect to the internal state of an object
The example below highlights that in java you can have a variable and a method with the same name. The idea is to display that the visibility of a variable can be limited to allow direct edits of a variable e.g. only inside the class itself. The method `isRunning()` enables us to ask the state of the variable outside this class.
```java
class Car {
private boolean isRunning;
boolean isRunning() { return isRunning; }
/**
@.pre isRunning() == false
@.post isRunning() == true (side-effect)
*/
void start() { isRunning = true; }
/*
@.pre isRunning() == true
@.post isRunning() == false (side-effect)
*/
void stopEngine() { isRunning = false; }
}
```
## Example: Global side-effect
Creating objects of the Object class has a side-effect on the global state:
```java
class ObjectPool {
static int createdObjects = 0;
}
class Object {
{ ObjectPool.createdObjects++; }
}
void main() {
new Object();
new Object();
System.out.println(ObjectPool.createdObjects);
}
```
In the example, the class keeps a tab on how many objects are created. This is a side-effect.
In the example above, you might notice that the `public static void main(String[] args) ` has been changed to `void main`. This is possible in newer versions of Java. However, ViLLE does not yet support this.
## Example: Side-effects outside the virtual machine
Once again, we use newer Java to shorten the example.
File creation:
```java
Files.writeString(Path.of("/tmp/test"), "some content");
```
Printing to the console:
```java
System.out.println("Hello world!");
```
Other side-effects:
- Memory allocation outside of the JVM (Java Virtual Machine)
- Changes to the graphical user interface.
- Network connections
-
## Limiting side-effects in language
- In most mainstream languages, the use of side-effects is common.
- BUT: side-effects make ensuring the correctness of the code challenging.
This raises a natural question: how to limit side-effects?
In a purely functional language a variable can only be set once.
In Java, the side-effects can be limited by e.g. using the **final** modifier, immutable data structures, and voluntarily taking a disciplined approach to avoid possible side-effects.
If side-effects are limited, assembling a program from separate components is more reliable. The scope of an function is apparent from its signature; on the other hand, this causes longer signatures.
# Handling of special cases
Method could be imagined as a mathematical function. This is not exact definition, but (figuratively) it works.
![reload button](images/part-4/routine-as-a-mathematical-functin.png)
![reload button](images/part-4/partitioning_of_the_domain.png)
- As an example, when opening a file:
- Precondition: name is null or empty
- Special case (in this case throws exception): File is not found.
## Partial and Total Routine
- A routine is said to be **total** if it has no preconditions, and **partial** if it does.
- A total routine can be called at any time.
- Users of a partial routine must ensure that preconditions are met when calling.
- Exceptions are often used to make a routine total, or less partial.
- However, making all routines total is not the goal.
- Raising exceptions in the implementation of a routine often requires runtime checks.
- Calling a routine that raises exceptions requires handling code from the caller as well.
- When exceptions are used in routine signatures, there should be an effort to use descriptive exception classes, defining them as necessary.
![absolute value example](images/part-4/absolute_value.png)
In the example above, the type of the variable (number) causes that there is no need to add (any) preconditions.
The left side presents input values and the right side output values The impossible values return values are marked with gray background.
We can look at another example (below).
![second square root example](images/part-4/square_root.png)
In this example we need to limit negative values from possible input values.
If this would be implemented in Java, we would need to describe this special case in precondition of the method. So the method could be defined in the following way:
```java
/* @.pre x >= 0
* @.post Math.abs(RESULT*RESULT - x) < 1.0e-10.0 */
double sqrt(double x)
```
Now the grayed values in input are limited with precondition.
An alternative way to solve this issue could be the following:
![total square root and special situations](images/part-4/total_square_root_and_special_situations.png)
```java
// R sqrt(D d) throws e1, e2, e3
double sqrt(double d) throws IllegalArgumentException
```
We could implement the method so that there is no precondition. This however would mean that the method should handle these situations. In this case, in case of invalid input values `d`, the method throws `IllegalArgumentException`.
On the image, the questions mark shows that the grayed out output values on the right are impossible to get as an ouput from the defined method.
## Rules of Thumb for Handling Special Cases
1. Shift the special case to a precondition if it is clear and understandable from the client's perspective.
2. If rule 1 does not work, handle special cases with manageable exceptions, thereby forcing the clients to address them.
3. If rules 1 and 2 do not work, handle the special case in another way (to be revisited later in the course).
## Use of the Exception Mechanism
The exception mechanism's use appears in three parts of Java syntax:
1. **throws**: Indicates in the signature the checked exceptions that the routine may raise.
2. **throw** T: Raises the exception **T** in a special situation.
3. **catch**(T e) {}: Handles exceptions of type **T** that were raised within the preceding **try** block.
The raised exception is an object of the exception class.
- If necessary, define the class.
The following example highlights the possible ways to use exceptions in Java:
```java
class EmptyArray extends Exception {}
float avg(int[] nums) throws EmptyArray { // 1
int sum = 0;
if (nums == null || nums.length == 0)
throw new EmptyArray(); // 2
for(int n: nums) sum += n;
return sum / nums.length;
}
void main() {
int[] nums = new int[] { 1, 2 };
Float result;
try { result = avg(nums); } // 3
catch(EmptyArray e) {} // 3
}
```
Java requires that if an exeption is thrown, it should also be defined in the method's signature (*throws*). In java, also the catch should define correct exception (So in this particular case, if the code would raise some other exception than `EmptyArray`, the `catch` block would not be executed and the code would crash.
## Key Challenges in Defining/Implementing Routines
- Have all 'special cases' and partialities been remembered in terms of the routine's calling situation?
- Is the use of exceptions well-considered?
- Considering side-effects in specification and implementation.
- Can the input or output values be null - are their meanings defined?
- From the perspective of encapsulation, reference value semantics (passing references to be stored within an object) can pose a threat to the implementation of encapsulation (to be addressed later in course when the class formation is discussed).
Now continue to:
[Part 2.4 - Encapsulation and integrity of a class](Part%202.4%20-%20Encapsulation%20and%20integrity%20of%20a%20class.md)
This diff is collapsed.
# Constructing groups of classes
Today's Topics:
- Constructing groups of classes
- Reflection on general advice
## Motivational Example:
Let's model a library management system that maintains information about books and library users. (Sharvit 2022)
Our example includes:
- Two types of library users: members & librarians.
- Users can log in with an email and password.
- Members can borrow books.
- Both members and librarians can search for books by name or author.
- Librarians can block members or remove a block (e.g., overdue book).
- Librarians can list a member's current loans.
- A library can have multiple copies of the same book.
- Books belong to a physical library building.
## Task:
What kinds of classes, class attributes, and features can you think of? What about the relationships between classes?
Next we are going to (lightly) inspect a set of different tools and techniques that aim to ease the design process.
### Entities and Relations
The entities and relations of the example could be presented in the following way:
![](images/part-6/entities_and_relations.jpg)
The black dimond (♦) means that element is constructed from smaller components. The diamond points to the parent/target class. In this case *Catalog* consists of many books. There can't be a catalog, if any Books are not present.
The lighter (white) diamond represents that the class can use the element, but it does not necessarily contain an instance of it.
The arrows show description of usage.
### Traditional Object Model
![](images/part-6/traditional_object_model.png)
There are classes that are deeply interconnected. The model becames quite easily quite complex.
### Data-Oriented View
There is also data-oriented view. Notice how the arrows are going only to one direction.
The relationships of data and the operations are presented separarately.
![](images/part-6/data_driven_view.png)
## Traditional Object Modeling
The Object-Oriented Thought Process (Weisfeld 2013):
*Rather than using a structured, or top-down, approach, where data and behavior are logically separate entities, the OO approach encapsulates the data and behavior into objects that interact with each other.*
*The elegance of this mindset is that classes literally model real-world objects and how these objects interact with other real- world objects. These interactions occur in a way similar to the interactions between real-world objects, such as people. Thus, when creating classes, you should design them in a way that represents the true behavior of the object.*
However, this approach can be a bit naïve, and will be revisited later in the course.
## Data-Oriented View
Data-oriented programming (Sharvit 2022):
Data-oriented (DO) programming separates executable code from data. This separation makes the system easier to understand and more flexible. DO is against encapsulation.
- Between classes containing methods, only UML's usage arrow is used.
- Between data-containing classes, only UML's association (diamond) and composition (black diamond) are used.
- Association = objects are independent.
- Composition = an object's existence depends on another object.
DO has advantages with code parallellization, synchronization, efficiency, and determinism. These are not the main themes of this course, but they are important aspects when trying to create robust and efficient programs.
## Object-Oriented Software Construction
We could also look at a bit older design principles presented in Object-Oriented Software Construction (1997):
- Ideal class
- Clearly describes an abstraction.
- Name is a noun or adjective, describing the abstraction.
- Represents a set of possible objects or a single object.
- Methods for queries.
- Commands to change the state.
- Abstract properties (contracts).
- Types of classes:
- Analysis class: directly from the task definition, describes an abstract or concrete concept.
- Design class: no direct counterpart in the modeled problem, facilitates implementation (e.g., iterator).
- Implementation class: frequently needed “lowest level” class, e.g., data structures.
## Course Book
Contract-based object programming in Java - chapter 4:
- Bottom-up and top-down design.
- A class is built around the information it's envisioned to contain.
- Define the content of information, then the handling routines.
- In a inheritance hierarchy, a feature is moved as high as it still fits into the whole.
- In a client diagram, a feature should be placed in the "most primitive" client where it still has relevance. ("primitiveness": the implementation of class A is based on class B, whose implementation is based on class C – C is the most primitive)
However, inheritance is not a problem free approach. Other possible approach in OO programming would be not to use top-down design, but use interfaces instead. Or one could use data-oriented programming.
## Reflection on General Advice
- Divide-and-conquer - solve the whole by identifying one class at a time?
- How should data related to the (task) description be structured?
- It’s important that each class implements a clear/unified concept.
- Often, there is not just one correct solution.
Designing a complex structure is never easy. However, experience helps to make good decisions. Additionally, utilizing design patterns can also prove to be helpful (however they are not the subject of this course).
Furthermore, here are some other methods that have been explored in object-oriented programming:
- **Cohesion metrics**: a cohesive class's parts extensively utilize each other.
- E.g., methods extensively use the class's instance variables.
- Aim for a high value of cohesion.
- For a groups of classes, corresponding can be measured with **coupling metrics**.
- Aim for a low value – few mutual dependencies.
- Classes are interpreted to implement separate concepts.
- The nature of coupling (e.g., method reference) varies in these metrics (multiple interpretations).
As you have probably realized by now, there is no silver bullet nor a one size fits all solution! This is also reflected on some of this course's exercises and in programming in general, as the same problem can often be solved in multiple different ways.
Now continue to:
[Part 2.6 - Creating a Class](Part%202.6%20-%20Creating%20a%20Class.md)
\ No newline at end of file
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment