Een paar weken geleden zag ik een tweet waarin stond: ‘Het schrijven van code is niet het probleem. Het beheersen van de complexiteit wel.” Ik wou dat ik me kon herinneren wie dat zei; Ik zal het de komende tijd vaak citeren. Deze verklaring vat mooi samen wat softwareontwikkeling moeilijk maakt. Het gaat niet alleen om het onthouden van de syntactische particulars van een bepaalde programmeertaal, of de vele functies in een bepaalde API, maar om het begrijpen en beheren van de complexiteit van het probleem dat je probeert op te lossen.
We hebben dit allemaal al vaker gezien. Veel applicaties en instruments beginnen eenvoudig. Ze doen 80% van het werk goed, misschien 90%. Maar dat is niet genoeg. Versie 1.1 krijgt nog een paar functies, versie 1.2 komt er nog meer bij, en tegen de tijd dat je bij 3.0 komt, is een elegante gebruikersinterface een puinhoop geworden. Deze toename in complexiteit is een van de redenen dat applicaties in de loop van de tijd minder bruikbaar worden. Dit fenomeen zien we ook als de ene applicatie de andere vervangt. RCS was nuttig, maar deed niet alles wat we nodig hadden; SVN was beter; Git doet zo ongeveer alles wat je zou willen, maar tegen enorme kosten in complexiteit. (Kan de complexiteit van Git beter worden beheerd? Ik ben niet degene die dat zegt.) OS X, dat vroeger “Het werkt gewoon” uitschreeuwde, is geëvolueerd naar “Vroeger werkte het gewoon”; het meest gebruikersgerichte Unix-achtige systeem ooit gebouwd wankelt nu onder de final van nieuwe en slecht doordachte functies.
Het probleem van complexiteit beperkt zich niet tot gebruikersinterfaces; dat is misschien wel het minst belangrijke (hoewel meest zichtbare) facet van het probleem. Iedereen die met programmeren werkt, heeft de broncode van een bepaald mission zien evolueren van iets korts, moois en schoons naar een ziedende massa bits. (Tegenwoordig is het vaak een ziedende massa van gedistribueerde bits.) Een deel van die evolutie wordt aangedreven door een steeds complexer wordende wereld die aandacht vereist voor veilige programmering, cloudimplementatie en andere problemen die een paar decennia geleden nog niet bestonden. Maar zelfs hier geldt: een vereiste als beveiliging heeft de neiging code complexer te maken, maar de complexiteit zelf verbergt beveiligingsproblemen. Zeggen “ja, het toevoegen van beveiliging heeft de code complexer gemaakt” is op verschillende fronten verkeerd. Beveiliging die als bijzaak wordt toegevoegd, mislukt bijna altijd. Het vanaf het start ontwerpen van beveiliging leidt bijna altijd tot een eenvoudiger resultaat dan het achteraf toevoegen van beveiliging, en de complexiteit zal beheersbaar blijven als nieuwe functies en beveiliging samen groeien. Als we de complexiteit serieus nemen, moet de complexiteit van het bouwen van veilige systemen in lijn met de relaxation van de software program worden beheerd en gecontroleerd, anders zullen er nog meer kwetsbaarheden ontstaan.
Dat brengt mij bij mijn belangrijkste punt. We zien meer code die is geschreven (althans in de eerste versie) door generatieve AI-tools, zoals GitHub Copilot, ChatGPT (vooral met Code Interpreter) en Google Codey. Een voordeel van computer systems is natuurlijk dat ze zich niets aantrekken van complexiteit. Maar dat voordeel is ook een belangrijk nadeel. Totdat AI-systemen code internet zo betrouwbaar kunnen genereren als onze huidige generatie compilers, zullen mensen de code die ze schrijven moeten begrijpen en debuggen. Brian Kernighan schreef: “Iedereen weet dat debuggen twee keer zo moeilijk is als het schrijven van een programma. Dus als je zo slim bent als je kunt zijn als je het schrijft, hoe kun je het dan ooit debuggen? We willen geen toekomst die bestaat uit code die te slim is om door mensen te worden gedebugd – tenminste niet totdat de AI’s klaar zijn om dat debuggen voor ons te doen. Echt briljante programmeurs schrijven code die een uitweg vindt uit de complexiteit: code die misschien iets langer, iets duidelijker, iets minder slim is, zodat iemand het later kan begrijpen. (Copilot die in VSCode draait, heeft een knop die de code vereenvoudigt, maar de mogelijkheden ervan zijn beperkt.)
Bovendien hebben we het, als we het over complexiteit hebben, niet alleen over individuele regels code en individuele functies of methoden. De meeste professionele programmeurs werken op grote systemen die uit duizenden functies en miljoenen regels code kunnen bestaan. Die code kan de vorm aannemen van tientallen microservices die als asynchrone processen draaien en by way of een netwerk communiceren. Wat is de algemene structuur, de algemene architectuur van deze programma’s? Hoe worden ze eenvoudig en beheersbaar gehouden? Hoe denkt u over complexiteit bij het schrijven of onderhouden van software program die de ontwikkelaars ervan kan overleven? Miljoenen regels oude code die teruggaan tot de jaren zestig en zeventig zijn nog steeds in gebruik, en een groot deel ervan is geschreven in talen die niet langer populair zijn. Hoe beheersen we de complexiteit als we hiermee werken?
Mensen kunnen dit soort complexiteit niet goed beheersen, maar dat betekent niet dat we het kunnen onderzoeken en vergeten. Door de jaren heen zijn we geleidelijk beter geworden in het beheersen van complexiteit. Softwarearchitectuur is een aside specialisme dat in de loop van de tijd alleen maar belangrijker is geworden. Het wordt steeds belangrijker naarmate systemen groter en complexer worden, omdat we erop vertrouwen om meer taken te automatiseren, en omdat die systemen moeten opschalen naar dimensies die een paar decennia geleden bijna ondenkbaar waren. Het verminderen van de complexiteit van moderne softwaresystemen is een probleem dat mensen kunnen oplossen – en ik heb nog geen bewijs gezien dat generatieve AI dat kan. Strikt genomen is dat nog geen vraag die gesteld kan worden. Claude 2 heeft een maximale context (de bovengrens voor de hoeveelheid tekst die hij in één keer kan verwerken) van 100.000 tokens1; op dit second zijn alle andere grote taalmodellen aanzienlijk kleiner. Hoewel 100.000 tokens enorm is, is het veel kleiner dan de broncode voor zelfs een middelgroot stukje bedrijfssoftware. En hoewel je niet elke regel code hoeft te begrijpen om een softwaresysteem op hoog niveau te ontwerpen, moet je wel veel informatie beheren: specificaties, gebruikersverhalen, protocollen, beperkingen, erfenissen en nog veel meer. Is een taalmodel daartoe in staat?
Kunnen we zelfs het doel van ‘het beheren van complexiteit’ in een immediate omschrijven? Een paar jaar geleden dachten veel ontwikkelaars dat het minimaliseren van “coderegels” de sleutel tot vereenvoudiging was – en het zou gemakkelijk zijn om ChatGPT te vertellen een probleem op te lossen met zo weinig mogelijk regels code. Maar dat is niet echt hoe de wereld werkt, niet nu, en niet in 2007. Het minimaliseren van coderegels leidt soms tot eenvoud, maar leidt internet zo vaak tot complexe bezweringen die meerdere ideeën op één lijn bundelen, vaak afhankelijk van ongedocumenteerde bijwerkingen. . Dat is niet de manier om met complexiteit om te gaan. Mantra’s zoals DRY (Do not Repeat Your self) zijn vaak nuttig (zoals de meeste adviezen in De pragmatische programmeur), maar ik heb de fout gemaakt door code te schrijven die te advanced was om een van de twee zeer vergelijkbare functies te elimineren. Minder herhaling, maar het resultaat was complexer en moeilijker te begrijpen. Coderegels zijn gemakkelijk te tellen, maar als dat je enige maatstaf is, verlies je eigenschappen als leesbaarheid uit het oog, die wellicht belangrijker zijn. Elke ingenieur weet dat design draait om afwegingen – in dit geval herhaling versus complexiteit – maar hoe moeilijk deze afwegingen ook mogen zijn voor mensen, het is mij niet duidelijk dat generatieve AI ze beter kan maken, of helemaal niet.
Ik beweer niet dat generatieve AI geen rol speelt in de softwareontwikkeling. Dat is zeker zo. Instruments die code kunnen schrijven zijn zeker nuttig: ze besparen ons het opzoeken van de particulars van bibliotheekfuncties in naslagwerken, ze besparen ons het onthouden van de syntactische particulars van de minder vaak gebruikte abstracties in onze favoriete programmeertalen. Zolang we onze eigen mentale spieren niet laten aftakelen, lopen we voorop. Ik beweer dat we niet zo verstrikt kunnen raken in het automatisch genereren van code dat we de beheersing van de complexiteit vergeten. Grote taalmodellen helpen daar nu niet bij, maar in de toekomst misschien wel. Als ze ons echter de vrijheid geven om meer tijd te besteden aan het begrijpen en oplossen van de problemen op een hoger niveau van complexiteit, zal dat een aanzienlijke winst zijn.
Zal de dag komen dat een groot taalmodel een ondernemingsprogramma met een miljoen regels kan schrijven? Waarschijnlijk. Maar iemand zal de immediate moeten schrijven en vertellen wat hij moet doen. En die persoon zal worden geconfronteerd met het probleem dat programmeren vanaf het start heeft gekenmerkt: het begrijpen van complexiteit, weten waar het onvermijdelijk is, en het beheersen ervan.
Voetnoten
- Het is gebruikelijk om te zeggen dat een token ongeveer ⅘ van een woord bedraagt. Het is echter niet duidelijk hoe dat van toepassing is op de broncode. Het is ook gebruikelijk om te zeggen dat 100.000 woorden de omvang van een roman zijn, maar dat geldt alleen voor vrij korte romans.