Debatte : KI wird Softwareingenieure nicht ersetzen!
Wir konnten am 21.2.2024 in dieser Zeitung die Überschrift lesen, dass „Programmierer ausgedient“ hätten, obwohl der Artikel ehrlicherweise eine ganz andere Stoßrichtung besitzt. Am 5. April lasen wir über „App-Entwickler ohne Programmierkenntnisse“, wie ein technikbegeisterter Vater ohne spezielle Vorkenntnisse eine App für seine iWatch entwickelte, mit der er als Schiedsrichter den Spielstand festhalten kann.
Nicht erwähnt wurde, dass ähnliche Apps und deren Quellcode bereits im Internet verfügbar sind, sodass die Verallgemeinerbarkeit der kreativen Leistung der verwendeten KI zumindest fraglich ist. Solche Überschriften und Beiträge hinterlassen den Eindruck, dass das Softwareengineering mittelfristig durch Maschinen erledigt werden kann und dass sich der wachsende Fachkräftemangel in der Schlüsseldisziplin der digitalen Transformation vielleicht doch von selbst erledigt.
Das Gegenteil ist der Fall. In diesem Beitrag reflektieren wir die Essenz des Softwareengineerings, um die Notwendigkeit eines Menschen „im Fahrersitz“ herauszuarbeiten. Dabei wollen wir nicht nur die auch für andere Berufe gerne verkündete Binsenweisheit wiederholen, dass die KI assistieren wird, aber nicht ersetzen. Stattdessen wollen wir konkret die Frage beantworten, warum das so ist. Wir vermuten, dass zentrale Ideen dieses Beitrags auch auf andere Berufe übertragbar sind.
Softwareengineering
Softwareingenieure bauen Softwaresysteme. Die intellektuelle und kreative Leistung besteht darin, Bedürfnisse zu erkennen, zu verstehen und in ein Softwaresystem zu überführen, das dieses Bedürfnis adressiert. Dabei findet eine Übersetzung von Phänomenen, Strukturen und Herausforderungen der realen physikalischen Welt in virtuelle technische Softwarestrukturen statt.
Notwendig für diesen Prozess sind die Fähigkeit zum Erkennen des Bedürfnisses, das Verständnis der fachlichen Domäne sowie des speziellen Kontextes der Anwendung und die Kenntnis der softwaretechnischen Domäne. Softwareingenieure müssen auch in der Lage sein, komplexe Zusammenhänge so zu durchdringen und präzise zu artikulieren, dass sie einer softwaretechnischen Behandlung zugänglich sind. Dies wird gemeinhin als Fähigkeit zur Modellbildung oder Abstraktion in Fach- und Technikdomäne bezeichnet.
Der Schritt vom Bedürfnis zum IT-System ist üblicherweise zu groß, als dass er direkt gegangen werden könnte. Deswegen hat das Softwareengineering zumindest konzeptionell Zwischenschritte etabliert. Diese Zwischenschritte lassen sich als Übergänge von tatsächlichen Bedürfnissen zu schriftlich fixierten Nutzeranforderungen verstehen, als Übergänge von Nutzer- zu Systemanforderungen, als Übergänge von Systemanforderungen zu Systemdesigns, schließlich als Übergänge von Designs zu lauffähigem Code. Alle diese Übergänge sind erfahrungsgemäß hochgradig fehleranfällig.
Ein missverstandenes Bedürfnis führt schnell zu inadäquaten Nutzeranforderungen und letztendlich zu einem Softwaresystem, das das falsche Problem löst. So macht es einen großen Unterschied, ob das tatsächliche Bedürfnis ist, ohne Auto zum Supermarkt zu kommen (eine Lösung: Fahrrad) oder ob das Bedürfnis ist, Lebensmittel vom Supermarkt in die eigene Wohnung zu bekommen (andere Lösung: Lieferdienst). Ganz analog können Nutzeranforderungen leicht in unpassende Systemanforderungen übersetzt werden. Zum Beispiel könnte die Anforderung, Tausenden von Studierenden die elektronische Abgabe ihrer Hausarbeiten zu ermöglichen, zu der Annahme führen, dass ein System mit redundanten Servern nötig ist. Eine einfachere Lösung besteht jedoch darin, den Abgabetermin auf Sonntagmorgen um drei Uhr zu legen, um eine Überlastung zu vermeiden. Und so weiter: Systemanforderungen können in Designs münden, die später nicht skalierbar sind oder Sicherheitsprobleme verursachen. Die Implementierung dieser Designs in Code kann ebenfalls fehleranfällig sein.
Es hängt von der fachlichen Domäne und vom gewählten Softwareentwicklungsprozess ab, ob die genannten Artefakte überhaupt explizit erstellt werden. In der agilen Softwareentwicklung etwa werden viele der genannten Aktivitäten verschmolzen. Unabhängig vom Entwicklungsprozess besteht die Softwareentwicklung aus unzähligen Entscheidungen und der Auswahl zwischen möglichen Optionen, was auch das Erkennen und Auflösen von Zielkonflikten beinhaltet.
Dieser Prozess wird dadurch erschwert, dass er in einem dynamischen Umfeld stattfindet. Bedürfnisse, Marktbedingungen, Wettbewerbsumfeld, Regulierungen und technische Infrastruktur ändern sich ständig. Im Gegensatz zu Hardware ermöglicht Software aufgrund ihrer immateriellen Natur derartige ständige Anpassungen, eine Eigenschaft übrigens, die sie zum zentralen Innovationstreiber macht. Softwareentwicklung ist also kein linearer Prozess, was unter anderem die Attraktivität agiler Entwicklungsmethoden erklärt.
Im Entwicklungsprozess gibt es wiederkehrende Elemente. Viele Teilprobleme und ihre Lösungen sind sowohl in der fachlichen als auch in der technischen Domäne bereits bekannt. Dieses Wissen manifestiert sich in Erfahrungswissen, Strukturmustern für Software, spezialisierten Programmiersprachen oder Bibliotheken, die eine Wiederverwendung von Funktionalität ermöglichen. Es besteht jedoch immer die reale Gefahr, das aktuelle Problem mit einem ähnlichen, aber nicht identischen Problem zu verwechseln, was zu einer unpassenden Lösung führen kann. Die Entwicklung von Software ist stark kontextabhängig, weshalb die Wiederverwendung von Problembeschreibungen und Lösungen nicht immer offensichtlich ist.
Neben der Fähigkeit zur Modellbildung sind weitere Fähigkeiten im Softwareengineering entscheidend: Kommunikation, das Erkennen von Zielkonflikten und deren Auflösung, das Wissen um wiederkehrende Problem- und Lösungsbausteine und das Gespür dafür, wann diese Bausteine nicht auf den aktuellen Kontext passen. Dazu kommt die Umsetzung der abstrakten Modelle in technischen Code. Diese Umsetzung erfordert weiterhin algorithmisches Denken, also die Fähigkeit, eine gewünschte Funktionalität in eine Reihe von Einzelschritten zu zerlegen, die auf einer Maschine effizient ausgeführt werden können. Schließlich müssen Softwareingenieure in der Lage sein, bestehende Anforderungsdokumente, Architekturen oder Code zu verstehen und zu beurteilen, wenn es ihre Aufgabe ist, bestehenden, teils jahrzehntealten Code zu warten und weiterzuentwickeln.
Generative KI
So gewendet, scheint es offensichtlich, dass viele der beschriebenen Aufgaben prinzipiell nicht von einer KI übernommen werden können. Der Grund dafür ist, dass diese Aufgaben Entscheidungen erfordern, die nur auf Basis eines tiefen Verständnisses der spezifischen Bedürfnisse und der Unterschiede zwischen möglichen Lösungen getroffen werden können. Unser zentrales Argument ist, dass diese Bedürfnisse nicht von einer KI „erraten“ werden können. Stattdessen ist es unerlässlich, dass ein Mensch in einem iterativen Prozess nach und nach mehr über die tatsächlich gewünschte Funktionalität des zu entwickelnden Systems lernt und Zielkonflikte versteht, so wie das in der agilen Softwareentwicklung gelebt wird. Irgendjemand muss letztlich in softwaretechnisch verwertbarer Form ausdrücken können, was genau sich die Nutzer wünschen. Wie soll eine KI das ex nihilo leisten können?
Viele von uns haben Schwierigkeiten, E-Mails zu diktieren. Stattdessen beginnen wir zu schreiben, korrigieren, machen uns Notizen, stellen Mängel in unserer Argumentation fest und ändern die Tonalität. Wir denken beim Schreiben, so wie Kleist das in seinem Aufsatz zur „Allmählichen Verfertigung von Gedanken beim Reden“ beschrieben hat. Die Entwicklung von Code unterliegt einem vergleichbaren Prozess, und das tut die Entwicklung anderer Artefakte des Softwareengineerings ebenfalls. Dieser iterative Prozess funktioniert, gerade weil er langsam ist und weil wir das eben Geschriebene/Gesagte/Codierte/Spezifizierte direkt überprüfen und gegebenenfalls korrigieren können: Wir tasten uns an einen akzeptablen Zustand heran.
Die Vorstellung, dass wir in der Lage wären, auch für ein einfaches System vollständig, konsistent und ohne Umschweife zu sagen, was seine gewünschte Funktionalität ist, hat sich als unrealistisch erwiesen. Eine derartige One-Shot-Generierung von Code erscheint also in den meisten Fällen ausgeschlossen. Das stimmt nicht so sehr, weil die Übersetzung in Code technisch vielleicht nicht möglich wäre, sondern weil die Eingabe für die generative KI in der Regel zu schlecht ist.
Diese Unzulänglichkeit resultiert aus Unvollständigkeiten, Vagheiten, Inkonsistenzen und Missverständnissen des tatsächlichen Bedürfnisses. Unvollständigkeiten können sowohl auf der Ebene der Problembeschreibung, also der Spezifikation oder im Fall generativer KI des Prompts, als auch auf der Ebene des generierten Codes statistisch repariert werden: Eine KI füllt die Lücken mit den wahrscheinlichsten, das heißt am häufigsten vorkommenden Informationen, basierend auf den Trainingsdaten. Dies mag angemessen sein, wenn das Problem tatsächlich ein Standardproblem in der Domäne ist. Es kann aber ebenso völlig unpassend sein.
Ein Argument in diesem Kontext lautet, dass Softwareingenieure in der Rolle der Programmierer heute auch ohne KI häufig nicht anders handeln: Im Internet angebotene Lösungen in Foren wie etwa Stack Overflow werden häufig in den eigenen Code übernommen. Wir wissen aber, dass das etwa im Bereich sicherheitskritischer Softwarekomponenten häufig zu unsicherem Code führt, weil der Einsatzkontext eben doch ein leicht anderer ist und weil die Feinheiten des kopierten Codes nicht verstanden werden.
Vagheiten sind oft sprachlicher Natur, manchmal aber auch konzeptionell bedingt. Das ist häufig der Fall, wenn Wissen um die fachliche Domäne implizit gelassen oder falsch formuliert wird. Es ist gerade eine der großen Stärken generativer KI, dieses Kontextwissen implizit zur Verfügung zu stellen. Ob dieses Kontextwissen aber seinerseits immer korrekt, vollständig und für den betrachteten Anwendungsfall adäquat ist, kann wiederum nur statistisch beantwortet werden: im typischsten Fall vielleicht!
Die Auflösung von Unvollständigkeiten und Vagheiten kann also direkt bei der Erzeugung eines Artefakts geschehen, wenn die generative KI einfach den „typischeren“ Fall auswählt. Die Auflösung kann aber auch als Teil eines anderen Workflows erfolgen: Die KI weist auf vermutete Vagheiten und Unvollständigkeiten hin und schlägt auf Basis der Trainingsdaten Verbesserungen vor. Hier sehen wir das eigentliche Potential der Verwendung von generativer KI im Softwareengineering.
In jedem Fall wird ein Softwareingenieur überprüfen und beurteilen müssen, ob das durch die generative KI erzeugte Artefakt fachlich und technisch angemessen ist. In der Regel wird dies zu einem iterativen Prozess von Erstellen und Überprüfen führen, so wie dies auch ohne KI der Fall ist. Dann stellt sich die Frage, ob es nicht kosteneffektiver ist, den Code oder die Spezifikation direkt selbst zu verfassen, ohne die Hilfe der KI. Es kommt darauf an: Für komplexe, spezifische Anforderungen mag die händische Erstellung effizienter sein, während die KI Standardcode mit hoher Genauigkeit generieren kann. Ähnliches gilt ja auch für einfache und komplexere E-Mails.
Hier könnte ein entscheidender Unterschied des Software-Engineering zu anderen Berufen oder Lebensbereichen liegen. Offenbar benötigen wir in der Co-Kreation mit generativer KI ein komplexes Orakel, das die Korrektheit, Vollständigkeit, Adäquatheit eines generierten Artefakts beurteilen kann: den Menschen. Für Liebesbriefe, Grußworte, Texte in sozialen Medien, Bilder von Katalogmodels und Werbevideos ist die Aufgabe des Orakels vergleichsweise einfach: Man sieht dort häufig sofort, ob das Artefakt den in der Regel impliziten Anforderungen genügt. Für komplexere Artefakte wie beispielsweise Code für Probleme, die nicht schon hundertfach gelöst worden sind, erscheint es möglich und sogar wahrscheinlich, dass der Aufwand der iterativen Erstellung und vor allem Überprüfung prohibitiv oder auch einfach zu wenig freudvoll wird.