Keep It Local

Or: (part of) what  “reasoning about your code” really means

Chris Krycho

March 2021

The C Programming Language cover by Prentice Hall

Code Complete 2 cover by Pearson

function doSomething(anArg) {
  let i, total = 0, max, min;
  max = getMax(anArg);
  min = max < 100 ? 0 : 100;

  for (i = min; i < max; i++) {
    total += i;
  }
}
function doSomething(anArg) {
  let max = getMax(anArg);
  let min = max < 100 ? 0 : 100;

  let total = 0;
  for (let i = min; i < max; i++) {
    total += i;
  }
}
function doSomething(anArg) {
  let max = getMax(anArg);
  let min = max < 100 ? 0 : 100;

  let total = getTotal(min, max);
}

function getTotal(min, max) {
  let total = 0;
  for (let i = min; i < max; i++) {
    total += i;
  }
  return total;
}

Rust!

Rust!

Two fundamental rules:

  1. Data has one owner
  2. No shared mutable data
    • ​​infinite read access OR
    • exactly one write access

 

Pure functional programming!

Pure functional programming!

Purity: when a function—

  • only has access to its arguments
    • ​no global state
    • no global functions (console.log)
  • cannot mutate values
    • not its arguments
    • not other state (no access!)

Pure functional programming!

Benefits:

  • consistent results: same input→same output
  • no mutating arguments or global state
  • “referential transparency”

Purity: when a function—

  • only has access to its arguments
    • ​no global state
    • no global functions (console.log)
  • cannot mutate values
    • not its arguments
    • not other state (no access!)

“Reasoning about your code”?

“Reasoning about your code”?

Computer Science” reasoning:

  • Algorithmic complexity
  • Space (memory) usage

“Reasoning about your code”?

“Computer Science” reasoning:

  • Algorithmic complexity
  • Space (memory) usage

Code change” reasoning:

  • fixing bugs
  • improving performance
  • adding new features
  • changing existing feature

“Reasoning about your code”?

“Computer Science” reasoning:

  • Algorithmic complexity
  • Space (memory) usage

“Code change” reasoning:

  • fixing bugs
  • improving performance
  • adding new features
  • changing existing feature

The most important reasoning:

  • does it actually work?

Local Reasoning!

“Reasoning about your code”?

Local Reasoning!

“Reasoning about your code”?

Shrink the radius of thought!

Local Reasoning!

“Reasoning about your code”?

Shrink the radius of thought!

Local Reasoning!

Local Reasoning!

Code Complete 2

Local Reasoning!

variable scope

  • comprehensibility
  • refactoring

Code Complete 2

Local Reasoning!

variable scope

  • comprehensibility
  • refactoring

Code Complete 2

Rust

Local Reasoning!

variable scope

  • comprehensibility
  • refactoring

control over mutability

  • comprehensibility
  • refactoring
  • elimination of bugs

Code Complete 2

Rust

Local Reasoning!

variable scope

  • comprehensibility
  • refactoring

control over mutability

  • comprehensibility
  • refactoring
  • elimination of bugs

Code Complete 2

Rust

Pure functional

Local Reasoning!

variable scope

  • comprehensibility
  • refactoring

control over mutability

  • comprehensibility
  • refactoring
  • elimination of bugs

Code Complete 2

Rust

Pure functional

purity & immutability referential transparency

  • comprehensibility
  • refactoring
  • elimination of bugs

Local Reasoning!

“Reasoning about your code”?

Shrink the radius of thought!

Case studies

Case studies

…our intellectual powers are rather geared to master static relations and… our powers to visualize processes evolving in time are relatively poorly developed. For that reason we should do (as wise programmers aware of our limitations) our utmost to shorten the conceptual gap between the static program and the dynamic process, to make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible.

—Edgar Djikstra, “Go To Considered Harmful”, 1968

: GOTO

Case studies

…our intellectual powers are rather geared to master static relations and… our powers to visualize processes evolving in time are relatively poorly developed. For that reason we should do (as wise programmers aware of our limitations) our utmost to shorten the conceptual gap between the static program and the dynamic process, to make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible.

—Edgar Djikstra, “Go To Considered Harmful”, 1968

shorten the conceptual gap between the

static program and the dynamic process,

between the program

in text space

and the process

in time

: GOTO

Case studies

—Edgar Djikstra, “Go To Considered Harmful”, 1968

shorten the conceptual gap between the

static program and the dynamic process,

between the program

in text space

and the process

in time

: GOTO

Case studies

: GOTO

global reasoning

GOTO:

Case studies

: GOTO

local reasoning

structured programming:

Case studies

: GOTO

local reasoning

structured programming:

global reasoning

GOTO:

reasoning about control flow

Case studies

: Global Mutable State

local reasoning

explicit argument passing:

global reasoning

global mutable variables:

reasoning about data change

Case studies

: Encapsulation

reasoning about class methods

encapsulated data:

reasoning about any function

shared mutable data:

reasoning about data change

Case studies

: SOLID

reasoning about interfaces

Case studies

: SOLID

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Case studies

: The Actor Model

Case studies

: The Actor Model

reasoning about fault tolerance

independent failure & recovery

actor-based systems:

system-wide failure & recovery

monolithic systems:

Case studies

: Types

class User {
  constructor(
    name: string,
    age: number,
    email: string,
    state: State,
  ) {}
}

Case studies

: Types

class User {
  constructor(
    name: string,
    age: number,
    email: string,
    state: State,
  ) {}
}

function describe(user: User): string {
  return `${user.name} is ${user.age} years old`;
}

Case studies

: Types

class User {
  constructor(
    name: string,
    age: number,
    email: string,
    state: State,
  ) {}
}

function describe(person: { name: string; age: number }): string {
  return `${person.name} is ${person.age} years old`;
}

Case studies

: Types

reasoning about a

describe(user: User) {...}

whole class

Case studies

: Types

describe(person: { name: string, age: number }) {...}

reasoning about

structured data

Case studies

: Types

describe(user: User) {...}
describe(person: { name: string, age: number }) {...}

reasoning about data coupling

reasoning about

structured data

reasoning about a

whole class

Case studies

: Autotracking

Case studies

: Autotracking

consumer-driven

Observable-based systems:

  1. Classic computed properties
    • computed(...)
    • get and set
    • two-way-binding
  2. Observers and observer-likes
    • ​arbitrary further “pushes”
    • observer(...), didReceiveAttrs, etc.

Case studies

: Autotracking

owner-managed

Autotracking:

  1. @tracked root state
  2. 1-way data flow
    • no 2-way binding
    • no observers

 no arbitrary reactivity

 no arbitrary “pushes”

Case studies

: Autotracking

reasoning about reactivity

consumer-driven

Observable-based systems:

owner-managed

Autotracking:

Local Reasoning!

“Reasoning about your code”?

Shrink the radius of thought!

Local Reasoning!

“Reasoning about your code”?

Shrink the radius of thought!

Keep It Local