Architektur

It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.

Dieses Dokument gibt zwei kleine Vorschläge, wie ein Programm grob strukturiert werden kann.

Begriffe

Die hier gewählten Definitionen sind nicht

Zustand / state

Der state eines Programms ist im weiteren Sinne sein gesamter Zustand: alle Variablen/Objekte, der Program counter, der Callstack etc.

Im engeren Sinne meine ich mit state den “interessanten” Zustand eines Programs. Bei einem simplen Schachspiel-Programm wären dies tendenziell die Positionen der Figuren auf dem Brett und wer gerade am Zug ist sein. Also grad eben nicht die Zustände irgendwelcher lokaler Variablen.

Seiteneffekt

Ein Seiteneffekt (Engl: side effect) ist, wenn eine Operation etwas jenseits des lokalen Kontext verändert.

I/O (Dateien lesen/schreiben, HTTP-Requests, print, …) ist immer ein Seiteneffekt.

Ein Seiteneffekt ist auch, wenn eine Funktion eine Variable/Objekt, die/das nicht Teil ihres lokalen scopes ist, ändert.

g = 123

def foo(xs: list):
    g = 0    # Seiteneffekt: Änderung globaler variable g
    print(g) # Seiteneffekt: I/O
    l = g    # kein Seiteneffekt: l ist lokale variable
    xs.append(1) # Seiteneffekt: nicht-lokales Objekt wird geändert

pure function

Eine Funktion ist pure, wenn sie keine Seiteneffekte hat und bei gleichem Input immer den gleichen Rückgabewert gibt. Damit ist eine pure function eine Funktion wie in der Mathematik.

Pure functions sind tendenziell leichter zu verstehen, zu debuggen und zu testen.

xs = [3, 1, 2]
sorted(xs) # pure.   returned [1, 2, 3], xs bleibt unverändert
xs.sort()  # impure. returned nichts, xs wird verändert

Architekturen

Eingabe, Verarbeitung, Ausgabe

Wenn man z.B. eine simple Kommandozeilen-ToDo-App bauen möchte, bietet sich folgendes Nutzerschnittstelle an:

$ python myTodo add "lernen"
$ python myTodo add "leben"
$ python myTodo list
1. lernen
2. leben
$ python myTodo delete 2
$ python myTodo list
1. lernen

Hier ist jede Zeile die mit $ beginnt ein Programmaufruf von der Kommandozeile. Zwischen den Aufrufen speichert das Programm die Daten bzw. den state z.B. in einer todos.db.

Das myTodo Programm arbeitet in drei Schritten:

  1. Eingabe: liest Befehl aus sys.argv und state aus todos.db.
  2. Verarbeitung: state wird entsprechend Befehl verändert.
  3. Ausgabe: state wird geprinted und wieder und todos.db überschrieben.

todo cli app, webserver

Kontinuierlich laufendes Programm

Wenn man z.B. ein simples Videospiel oder eine Simulation bauen möchte, liegt der Fokus weniger auf Ein-/Ausgabe sondern primär darin, den internen Zustand korrekt zu halten.

Hier kann es sich anbieten, den state komplett in einem Objekt s einzufangen.

s = init_state()
while not should_stop(s):
    args = read_input_if_avail()
    s = f(s, args)

    assert valid(s)

Es wird dann bei jeder Iteration geprüft, dass weiter gespielt/simuliert werden soll. Falls neuer Input (z.B. eine gepresste Taste) vorliegt, wird dieser in args gespeichert. Ohne neuen Input ist args z.B. None. Aus s und args berechnet dann die pure function f einen neuen Zustand. Zu guter letzt kann z.B. noch geprüft werden, dass s wirklich ein gültiger Zustand ist, z.B. dass bei einem Schachbrett höchstens eine Figur steht usw.


Aufgaben

  1. Denk dir ein simples Spiel oder eine simple Simulation aus. Wie sieht der State s aus?
  2. Wie muss sich s ändern, wenn eine Rewind-Funktion hinzugefügt werden soll?