Nešto se u softverskom inženjerstvu tiho izvrnulo u poslednje dve godine. Tokom najvećeg dela istorije struke kod je bio artefakt, a specifikacija potrošna skela oko njega: dokument napisan jednom, preleten dvaput i napušten do trećeg sprinta. Sada AI agenti pišu sve veći deo koda i računica se preokrenula. Kod je postao jeftin za proizvodnju i jeftin za regenerisanje, a retko je postalo ono što se ranije podrazumevalo, precizan iskaz o tome šta kod mora da radi. Specifikacija više nije skela, nego glavni inženjerski artefakt.
Timovi koji je tako i tretiraju od agenata dobijaju polugu koja se akumulira: predaju dobro definisan posao, rezultat verifikuju mehanički i spajaju bez zebnje. Timovi koji je tako ne tretiraju dobijaju novu vrstu tehničkog duga, kod koji izgleda uverljivo, koji niko nije tražio i koji se generiše brže nego što iko stiže da ga pročita. Razlika između te dve grupe nije ni u modelu ni u alatima, nego u tome da li je specifikacija ugovor.
Prompt nije specifikacija
Najčešći promašaj je tretirati chat prozor kao specifikaciju. Prompt je uvod u razgovor: nameru opisuje u prozi, granične slučajeve prepušta dobroj volji čitaoca i nestaje u istoriji razgovora čim ga pošaljete. Niko ne može da ga pregleda, uporedi u diffu ni da pokaže na njega tri nedelje kasnije, kada se ponašanje ospori. Agent kome date prompt ponaša se kao izvođač kome ste posao objasnili u hodniku: svaku rupu popunjava sopstvenim pretpostavkama, i to samouvereno.
Specifikacija je sasvim drugi objekat. Živi u repozitorijumu, pored koda koji uređuje. Verzionisana je, pa se vidi šta je dogovoreno i kada se promenilo. Može da se pregleda, pa kolega ima gde da ospori zahtev umesto implementacije. I proverljiva je, što je ono ključno svojstvo, jer za svaki isporučen posao postoji mehanički način da se pita: zadovoljava li ovo specifikaciju, da ili ne? Onog trenutka kada to pitanje traži ličnu procenu, imate prozu, a ne ugovor.
Šta specifikaciju čini ugovorom
Četiri sastojka razdvajaju ugovor od želje. Kriterijumi prihvatanja dolaze prvi, kao uočljive i binarne tvrdnje o ponašanju. “Brzo” je želja; “p95 ispod 200 ms pri 50 zahteva u sekundi” je kriterijum. “Obrađuje loš ulaz” je želja; “odbija neispravan payload sa statusom 422 i mašinski čitljivim kodom greške” je kriterijum. Ako iz perspektive izvan implementacije ne možete da odlučite prolazi li ili pada, prepisujte ga dok ne budete mogli.
Razrađeni primeri dolaze drugi i rade više posla od svakog drugog odeljka. Konkretni parovi ulaz-izlaz, uključujući i one ružne: prazna lista, duplo slanje, granični slučaj sa vremenskom zonom, korisnik koji nalepi emodži u polje za iznos. Primeri su mesto na kome skrivene pretpostavke izlaze na videlo. Oni su, i to nimalo slučajno, već i test slučajevi koji čekaju da budu prepisani.
Invarijante dolaze treće, kao svojstva koja moraju da važe bez obzira na to kojim putem implementacija krene. Saldo nikada ne ide u minus. Svaki prelaz stanja se loguje. Brisanja su meka. Nema poziva ka procesoru plaćanja bez idempotency ključa. Invarijante su zaštitna ograda koja preživljava refaktorisanje, što je dvostruko važno kada refaktoriše agent koji nije bio na sastanku gde je pravilo objašnjeno.
Ne-ciljevi dolaze poslednji i najviše se zanemaruju. Agent retko greši tako što uradi premalo; obično uradi previše, pa izmisli opcije konfiguracije, doda keširanje koje niko nije tražio i “popravi” susedni kod. Kratak spisak onoga što ova izmena ne sme da dodirne i koje probleme ne sme da rešava jeste najjeftinija kontrola obima koju ćete ikada napisati.
Agent svaku rupu u specifikaciji popunjava sopstvenim pretpostavkama, i to samouvereno. Ugovor postoji zato da rupe vredne popunjavanja ni ne ostanu.
Ugovori traže sprovođenje
Ugovor koji niko ne sprovodi je dekoracija. Sloj sprovođenja je ono što od spec-driven developmenta pravi inženjersku praksu, a ne dokumentacionu naviku, i ima tri dela. Prvo, testovi kao izvršni oblik ugovora. Kriterijumi prihvatanja i razrađeni primeri prepisuju se u skup testova pre nego što agent napiše ijednu liniju implementacije. Taj skup je specifikacija kompajlirana u nešto što mašina može da proveri, a agentov posao je da ga natera da prođe bez ijednog slabljenja. Čuvajte i same testove u pregledu: agent koji izmeni assertion da bi test prošao jednostrano je iznova pregovarao ugovor.
Drugo, tipizirane granice. Šeme, tipovi i API ugovori prikivaju svaki interfejs koji agentov kod dodiruje. Stroga šema na granici pretvara čitavu klasu uverljivih ali pogrešnih izlaza u greške kompajliranja i pad validacije, odnosno u povratnu informaciju koju agent dobija za sekunde i unutar petlje, umesto one koju bi mu čovek isporučio danima kasnije. Što je granica stroža, to je manje prostora da samouvereno smeće procuri kroz nju.
Treće, kapije pregleda koje diff porede sa ugovorom. Ljudski pregled ne nestaje, nego menja metu. Čitati izlaz agenta liniju po liniju, i to agentovom brzinom, unapred je izgubljena igra, jer prosto ima više koda nego pažnje. Uporediti diff sa ugovorom jeste izvodljivo: zadovoljava li izmena svaki kriterijum prihvatanja, poštuje li svaku invarijantu, ostaje li unutar ne-ciljeva? Sve u diffu što ugovor ne objašnjava prvo je pitanje pregleda, a ne bonus.
Tri načina na koja ovo pođe naopako
Maglovita specifikacija, samouvereno smeće. Tim napiše tri stavke, agent proizvede osamsto linija, a jaz između namere i ponašanja otkrije se u produkciji. Prepoznaćete ga po pregledima koji počinju rečenicom “nisam na to mislio”, a ta rečenica optužuje specifikaciju, ne agenta. Rešenje je dosadno: više primera i oštriji kriterijumi, napisani pre generisanja, a ne posle.
Preterana specifikacija, nikakva poluga. Druga krajnost. Ako specifikacija diktira implementaciju, fajl po fajl i funkciju po funkciju, napisali ste kod dvaput, jednom na srpskom i jednom na jeziku na kome se izvršava, a agent je sveden na daktilografa. Specifikujte ponašanje na granici i invarijante unutar nje, a implementaciju prepustite onome ko je piše. Ako specifikovanje zadatka traje duže od njegovog obavljanja, tom zadatku agent nije ni bio potreban.
Razilaženje specifikacije i stvarnosti. Najtiši promašaj od sva tri. Ugovor je rekao jedno, produkcija se razvila u nešto drugo i posle tri meseca sitnih pregovaranja usput specifikacija opisuje sistem koji više ne postoji. Razilaženje je neizbežno, ali nezabeleženo razilaženje je izbor. Pravilo koje ga sprečava staje u jednu rečenicu: promene ponašanja prvo sleću kao promene specifikacije, u istom pull requestu, ili ne sleću.
Tok rada, s kraja na kraj
U praksi to izgleda kao kratka petlja. Napišite ugovor, sa kriterijumima, primerima, invarijantama i ne-ciljevima, pa ga dajte na pregled ljudima koji će sa tim ponašanjem živeti; tu je neslaganje najjeftinije. Prepišite proverljive delove u testove koji padaju. Predajte ugovor agentu i pustite ga da radi prema tom skupu testova, iterirajući dok mehaničke provere ne prođu, a to su testovi, tipovi, šeme i linteri, i to bez ijedne izmene samih provera. Tek onda uporedite diff sa ugovorom, a ne sa sopstvenim strpljenjem: kriterijumi ispunjeni, invarijante održane, ništa izvan ne-ciljeva. Merge beleži ugovor i njegovo ispunjenje u jednoj atomskoj celini, pa sledeći zadatak kreće sa poštene osnove.
Petlja ima i efekat drugog reda koji timove iznenadi: ugovori čine rad agenata paralelizovanim. Dva agenta nad jednim maglovitim brifom se sudaraju; pet agenata nad pet ugovora, sa tipiziranim granicama među njima, spaja se čisto, jer svaki ugovor kaže šta njegov deo mora da radi i gde se završava. Isto ono svojstvo koje specifikaciju čini proverljivom čini je i sastavljivom, a tako mali seniorski tim nadgleda količinu istovremene implementacije za koju je pre pet godina bilo potrebno celo odeljenje.
Još jedno mehaničko pravilo drži petlju poštenom: provere se izvršavaju u CI-ju, a ne na agentovu reč. Agent koji javlja “svi testovi prolaze” iznosi tvrdnju, dok je zeleni pipeline nad neizmenjenim skupom testova činjenica. Ugovor, njegovi testovi i pipeline zajedno čine neutralnog arbitra kome je svejedno da li je kod napisao čovek koji ima loš dan ili model koji ima samouveren, a upravo ta ravnodušnost čini izlaz vrednim poverenja.
Timovi koji ovo usvoje otkriju da im se usko grlo pomerilo. Retka veština više nije proizvodnja koda, nego precizno odlučivanje šta kod treba da radi i proverljivo izražavanje toga. To je oduvek bio težak deo softvera; agenti taj problem nisu stvorili, samo su uklonili mesto na kome se krio.
Kada se isplati, a kada je samo režija
Spec-driven development nije besplatan i pretvaranje da jeste ubija usvajanje. Ugovor se isplati kada je posao značajan i kada je cena greške stvarna: sistemi koji pomeraju novac, dodiruju podatke o klijentima, sede na granicama između timova ili će ih održavati ljudi i agenti koji nisu bili prisutni kada su odluke donete. Najbrže se isplati baš tamo gde je izlaz agenta najteže pregledati okom.
Režija je za brze probe, prototipove i jednokratne skripte, za kod čija je cela svrha da bude bačen čim odgovori na pitanje. Napišite rečenicu namere, pustite agenta, zadržite nauk i obrišite kod. Disciplina je u tome da znate u kom ste režimu i da budete pošteni onog trenutka kada prototip počne tiho da postaje sistem. Ugovor se piše u tom trenutku, a ne prilikom prepisivanja sistema šest meseci kasnije.
Industriji su bile potrebne dve decenije da nauči da testovi nisu birokratija, nego ono što joj dozvoljava da se kreće brzo bez lomljenja onoga što je važno. Specifikacije sada prolaze isti put, iz istog razloga. Kada se marginalni trošak pisanja koda približi nuli, vrednost se skuplja u iskazu o tome šta kod mora da radi, a timovi koji taj iskaz pišu kao ugovor jesu oni kod kojih se rad agenata zaista akumulira.