1 of 266

Principles of Software Construction: Objects, Design, and Concurrency��The Last One:

Looking Back & Looking Forward��Claire Le Goues Jeremy Lacomis

1

17-214/514

2 of 266

Administrivia?

2

17-214/514

3 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Introduction, Overview, and Syllabus��Jeremy Lacomis and Claire Le Goues

3

17-214/514

4 of 266

Modern Software Engineering

Nobody wants to write a million lines of code.

  • You don’t want to write Twitter X.

Instead, you use libraries

  • E.g., import Android => +12M LOC
  • You don’t write most of the code you use�And why would you want to?

And your libraries use libraries

4

17-214/514

5 of 266

17-214/514: From Programs to Applications & Systems

Writing algorithms, data structures from scratch

Functions with inputs �and outputs

Sequential and local computation�

Full functional specifications

Reuse of libraries, �frameworks�

Asynchronous and �reactive designs

Parallel and distributed computation

Partial, composable, �targeted models

Our goal: understanding both the building blocks and also the �design principles for construction of software systems at scale

5

17-214/514

6 of 266

https://softwareengineering.stackexchange.com/questions/370135

6

17-214/514

7 of 266

Which version is better?

static void sort(int[] list, boolean ascending) {

boolean mustSwap;if (ascending) {

mustSwap = list[i] > list[j];

} else {

mustSwap = list[i] < list[j];

}

}

interface Order {

boolean lessThan(int i, int j);

}

class AscendingOrder implements Order {

public boolean lessThan(int i, int j) { return i < j; }

}

class DescendingOrder implements Order {

public boolean lessThan(int i, int j) { return i > j; }

}

static void sort(int[] list, Order order) {

boolean mustSwap =

order.lessThan(list[j], list[i]);

}

Version A:

Version B:

7

17-214/514

8 of 266

Some qualities of interest, i.e., design goals

Functional correctness

Adherence of implementation to the specifications

Robustness

Ability to handle anomalous events

Flexibility

Ability to accommodate changes in specifications

Reusability

Ability to be reused in another application

Efficiency

Satisfaction of speed and storage requirements

Scalability

Ability to serve as the basis of a larger version of the application

Security

Level of consideration of application security

Source: Braude, Bernstein,

Software Engineering. Wiley 2011

8

17-214/514

9 of 266

it depends

Depends on what?

What are scenarios?

What are tradeoffs?

In this specific case, what would you recommend?

(Engineering judgement)

9

17-214/514

10 of 266

"Software engineering is the branch of computer science that creates practical, cost-effective solutions to computing and information processing problems, preferentially by applying scientific knowledge, developing software systems in the service of mankind.

Software engineering entails making decisions under constraints of limited time, knowledge, and resources. […]

Engineering quality resides in engineering judgment. […]

Quality of the software product depends on the engineer's faithfulness to the engineered artifact. […]

Engineering requires reconciling conflicting constraints. […]

Engineering skills improve as a result of careful systematic reflection on experience. […]

Costs and time constraints matter, not just capability. […]

Software Engineering for the 21st Century: A basis for rethinking the curriculum

Manifesto, CMU-ISRI-05-108

10

17-214/514

11 of 266

Semester overview

  • Introduction to Object-Oriented Programming
  • Introduction to design
    • Design goals, principles, patterns
  • Designing objects/classes
    • Design for change
    • Design for reuse
  • Designing (sub)systems
    • Design for robustness
    • Design for change (cont.)
  • Design for large-scale reuse

Crosscutting topics:

  • Building on libraries and frameworks
  • Building libraries and frameworks
  • Modern development tools: IDEs, version control, refactoring, build and test automation, static analysis
  • Testing, testing, testing
  • Concurrency basics

11

17-214/514

12 of 266

int a = 010 + 3;

System.out.println("A" + a);

const a = 010 + 3;

console.log("A" + a);

12

17-214/514

13 of 266

Homework & Exams

6 homeworks, 4 smaller + 2 large (with milestones), 1000 points total

(1) intro, (2a) first design (2b) peer review, (3) testing, (4) refactoring, �(2c) final design, (5) concurrency + GUI, (6) extended design + GUI

Homework milestones usually due Mondays, see course calendar

Homework 1 due September 9 at 11:59pm

Two midterms + final (Do not make plans before you know the finals schedule)

M2

1

3

2a

M1

4

5

6a

6b

F

2b

2c

13

17-214/514

14 of 266

Principles of Software Construction �(Design for change, class level)��Starting with Objects

(dynamic dispatch, encapsulation, entry points)

Jeremy Lacomis, Claire Le Goues

14

17-214/514

15 of 266

TypeScript

Java

class HelloWorld {

public static void main(String[] args) {

System.out.println("Hello world!");

}

}

let message: string = "Hello, World!"; console.log(message);

15

17-214/514

16 of 266

Programming with primitives in Java looks a lot like any other imperative programming.

1 public class TrailingZeros {

2 public static void main(String[] args) {

3 int i = Integer.parseInt(args[0]);

4 System.out.println(trailingZerosInFactorial(i));

5 }

6 static int trailingZerosInFactorial(int i) {

7 int result = 0; // Conventional name for return value

8 while (i >= 5) {

9 i /= 5; // Same as i = i / 5; Remainder discarded

10 result += i;

11 }

12 return result;

13 }

14 }

16

17-214/514

17 of 266

Objects respond to messages, methods define interface

const obj = {

v: 1,

inc: function() { this.v++; },

get: function() { return this.v; },

add: function(y) { return this.v + y; }

}

obj.get() + 2

// 3

obj.add(obj.get()+2)

// 4

obj.send()

// Uncaught TypeError: obj.send is not a function

Calling a method that does not exist results in an error

This is JavaScript code!

17

17-214/514

18 of 266

Multiple Implementations of Interface

interface Point {

int getX();

int getY();

}

class PolarPoint implements Point {

double len, angle;

PolarPoint(double len, double angle)

{ this.len=len; this.angle=angle; }

int getX() { return this.len * cos(this.angle); }

int getY() { return this.len * sin(this.angle); }

double getAngle() {...}

}

Point p = new PolarPoint(5, .245);

This is Java code!

18

17-214/514

19 of 266

Method Call Internals

l: Line

points: Point[]

draw(Canvas)

s0: PolarPoint

angle

len

getX()

getY()

s1: CartesianPoint

x

y

getX()

getY()

Using the object’s own method implementation,

not a fixed jump to an address

19

17-214/514

20 of 266

Flexibility of dynamic dispatch (Java)

Each class decides implementation, �client does not care

Static methods are global functions, only single copy exists; class provides only namespace

Java does not allow global functions outside of classes

interface Point {

void move(int x, int y) { ... }

}

class Helper {

static void movePoint(Point p,

int x, int y) {...}

}

Point p = createPoint(...);

// dynamic dispatch, object’s method

p.move(4, 5);

// single global method, less flexible

Helper.movePoint(p, 4, 5);

The “main” function is defined this way!

This is Java code!

Design for Change!

20

17-214/514

21 of 266

JavaScript: �Closures for Hiding

All methods and fields are public, no language constructs for access control

TypeScript added them, so it’s quite similar to Java!

In JS: Encoding hiding with closures

function createPolarPoint(len, angle) {

let xcache = -1;

let internalLen = len;

function computeX() {...}

return {

getX: function() {

computeX(); return xcache; },

getY: function() {

return len * sin(angle); }

};

}

const pp = createPolarPoint(1, 0);

pp.getX(); // works

pp.computeX(); // runtime error

pp.xcache // undefined

pp.len // undefined

21

17-214/514

22 of 266

Starting a Program: Javascript

Objects do not do anything on their own, they wait for method calls

Every program needs a starting point, or waits for events

// start with: node file.js

function createPrinter() {

return {

print: function() { console.log("hi"); }

}

}

const printer = createPrinter();

printer.print();

// hi

Defining interfaces,

functions, classes

Starting:

Creating objects and

calling methods

This is Typescript code!

22

17-214/514

23 of 266

Starting a program: Java

All Java code is in classes, so how to create an object and call a method?

Special syntax for main method in class (java X calls main in X)

// start with: java Printer

class Printer {

void print() {

System.out.println("hi");

}

public static void main(String[] args) {

Printer obj = new Printer();

obj.print();

}

}

Main method to be

executed, here used to

create object and invoke

method

Static methods belong to

class not the object,

generally avoid them

This is Java code!

in Java, everything is a class

main must be public and static

23

17-214/514

24 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Object-oriented Analysis

Jeremy Lacomis and Claire Le Goues

24

17-214/514

25 of 266

25

17-214/514

26 of 266

Input to the analysis process: Requirements and use cases

Time to start�coding?

26

17-214/514

27 of 266

  • Real-world things
  • Requirements, concepts
  • Relationships among concepts
  • Solving a problem
  • Building a vocabulary
  • System implementation
  • Classes, objects
  • References among objects and inheritance hierarchies
  • Computing a result
  • Finding a solution

Problem

Space

(Domain Model)

Solution

Space

(Object Model)

27

17-214/514

28 of 266

Visual notation: UML

Library Account

accountID

lateFees

Name of real-world concept

(not software class)

Properties of concept

Book

title

author

borrow

1

*

Associations between concepts

Multiplicities/cardinalities

indicate “how many”

28

17-214/514

29 of 266

UML Sequence Diagram Notation

User

System

Actors in this �use case (systems and real-world objects/people)

Messages and responses for interactions,

text describes what happens conceptually

Time proceeds from top to bottom

login(card)

borrow(book)

success?, due date

29

17-214/514

30 of 266

System behavioral contract example

Operation: borrow(item)

Pre-conditions: Library member has already logged in to the system.

Item is not currently borrowed by another member.

Post-conditions: Logged-in member's account records the� newly-borrowed item, or the member is warned she has an� outstanding late fee.

The newly-borrowed item contains a future due date,� computed as the item's rental period plus the current date.

30

17-214/514

31 of 266

Low Representational Gap

Identified concepts provide inspiration for classes in the implementation

Classes mirroring domain concepts often �intuitive to understand:�Low representational gap principle

class Account {

id: int;

lateFees: int;

borrowed: List<Book>;

boolean borrow(Book) {...}

void save();

}

class Book {...}

Library Account

accountID

lateFees

Book

title

author

borrow

1

*

31

17-214/514

32 of 266

Design Goals, Principles, and Patterns

Get familiar with design terminology – we’ll see a lot of these

  • Design Goals
    • Design for understanding, change
  • Design Principles
    • Low representational gap
  • Design Heuristics
    • Match concepts to classes

32

Goals

Heuristics

Patterns

Principles

32

17-214/514

33 of 266

Principles of Software Construction��Responsibility assignment��Jeremy Lacomis, Claire Le Goues

33

17-214/514

34 of 266

Domain model (left) vs. object model (right)

34

17-214/514

35 of 266

Object-level interaction diagrams

  • Like system sequence diagrams, but show interactions between objects/classes
  • Two common notations
    • Sequence diagrams
    • Communication diagrams

35

17-214/514

36 of 266

Representational gap

  • Real-world concepts:

  • Software concepts:

36

17-214/514

37 of 266

Visualizing coupling

37

17-214/514

38 of 266

Visualizing cohesion

38

17-214/514

39 of 266

Anti-pattern: God object

class Chat {

List<String> channels;

Map<String, List<Msg>> messages;

Map<String, String> accounts;

Set<String> bannedUsers;

File logFile;

File bannedWords;

URL serverAddress;

Map<String, Int> globalSettings;

Map<String, Int> userSettings;

Map<String, Graphic> smileys;

CryptStrategy encryption;

Widget sendButton, messageList;

}

39

17-214/514

40 of 266

DESIGN HEURISTIC: controller

  • Problem: What object receives and coordinates system events?
  • Solution: Assign the responsibility to an object representing:
    • The overall system, device, or subsystem (a.k.a. façade controller), or
    • A use case scenario within which the system event occurs (a.k.a. use case controller)
  • Process: Derive from system sequence diagram

40

17-214/514

41 of 266

DESIGN HEURISTIC: Information expert

  • Assign a responsibility to the class that has the information necessary to fulfill the responsibility
  • Design process: Derive from domain model
    • Domain model classes become software classes
    • Put responsibility in the class with the necessary data

41

17-214/514

42 of 266

class Shipment {

private List<Box> boxes;

int getWeight() {

int w=0;

for (Box box: boxes)

for (Item item: box.getItems())

w += item.weight;

return w;

}

class Box {

private List<Item> items;

Iterable<Item> getItems() { return items;}

}

class Item {

String description;

int weight;

}

“Quiz”: Critique this design: https://tinyurl.com/214-quiz4

Access code: shipment

42

17-214/514

43 of 266

A creator example

  • Who is responsible for creating Beetle objects?

43

17-214/514

44 of 266

Which design is better? Argue with design goals, principles, heuristics, and patterns that you know

* old midterm question

44

17-214/514

45 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Inheritance and delegation�Claire Le Goues and Jeremy Lacomis

45

17-214/514

46 of 266

Quick Santorini demonstration

46

17-214/514

47 of 266

All object types exist in a class hierarchy

In Java:

Object

Collection

Error

List

RuntimeError

Exception

47

17-214/514

48 of 266

Using Inheritance: LoggingList

public class LoggingList extends IntArrayList {

@Override

public void add(int i) {

System.out.println("adding " + i);

super.add(i);

}

}

LoggingList is a special kind of IntArrayList. It is a subclass of IntArrayList and inherits all of the fields and methods of IntArrayList.

We override the implementation of add to incorporate logging functionality.

super means that method calls made on it dispatch to the superclass implementation from IntArrayList (contrast with this) .

48

17-214/514

49 of 266

“Can I inherit from this type?”

Yes.

49

17-214/514

50 of 266

Behavioral Subtyping gives a more formal principle behind when extension should be considered.

Animal dog = new Dog();

The Liskov substitution principle:

“Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.”

Barbara Liskov

Roughly:

  • anything an Animal does, a Dog should do
  • You should be able to use a subtype as if it was its parent
  • But, dog may be more specific

50

17-214/514

51 of 266

Design option 2

abstract class GenericCard

implements PaymentCard {

public String getCardHolderName() {

return this.cardHolderName;

}

public BigInteger getDigits() {

return this.digits;

}

public Date getExpiration() {

return this.expirationDate;

}

abstract boolean pay(int amount);

}

PaymentCard

GenericCard

CreditCard

DeditCard

class CreditCard extends GenericCard {

@Override

public boolean pay(int amount) {

}

}

class DebitCard extends AbstractGenericCard {

@Override

public boolean pay(int amount) {

}

}

Much more reuse; inheritance is probably a good choice here.

But not always!

51

17-214/514

52 of 266

Design option 3

class CreditCard implements PaymentCard {

private CardData cardData = new();

public BigInteger getDigits() {

return cardData.getDigits();

}

}

class DebitCard implements PaymentCard {

}

PaymentCard

CardData

CreditCard

DeditCard

You can still achieve good reuse with composition+delegation!

class CardData {

private final String cardHolderName;

private final BigInteger digits;

private final Date expirationDate;

public CardData() {}

public String getCardHolderName() {}

public BigInteger getDigits() {}

public Date getExpiration() {}

}

Is this better?

52

17-214/514

53 of 266

Template Method vs. Strategy Pattern

  • Template method uses inheritance to vary part of an algorithm
    • Template method implemented in supertype, primitive operations implemented in subtypes
  • Strategy pattern uses delegation to vary the entire algorithm
    • Strategy objects are reusable across multiple classes
    • Multiple strategy objects are possible per class
    • Where have we seen this?

53

17-214/514

54 of 266

Strategy Pattern in UML.

Context

Strategy

execute()

ConcreteStrA

ConcreteStrB

algorithm()

execute()

execute()

54

17-214/514

55 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Design Patterns��Jeremy Lacomis and Claire Le Goues

55

17-214/514

56 of 266

Discussion with design patterns

  • Carpentry:
    • "Is a dovetail joint or a miter joint better here?"
  • Software Engineering:
    • "Is a strategy pattern or a template method better here?"

56

17-214/514

57 of 266

History: �Design Patterns �(1994)

57

17-214/514

58 of 266

58

17-214/514

59 of 266

Strategy Pattern vs Higher-Order Function

const ASC =

function(i: number, j: number): boolean {

return i < j;

}

const DESC =function(i: number, j: number): boolean {

return i > j;

}

function sort(

list: number[],

order: (number, number) => boolean) ...;

interface Order {boolean lessThan(int i, int j);

}

class AscendingOrder implements Order {

public boolean lessThan(int i, int j) {

return i < j; }

}

class DescendingOrder implements Order {

public boolean lessThan(int i, int j) {

return i > j; }

}

void sort(int[] list, Order order) ;

59

17-214/514

60 of 266

The Template Method design pattern

  • Applicability
    • When an algorithm consists of varying and invariant parts that must be customized
    • When common behavior in subclasses should be factored and localized to avoid code duplication
    • To control subclass extensions to specific operations
  • Consequences
    • Code reuse
    • Inverted “Hollywood” control: don’t call us, we’ll call you
    • Ensures the invariant parts of the algorithm are not changed by subclasses

60

17-214/514

61 of 266

Inheritance vs. Composition + Delegation

  • Inheritance can enable substantial reuse when strong coupling is reasonable
    • Sometimes a natural fit for reuse -- look for “is-a” relationships.
    • Does not mean “no delegation”
  • That said, good design typically favors composition + delegation
    • Enables reuse, encapsulation by programming against interfaces
    • Delegation supports information hiding; inheritance violates it
    • Usually results in more testable code.
    • Composition facilitates adding multiple behaviors, much less messily than multiple inheritance.

61

17-214/514

62 of 266

The Composite Design Pattern

62

17-214/514

63 of 266

We have seen this before

function newCombinedCardOrganizer (cardOrganizers: CardOrganizer[]): CardOrganizer {

return {

reorganize: function (cards: CardStatus[]): CardStatus[] {

let status = cards.slice()

for (const cardOrganizer of cardOrganizers) {

status = cardOrganizer.reorganize(status)

}

return status

}

}

}

63

17-214/514

64 of 266

Module pattern: Hide internals in closure

Function provides local scope, internals not accessible

Function directly invoked to execute it once

Wrapped in parentheses to make it expression

Discovered around 2007, became very popular, part of Node

(function () {

// ... all vars and functions are in this scope only

// still maintains access to all globals

}());

64

17-214/514

65 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Design Practice��Jeremy Lacomis and Claire Le Goues

65

17-214/514

66 of 266

Limitations of inheritance

Cannot combine features

Intermediate functionality required

66

17-214/514

67 of 266

The Decorator design pattern

Common interface for both wrappers and wrapped objects

Class of objects being wrapped. Defines basic behavior that can be altered by decorators

Define extra behaviors that can be added to components dynamically. Override methods of the base decorator and execute their behavior either before or after calling the parent method.

Has a wrappee field for referencing wrapped objects. The type is the interface so it can contain both concrete components and decorators. The base decorator delegates all operations to the wrapped object.

The client can wrap components in multiple layers of decorators, as long as they work with all objects via the component interface.

67

17-214/514

68 of 266

Trouble Rules

  • 2-4 Players are trying to go from their home to their respective finish zone.
  • Each turn, players roll a six-sided die and move their pieces clockwise around the board. Rolling a 6 lets them take another turn.

Home

Finish

68

17-214/514

69 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Refactoring & Anti-patterns��Jeremy Lacomis and Claire Le Goues

69

17-214/514

70 of 266

The Java class hierarchy

  • The root is Object (all non-primitives are objects)
  • All classes except Object have one parent class
    • Specified with an extends clause�class Guitar extends Instrument { ... }
    • If extends clause omitted, defaults to Object
  • A class is an instance of all its superclasses

Object

Game

Instrument

Santorini

Guitar

70

17-214/514

71 of 266

How could this code be improved?

class Player {

Board board;

/* in code somewhere… */

this.getSquare(n);

Square getSquare(String name) {

// named monopoly squares

for (Square s: board.getSquares())

if (s.getName().equals(name))

return s;

return null;

}

}

71

17-214/514

72 of 266

Refactoring: IDE Support

  • Many IDEs offer automated refactoring
    • Rename class, method, variable
    • Extract method/inline method
    • Extract interface
    • Move method (up, down, laterally)
    • Replace duplicates

72

17-214/514

73 of 266

Example: instanceof

class Animal { }

class Dog extends Animal {

public Dog() { super("dog"); }

public String bark() {

return "Woof!";

}

}

// Example 3:

if (animal instanceof Dog)

((Dog) animal).bark();

// Example 2:

((Dog) animal).bark();

// Example 1:

Animal animal = new Dog();

animal.bark();

73

17-214/514

74 of 266

Liquid APIs

Each method changes �state, �then returns this

(Immutable version: �Return modified copy)

class OptBuilder {

private String argName = "";

private boolean hasArg = false;

...

OptBuilder withArgName(String n) {

this.argName = n;

return this;

}

OptBuilder hasArg() {

this.hasArg = true;

return this;

}

...

Option create() {

return new Option(argName,� hasArgs, ...)

}

}

74

17-214/514

75 of 266

Iterator Pattern & Streams

(what’s up with for(Person p : this.records)?)

75

17-214/514

76 of 266

Principles of Software Construction��Specifications, unit testing, exceptions��Claire Le Goues, Jeremy Lacomis

76

17-214/514

77 of 266

Who’s to blame?

/**

* This method finds the shortest

* distance between two vertices.

* It returns -1 if the two nodes

* are not connected.

*/

function shortestDistance(): number {}

Think of this (textual) specification as a “contract”

77

17-214/514

78 of 266

Most real-world code has a contract

  • Imperative to build systems that scale!
  • This is why we:
    • Encode specifications
    • Write tests

Service* �implementation

Service interface

Client�environment

Hidden from �service provider

Hidden from �service client

* service = object, �subsystem, …

78

17-214/514

79 of 266

Control-flow of exceptions

Handle errors at a level you choose, not necessarily in the low-level methods where they originally occur.

public static void test() {

try {

System.out.println("Top");

int[] a = new int[10];

a[42] = 42;

System.out.println("Bottom");

} catch (NegativeArraySizeException e) {

System.out.println("Caught negative array size");

}

}

public static void main(String[] args) {

try {

test();

} catch (IndexOutOfBoundsException e) {

System.out.println"("Caught index out of bounds");

}

}

This is Java code

79

17-214/514

80 of 266

Java’s exception hierarchy

Throwable

Exception

RuntimeException

IOException

EOFException

FileNotFoundException

NullPointerException

IndexOutOfBoundsException

ClassNotFoundException

Object

Error

StackOverflowError

Checked Exceptions

80

17-214/514

81 of 266

Quiz: Check your understanding

public class Main {

private String str = "";

public void A() {

str += "a";

try {

str += "b";

B();

} catch (Exception e) {

str += "c";

}

str += "d";

}

public void B() throws Exception {

try {

str += "d";

C();

} catch (IndexOutOfBoundsException e) {

str += "e";

} finally {

str += "f";

}

str += "g";

}

public void C() throws Exception {

int x = 1 / 0;

}

public void display() {

System.out.println(str);

}

public static void main(String[] args) {

try {

Main test = new Main();

test.A();

test.display();

} catch (Exception e) {

System.out.println("nothing");

}

}

}

https://tinyurl.com/ykduhu7e

81

17-214/514

82 of 266

Re: Formal verification, Testing

“Testing shows the presence, not the absence of bugs.”

Edsger W. Dijkstra, 1969

“Beware of bugs in the above code; I� have only proved it correct, not tried it.”

Donald Knuth, 1977

82

17-214/514

83 of 266

Test boundary values

If you cannot test every input:

Choose representative values:�1 for positives, -1 for negatives

And boundary cases: 0 is a likely�candidate for mistakes

  • Think like an attacker

boolean isPositive(int x) {

return x >= 0; // What if?

}

@Test

public void test1IsPos() {

assertTrue(isPositive(1));

}

@Test

public void test0IsNotPos() {

assertFalse(isPositive(0)); // Fails

}

83

17-214/514

84 of 266

For Java: JUnit

Syntax:

import static org.junit.Assert.*;

class PosTests {

@Before

void setUp() {

// Anything you want to run

before each test

}

@Test

void test1IsPos() {

assertTrue(isPos(1));

}

}

This is Java code

84

17-214/514

85 of 266

Specification vs. Structural Testing

/**

* Checks if the provided card has been answered correctly the required number of times.

* @param card The {@link CardStatus} object to check.

* @return {@code true} if this card has been answered correctly at least {@code this.repetitions} times.

*/

public boolean isComplete(CardStatus card) {

return card.getSuccesses.get(0); // <-- Bad, but passes both tests

}

This is Java code

85

17-214/514

86 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Test case design��Claire Le Goues, Jeremy Lacomis

86

17-214/514

87 of 266

Use the specification for testing

/**

* Checks if the provided card has been answered correctly the required number of times.

* @param card The {@link CardStatus} object to check.

* @return {@code true} if this card has been answered correctly at least {@code this.repetitions} times.

*/

public boolean isComplete(CardStatus card);

// What is specified?

// - Parameter type (no constraints)

// - Return constraints: “at least” this.repetitions correct answers

// So what do we test?

87

17-214/514

88 of 266

There also exist strategies to identify plausible mistakes

  • Boundary Value Testing: errors often occur at boundary conditions
    • E.g.:

/** Returns true and subtracts cost if enough � * money is available, false otherwise.

*/

public boolean pay(int cost) {

if (cost < this.money) {

this.money -= cost;

return true;

}

return false;

}

88

17-214/514

89 of 266

Decision Tables

We need a strategy to identify plausible mistakes

  • Decision Tables
    • Enumerate condition options
      • Leave out impossibles
      • Identify “don’t-matter” values
    • Useful for redundant input domains

Test case

useCredit

enough Credit

enough Cash

Result

1

T

T

-

Pass

2

F

-

T

Pass

3

F

-

F

Fails

4

T

F

T

Pass

5

T

F

F

Fails

89

17-214/514

90 of 266

CreditWallet.pay()

public boolean pay(int cost, boolean useCredit) {

if (useCredit) {

if (enoughCredit) {

return true;

}

}

if (enoughCash) {

return true;

}

return false;

}

Test case

useCredit

enough Credit

enough Cash

Result

Coverage

1

T

T

-

Pass

--

2

F

-

T

Pass

--

3

F

-

F

Fails

Statement

90

17-214/514

91 of 266

Specification vs. Structural testing

Structural: five paths through the code.

Specification: possibly only 3.

What do you think?

/** Pays with credit if useCredit is set and enough

* credit is available; otherwise, pays with cash if

* enough cash is available; otherwise, returns false.

*/

public boolean pay(int cost, boolean useCredit);

91

17-214/514

92 of 266

Real-world systems are complex.

  • 3rd party Facebook apps
  • Android user interface
  • Backend uses Facebook API & data

92

17-214/514

93 of 266

Eliminating the Android Dependency

Code

Facebook

Test Driver

@Test void testGetFriends() {

assert getFriends() == ...;

}

List<Friend> getFriends() {

Connection c = http.getConnection();

FacebookAPI api = new FacebookAPI(c);

return api.getFriends("john");

}

93

17-214/514

94 of 266

There are Several Kinds of Test Double

94

94

17-214/514

95 of 266

Test Double: Mock

Used to test for expected interactions with a collaborator (i.e., method calls). Can behave like a spy, a stub, or both.

95

// Pass in a mock that was created by a mocking framework.�AccessManager accessManager = new AccessManager(mockAuthenticationService);

accessManager.userHasAccess(USER_ID);�

// The test should fail if accessManager.userHasAccess(USER_ID) didn't call

// authenticationService.isAuthenticated(USER_ID) or if it called it more than once.

verify(mockAuthenticationService).isAuthenticated(USER_ID);

https://testing.googleblog.com/2013/07/testing-on-toilet-know-your-test-doubles.html

95

17-214/514

96 of 266

Which Type Was This?

Code

FacebookInterface

Test Driver

@Test void testGetFriends() {

assert getFriends() == ...;

}

List<Friend> getFriends() {

Connection c = http.getConnection();

FacebookAPI api = new Facebook???(c);

return api.getFriends("john");

}

Facebook???

class Facebook???� implements FacebookAPI {

void connect() {}

List<Node> getFriends(String name) {

if (name.equals("john")) {

return List.of(...);

} // ...

}

}

96

17-214/514

97 of 266

Fault injection

Code

Mock Facebook

Test driver

class FacebookErrorStub implements FacebookAPI {

void connect() {}

int counter = 0;

List<Node> getFriends(String name) {

counter++;

if (counter % 3 == 0)

throw new SocketException("Network is unreachable");

else if (name.equals("john")) {

return List.of(...);

} // ...

}

}

97

17-214/514

98 of 266

Use A Mocking Framework For Your Test Doubles

98

98

17-214/514

99 of 266

The test Double Cheatsheet

  • Dummy isn’t used very often
  • Stub if you just want collaborators to provide canned responses
  • Mock if you want to test interactions
  • Spy if you want to track the hidden internal state
  • Fake if you test a complex scenario that relies on a service or component that’s unavailable or unusable for your test’s purposes, and stubbing does not do the job

99

99

17-214/514

100 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Testability��Claire Le Goues, Jeremy Lacomis

100

17-214/514

101 of 266

Testing Queries and Commands

Testing queries is easy because we only care about responses

Replace expensive dependencies and to define their behaviors for the different scenarios

  • Stubs and fakes often suffice

Testing commands is harder

Need to verify that expected outcome by looking for effect on the system

  • Spies and mocks

101

17-214/514

102 of 266

Design principle for testable code: SOLID

  • Single Responsibility Principle (SRP)
  • Open-Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)

102

17-214/514

103 of 266

Classic OCP example.

Bad Example

Good Example

class GraphicEditor {� public void drawShape(Shape s)

{ s.draw(); }�}��class Shape {� abstract void draw();�}��class Rectangle extends Shape {� public void draw() {...}�}

class GraphicEditor {� public void drawShape(Shape s) {� if (s.m_type==1) drawRectangle(s);� else if (s.m_type==2) drawCircle(s);� }� public void drawCircle(Circle r) {....}� public void drawRectangle(Rectangle r)

{....}�}��class Shape {� int m_type;�}�class Rectangle extends Shape {� Rectangle() { super.m_type=1; }�}��class Circle extends Shape {� Circle() { super.m_type=2; }�}

103

17-214/514

104 of 266

Dependency Injection

104

..is giving an object its instance variables (James Shore)

public class Example {

private DatabaseThingie myDatabase;

public Example() {

myDatabase = new DatabaseThingie();

} defined

public void doStuff() {

...

myDatabase.getData();

... used

}

}

Dependency: class Example depends on an instance of DatabaseThingy

http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html

Unfortunately, it is defined (instantiated) in the same class it’s used

Can’t double myDatabase when testing Example class!

How can you fix this?

104

17-214/514

105 of 266

Design: System should enforce clear separation of responsibilities

External service

External service

Hexagonal/Ports and Adapters pattern:

  • The internal core concerns the business logic, functional requirements; is unaware of external systems.
  • Core does not interact with outside world, only with the ports/adapters.
  • Port is agnostic of the technology, abstracts away how communication works.

105

17-214/514

106 of 266

Good Testing Practices

  1. Tests Should Be Fast
  2. Tests Should Be Simple
  3. Test Shouldn’t Duplicate Implementation Logic
  4. Tests Should Be Readable
  5. Tests Should Be Deterministic
  6. Make Sure They’re Part of the Build Process
  7. Distinguish Between The Many Types of Test Doubles and Use Them Appropriately
  8. Adopt a Sound Naming Convention for Your Tests
  9. Don’t Couple Your Tests With Implementation Details

106

17-214/514

107 of 266

Testing offers clues about the quality of your design.

All unit tests do basically three things. If any of them are difficult, can they be changed?

    • Instantiates a class (and its dependencies).
      • Can it be designed with fewer dependencies?
    • Invokes a method under test, after satisfying preconditions
      • Are the preconditions hard to satisfied?
    • Asserts the method behaved as expected.
      • Can its observability be improved?

107

17-214/514

108 of 266

Key functionality that plays into testability/debuggability goes beyond how the code is written

If something goes wrong, do you have data to diagnose it?

  • Need informative data logs, often with timestamps.
  • Configurable levels of logging ideal.

Can you use that data to reproduce the problem?

  • Ideally, sub-systems can be run independently.
  • Is particularly nice (though not required) to be able to visualize data logs in some way.

If something goes wrong, can you tell?

  • Related to observability.
  • Can you write simple code that can detect it?
  • Can you replay the system based on the log, and get the same error?
    • This doesn’t always happen.

108

17-214/514

109 of 266

Principles of Software Construction�

Intro to Concurrency��Jeremy Lacomis and Claire Le Goues

109

17-214/514

110 of 266

Writing Testable Code

What is the problem with this?

public boolean hasHeader(String path) throws IOException {

List<String> lines = Files.readAllLines(Path.of(path));

return !lines.get(0).isEmpty()

}

// to achieve a ‘false’ output without having a test input file:

try {

Path tempFile = Files.createTempFile(null, null);

Files.write(tempFile,"\n".getBytes(StandardCharsets.UTF_8));

hasHeader(tempFile.toFile().getAbsolutePath()); // false

} catch (IOException e) {

e.printStackTrace();

}

110

17-214/514

111 of 266

Three Concepts of Importance

  • Thread: instructions executed in sequence
    • Within a thread, everything happens in order.
    • A thread can start, sleep, and die.
    • You often work on the “main” thread.
  • Concurrency: multiple threads running at the same time
    • Not necessarily executing in parallel
  • Asynchrony: computation happening outside the main flow

111

17-214/514

112 of 266

A simple threads example

public static void main(String[] args) {

int n = 5; // number of threads

Runnable greeter = new Runnable() {

public void run() {

System.out.println("Hello world");

}

};

for (int i = 0; i < n; i++) {

new Thread(greeter).start();

}

}

112

17-214/514

113 of 266

e.g., concurrent I/O in a machine learning task

Different devices:

113

17-214/514

114 of 266

Safety failure: Money-grab (1)

public class BankAccount {

private long balance;

public BankAccount(long balance) {

this.balance = balance;

}

static void transferFrom(BankAccount source,

BankAccount dest, long amount) {

source.balance -= amount;

dest.balance += amount;

}

public long balance() {

return balance;

}

}

114

17-214/514

115 of 266

Concurrency control with Java’s intrinsic locks

  • synchronized (lock) { … }
    • Synchronizes entire block on object lock; cannot forget to unlock
    • Intrinsic locks are exclusive: One thread at a time holds the lock
    • Intrinsic locks are reentrant: A thread can repeatedly get same lock

Thread1�Thread2�Thread3

115

17-214/514

116 of 266

Atomicity

  • An action is atomic if it is indivisible
    • Effectively, it happens all at once
      • No effects of the action are visible until it is complete
      • No other actions have an effect during the action
  • In Java, integer increment is not atomic

i++;

1. Load data from variable i

2. Increment data by 1

3. Store data to variable i

is actually

116

17-214/514

117 of 266

Making a class immutable

public class Complex {

double re, im;

public Complex(double re, double im) {

this.re = re;

this.im = im;

}

public double getRealPart() { return re; }

public double getImaginaryPart() { return im; }

public double setRealPart(double re) { this.re = re; }

public double setImaginaryPart(double im) { this.im = im; }

public void add(Complex c) { re += c.re; im += c.im; }

117

17-214/514

118 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Concurrency and Hazards��Jeremy Lacomis and Claire Le Goues

118

17-214/514

119 of 266

Liveness Hazard

  • Safety: “nothing bad ever happens”
  • Liveness: “something good eventually happens”
  • Deadlock
    • Infinite loop in sequential programs
    • Thread A waits for a resource that thread B holds exclusively, and B never releases it A will wait forever
  • Elusive: depend on relative timing of events in different threads

119

17-214/514

120 of 266

Deadlock example

Two threads:

A does transfer(a, b, 10) B does transfer(b, a, 10)

Execution trace:

A: lock a (✓)

B: lock b (✓)

A: lock b (x)

B: lock a (x)

A: wait

B: wait

Deadlock!

class Account {

double balance;

void withdraw(double amount){ balance -= amount; }

void deposit(double amount){ balance += amount; }

void transfer(Account from, Account to, double amount){

synchronized(from) {

from.withdraw(amount);

synchronized(to) {

to.deposit(amount);

}

}

}

}

120

17-214/514

121 of 266

Circular �references & �Caching

Immutable data structures often from a directed acyclic graph

Cycles challenging

Cycles often useful for performance (caching)

class TreeNode {

readonly #parent: TreeNode

readonly #children: TreeNode[]

constructor(parent: TreeNode,

children: TreeNode[]) {

this.#parent = parent

this.#children = children

}

addChild(child: TreeNode) {

const newChildren = this.#children.slice()

//const newChild = child.setParent(this) ??

newChildren.push(child)

const newNode = new TreeNode(this.#parent,

newChildren)

//child.setParent(newNode) ??

return newNode

}

}

121

17-214/514

122 of 266

Performance Hazard

  • Liveness: “something good eventually happens”
  • Performance: we want something good to happen quickly

  • Multi-threading involves runtime overhead:
    • Coordinating between threads (locking, signaling, memory sync)
    • Context switches
    • Thread creation & teardown
    • Scheduling
  • Not all problems can be solved faster with more resources
    • One cook can make a single order of fries in 5 minutes, adding more cooks does not make the fryer work faster.

122

17-214/514

123 of 266

What are some disadvantages of this parallel implementation?

123

17-214/514

124 of 266

Principles of Software Construction�

Java Parallelism��Jeremy Lacomis and Claire Le Goues

124

17-214/514

125 of 266

Amdahl’s law

  • The speedup is limited by the serial part of the program.

125

17-214/514

126 of 266

Upsweep

Downsweep

126

17-214/514

127 of 266

Parallel Prefix Sum

  • Code is much more complex than the serial implementation.
  • Runtime improved from O(n) to O(log n) time, but that is only theoretical. Runtime is also strongly dependent on the size of the array compared to the number of parallel computations that can be done.
  • Cache and constants matter!

127

17-214/514

128 of 266

class MyHouse {

private boolean pizzaArrived = false;

public void eatPizza(){

synchronized(this){

while(!pizzaArrived){

wait();

}

}

System.out.println("yumyum..");

}

public void pizzaGuy(){

synchronized(this){

this.pizzaArrived = true;

notifyAll();

}

}

}

wait on the lock for this instance of MyHouse to be released until pizzaArrived. Release the lock on MyHouse.

While holding the lock on this instance of MyHouse, notify all threads waiting on this lock that they can wake up.

128

17-214/514

129 of 266

2. Concurrent Collections

Unsynchronized

Concurrent

HashMap

ConcurrentHashMap

HashSet

ConcurrentHashSet

TreeMap

ConcurrentSkipListMap

TreeSet

ConcurrentSkipListSet

129

17-214/514

130 of 266

5. Synchronizers

  • CountDownLatch
    • One or more threads wait for others to count down from n to zero
  • CyclicBarrier
    • A set of threads wait for each other to be ready (repeatedly if desired)
  • Semaphore
    • Like a lock with a maximum number of holders
  • Phaser - A cyclic barrier on steroids
    • Extremely flexible and complex
  • AbstractQueuedSynchronizer
    • Roll your own!

130

17-214/514

131 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Concurrency and Asynchrony in TypeScript��Jeremy Lacomis and Claire Le Goues

131

17-214/514

132 of 266

The JavaScript Engine (e.g., V8)

Two main components:

  • Memory Heap — this is where the memory allocation happens
  • Call Stack — this is where your stack frames are as your code executes

132

17-214/514

133 of 266

What happens when things are slow?

JavaScript is single threaded (single Call Stack).

Problem: while the Call Stack has functions to execute, the browser can’t actually do anything else — it’s getting blocked.

function downloadIt(url) {

// download the file

let f = fetchIt('big-file.txt');

console.log('Got it');

}

console.log('Start downloading...');

downloadIt('big-file.txt');

console.log('Done!');

Start downloading...

Got it

Done!

Note: this is an illustration–JavaScript libraries don’t actually work like this!

133

17-214/514

134 of 266

Asynchrony in User Interfaces

Callback functions

  • Perhaps the building blocks of the internet’s UI.
  • Specifies work that should be done once something happens
    • Called asynchronously from the literal flow of the code

document.addEventListener('click', () => console.log('Clicked!'))

134

17-214/514

135 of 266

The Event Loop

The state is clear.

console.log('Hi');

setTimeout(function cb1() {

console.log('cb1');

}, 5000);

console.log('Bye');

135

17-214/514

136 of 266

Design Goals

  • What design goals does this support & hurt?
    • Reuse
    • Readability
    • Robustness
    • Extensibility
    • Performance
    • ...

const makeBurger = nextStep => {

getBeef(function (beef) {

cookBeef(beef, function (patty) {

getBuns(function (buns) {

putBeefBetweenBuns(buns, patty, function(burger) {

nextStep(burger)

});

});

});

});

};

// Make and serve the burger

makeBurger(function (burger) => {

serve(burger);

});

136

17-214/514

137 of 266

Solving “Callback Hell” with Promises

  • You can chain promises.
    • ‘then’ returns a promise
  • No more deep nesting
  • Easy to follow control-flow

function makeBurger() : Promise<Burger> {

return getBeef()

.then(beef => cookBeef(beef))

.then(cookedBeef => getBuns(beef))

.then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef));

};

// Make and serve the burger

makeBurger().then(burger => serve(burger));

137

17-214/514

138 of 266

Promise-like syntax is in most languages

public class SquareCalculator {

private ExecutorService executor = Executors.newSingleThreadExecutor();

public Future<Integer> calculate(Integer input) {

return executor.submit(() -> {

Thread.sleep(1000);

return input * input;

});

}

}

138

17-214/514

139 of 266

Solving “Callback Hell” with Async/Await

  • Async functions return a Promise
    • And are allowed to ‘await’ synchronously
    • May wrap concrete values
    • May return rejected promises on exceptions

const makeBurger = async () => {

const beef = await getBeef();

const cookedBeef = await cookBeef(beef);

const buns = await getBuns();

const burger = await putBeefBetweenBuns(cookedBeef, buns);

return burger;

};

// Make and serve the burger

makeBurger().then(serve);

139

17-214/514

140 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Concurrency and Patterns��Jeremy Lacomis and Claire Le Goues

140

17-214/514

141 of 266

The Event Loop Redux

function a() {

console.log("as");

setTimeout(function acb() {

console.log("acb");

}, 10);

console.log("ae");

}

function b() {

console.log("bs");

setTimeout(function bcb() {

console.log("bcb");

}, 0);

a();

console.log("be");

}

b();

b()

141

17-214/514

142 of 266

Async/Await Redux

142

17-214/514

143 of 266

async function getData() {

console.log("A");

const result = await fetchData();

console.log("B");

return result;

}

async function fetchData() {

console.log("C");

return new Promise((resolve) => {

setTimeout(() => {

console.log("D");

resolve("Data");

}, 1000);

});

}

console.log("E");

getData().then((data) => console.log("F"));

console.log("G");

What does this print?

Console:

E

143

17-214/514

144 of 266

Sample problem:

You have a store with customers. The customers are very interested in a particular product (e.g., a new model of computer) that should be available at your store soon.

Imagine you have two objects: Store and Customer. How could the Customer object find out when the new computer arrives at the store?

144

17-214/514

145 of 266

Observer Pattern

145

17-214/514

146 of 266

Proxy Design Pattern

Declares the interface of the Service. The proxy must follow this interface to disguise itself as a service object

146

17-214/514

147 of 266

The Adapter �Design Pattern

Applicability

  • You want to use an existing class, and its interface does not match the one you need
  • You want to create a reusable class that cooperates with unrelated classes that don’t necessarily have compatible interfaces
  • You need to use several subclasses, but it’s impractical to adapt their interface by subclassing each one

Consequences

    • Exposes the functionality of an object in another form
    • Unifies the interfaces of multiple incompatible adaptee objects
    • Lets a single adapter work with multiple adaptees in a hierarchy
    • -> Low coupling, high cohesion

147

17-214/514

148 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Introduction to GUIs��Claire Le Goues and Jeremy Lacomis

148

17-214/514

149 of 266

How do you GUI? Multiplayer?

https://www.cloudsavvyit.com/2586/how-to-build-your-multiplayer-games-server-architecture/

while (true) {

if (player === “player1”) {

hasWon = play(“player1”);

if (hasWon) break;

player = “player2”;

} else (player === “player2”) {

hasWon = play(“player2”)

if (hasWon) break;

player = “player1”;

}

}

149

17-214/514

150 of 266

Event-based programming

Style of programming where control-flow is driven by (usually external) events

public void performAction(ActionEvent e) {

List<String> lst = Arrays.asList(bar);

foo.peek(42)

}

public void performAction(ActionEvent e) {

bigBloatedPowerPointFunction(e);

withANameSoLongIMadeItTwoMethods(e);

yesIKnowJavaDoesntWorkLikeThat(e);

}

public void performAction(ActionEvent e) {

List<String> lst = Arrays.asList(bar);

foo.peek(40)

}

150

17-214/514

151 of 266

Anatomy of an HTML Page

Nested elements

  • Sizing
  • Attributes
  • Text

151

17-214/514

152 of 266

That’s extremely simple, let’s try something slightly more complicated.

Consider: TicTacToe

152

17-214/514

153 of 266

Decoupling with the Observer pattern

  • Let the Game tell all interested components about updates

153

17-214/514

154 of 266

Actions: JavaScript

  • Key: event listeners/the Observer Pattern
  • (frontend) JS is highly event-driven
    • Respond to window `onLoad` event, content loads (e.g., ads)
    • Respond to clicks, moves
  • This is what happened with our simple button!

154

17-214/514

155 of 266

Web Servers

Dynamic sites can do more work

https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#anatomy_of_a_dynamic_request

155

17-214/514

156 of 266

An architectural pattern: Model-View-Controller (MVC)

Manage inputs from user: mouse, keyboard, menu, etc.

Manage display of information on the screen

Manage data related to the application domain

156

17-214/514

157 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Libraries and Frameworks

(Design for large-scale reuse)

Jeremy Lacomis and Claire Le Goues

157

17-214/514

158 of 266

Earlier in this course: Class-level reuse

Language mechanisms supporting reuse

  • Inheritance
  • Subtype polymorphism (dynamic dispatch)
  • Parametric polymorphism (generics)

Design principles supporting reuse

  • Small interfaces
  • Information hiding
  • Low coupling
  • High cohesion

Design patterns supporting reuse

  • Template method, decorator, strategy, composite, adapter, …

158

17-214/514

159 of 266

Reuse and variation: �Visual Studio Code Extensions

159

17-214/514

160 of 266

Reuse and variation: �Product lines

160

17-214/514

161 of 266

A calculator example (without a framework)

public class Calc extends JFrame {

private JTextField textField;

public Calc() {

JPanel contentPane = new JPanel(new BorderLayout());

contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));

JButton button = new JButton();

button.setText("calculate");

contentPane.add(button, BorderLayout.EAST);

textField = new JTextField("");

textField.setText("10 / 2 + 6");

textField.setPreferredSize(new Dimension(200, 20));

contentPane.add(textfield, BorderLayout.WEST);

button.addActionListener(/* calculation code */);

this.setContentPane(contentPane);

this.pack();

this.setLocation(100, 100);

this.setTitle("My Great Calculator");

// ...

}

}

161

17-214/514

162 of 266

Using the example framework again

public abstract class Application extends JFrame {

protected String getApplicationTitle() { return ""; }

protected String getButtonText() { return ""; }

protected String getInitialText() { return ""; }

protected void buttonClicked() { }

private JTextField textField;

public Application() {

JPanel contentPane = new JPanel(new BorderLayout());

contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));

JButton button = new JButton();

button.setText(getButtonText());

contentPane.add(button, BorderLayout.EAST);

textField = new JTextField("");

textField.setText(getInitialText());

textField.setPreferredSize(new Dimension(200, 20));

contentPane.add(textField, BorderLayout.WEST);

button.addActionListener((e) -> { buttonClicked(); });

this.setContentPane(contentPane);

this.pack();

this.setLocation(100, 100);

this.setTitle(getApplicationTitle());

// ...

}

}

public class Ping extends Application {

protected String getApplicationTitle() { return "Ping"; }

protected String getButtonText() { return "ping"; }

protected String getInititalText() { return "127.0.0.1"; }

protected void buttonClicked() { /* ... */ }

}

162

17-214/514

163 of 266

General distinction: Library vs. framework

Library

Framework

public MyWidget extends JContainer {

ublic MyWidget(int param) {/ setup internals, without rendering

}

/ render component on first view and resizing

protected void paintComponent(Graphics g) {

// draw a red box on his componentDimension d = getSize();

g.setColor(Color.red);

g.drawRect(0, 0, d.getWidth(), d.getHeight()); }

}

public MyWidget extends JContainer {

ublic MyWidget(int param) {/ setup internals, without rendering

}

/ render component on first view and resizing

protected void paintComponent(Graphics g) {

// draw a red box on his componentDimension d = getSize();

g.setColor(Color.red);

g.drawRect(0, 0, d.getWidth(), d.getHeight()); }

}

your code

user

interacts

your code

user

interacts

163

17-214/514

164 of 266

More terms

  • API: Application Programming Interface, the interface of a library or framework - We’ll be talking about these in the next several lectures
  • Client: The code that uses an API
  • Plugin: Client code that customizes a framework
  • Extension point: A place where a framework supports extension with a plugin

164

17-214/514

165 of 266

An example blackbox framework

public class Application extends JFrame implements InputProvider {

private JTextField textField;

private Plugin plugin;

public Application() { }

protected void init(Plugin p) {

p.setApplication(this);

this.plugin = p;

JPanel contentPane = new JPanel(new BorderLayout());

contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));

JButton button = new JButton();

button.setText(plugin != null ? plugin.getButtonText() : "ok");

contentPane.add(button, BorderLayout.EAST);

textField = new JTextField("");

if (plugin != null) textField.setText(plugin.getInititalText());

textField.setPreferredSize(new Dimension(200, 20));

contentPane.add(textField, BorderLayout.WEST);

if (plugin != null)

button.addActionListener((e) -> { plugin.buttonClicked(); } );

this.setContentPane(contentPane);

// ...

}

public String getInput() { return textField.getText(); }

}

public interface Plugin {

String getApplicationTitle();

String getButtonText();

String getInititalText();

void buttonClicked() ;

void setApplication(InputProvider app);

}

public class CalcPlugin implements Plugin {

private InputProvider app;

public void setApplication(InputProvider app) { this.app = app; }

public String getButtonText() { return "calculate"; }

public String getInititalText() { return "10 / 2 + 6"; }

public void buttonClicked() {

JOptionPane.showMessageDialog(null, "The result of "

+ app.getInput() + " is "

+ calculate(app.getInput()));

}

public String getApplicationTitle() { return "My Great Calculator"; }

}

public interface InputProvider {

String getInput();

}

165

17-214/514

166 of 266

Tangrams

166

17-214/514

167 of 266

The use vs. reuse dilemma

  • Large rich components are very useful, but rarely fit a specific need
  • Small or extremely generic components often fit a specific need, but provide little benefit

“maximizing reuse minimizes use”

C. Szyperski

167

17-214/514

168 of 266

Example: A JUnit Plugin

public class SampleTest {

private List<String> emptyList;

@Before

public void setUp() {

emptyList = new ArrayList<String>();

}

@After

public void tearDown() {

emptyList = null;

}

@Test

public void testEmptyList() {

assertEquals("Empty list should have 0 elements",

0, emptyList.size());

}

}

Here the important plugin

mechanism is Java

annotations

168

17-214/514

169 of 266

Principles of Software Construction: Objects, Design, and Concurrency��API Design I

Jeremy Lacomis and Claire Le Goues

169

17-214/514

170 of 266

Example: A user says this to you

  • “Can you write me an API for managing a to-do list? I should be able to add, delete, and update tasks”
  • What do you ask them?

170

17-214/514

171 of 266

Names can be literal or metaphorical

  • Literal names have literal associations
    • e.g., matrix suggests inverse, determinant, eigenvalue, etc.
    • e.g., calendar suggests date, day, week, etc.
  • Metaphorical names enable reasoning by analogy
    • e.g., mail suggests send, cc, bcc, inbox, etc.

171

17-214/514

172 of 266

172

17-214/514

173 of 266

173

17-214/514

174 of 266

Precondition

Postcondition

174

17-214/514

175 of 266

key not found

key can be null

Does not throw

How it compares keys

175

17-214/514

176 of 266

When you are done, test your documentation!

  • Have a colleague use the documentation to write against the API
    • It is not enough to just read over the documentation
  • Don’t make your customer do this
  • Testing documentation is similar to testing code
    • Start early, test often
    • If it hasn’t been tested, it probably doesn’t work

176

17-214/514

177 of 266

Principles of Software Construction: Objects, Design, and Concurrency��API Design II

Jeremy Lacomis and Claire Le Goues

177

17-214/514

178 of 266

Conceptual weight

  • The amount of new concepts in your API
    • The space it takes up in your brain
  • Examples of growth with low conceptual weight:
    • Adding an overload that is consistent with existing methods
    • Adding new static methods to a utility class
    • Adding cos when your class already has sin
    • Adding a new implementation of an existing interface
  • You want a high power-to-weight ratio

178

17-214/514

179 of 266

Generalizing an API can make it smaller

public class Vector {

public int indexOf(Object elem, int index);

public int lastIndexOf(Object elem, int index);

}

  • Only supports searching over certain ranges
  • What are the semantics of index?

179

17-214/514

180 of 266

Make it easy to do what’s common, possible to do what’s less common

  • If it is hard to do common tasks users get upset
  • For common cases
    • Don’t make users think about obscure issues
    • Don’t make users do multiple calls
    • Don’t make users consult documentation
  • For uncommon cases it’s OK to make users work more

180

17-214/514

181 of 266

…and impossible to do what is wrong!

inherits public Object put(Object key, Object value); from Hashtable

Also provides a method to save the contents to a file: public void save(OutputStream out, String comments);

181

17-214/514

182 of 266

Examples of “just trust me”

  • public static void main(String[] args)
  • System.out.println
  • Known as incantations

182

17-214/514

183 of 266

Hyrum’s Law

“With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.”

- Hyrum Wright, Google, 2016

183

17-214/514

184 of 266

Even if you never intend to use the API externally, design it as if it were public.

  • Don’t wait to design in quality, once you ship it this may not be possible.
  • Your API may take off while it still is terrible
  • Helps establish good programming habits

184

17-214/514

185 of 266

What is a Calendar and what does it do?

  • No idea, it conflates many concepts into one class
  • Obsolete as of Java 8, use java.time instead
  • If you are designing an API and you see a description that looks like this, it is a sign that something has gone horribly wrong

185

17-214/514

186 of 266

// A collection of elements (root of the collection hierarchy)

public interface Collection<E> {

// Returns true if the collection is empty

boolean isEmpty();

// Ensures that the collection contains e

void add(E o);

// Removes an instance of e from the collection, if present

boolean remove(Object o);

// Returns true iff collection contains e

boolean contains(Object o);

// Returns the number of elements in the collection

int size();

}

186

17-214/514

187 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Supply Chain Security �and Sustainability��Jeremy Lacomis and Claire Le Goues

187

17-214/514

188 of 266

188

17-214/514

189 of 266

How do you fix it?

public static void main(String [] args) {

BigInteger fiveThousand = new BigInteger("5000");

BigInteger fiftyThousand = new BigInteger("50000");

BigInteger fiveHundredThousand = new BigInteger("500000");

BigInteger total = BigInteger.ZERO;

total = total.add(fiveThousand);

total = total.add(fiftyThousand);

total = total.add(fiveHundredThousand);

System.out.println(total);

}

Now it prints 555000

189

17-214/514

190 of 266

The moral

  • Blame the API designer
  • Names like add, subtract, negate suggest mutation
  • Better names: plus, minus, negation
  • Generally (and loosely) speaking:
    • Use verbs for mutation
    • Use prepositions, nouns, or adjectives for pure functions
  • Grammatical naming conventions matter!

190

17-214/514

191 of 266

Breaking changes can be hard to avoid

  • Requires better planning
  • Requirements and context can change
  • Bugs and security vulnerabilities
  • Inefficiencies
  • Rippling effects from upstream changes
  • Technical debt, style

191

17-214/514

192 of 266

Awareness is Distracting and Expensive

192

17-214/514

193 of 266

minimizing number of dependencies

193

preparations

once adopted

193

17-214/514

194 of 266

194

17-214/514

195 of 266

195

looking at dependency’s project characteristics

identifying abandonment

195

17-214/514

196 of 266

Dependency is abandoned. What now?

switch to alternative dependency

seek support from others

fork/vendor dependency

[try to] contribute to dependency

create workaround yourself

refactor code to minimize dependency use

help find new maintainers

196

196

17-214/514

197 of 266

197

17-214/514

198 of 266

Heartbleed Bug

  • 2012 bug in OpenSSL that allowed anyone to read sensitive data off of a server, discovered in 2014
  • EVERYONE uses OpenSSL
  • OpenSSL had very few core developers

198

17-214/514

199 of 266

“In this case, the software [...] was maintained by a single person for close to 20 years. And that single person was, as far as I know, not being paid for it, despite the software being used extremely widely.”� - Andres Freund, Microsoft

199

17-214/514

200 of 266

Ever looked at npm install output (again)?

200

17-214/514

201 of 266

201

17-214/514

202 of 266

202

17-214/514

203 of 266

203

17-214/514

204 of 266

Large Attack Surface

204

17-214/514

205 of 266

As a developer:�How to protect against malicious CI tools, vulnerabilities in IDE plugins, …?

205

17-214/514

206 of 266

Reproducible Builds

206

17-214/514

207 of 266

Principles of Software Construction: Objects, Design, and Concurrency��Designing and Testing for Robustness in�Large & Distributed Systems��Claire Le Goues and Jeremy Lacomis

207

17-214/514

208 of 266

Modern software is dominated by systems composed of [components, APIs, modules], developed by completely different people, communicating over a network!

208

17-214/514

209 of 266

Distributed systems

  • A collection of autonomous systems working together to form a single system
    • Enable scalability, availability, resiliency, performance, etc …
  • Remote procedure calls instead of function calls
    • Typically REST API to URL

  • Benefits? Drawbacks?

209

17-214/514

210 of 266

Retry!

  • Exponential Backoff
    • Retry, but wait exponentially longer each time
    • Assumes that failures are exponentially distributed
      • E.g., a 10h outage is extremely rare, a 10s one not so crazy

const delay = retryCount => new Promise(resolve =>setTimeout(resolve, 10 ** retryCount));

const getResource = async (retryCount = 0, lastError = null) => {

if (retryCount > 5) throw new Error(lastError);

try {

return apiCall();

} catch (e) {

await delay(retryCount);

return getResource(retryCount + 1, e);

}

};

210

17-214/514

211 of 266

What’s the difference between the �Proxy and the Adapter Design Patterns?

211

17-214/514

212 of 266

Principle: Modular Protection

  • Online: use HTTP response status codes effectively
    • Don’t just hand out 404, 500
      • Unless they really apply
    • Provide and document fall-back options, information
      • Good RESTful design helps

212

17-214/514

213 of 266

Ensuring Idempotence

  • How about writing/sending new data?
    • Could fail anywhere
      • Including in displaying success message after payment!
    • POST is not idempotent
    • Use Unique Identifiers
    • Server keeps track of �requests already handled

https://stripe.com/blog/idempotency

213

17-214/514

214 of 266

Java Platform Module System

Since Java 9 (2017); built-in alternative to OSGi

Modularized JDK libraries itself

Several technical differences to OSGi (e.g., visibility vs access protection, handling of diamond problem)

module A {

exports org.example.foo;

exports org.example.bar;

}

module B {

require A;

}

214

17-214/514

215 of 266

The Module Pattern

<html>

<header>

<script type="text/javascript" src="lib1.js"></script>

<script type="text/javascript">

const m1 = (function () {

const export = {}

const x = 1;

export.x = x;

return export;

}());

</script>

<script type="text/javascript" src="lib2.js"></script>

...

215

17-214/514

216 of 266

The Diamond Problem

What now?

D

A

B

C

v1.4.1

v0.1.2

v2.7.3

v2.7.5

216

17-214/514

217 of 266

Fallacies of Distributed Computing by Peter Deutsch

  1. The network is reliable.
  2. Latency is zero.
  3. Bandwidth is infinite.
  4. The network is secure.
  5. Topology doesn't change.
  6. There is one administrator.
  7. Transport cost is zero.
  8. The network is homogeneous.

217

17-214/514

218 of 266

Recall: Test Doubles

Concern that the third-party API might fail is not the only reason to use test doubles

  • Most big, public APIs are extremely reliable
  • Ideas for other reasons?
    • Modularity/isolation: testing just our code speeds up development (conf. unit vs. integration testing), simplifies prototyping
    • Performance: APIs can be slow (network traffic, large databases, …)
      • Good test suites execute quickly; that pays off by enabling more test scenarios
    • Simulating other types of problems: changing APIs, slow responses, …

218

17-214/514

219 of 266

Fault injection

  • Mocks can emulate failures such as timeouts
  • Allows you to verify the robustness of system

Code

Mock Facebook

Test driver

class FacebookSlowStub implements FacebookAPI {

void connect() {}

List<Node> getFriends(String name) {

Thread.sleep(4000);

if (name.equals("john")) {

return List.of(...);

} // ...

}

}

219

17-214/514

220 of 266

What Can Go Wrong?

Let’s say Service A is your framework, B is a plugin

220

17-214/514

221 of 266

Chaos Engineering

Experimenting on a distributed system in order to build confidence in the system’s capability to withstand turbulent conditions in production

221

17-214/514

222 of 266

Principles of Software Construction�

DevOps��Claire Le Goues, Jeremy Lacomis

222

17-214/514

223 of 266

So you want to build a cathedral

https://i.etsystatic.com/6245917/r/il/7cb2e4/870606290/il_fullxfull.870606290_mab1.jpg

223

17-214/514

224 of 266

So you want to build a cathedral large system

224

17-214/514

225 of 266

Release cycle of Facebook’s apps

225

17-214/514

226 of 266

226

17-214/514

227 of 266

Early days: Boxed software, infrequent releases

227

17-214/514

228 of 266

Another example Facebook release cycle

228

17-214/514

229 of 266

229

17-214/514

230 of 266

230

17-214/514

231 of 266

Principle: Automation everywhere

231

17-214/514

232 of 266

  • A virtual machine, but:
  • Lightweight virtualization
  • Sub-second boot time
  • Shareable virtual images with full setup incl. configuration settings
  • Used in development and deployment
  • Separate docker images for separate services (web server, business logic, database, …)

232

17-214/514

233 of 266

Virtual Machines offer Machines as Code

Multiple VMs can sit on one server

VMs provide complete isolation

But, “translation” from guest OS to�host is slow, clunky

And each VM has entire OS, filesys

https://www.docker.com/resources/what-container/

233

17-214/514

234 of 266

CC BY-SA 4.0 Khtan66

234

17-214/514

235 of 266

235

17-214/514

236 of 266

Testing in �Production

236

17-214/514

237 of 266

237

237

17-214/514

238 of 266

Canary�Releases

238

17-214/514

239 of 266

Real DevOps Pipelines are �Complex

  • Incremental rollout, reconfiguring routers
  • Canary testing
  • Automatic rolling back changes

239

Chunqiang Tang, Thawan Kooburat, Pradeep Venkatachalam, Akshay Chander, Zhe Wen, Aravind Narayanan, Patrick Dowell, and Robert Karl. Holistic Configuration Management at Facebook. Proc. of SOSP: 328--343 (2015).

239

17-214/514

240 of 266

Quick/genuine survey!

https://canvas.cmu.edu/courses/40772/quizzes/133958

240

17-214/514

241 of 266

Towards People and Process

241

17-214/514

242 of 266

What is software engineering?

242

17-214/514

243 of 266

Compare to other forms of engineering

  • e.g., Producing a car or bridge
    • Estimable costs and risks
    • Well-defined expected results
    • High quality
  • Separation between plan and production
  • Simulation before construction
  • Quality assurance through measurement
  • Potential for automation

243

17-214/514

244 of 266

1968 NATO Conference on Software Engineering

244

17-214/514

245 of 266

Compare to other forms of engineering

  • e.g., healthcare.gov
    • Estimable costs and risks
    • Well-defined expected results
    • High quality
  • Separation between plan and production
  • Simulation before construction
  • Quality assurance through measurement
  • Potential for automation

245

17-214/514

246 of 266

Sociotechnical systems

  • A sociotechnical system is, roughly, any interlinked system of people, technology, and their environment

246

17-214/514

247 of 266

247

17-214/514

248 of 266

248

17-214/514

249 of 266

Major topics in 17-313 (Foundations of SE)

  • Process considerations for software development
  • Requirements elicitation, documentation, and evaluation
  • Design for quality attributes
  • Strategies for quality assurance
  • Empirical methods in software engineering
  • Time and team management
  • Economics of software development

249

17-214/514

250 of 266

Q: How does a software project become a year late?

250

17-214/514

251 of 266

The “almost done” problem

  • The last 10% takes 40% of the time

time

% completed

reported

progress

planned actual

251

17-214/514

252 of 266

Brooks’s law

  • Adding people to a late software project makes it later.

252

17-214/514

253 of 266

Large teams (29 people) create around six times as many defects as small teams (three people) and obviously burn through a lot more money. Yet the large team appears to produce about the same amount of output in only an average of 12 days less time. This is a truly astonishing finding, though it fits with my personal experience on projects over 35 years.

- Phillip Amour, 2006, CACM 49:9

253

17-214/514

254 of 266

Software development process

254

17-214/514

255 of 266

Wishful thinking

Effort

Time

Project

start

Project

end

100%

0%

Trashing / rework

Productive coding

255

17-214/514

256 of 266

Developers perceive process as overhead, waste

Effort

Time

Project

start

Project

end

100%

0%

Trashing / rework

Productive coding

Process: Cost and time estimates, writing requirements, design,

change management, quality assurance plan,

development and integration plan, …

256

17-214/514

257 of 266

The reality, without enough process

Trashing / rework

Effort

Time

Project

start

Project

end

100%

0%

Productive coding

Process

257

17-214/514

258 of 266

A better ideal

Effort

Time

Project

start

Project

end

100%

0%

Productive coding

Process

Trashing / rework

258

17-214/514

259 of 266

Testimonials from the real world

“We had initially scheduled time to write tests for both front and back end systems, although this never happened.”

259

17-214/514

260 of 266

Testimonials from the real world

“We had initially scheduled time to write tests for both front and back end systems, although this never happened.”

“Due to the lack of time, we could only conduct individual pages’ unit testing. Limited testing was done using use cases. Our team felt that this testing process was rushed and more time and effort should be allocated.”

260

17-214/514

261 of 266

How do you get developers to write software tests?

261

17-214/514

262 of 266

How do you get developers to write software tests?

Better: How do you get developers to write the right amount of tests?

262

17-214/514

263 of 266

One approach: Test driven development (TDD)

  • Write tests before writing code
    • Never write code unless there’s a failing test
    • The tests ≈ the specification

263

17-214/514

264 of 266

Another approach: Test automation via CI

264

17-214/514

265 of 266

What next?

  • 17-313 Foundations of Software Engineering: Teamwork, requirements, architecture, process, QA beyond testing
  • 17-356/766 SE for Startups: Prototyping, QA, devops, economics
  • 17-445/645 ML in Production: Requirements, design, scaling, MLOps, teams applied to ML
  • 17-437/637 WebApps: HTML+CSS, data management, sessions, security, internationalization, prototyping, process
  • 17-480/780 API Design: Requirements, process, QA
  • Many Master’s-level courses on requirements, QA, architecture, DevOps, agile processes, software security, privacy, ethics

265

17-214/514

266 of 266

Summary

  • Looking back: Code-level design, testing, and concurrency
  • Looking forward: Human aspects of software engineering, including process and requirements
    • There are many other SE courses at CMU. Consider taking them!

266

17-214/514