čísla, která nikdo nechce slyšet
Lightrun ve své zprávě 2026 State of AI-Powered Engineering uvádí statistiku, ze které by vám mělo být nepříjemně, pokud jste celý rok mergovali výstup z Copilota a Clauda: 43 % změn vygenerovaných AI pořád vyžaduje manuální debugging v produkci, a to i poté, co prošly všemi QA branami, které máte. Ne většina. Necelá polovina. Nejvíc mě ale děsí to, že nula procent z dotázaných engineering leaderů se označilo za „velmi sebejisté" v tom, jak se AI kód chová po nasazení. Nula. To není statistická odchylka, to je konsenzus, že tomuhle v produkci nikdo nevěří, i když to všichni dál shipují.
Pak je tu analýza 470 repozitářů od CodeRabbit, podle které AI produkuje zhruba 1,7krát víc chyb než lidi, a co je horší, někde mezi 1,3 a 1,7krát víc těch kritických. Těch, co položí platební flow nebo unesou řádek dat, který nikdy neměl opustit databázi. Viděl jsem lidi, kteří si tahle čísla přečtou a usoudí, že modely zatím prostě nejsou dost dobré, že GPT-5 nebo cokoli příštího ten rozdíl smaže. Podle mě je to úplně špatné čtení. Bottleneck není model. Je to vaše pipeline. Celý ten aparát, který jsme za posledních patnáct let postavili na validaci kódu, předpokládá, že autor byl člověk uvažující o systému, kterému rozuměl. A ten předpoklad teď potichu neplatí.
proč vaše CI nikdy nebylo stavěné na tohle
CI pipeline je deterministická brána pro deterministický kód. Napíšete funkci, napíšete test, který tvrdí, že ta funkce dělá X při vstupu Y, test proběhne, projde, mergujete. Implicitní kontrakt je, že člověk, který tu funkci psal, měl mentální model okolního systému, věděl, na kterých edge casech záleží, a psal testy mířené na ty části, kterými si nebyl jistý. Testy pokrývají to, co autor ví, že neví.
AI tohle obrací. Když model vygeneruje Django view nebo Go handler, vyplodí kód, který je lokálně věrohodný, syntakticky dokonalý a často projde právě těmi testy, které si taky sám napsal, protože implementaci i asserce psal ze stejně mělkého pochopení vašeho codebase. Testy nezkoumají nebezpečné části. Potvrzují happy path, kterou už měl model v hlavě. Vaše zelená fajfka tedy znamená něco úplně jiného než dřív. Dřív znamenala, že člověk zkontroloval rizikové místo. Teď znamená, že vygenerovaný kód souhlasí s vygenerovanými testy, což je tautologie převlečená za verifikaci.
Ta mezera žije na třech konkrétních místech. Integrační kontrakty, kde AI předpokládá, že upstream služba vrací pole, které ve skutečnosti v polovině případů vrací null. Pokrytí edge casů, kde model napíše ten zřejmý test a vynechá prázdný seznam, unicode ve slugu nebo časový posun přes půlnoc, na který by člověk, který už se na tom jednou spálil, nikdy nezapomněl. A behaviorální drift, kdy dnešní vygenerovaný patch jemně protiřečí předpokladu z patche, který stejný model vygeneroval před třemi týdny, protože mezi těmi dvěma sezeními neexistuje žádná sdílená paměť a žádný člověk, který by držel celý obrázek v hlavě. Nic z toho se neukáže v unit testech. Všechno se to ukáže v produkci.
kontraktové testy na každém švu
První věc, kterou bych přidal, ještě než cokoli vychytaného, jsou pořádné kontraktové testy na každé integrační hranici, protože právě tam AI kód selhává nejtvrději a nejtišeji. Pokud vaše Go služba volá payments API nebo jinou interní službu, AI nemá tušení, co ta věc reálně vrací pod zátěží, během částečného výpadku nebo když upstream tým minulé úterý vydal breaking change. Prostě předpokládá tvar, který viděla v nějakém příkladu.
Pact je tady zřejmý nástroj a funguje dobře, ale upřímně u interní Go služby získáte většinu hodnoty s mnohem lehčím setupem. Definujte kontrakt jako konkrétní fixture, přesné JSON, které konzument očekává, a na straně providera spusťte test, který tu fixture přehraje proti reálnému handleru. Když AI vygeneruje kód, který konzumuje endpoint, kontraktový test je to, co odhalí, že předpokládá non-nullable pole, protože fixture obsahuje null case a vygenerovaný parsovací kód se na něm zasekne.
U Djanga sázím na validaci schématu na hranici, třeba pydantic modely obalující každou externí odpověď, a sadu testů, která těm modelům cpe vyloženě ošklivé payloady, ne ty učebnicové. Prázdný string tam, kde čekáte číslo. Seznam s jedním prvkem, když kód počítal s mnoha. Pointa je, že kontrakt vlastní člověk a je napsaný proti realitě, a AI kód ho musí splnit, ne splnit svůj vlastní optimistický odhad. Kontrakt napíšete jednou, žije v repu a každá budoucí vygenerovaná změna jím musí projít, ať už si model to omezení pamatuje, nebo ne.
property-based fuzzing pro případy, které nikdo nenapsal
Kontraktové testy chytají hranice. Property-based testing chytá všechno, u čeho se model rozhodl, že to nestojí za test case. Tady podle mě většina týmů nechává ležet ten největší výhru, protože přidat to nestojí skoro nic a útočí to přímo na slepé místo s edge casy.
Místo assertu f(2) == 4 zformulujete property, která má platit pro všechny validní vstupy, a necháte framework vygenerovat tisíce vstupů, které ji zkoušejí rozbít. V Pythonu je Hypothesis vyzrálý a vážně příjemný, a zabijácká featura je shrinking: poté, co najde selhání, ořeže vstup na minimální reprodukující případ a předá vám něco jako prázdný bytestring nebo integer, který přeteče. Pro Go je vestavěný fuzzing přímo v standardním testing balíčku, který je nedoceněný a dost dobrý na to, abyste neměli výmluvu ho nehodit na jakoukoli funkci, která parsuje, transformuje nebo validuje vstup.
Trik u AI kódu je napsat property ručně, vyjádřit invariant, na kterém vám reálně záleží, ten druh, o kterém člověk ví, že je důležitý. Serializace a deserializace má udělat round-trip. Seřazený seznam zůstane seřazený. Součet nikdy neklesne pod nulu. Pak na vygenerovanou implementaci nasměrujete fuzzer. Model napsal kód, který zvládá vstupy, které si představil. Fuzzer vygeneruje ty, které si nepředstavil. Dělali jsme to na document processing pipeline pro klienta, kde AI napsaný parser prošel každým příkladovým testem a pak Hypothesis asi za čtyři sekundy našel vícebajtovou sekvenci znaků, která posunula byte offset za konec bufferu. Tahle chyba v normálním CI setupu potichu shipuje do produkce. S jedním property testem ale nikdy neopustí laptop.
shadow deployment proti behaviorálnímu driftu
Statická analýza a testy chytí spoustu věcí, ale behaviorální drift v čase je runtime problém a potřebuje runtime odpověď. Nejčistší, kterou znám, je shadow deployment, kdy běží nová verze vedle staré a obě dostávají stejný živý provoz, ale uživateli se reálně vrací jen odpověď té staré. Výstup nové verze se loguje a porovnává, nikdy se neservíruje.
U Go služby je tohle vážně levné zapojit. Dejte kandidátskou verzi za goroutinu, která dostane kopii requestu, spusťte ji, udělejte diff odpovědi proti produkční verzi a zaznamenejte každou divergenci s připojeným plným payloadem requestu. V CI nic netvrdíte. Sledujete, co AI kód reálně dělá proti chaotické distribuci skutečných requestů, což je jediný test, který kdy řekl pravdu o pravděpodobnostním výstupu. U Djanga uděláte ekvivalent middlewarem, který zrcadlí určité procento requestů do shadow instance, asynchronně, aby to nepřidávalo latenci reálné cestě.
Lovíte ten pomalý drift. Patch z tohoto sprintu, který vrací mírně jiné zaokrouhlení u currency pole než patch z minulého měsíce, protože dvě různá generovací sezení udělala dvě různá, rozumná, ale nekompatibilní rozhodnutí. Žádný unit test to nechytí, protože každý patch je sám o sobě správný. Diff proti živému provozu to chytí hned první den, ještě než si zákazník všimne, že má fakturu o cent jinak, a vaše finance tým stráví týden srovnáváním. Nemusíte kvůli tomu přestavovat pipeline. Na deploy, který už máte, přišroubujete porovnávací vrstvu a necháte produkční provoz dělat validaci, kterou vaše testy strukturálně nezvládnou.
co reálně udělat v pondělí
Nepotřebujete nový platformní tým ani šestiměsíční migraci, abyste tohle vyřešili. Validační vrstva, kterou popisuju, sedí na jakékoli Go nebo Django službě, kterou už provozujete, a první kus zvládnete za odpoledne. Vyberte si svou jedinou nejdražší integrační hranici, tu, kde výpadek stojí skutečné peníze, a napište kontraktový test proti opravdu ošklivé verzi upstream odpovědi. Jen tohle odchytí kus těch 43 %.
Pak vyberte funkci, která dělá nejvíc parsování vstupu nebo počítání s penězi, a obalte ji property testem, Hypothesis pro Django, standardní fuzzer pro Go, s jedním invariantem, o kterém fakt věříte, že má platit vždycky. Spusťte ho na třicet vteřin a koukněte, co vypadne. Z mojí zkušenosti většinou něco vypadne, a obvykle něco, o čem AI vygenerované testy přísahaly, že je v pořádku.
Shadow deployment je větší práce, ale je to ten kus, který se vyplácí napořád, protože jako jediný odhalí drift, který nedokážete dopředu předvídat. Postavte ho nejdřív na svůj nejvytíženější endpoint a nechte ho pár týdnů tiše běžet, než mu začnete věřit. Společná nit všech tří věcí je stejná myšlenka: omezení vlastní člověk a AI si průchod přes ně musí zasloužit, místo aby si známkovala vlastní domácí úkol. Tuhle validační vrstvu stavíme klientům do systémů ve steezr právě proto, že týmy, které shipují AI kód nejrychleji, dostávají na frak nejvíc, a řešením není zpomalit, ale donutit pipeline, aby vám přestala lhát o tom, co prošlo.
