Skriv ut

Kodning och programmering med hjälp av generativ AI är ett väldigt hett ämne. För en del är det uppenbarligen framtiden, medan andra ser det som en tillfällig hype. Är det ett hot mot alla ­programmerare eller bara en fluga? Det finns mycket att säga och fundera på, så låt oss titta på generativ AI från ett antal olika perspektiv.

AI i sig är ett mycket bredare begrepp med en lång historia. Många olika saker har fogats in under taket, inklusive sånt som produktrekommendationssystem, schackprogram, datormotståndare i datorspel, och maskininlärning. 

I kontexten programmering är ”AI” i princip alltid ”generativ AI”. Generativ AI är en tillämpning av LLM:er, Large Language Models. Det vill säga stora språkmodeller som tränats på enorma mängder data, typiskt skrivet språk (typ hela Internet plus böcker och vad man lyckas få tag på). Modellerna är tillräckligt bra på naturligt språk att det går att konversera med dem. Man ger systemet en prompt, och får tillbaka ett svar. Under en konversation med systemet bygger man upp en kontext som även kan innehålla dokument och kodsnuttar. Kontexten påverkar de svar man får, precis som de frågor man ställer och även tidigare svar i samma konversation.

Generativ AI som generell term omfattar även system som skapar bild och film eller omvandlar mellan tal och text, men det är inte relevant för den här artikeln. 

Höjd abstraktionsnivå?

En tolkning av generativ AI är att se det som en ny högre abstraktionsnivå. Man jämför med steget från assembler till högnivåspråk eller användningen av modell-driven utveckling (med kodgenerering). Det fungerar dock inte så i praktiken. 

Om det ska fungera som en ny abstraktionsnivå skulle man kunna checka in sin prompt som den enda källkoden till ett program och sedan underhålla koden genom att skriva om prompten. Där är vi inte (än). Generativ AI är en slumpmässig process som genererar någon sorts svar från en mer eller mindre precis input. Men varje gång en prompt används får man typiskt olika svar. Speciellt om man ändrar vilken LLM man använder eller väntar en längre tid – då ju systemet kommer att ha uppdaterat sin träning och lärt sig nya saker.

För att man ska kunna koda på en abstraktionsnivå krävs det en precis och väldefinierad språkstandard. Det måste finnas en språkspecifikation som kan implementeras i olika system men som alltid ger samma output för samma input (modulo definierade variationer som ”undefined” i C). Man måste kunna titta på koden om tio, tjugo år och veta vad den gör. Generativ AI är inte definierad till den nivån. 

Så, nej, generativ AI är inte en ny abstraktionsnivå i traditionell mening. Men det innebär inte att det inte är användbart. 

Notera att om man infogar kod från en generativ AI i sitt program är det en bra idé att lägga in prompten som en kommentar. Det kan hjälpa andra att underhålla koden, kanske med en modifierad prompt . . .

Kodunderhåll

Om man inte kan hantera en prompt som källkod utan bara som en generator som skapar kod kommer man genast till frågan om hur lätt den genererade koden är att underhålla. En generativ AI kan enkelt skapa (väldigt) mycket kod från en prompt. Men när det är dags att utöka funktionaliteten eller ändra på koden… då måste man sätta sig in i hur den faktiskt fungerar. Det är i princip samma problem som att förstå kod som en annan människa skrivit, och det kan innebära mindre arbete på lång sikt att skriva kod ”för hand” – speciellt om designen och algoritmerna dokumenteras på vanligt maner. 

För relativt enkelt strukturerad kod som anropar vanliga API:er fungerar AI-genererad kod alldeles utmärkt. En AI kan skapa väldigt mycket tråk-kod som skulle ta tid och energi att skriva för hand. Det viktiga är att man läser igenom vad som genererats och passar in koden i den större strukturen hos det program man håller på att skapa. 

Om man vänder på problemet kan man använda en generativ AI för att förklara kod – men detta är begränsat av att den typiskt läser koden mer som en människa än en maskin. Dåliga variabelnamn och felaktiga kommentarer fungerar lika bra (eller dåligt) på en AI, speciellt om man avsiktligt vill vilseleda ­läsaren. 

Säkerhetsanalys

Ett område som är väldigt omdebatterat är hur generativ AI kan användas inom programanalys och säker programmering. 

Kan en AI hitta fel i kod? Curl-projektet och andra open-source projekt har rapporterat om stora problem med ”AI slop”, det vill säga falska rapporter om säkerhetsluckor som uppenbarligen skapats av AI. AI häver ur sig väldiga mängder med text som låter övertygande – men i praktiken finns det i princip aldrig ett riktigt problem i botten. Deras erfarenhet indikerar att generativ AI inte är särskilt bra på att hitta verkliga fel i verklig kod, i varje fall i händerna på utvecklare som inte riktigt vet vad de håller på med. Man måste alltid titta skeptiskt på vad en LLM genererar, och verifiera att det stämmer. Sådana värdelösa buggrapporter tar tid från utvecklare. De måste ju i varje fall titta på rapporterna… vilket innebär att de har mindre tid att hitta och fixa riktiga fel. 

Om du vill hitta säkerhetsproblem i kod bör du börja med att använda klassiska verktyg som sanitizers och statisk kodanalys. De fungerar bra och är väldigt billiga att köra i jämförelse med en LLM. Notera att generativ AI kan hjälpa till att skapa skript för att köra sådana verktyg och hantera deras output. 

Men LLM:er kan också hjälpa till att hitta fel i kod eftersom de läser koden på ett annat sätt än statiska analysverktyg. Typiskt guidar sådana verktyg en LLM till en viss del av koden genom att använda klassiska tekniker, och de använder LLM:er med en väl förberedd kontext. Här visar det sig att nackdelen att en AI läser kod som en människa också kan användas som en fördel. En AI kan notera skillnader mellan kommentarer och kod, där vanliga verktyg helt ignorerar kommentarer. Den kan också jämföra specifikationer i fri text med en implementation och hitta skillnader, något som tidigare krävde en formalisering av både protokoll och implementationer. En AI kan ha synpunkter på variabelnamn och notera när en variabel med ett ledande namn används i en konstig kontext. 

Detta är verkligen inte samma sak som att använda en LLM-prompt direkt, utan ett exempel på ”AI-agenter”, som jag återkommer till senare. En LLM är bra på vissa saker, och det finns stora möjligheter med att använda dem som en motor ”under huven”. 

Säker kodning

En annan aspekt av säkerhet är säkerheten hos koden som skapas av en AI. Följer den regler och råd om hur man skriver kod för att undvika säkerhetshot? Typiskt så gör generativ AI inte det – den skapar kod från befintliga mönster, och om man inte bara tränat den på väldigt välskriven kod kommer den troligen skapa kod som innehåller svagheter. 

Men det behöver inte vara ett problem. Koden från en AI måste hanteras precis som handskriven kod och kontrolleras, men det vi vet hur vi gör. Vi har väletablerade metoder och verktyg för att säkra upp kod. Detta till skillnad från när man använder AI för att skriva vanlig text, då man måste kontrolläsa allt för hand för att hitta eventuella felaktigheter och subtila betydelseglidningar. 

Notera att det kan vara helt OK att använda snabbskriven kod och skript utan att dra den genom en full säkerhetskontroll – allt handlar om kontext. Ett Pythonskript som du använder själv på din egen dator har inte samma krav på säkerhet som Javascript som går i produktion på en webbserver och kommer utsättas för attacker. Låt en LLM skriva ihop den själv och använd den som en slit-och-släng lösning.

En mer subtil aspekt av säkerhet i AI-genererad kod är huruvida man kan lita på att AI-motorn som används är pålitlig. Det är i de allra flesta fall ett verktyg som tas in utifrån, och i princip kan en LLM tränas för att bygga in subtila fel som öppnar för säkerhetsintrång i koden som den genererar. Givet hur en LLM är uppbyggd finns det inget sätt idag att avgöra om det finns sådana avsiktliga bakdörrar. Återigen kommer vi tillbaka till att all output från en AI måste kontrolleras mot någon oberoende auktoritet. 

Kriminell kodning som exempel

Vad som står utom allt tvivel är att generativ AI har visat sig väldigt användbart för cyberkriminella och andra som vill bryta sig in i system. Generativ AI skriver bättre phishing-email än de flesta människor, på fler språk, och kan automatisera utnyttjandet av personliga data för att skriva personanpassade mejl på en skrämmande stor skala. 

AI kan också skriva om skadliga program så att de gör samma sak men inte på riktigt samma sätt, för att öka chansen att ta sig förbi skyddsmekanismer. Man kan bygga automatiska inlärningsloopar där en AI provar varianter tills den hittar en som inte flaggas av kända system. 

Dessutom har en (elak) hacker typiskt inte krav på sig att följa specifika kodningsstandarder eller skriva säker kod. Fungerar det så fungerar det, och fungerar det inte imorgon gör det inte så mycket. Då genererar man en ny variant med lite mer ”vibe coding”. Långsiktig underhållbarhet är totalt irrelevant. 

Det kan tyckas lite deprimerande, men det visar hur användbart generativ AI är för att snabbt slänga ihop kod och skript för att lösa ett problem för stunden. Om man behöver använda ett nytt API eller programspråk för att göra någonting man gör sällan kan generativ AI spara väldigt mycket tid. Resultatet kanske inte är särskilt snyggt eller genomtänkt, men det behövs inte alltid vara det. 

Hemmabyggets återkomst

Apropå att slänga ihop kod snabbt – som är en intressant möjlig effekt av generativ AI – är att det ändrar ekvationen ”buy vs build” för mjukvara. Det blir lättare att skriva stora mängder av den ”tråk-kod” som ligger bakom användargränssnitt. För typiska affärstillämpningar kan det innebära en återgång till att man hellre bygger interna lösningar än att man köper in färdiga program eller konsulttjänster för att skapa speciallösningar. Kostnadsekvationen för vad som lönar sig att göra själv kan komma att ändras.

Här är det dock viktigt att ha tidigare kommentarer om underhåll och säkerhet i åtanke. Är generativ AI en genväg till bra kod eller dålig kod? 

Testning

En väldigt populär tillämpning av AI är att skriva tester. Eller i vart fall att skriva skelett och ramverk för att köra tester på ett program. Väldigt mycket testkod upprepar befintliga mönster med små variationer, och AI är väldigt bra på den typen av jobb. Det går normalt sett mycket snabbare att be en AI att skapa början till en uppsättning tester än att skriva samma kod för hand, oavsett om testramverket är nytt eller bekant. 

Själva innehållet i testerna måste man däremot vara försiktig med. En AI vet inte egentligen vad koden gör, och kan inte avgöra vad som är rätt svar för ett test. Den kan ofta gissa rätt, men långt ifrån alltid. Om man har en lista av input och output som ska byggas om till test för en funktion eller ett subsystem är en AI en väldigt bra assistent. 

Om man är ute efter att leta fel och hörnfall i koden har vi i min mening bättre etablerade metoder som fuzzing och constrained random. En AI har varken en semantisk modell eller kunskap om vad ett program är tänkt att göra på riktigt. Det blir många processorcykler som bränns på vad som i slutändan ändå måste kontrolleras av en människa eller någon form av oberoende implementation – och om man hade en oberoende implementation kan man använda den till att börja med. 

Specifikation till algoritm

Notera att om man ser ”lista av input och output” som en komplett specifikation av en algoritms beteende, så kan man göra mer än att bara skriva tester. Man kan låta ”AI” skriva själva programmet – eller snarare, man applicerar klassisk maskininlärning och skapar automatiskt en modell som mappar input till output enligt givna exempel. Detta är inte generativ AI eller LLM-AI, utan ”bara” maskininlärning – som kan bygga på neurala nät, beslutsträd, regressioner, och andra metoder.

Till exempel kan man skapa reglerkod baserat på vad man vill åstadkomma utan att ta omvägen via att en människa måste skapa en algoritm. Istället kan man träna fram en approximation (när allt kommer omkring kan nästan all AI ses som en funktionsapproximator).

Explosionen i generativ AI och därtill hörande hårdvara och mjukvara har gjort det billigare och praktiskt enklare att använda den här typen av modeller i riktiga tillämpningar. Det finns gott om processorer för inbyggda system som har AI/ML-acceleratorer som låter dig exekvera modeller snabbt och effektivt, om inte den vanliga processorkärnan räcker till. Speciellt om modellerna råkar bygga på neurala nät. 

Specialkunskaper

Generativ AI är bra på saker som den sett förr. Det vill säga som ingår i dess träningsdata och kan härledas från träningsdata. Typiskt är de stora LLM:erna tränade på information som går att hitta publikt, och ju mer information som finns om ett visst ämne, desto bättre går det. Större relativ prominens leder till att modellen lägger större vikt på att ”komma ihåg” ett visst ämne, medan sällsynta ämnen naturligt nedprioriteras. 

Detta är ett problem för de som använder språk eller API:er som inte är så välrepresenterade i träningsdata. Vilket i praktiken är samma sak som att det inte finns så mycket på Internet om dem. Det kan handla om språk som inte används av så många, egenhändigt skapade domänspecifika specialspråk, API:er för kommersiella produkter eller företagsinterna API:er och ramverk – det som utgör grunden till mycket av det mest avancerade vi bygger i teknikväg. 

Ett bra exempel är hårdvarubeskrivningsspråk som VHDL och Verilog. De är standardiserade och har funnits i många år – men det finns väldigt lite kod publikt tillgänglig, och de flesta riktigt bra verktygslösningarna är kommersiella och inte open-source. Hårdvarudesign görs i princip uteslutande inom kommersiella verksamheter som inte är särskilt intresserade av att släppa ut sina företagshemligheter i offentligheten. Dessutom använder koden typiskt olika former av interna ramverk, kodningsstandarder och strukturer som man absolut inte vill att konkurrenterna ska se. Försöker man använda en standard-LLM för att koda hårdvara går det alltså inte jättebra.

RAG för specialkunskaper

En lösning på specialkunskapsproblemet är att använda RAG – Retrieval-Augmented Generation. Det innebär i korthet att man bygger en databas från dokumentation, kod­exempel, interna supportforum, eller vad man nu har tillgång till. När man ställer en fråga kommer systemet först att fiska fram (”retrieve”) relevanta textfragment ur databasen – ungefär som att en människa skulle göra en sökning. En LLM används sedan för att sammanfatta vad som hittats som ett svar. Svaren kan innehålla referenser tillbaka till ursprungliga dokument vilket gör det lättare att kontrollera resultaten. 

En RAG använder LLM:er indirekt, som en motor som kan processa text, istället för att förlita sig på vad som finns lagrat i modellen själv. Det är inte helt trivialt att bygga sådana system, men de är väldigt kraftfulla när de fungerar. 

Med en RAG kan man använda en generisk standard-LLM för att svara på specifika frågor, utan att behöva träna om den och utan att läcka företagshemligheter (speciellt om man kör en LLM på egna maskiner eller på en hyrd säker partition). Det är också mycket billigare att processa dokument än att träna upp en LLM. Och så kan man byta LLM och testa fram vilken standard-LLM som är bäst lämpad för en viss problemrymd. 

Det är värt att notera att RAG-lösningar inte är immuna mot fabulerande. Om man ställer en fråga som inte matchas av de data den har att jobba med kommer den mest troligt att hitta på saker. Dagens AI-motorer försöker alltid skapa ett svar, eftersom det är det som de tränats att göra. 

En annan metod för att styra en generativ AI som liknar RAG är att skeppa med all information som krävs i en stor prompt – man skippar databasen och sökningen. De mest avancerade moderna LLM:erna kan hantera väldigt mycket data i sin kontext, och det är mycket möjligt att den kod, data och dokumentation som man vill bygga från får plats. Detta är dock en potentiellt dyr lösning – modellen idag är att LLM-leverantörer tar betalt per ”token” som processas, och större kontexter kostar mer pengar. Om man kör modeller på egna datacenter kostar det istället mer energi och hårdvara att hantera större kontext. 

Specialträning

Det finns fall där det är motiverat att träna helt egna LLM:er – eller mer troligt, ta en befintlig LLM och träna den lite till med egna data. Detta kan dock vara ganska dyrt och tidskrävande, medan en RAG i princip kan uppdateras dagligen. Om det handlar om att processa ett visst programmeringsspråk och liknande kan specialtränade LLM:er vara motiverade, eftersom det handlar just om språk. 

AI-agenter

”Agentic AI” är en paketering av generativ AI med specialkunskaper. Tanken är att en agent ska lösa ett specifikt problem och samarbeta med andra agenter i AI-drivna ­arbetsflöden. Specialisering är nyckeln. Genom att ge agenten en väldefinierad uppgift med tydliga gränssnitt mot resten av systemet och en förbestämd och noga utvald mängd information kan man minska problemen med hallucinationer och få ett pålitligt delsystem. Agenter använder ofta andra verktyg (som inte bygger på LLM-teknik) för att processa input och output. De LLM-drivna säkerhetsanalyser som diskuterades tidigare är ett bra exempel på en AI-agent.

Precis som RAG-lösningar använder agentbaserad AI en LLM som motor för att driva systemet, men man bygger en struktur runt den som skiljer sig från att använda en LLM direkt. Det är väldigt viktigt att agenten byggs baserat på specifika data och med kopplingar till rätt externa system. Om man inte har det på plats får man bara en kedja av generiska LLM:er som pratar med varandra – vilket är att be om flummigt fabulerande. 

Agenter kan användas fristående, men de flesta lösningar siktar på att använda flera agenter tillsammans. Man bryter ner ett komplext problem i mindre delar och designar en agent för varje delproblem. Ofta är avsikten att agenterna ska arbeta helt automatiskt (eller autonomt) och bete sig mer som klassisk automatisering av arbetsflöden än som interaktiva samtalspartners. 

Ett intressant användningsfall för agenter är att driva debuggning och liknande interaktiva uppgifter. Verktygen behöver ett gränssnitt för MCP, Model Context Protocol (vilket verkar bli standard). Agenter kan läsa output från debuggern och använda informationen för att välja handlingar som att titta på variabler, sätta nya brytpunkter, eller stega i koden. Förhoppningen är att om man gett agenterna bra exempel på strategier, så kan de själva driva mycket av analysarbetet utan att programmeraren behöver blanda sig i. 

Kodning och programmering

I grund och botten anser jag att man måste skilja på ”programmering” och ”kodning”. Förenklat är programmering konsten att lösa problem med mjukvara, medan kodning är att skriva kod som gör jobbet – givet att man har designat en lösning som behöver implementeras. 

Generativ AI är ett verktyg som utan tvekan kan hjälpa programmerare att skriva kod. Men kodning kommer efter problemlösningen och lösningsdesignen. Väldesignade API:er och sund uppdelning i abstraktionslager kommer inte ut ur AI-genererad kod utan måste finnas på plats innan man applicerar AI för att skriva kod. 

Det viktigaste att komma ihåg vid användning av generativ AI är att alla resultat från en AI-modell måste kontrolleras mot någon form av känd sanning. Det kan handla om specifikationer, enhetstester och systemtester som utvecklats oberoende, statiska analysverktyg, att andra programmerare recenserar koden – eller vad som än står till buds. Man måste förstå domänen man arbetar i och läsa och förstå koden som AI-systemet genererar.

Klassiska lösningar

Om man kan lösa ett problem med klassisk deterministisk teknik är det normalt sett en bra idé att göra det. Det brukar resultera i lösningar som är mer pålitliga och enklare att underhålla och som kräver mycket mindre energi för att köras. Om man till exempel har välstrukturerade maskinläsbara specifikationer för hårdvara kan man enkelt skriva generatorer som skapar både modeller, RTL-kod, och skelett till drivrutiner. Där behövs ingen generativ AI, och en lösning byggd på AI är troligen svårare att underhålla och mindre robust i praktiken. 

Å andra sidan, om specifikationerna är lite vaga och skrivna med naturligt språk är en LLM väldigt användbar som en motor i en kodgenerator från spec. LLM:er excellerar med brusiga data och mänskligt språk – de kan processa information och hitta data och mönster när informationen inte följer strikta strukturer. 

Framåt 

En kunnig programmerare och en generativ AI med bra träningsdata eller AI-agenter är en kraftfull kombination som redan är på väg att bli standard. Det finns uppskattningar på allt mellan 20 och 1000 procent högre produktivitet – men även en del resultat som pekar på negativa effekter på lång sikt.

Generativ AI är ett nytt kraftfullt verktyg, och som alla kraftfulla verktyg kräver det kunskap, erfarenhet och viss försiktighet för att användas väl. En yxa kan användas till att hugga ved, fälla träd, eller hugga sig i benet. Och bara för att man har en yxa blir man inte en snickare. 

Generativ AI för programmeringsrelaterade jobb kommer troligen att gå mot att använda specifika lösningar som fokuserar på specifika problem och kombinerar generativ AI med andra tekniker – det vill säga AI-agenter. Sådana AI-agenter kommer att kopplas in i fler och fler flöden, och allt fler verktyg och andra system kommer att förses med gränssnitt för att kunna drivas av AI. Istället för att prata om ”ChatGPT” kommer man att referera till ”JedAI” eller ”ZeroPath”. Man bygger inte sin egen AI-lösning från grunden lika lite som man bygger sin egen webserver eller kompilator. 

Filosofisk eftersläng

En intressant fråga att följa är hur användning av generativ AI kommer att påverka kodning och programmering. Generativ AI kommer definitivt att påverka hur vi skriver kod, på samma sätt som det redan börjat påverka vårt vanliga språk. LLM:er skriver inte riktigt som människor, utan har en tendens att använda vissa ord, och allteftersom genererad text blir vanligare i offentligheten förändras hur vi skriver och talar. Samma effekt kommer säkert att hända inom programmering. 

Det finns en risk att användning av AI kommer att driva programmerare till lösningar som har bra AI-stöd… vilket på sikt kan utarma ekosystemet. Nya spännande språk kommer att ha ännu svårare att ta sig fram eftersom man behöver en stor mängd användare och mycket kod för att ”komma med” i LLM:ernas modeller.