Kurzfassung
Natürliche Sprache, Pseudocode und Code sind keine konkurrierenden Werkzeuge. Es sind unterschiedliche Sprachen für unterschiedliche Präzisionsstufen.
Im Zeitalter der KI-gestützten Programmierung bedeutet Programmierenlernen nicht nur, jede Codezeile von Hand zu schreiben. Es bedeutet zu erkennen, wann eine Idee präziser werden muss, wie man diese Übersetzung kontrolliert und wie man menschliche Entscheidungen von Ergänzungen eines KI-Systems unterscheidet.
Ein Zitat von Richard Feynman
Um zu verstehen, warum Programmierung durch KI nicht einfach verschwindet, hilft eine allgemeinere Unterscheidung: der Unterschied zwischen Klarheit und Präzision.
Richard Feynman erläuterte diesen Gedanken einmal im Zusammenhang mit Mathematik:
Das eigentliche Problem beim Sprechen ist nicht präzise Sprache. Das Problem ist klare Sprache. Man möchte dem anderen Menschen die Idee klar vermitteln. Präzision ist nur nötig, wenn Zweifel an der Bedeutung einer Formulierung bestehen. Dann sollte sie genau dort eingeführt werden, wo diese Zweifel liegen.
Tatsächlich ist es unmöglich, etwas mit absoluter Präzision auszudrücken, sofern es nicht so weit von der Wirklichkeit abstrahiert wurde, dass es keine reale Sache mehr darstellt. Die reine Mathematik ist eine solche Abstraktion von der Wirklichkeit und besitzt eine besondere, präzise Sprache für ihre eigenen speziellen und technischen Gegenstände.
Doch bei realen Gegenständen der Welt ist diese präzise Sprache in keinem solchen Sinn präzise. Sie zu verwenden ist lediglich pedantisch und verwirrend, sofern es keine besonderen Feinheiten gibt, die sorgfältig unterschieden werden müssen.
Interessant ist, dass Feynman nicht die Mathematik kritisiert. Er kritisiert die Vorstellung, dass mehr Formalismus automatisch mehr Verständnis bedeutet.
Seine Aussage ist einfach:
- Klarheit ist wichtiger als Präzision;
- Präzision sollte nur dort eingeführt werden, wo tatsächlich Mehrdeutigkeit besteht;
- reine Mathematik kann vollkommen präzise sein, weil sie mit Abstraktionen arbeitet, die sie selbst definiert;
- wenn wir über die reale Welt sprechen, gibt es immer Näherungen, Annahmen und unvollkommene Definitionen.
Obwohl Feynman über Mathematik sprach, ist diese Unterscheidung auch für die Programmierung hilfreich. Nicht weil Programmierung mit reiner Mathematik identisch wäre, sondern weil auch sie formale Präzision dort einführt, wo natürliche Sprache zu mehrdeutig ist.
Der zentrale Gedanke dieses Artikels lautet:
Programmierenlernen bleibt wichtig, denn das schwierige Problem ist nicht allein die Erzeugung von Code. Es ist die Kontrolle der Übersetzung zwischen einer mehrdeutigen menschlichen Absicht und einem formalen System, das Anweisungen ohne gesunden Menschenverstand ausführt.
Die Sprache der Menschen und die Sprache der Computer
Programmierung ist eine Art, ein Verfahren zu beschreiben. Sie sagt einer Maschine, was sie tun soll.
Eine CPU, also eine Central Processing Unit, versteht Englisch, Spanisch, Python oder C++ nicht so wie ein Mensch. Auf der untersten Ebene führt sie sehr einfache Anweisungen aus: Zahlen lesen, vergleichen und addieren, Daten zwischen Speicherstellen kopieren und von einer Anweisung zur nächsten springen.
Programmiersprachen sind eine menschenfreundlichere Ebene zur Beschreibung von Verfahren. Diese werden später interpretiert oder in Anweisungen übersetzt, die eine Maschine ausführen kann.
In natürlicher Sprache lässt eine Anweisung wie:
Sortiere diesen Kartenstapel.
viel Spielraum für Interpretation.
Sie setzt voraus, dass die angesprochene Person weiß, was eine Karte ist, was Sortieren bedeutet, welches Ordnungskriterium gilt, welche Karten höher sind als andere, ob die Kartenfarben eine Rolle spielen und welcher Zeitaufwand akzeptabel ist.
Bei der Programmierung müssen diese Entscheidungen irgendwo im System definiert sein.
Wir schreiben nicht jedes Detail von Hand, weil Programmiersprachen, Bibliotheken, Frameworks und Abstraktionen bereits viele Entscheidungen enthalten. Aber irgendjemand musste diese Entscheidungen auf irgendeiner Ebene präzise festlegen.
Natürliche Sprache und Programmiersprachen sind keine besseren oder schlechteren Versionen derselben Sache. Sie sind unterschiedliche Werkzeuge. Die Frage, ob wir wegen KI keine Programmierung mehr lernen müssen, ähnelt der Frage, ob wir den Zweck eines Mikroskops nicht mehr verstehen müssen, weil wir eine Lupe bedienen können.
Wie Software in einem Softwareunternehmen entsteht
In einem Softwareunternehmen beginnt fast alles mit natürlicher Sprache.
Ein Kunde erklärt seinen Wunsch:
Ich brauche eine App zur Verwaltung von Bestellungen.
Oder:
Ich brauche eine Website, um Produkte zu verkaufen.
Oder:
Ich brauche ein System zur Verwaltung von Terminen.
Diese erste Beschreibung ist selten präzise. Nicht weil der Kunde sich schlecht ausdrückt, sondern weil er umfangreiches Fachwissen in wenige Sätze verdichten möchte.
Dieses Wissen kann Gewohnheiten, Ausnahmen, Geschäftsregeln, Vorlieben, Grenzfälle, frühere Probleme und unausgesprochene Entscheidungen umfassen.
Deshalb gibt es Zwischenebenen.
Ein Produktmanager, Business Analyst oder funktionaler Analyst spricht mit dem Kunden, stellt Fragen, erkennt Mehrdeutigkeiten und übersetzt die ursprüngliche Bitte in klarere Anforderungen.
Danach übersetzt ein technischer Leiter, Architekt oder Programmierer diese Anforderungen in immer konkretere technische Entscheidungen, bis schließlich Code entsteht.
Jede Ebene kann Probleme entdecken oder Änderungen vorschlagen. Keine sollte jedoch stillschweigend eine Entscheidung einer übergeordneten Ebene überschreiben.
Findet ein Programmierer einen Widerspruch, sollte er ihn ansprechen. Formuliert ein Produktmanager eine Anforderung neu, sollte diese Formulierung auf die ursprüngliche Kundenanfrage zurückführbar bleiben.
Drei Wege von einer Idee zum Code
Das folgende Diagramm vergleicht drei Abläufe: traditionelle Softwareentwicklung, direkte Codegenerierung durch ein LLM und einen vorgeschlagenen Ablauf mit einer ausdrücklich vorgesehenen Zwischenebene.
Traditioneller Ablauf
LLM ohne Methode
Black Box Approach
Was passiert, wenn ein Mensch mit einem LLM Code erzeugt?
Wenn ein Mensch unmittelbar mit einem LLM arbeitet, um Code zu erzeugen, verschwindet die Zwischenebene nicht vollständig. Sie wird implizit.
Das Modell interpretiert den Prompt, ergänzt fehlende Details, priorisiert Anweisungen und trifft Implementierungsentscheidungen. Viele dieser Entscheidungen werden jedoch nirgends dauerhaft festgehalten.
Deshalb besitzen viele KI-Programmierwerkzeuge etwas wie einen Planungsmodus. Das Modell soll vor der Umsetzung nachdenken.
Das Problem ist, dass der Plan oft nur vorübergehend existiert. Er kann in einer Unterhaltung stecken bleiben, vom endgültigen Code getrennt sein oder vom Nutzer ignoriert werden.
Programmierer verbringen häufig viel Zeit damit, Spezifikationen, Anforderungsdokumente, Architekturnotizen und Kontextdateien zur Anleitung des LLM zu schreiben. Dadurch entsteht jedoch ein weiteres Problem: Dutzende verstreute Dateien mit unterschiedlichen Verbindlichkeiten, Prioritäten und Aktualitätsständen.
Auch sorgfältig geschriebene Spezifikationen beseitigen das Problem nicht.
Das LLM kann Widersprüche entdecken, Lücken füllen oder Entscheidungen treffen, die nicht genau den Anforderungen entsprechen. Manchmal liegt das an mehrdeutiger natürlicher Sprache, manchmal an der Reihenfolge, in der das Modell Dateien liest, manchmal an widersprüchlichen Anweisungen. Und manchmal liegt es daran, dass LLMs probabilistische Systeme sind.
Das Problem ist nicht, dass das LLM Entscheidungen trifft.
Das Problem ist, nicht zu wissen, welche Entscheidungen es getroffen hat, warum es sie getroffen hat und ob sie einer früheren menschlichen Entscheidung widersprechen.
Pseudocode als Zwischenebene
Pseudocode kann die Rolle übernehmen, die in der traditionellen Softwareentwicklung oft auf Gespräche zwischen Kunden, Managern, Architekten und Programmierern verteilt ist.
Er ist keine reine natürliche Sprache, denn er hat eine Struktur.
Er ist aber auch kein ausführbarer Code. Wir können weiterhin über Absichten, Module und Verantwortlichkeiten sprechen.
Diese Zwischenebene ist bei LLMs besonders nützlich, weil sie zwei häufig zu schnell zusammengefasste Schritte trennt:
- entscheiden, was das System tun soll;
- entscheiden, wie es umgesetzt wird.
Zum Beispiel:
IF user selects a subscription plan
CALL create_checkout_session
IF payment succeeds
CALL activate_subscription
CALL send_confirmation_email
ELSE
CALL show_payment_error
Dieser Pseudocode entscheidet noch nicht, ob wir Stripe, Paddle, Mercado Pago, eine eigene API oder eine bestimmte Bibliothek verwenden.
Er definiert aber eine Struktur:
- es gibt einen Tarif;
- es gibt eine Checkout-Sitzung;
- es gibt einen Schritt zur Aktivierung des Abonnements;
- es gibt einen Ablauf für erfolgreiche Zahlungen;
- es gibt einen Ablauf für Fehler.
Das ist bereits wesentlich präziser als eine vage Bitte wie:
Füge Zahlungen zur App hinzu.
Es ist aber noch nicht so starr wie der endgültige Implementierungscode.
Der Vorschlag: Black Box Approach
Die vorgeschlagene Methode ist einfach:
Behandle jeden Teil des Systems als eine verfeinerbare Blackbox mit klaren Grenzen, Pseudocode als Zwischenebene und ausdrücklich dokumentierter Entscheidungsherkunft.
Jedes App-Projekt erhält einen Ordner blackboxes/ mit:
- YAML-Boxen;
- gemeinsamen Datenstrukturen;
- einer Skill-Datei, der das LLM bei ausdrücklichem Aufruf folgen muss;
- einem Protokoll der Entscheidungsherkunft;
- optional einem Visualisierer zur Darstellung des Boxbaums.
Das Ziel ist nicht, Code zu ersetzen.
Das Ziel ist, die Übersetzung von menschlicher Absicht in Code sichtbar, bearbeitbar und überprüfbar zu machen.
Welche Probleme der Ansatz lösen soll
1. Menschliche Entscheidungen und KI-Entscheidungen
Jede Box und jedes Datenfeld dokumentiert die Herkunft von Entscheidungen:
human_definedai_definedmixed
Sobald ein Mensch etwas bestätigt, darf die KI es nicht stillschweigend wieder zu einer Vermutung machen.
Entscheidet sich das LLM zum Beispiel für Stripe, kann diese Entscheidung zunächst als ai_defined gekennzeichnet werden.
Bestätigt ein Mensch diese Entscheidung, ist sie menschlich genehmigt. Von diesem Zeitpunkt an sollte das Modell Stripe nicht durch einen anderen Anbieter ersetzen, ohne die Änderung ausdrücklich anzusprechen.
2. Pseudocode als Zwischenebene
Die Boxen verwenden gemischten Pseudocode:
CALL create_checkout_session
IF payment succeeds
WHILE there are pending invoices
Damit gibt es genug Struktur, um ein System zu zerlegen, aber nicht so viel, dass wir uns vorzeitig in Implementierungsdetails verlieren.
3. Modulgrenzen
Das System wird jeweils um eine Blackbox weiter verfeinert.
Übergeordnete Boxen sind schreibgeschützter Kontext. Nur der ausgewählte Teilbaum darf sich ändern.
Das ist wichtig, weil eine Bitte wie:
Repariere den Zahlungsablauf.
dem LLM nicht erlauben sollte, die Authentifizierungsschicht, das Datenbankschema und das Benachrichtigungssystem umzuschreiben, sofern diese Änderungen nicht ausdrücklich miteinander verbunden und genehmigt sind.
4. Eine für Menschen verständliche Ansicht
Ein Visualisierer kann den Boxbaum wesentlich einfacher darstellen als Dutzende verstreute Spezifikationsdateien.
Es geht nicht bloß um Dokumentation. Es geht darum, die Grenzen von Entscheidungen zu bewahren.
Ein kleines Beispiel
Hier ist ein vereinfachtes Beispiel einer Blackbox für einen Zahlungsablauf:
box: payment_flow
goal: allow the user to pay for a subscription
provenance: human_defined
inputs:
- selected_plan
- user_id
pseudocode:
- IF user selects plan
- CALL create_checkout_session
- IF payment succeeds
- CALL activate_subscription
- ELSE show_error_message
decisions:
payment_provider:
value: Stripe
provenance: ai_defined
status: needs_human_confirmation
constraints:
- Do not modify auth_flow without human approval
- Do not store sensitive card data
Interessant ist nicht die YAML-Datei an sich.
Interessant ist, dass jede Entscheidung einen Platz hat. Das System weiß, was ein Mensch festgelegt hat, was die KI erschlossen hat und was noch bestätigt werden muss.
Die Zwischenebene ist keine Bürokratie. Sie verhindert, dass KI Mehrdeutigkeit in unsichtbare Entscheidungen verwandelt.
Was bedeutet es heute, Programmieren zu lernen?
Vielleicht bedeutet Programmierenlernen nicht mehr, Syntax auswendig zu lernen oder jede Funktion von Grund auf zu schreiben.
Vielleicht bedeutet es, auf unterschiedlichen Präzisionsstufen denken zu lernen.
Es bedeutet zu erkennen, wann natürliche Sprache ausreicht, wann Pseudocode nötig ist, wann ausführbarer Code benötigt wird und wann eine Entscheidung dokumentiert werden muss, weil sie das gesamte System beeinflusst.
Programmierenlernen vermittelt außerdem:
- wie man Probleme zerlegt;
- wie man Grenzfälle erkennt;
- wie man Daten modelliert;
- wie man generierten Code kritisch liest;
- wie man Fehler sucht und behebt;
- wie man Vor- und Nachteile abwägt;
- wie man über Einschränkungen wie Speicher, Latenz, Sicherheit, Nebenläufigkeit, Berechtigungen und Kosten nachdenkt.
KI kann die Erzeugung von Code erheblich beschleunigen.
Je mehr Code die KI erzeugt, desto wichtiger wird es jedoch, ihn zu lesen, zu hinterfragen und wieder mit den menschlichen Entscheidungen zu verbinden, die er umsetzen soll.
Fazit
Die Frage „Müssen wir noch Programmieren lernen?“ ist möglicherweise ungünstig gestellt.
Wenn Programmieren bedeutet, Syntax auswendig zu lernen und jede Codezeile von Hand zu schreiben, werden wir davon mit der Zeit wahrscheinlich weniger benötigen.
Wenn Programmieren aber bedeutet zu verstehen, wie aus einer mehrdeutigen Absicht ein formales System wird, das Entscheidungen trifft und Handlungen ausführt, lautet die Antwort eindeutig ja.
KI kann Code schreiben.
Doch jemand muss die Übersetzung weiterhin kontrollieren.
Und um eine Übersetzung zu kontrollieren, muss man zumindest einen Teil beider Sprachen verstehen.
Der in diesem Artikel beschriebene Black Box Approach ist ein Experiment als Machbarkeitsnachweis. github.com/amiune/theblackboxapproach