[SL] Lesbarer Code als Spezifikation

Ich will nochmal eine Lanze brechen dafür, Code so lesbar zu machen, dass auch Software-Laien verstehen können, was da abläuft. Sie müssen es nicht korrekt schreiben können, aber korrekturlesen auf jeden Fall, und das kann ein großer Fortschritt sein gegenüber Spezifikations-Prosa und späterem Blackbox-Testing.

Hier ist ein Beispiel für ein Stück Code, das ein Datenmodell beschreibt:

class Book < ApplicationRecord
  has_and_belongs_to_many :authors
  validates :price, numericality: { greater_than: 0 }
end

class Author < ApplicationRecord
  has_and_belongs_to_many :books
  has_one :address
  validates :name, presence: true
end

Man sieht (mit etwas Einführung), dass es um Bücher und Autoren geht. Ein Buch kann einen oder mehrere Autoren haben (has_and_belongs_to_many), und umgekehrt kann eine Autorin auch mehrere Bücher geschrieben haben. Bei has_one :address könnte der Fachabteilung auffallen, dass wir vielleicht mehrere Adressen für dieselbe Person erlauben sollten.

Die Validierung von Eingaben ist gleich mit abgehandelt, so muss ein Autor einen Namen haben, und der Preis eines Buches muss größer als Null sein. An dieser Stelle könnte die Fachabteilung einwerfen, dass wir auch kostenlose Bücher anbieten wollen. Ein Preis von 0 Euro sollte also zulässig sein.

Das obige Beispiel kommt aus einer sogenannten “Internen DSL”, also einer Domain Specific Language, die einfach in einer einer gängigen Programmiersprache geschrieben ist. Die obigen Beispiele sind Ruby-Quelltext (Ruby on Rails), wegen der flexiblen Syntax von Ruby wirken sie näher an natürlicher Sprache, als das z.B. in Java der Fall wäre.

Hier noch ein Beispiel, diesmal ein Workflow für einen Antrag. Der Antrag wird geprüft, dann angenommen bzw. abgelehnt. Manchmal brauchen wir noch mehr Informationen zur Klärung, dann dreht der Antrag nochmal eine Extra-Runde durch den Workflow.

Workflow für einen Antrag

Hier ist eine Spezifikation dafür in Ruby:

class Antrag
  include Workflow
  workflow do
    state 'neu' do
      event :einreichen, transitions_to: 'wartet auf Prüfung'
    end
    state 'wartet auf Prüfung' do
      event :prüfen, transitions_to: 'wird geprüft'
    end
    state 'wird geprüft' do
      event :klären, transitions_to: 'wartet auf Info'
      event :annehmen, transitions_to: 'angenommen'
      event :ablehnen, transitions_to: 'abgelehnt'
    end
    state 'wartet auf Info' do
      event :information_erhalten, transitions_to: 'wartet auf Prüfung'
    end
    state 'angenommen'
    state 'abgelehnt'
  end
end

Das Schlüsselwort state bezeichnet einen Zustand, in dem sich der Antrag befinden kann, zum Beispiel “wird geprüft” oder “abgelehnt”. Zwischen diesen Zuständen gibt es Übergänge (transitions_to), die durch gewisse Ereignisse (event) ausgelöst werden. So wird ein Antrag aus dem Zustand ‘wartet auf Info’ in den Zustand ‘wartet auf Prüfung’ überführt, sobald wir die gewünsche Information erhalten haben.

Für die Entwickler wird aus dieser Spezifikation Einiges an Unterstützung zur Abarbeitung des Workflows in Software generiert. Ebenso könnte man ein einfaches User Interface erzeugen, etwa mit Knöpfen für die genannten Übergänge (“einreichen”, “klären”, “ablehnen” usw.). Das obige Diagramm würde übrigens auch automatisch aus der Spezifikation erstellt, ein nettes Extra der Ruby Workflow Bibliothek.

Domain Specific Languages sind ein weiteres Werkzeug, um die Kommunikation zwischen Fachabteilung und Entwicklern zu erleichtern. Wenn man es schafft, so eine Sonder-Sprache innerhalb der verwendeten Haupt-Programmiersprache zu realisieren, kann man eine Menge Arbeit sparen.

Matthias Berth

Alle Emails