Przez 4 tygodnie budowałem aplikację. Prawdziwą - taką, z której korzystają żywi użytkownicy. Nie demo do pokazania w internecie, nie projekt do szuflady. I muszę się do czegoś przyznać: nie napisałem w niej ręcznie ani jednej linijki kodu. Zero. 100% kodu napisał agent AI.
Jaki wniosek wyciągnąłem z tego eksperymentu? Na pewno nie taki, że programiści są skończeni. Dokładnie odwrotny. Jestem dziś spokojniejszy o naszą pracę niż kiedykolwiek - ale pod jednym warunkiem. I właśnie o tym warunku jest ten artykuł.
Zobaczysz, co się naprawdę dzieje, kiedy AI pisze cały kod: co działa zaskakująco dobrze, w którym momencie wszystko zaczyna się sypać (razem z konkretnymi grzechami, które znalazłem w kodzie) i jaka umiejętność właśnie stała się najcenniejsza na rynku. Na koniec dostaniesz mój workflow pracy z agentem w 5 zasadach oraz checklistę 6 pytań do review kodu od AI, którą możesz stosować od jutra.
Jedno zastrzeżenie na start: to nie będzie tekst o tym, że AI to magia. Ani o tym, że AI to ściema. To relacja z pola bitwy - co się dzieje, kiedy oddasz agentowi klawiaturę i przez kilka tygodni ani razu jej nie dotkniesz.
Dwa obozy w dyskusji o AI. Oba w błędzie
W sieci widać dziś 2 obozy. Pierwszy krzyczy: programiści są skończeni. Ktoś chwali się, że wyklikał 20 aplikacji w miesiąc, i już szykuje się na emeryturę. Drugi obóz macha ręką: AI to zabawka, generuje bzdury, prawdziwy programista tego nie potrzebuje.
Problem w tym, że oba obozy najczęściej mówią o czymś, czego porządnie nie przetestowały. Entuzjaści oceniają AI po pierwszych dniach zachwytu, sceptycy - po kilku nieudanych próbach. Mało kto przeszedł z agentem całą drogę: od pustego repozytorium do aplikacji, z której codziennie korzystają ludzie.
Postanowiłem więc sprawdzić to na własnej skórze. Na prawdziwym projekcie. Z prawdziwymi użytkownikami. I z jedną twardą zasadą: ja nie piszę kodu - kod pisze wyłącznie agent.
Agent AI to nie czat: jak to właściwie działa
Najpierw ustalmy, o czym w ogóle mówimy. Agent AI to nie jest czat, do którego wklejasz kawałki kodu, a potem kopiujesz odpowiedź z powrotem do IDE. Agent pracuje bezpośrednio na Twoim repozytorium:
1. Opisujesz zadanie normalnym językiem.
2. Agent sam czyta Twój kod.
3. Sam edytuje pliki.
4. Sam kompiluje projekt i odpala testy.
5. Czyta błędy - i sam je poprawia.
Ty w tym czasie możesz robić coś innego. Ja pracowałem głównie w Claude Code, ale zasada działania innych narzędzi tego typu jest taka sama.
Jeśli do tego nie czytasz kodu, tylko przyjmujesz wszystko, co agent wygeneruje, dopóki aplikacja "działa" - to właśnie jest vibe coding. I dokładnie tak wyglądał początek mojego eksperymentu.
Zapamiętaj jedną rzecz: pętla "napisz - skompiluj - przetestuj - popraw" sprawia, że agent bardzo skutecznie doprowadza kod do stanu "kompiluje się, testy są zielone". Tylko że to jeszcze nie znaczy "dobrze zaprojektowany". Za chwilę zobaczysz, jak duża potrafi być ta różnica.
Faza 1: magia. Serio
Nie będę ściemniał - pierwsza faza to był miesiąc miodowy. Prototyp, który normalnie zająłby mi kilka tygodni, stanął w kilka dni. Endpointy, walidacja, migracje, cały ten boilerplate, który pisałeś już 100 razy - wjeżdżał sam. Mówisz, co ma powstać, idziesz zrobić kawę, wracasz - działa.
Naprawdę rozumiem ludzi, którzy po tygodniu takiej pracy ogłaszają koniec programowania. Sam przez moment miałem takie myśli. Kiedy kolejne funkcje powstają szybciej, niż nadążasz je wymyślać, łatwo uwierzyć, że tak już zostanie.
Nie zostało.
Faza 2: bugi, których nie da się odtworzyć
A potem przyszła faza druga. Zaczęły się bugi. Ale nie takie zwykłe, z czytelnym stack trace'em i oczywistą przyczyną. Dziwne. Losowe. Raz są, raz ich nie ma. U mnie nie do odtworzenia - za to bezbłędnie pojawiały się w najgorszym możliwym momencie, czyli wtedy, kiedy pokazywałem aplikację komuś innemu :)
Każdy programista zna ten scenariusz. Przy vibe codingu dochodzi jednak dodatkowy problem: szukasz błędu w kodzie, którego nie napisałeś i którego - skoro nie czytałeś go na bieżąco - po prostu nie znasz.
Otworzyłem kod napisany przez AI. No i...
W końcu zrobiłem to, czego przy vibe codingu podobno robić nie trzeba. Usiadłem i przeczytałem kod. Cały. Oto, co znalazłem:
• jeden serwis na 900 linii, który robił dosłownie wszystko,
• logikę biznesową powciskaną w kontrolery,
• sync-over-async, czyli blokowanie wątków na Taskach - klasyka gatunku,
• CancellationToken? Ani jednego,
• stan aplikacji zmieniany z 10 różnych miejsc, każde po swojemu.
I żeby było jasne - to nie był jakiś poboczny moduł. To była najważniejsza funkcja całej aplikacji.
Trzy ostatnie problemy warto omówić dokładniej, bo w .NET są wyjątkowo podstępne: kod się kompiluje, testy przechodzą, a kłopoty wychodzą dopiero pod obciążeniem.
Sync-over-async. Chodzi o taki kod:
/* Sync-over-async: wątek stoi i czeka na zakończenie Taska */
public IActionResult GetOrder(int id)
{
var order = _orderService.GetOrderAsync(id).Result;
return Ok(order);
}Wywołanie .Result (podobnie jak .Wait() czy GetAwaiter().GetResult()) blokuje wątek, dopóki operacja asynchroniczna się nie zakończy. W ASP.NET Core zwykle nie kończy się to klasycznym deadlockiem, ale pod obciążeniem zjada wątki z puli i potrafi doprowadzić do zagłodzenia puli wątków (thread pool starvation): aplikacja nagle zwalnia albo przestaje odpowiadać, choć procesor się nudzi. A w aplikacjach z SynchronizationContext (WPF, WinForms, klasyczny ASP.NET) taki kod potrafi zakleszczyć się na amen. Idealny przepis na bug, który "raz jest, raz go nie ma".
Brak CancellationToken. Drugi grzech chodzi w parze z pierwszym. Kiedy użytkownik zamknie kartę albo żądanie przekroczy timeout, aplikacja i tak dalej mieli zapytanie do bazy czy wywołanie zewnętrznego API - do samego końca i dla nikogo. Poprawna wersja to async od góry do dołu i token przekazywany w dół:
/* Async od góry do dołu + CancellationToken przekazany dalej */
public async Task<IActionResult> GetOrder(int id, CancellationToken cancellationToken)
{
var order = await _orderService.GetOrderAsync(id, cancellationToken);
return Ok(order);
}W ASP.NET Core parametr CancellationToken w akcji kontrolera jest automatycznie powiązany z przerwaniem żądania (HttpContext.RequestAborted). Wystarczy go przyjąć i konsekwentnie przekazywać dalej - aż do EF Core czy HttpClienta.
Stan zmieniany z 10 miejsc. Ten problem jest najmniej widoczny, a chyba najgroźniejszy. Jeśli te same dane modyfikuje 10 różnych kawałków kodu, każdy według własnych reguł, nikt - ani Ty, ani agent - nie jest w stanie przewidzieć, w jakim stanie aplikacja będzie po danej operacji. Dorzuć do tego równoległe żądania i masz kolejne źródło losowych bugów.
Dlaczego AI pisze taki kod
I tu jest sedno, o którym hype milczy. AI nie projektuje. AI uzupełnia wzorce, które widziało w milionach cudzych repozytoriów. A Twój projekt to nie jest średnia z internetu.
Widzę tu 3 mechanizmy, przez które kod od agenta z czasem się psuje:
• Wzorce zamiast projektu. Agent generuje kod, który pasuje do tego, co już widział, a nie do Twojego systemu. Dlatego dostajesz rozwiązania "typowe", a nie przemyślane pod Twoje wymagania.
• Łatanie zamiast refaktoryzacji. Kiedy zgłaszasz buga, agent nie robi refaktoryzacji - dokleja kolejnego ifa, kolejną flagę, kolejny quick fix. Warstwa na warstwie. Każda poprawka z osobna wygląda rozsądnie, ale razem tworzą potwora. Tak właśnie rosną serwisy na 900 linii.
• AI nie wie, czego nie wie. Nie zna Twojej domeny, nie zna wymagań, których nie zapisałeś, i samo z siebie nie powie: "słuchaj, ta architektura zaraz się zawali".
Ktoś musi podejmować decyzje projektowe. I tym kimś jesteś Ty.
3 dni prawdziwej inżynierii
Zatrzymałem więc dowożenie ficzerów i przez 3 dni robiłem starą, dobrą inżynierię:
• rozbiłem potwora na małe klasy, każdą z jedną odpowiedzialnością,
• wyznaczyłem jasne granice między modułami,
• uporządkowałem warstwy: API osobno, logika osobno, dostęp do danych osobno.
I teraz uwaga - kod tej refaktoryzacji też pisało AI. Zasada eksperymentu nadal obowiązywała: ani jednej linijki napisanej ręcznie. Ale każdą decyzję - co wydzielić, gdzie postawić granicę, co jest nie tak i dlaczego - podejmowałem ja. AI klepało. Ja projektowałem.
To chyba najważniejsza obserwacja z całego eksperymentu. Agent świetnie wykonuje dobrze opisane polecenia w stylu "przenieś logikę biznesową z tego kontrolera do osobnego serwisu". Sam z siebie nie zada natomiast pytania, czy struktura całego projektu w ogóle ma sens. To pytanie zawsze zostaje po Twojej stronie.
Pułapka: testy od AI, które nic nie sprawdzają
Po refaktoryzacji poprosiłem agenta o testy do całości. I tu bardzo ważne ostrzeżenie, bo w tę pułapkę wpada mnóstwo osób: AI z radością napisze Ci testy, które przechodzą... i nie sprawdzają kompletnie niczego. Typowe objawy:
• asercja, że wynik nie jest nullem - i koniec testu,
• mock, który testuje sam siebie,
• sam happy path, zero przypadków brzegowych.
W kodzie wygląda to mniej więcej tak:
/* Zielony, ale sprawdza tylko, że "coś" wróciło */
[Fact]
public async Task CreateOrder_ReturnsResult()
{
var result = await _orderService.CreateOrderAsync(_validRequest);
Assert.NotNull(result);
}
/* Zielony, ale testuje mocka, a nie nasz kod */
[Fact]
public async Task GetOrder_ReturnsOrder()
{
_repositoryMock.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(_order);
var result = await _repositoryMock.Object.GetByIdAsync(1);
Assert.Equal(_order, result);
}Oba testy są zielone. Pierwszy przejdzie niezależnie od tego, czy zamówienie zostało poprawnie wyliczone - a do tego potrafi podbić pokrycie kodu, bo wykonuje sporo linii, tylko niczego w nich nie sprawdza. Drugi w ogóle nie wywołuje naszego kodu: sprawdza wyłącznie, czy działa biblioteka do mocków. Pasek świeci na zielono, sumienie czyste, wartość - zerowa.
Test, który ma wartość, sprawdza konkretne zachowanie, także w nieprzyjemnych przypadkach:
/* Sprawdza konkretny wynik, a nie tylko "nie null" */
[Fact]
public async Task CreateOrder_CalculatesTotalFromItems()
{
var request = new CreateOrderRequest
{
CustomerId = 1,
Items = new List<OrderItemDto>
{
new(ProductId: 1, Quantity: 2, UnitPrice: 50m),
new(ProductId: 2, Quantity: 1, UnitPrice: 20m)
}
};
var result = await _orderService.CreateOrderAsync(request);
Assert.Equal(120m, result.Total);
}
/* Sprawdza przypadek brzegowy: pusty koszyk */
[Fact]
public async Task CreateOrder_WithEmptyCart_ThrowsValidationException()
{
var request = new CreateOrderRequest { CustomerId = 1, Items = new List<OrderItemDto>() };
await Assert.ThrowsAsync<ValidationException>(
() => _orderService.CreateOrderAsync(request));
}Dlatego mam prostą zasadę: test, którego nie przeczytałem, nie istnieje.
Jest też szybki sposób, żeby sprawdzić, czy test od AI cokolwiek pilnuje: celowo zepsuj testowany kod (np. odwróć warunek albo zmień wyliczaną wartość) i odpal testy. Jeśli dalej są zielone, to nic nie testują.
Zabawka vs aplikacja produkcyjna
Czy da się więc zbudować aplikację bez umiejętności programowania? Da się. Zabawkę. Zrobiłem kumplowi apkę do rozliczania wspólnych wyjazdów. Godzina roboty, kumpel zachwycony, super sprawa. A jeśli coś się w niej wysypie, najgorsze, co może się stać, to dyskusja o tym, kto komu wisi za pizzę.
Aplikacja produkcyjna, na której codziennie polegają ludzie, to jednak zupełnie inna liga. Tam potrzebujesz:
• architektury, która przetrwa kolejne zmiany,
• testów, które naprawdę coś sprawdzają,
• bezpieczeństwa,
• wydajności,
• i kogoś, kto wyczuje moment, w którym AI myli się z pełnym przekonaniem.
Ten ostatni punkt jest najtrudniejszy. AI nie sygnalizuje niepewności - błędny kod dostajesz w tym samym pewnym siebie tonie co poprawny. Żeby odróżnić jedno od drugiego, musisz wiedzieć, jak wygląda dobry kod.
Kto Ci mówi, że to wszystko przyjdzie samo z prompta - ten albo się myli, albo coś Ci sprzedaje. Zwykle jedno i drugie.
Mój workflow z agentem AI: 5 zasad
Jak w takim razie pracuję z agentem na co dzień? Według 5 zasad:
1. Krótkie prompty i małe kroki. Nie piszę elaboratów na pół strony - jedno konkretne zadanie naraz. Mały krok łatwiej sprawdzić, a pomyłkę agenta łatwiej wyłapać.
2. Częste commity. Jak agent pojedzie w krzaki, cofam się jednym poleceniem w Gicie i nie płaczę. Commit po każdym zaakceptowanym kroku to najtańsze ubezpieczenie w pracy z AI.
3. Review każdego diffa. Czytam wszystko, co agent wygenerował, zanim to przyjmę. To dokładnie ten krok, który pominąłem w fazie miesiąca miodowego - i za który zapłaciłem 3 dniami refaktoryzacji.
4. Refaktoryzacja na bieżąco, nie "kiedyś po sprincie". Bo "kiedyś" nie istnieje. Agent sam z siebie nie posprząta - raczej doklei kolejnego ifa - więc sprzątanie musi być częścią każdego kroku.
5. Testy: kryteria wymyślam ja, kod testu pisze AI, review robię ja. To ja decyduję, co ma zostać sprawdzone, łącznie z przypadkami brzegowymi. AI zajmuje się tylko nudnym pisaniem.
Checklista: 6 pytań do review kodu od AI
A teraz obiecana checklista. To 6 pytań, które zadaję przy każdym kawałku kodu od AI, zanim trafi na główną gałąź:
1. Czy rozumiem każdą linijkę? Jeśli nie - nie wchodzi na główną gałąź. Bez wyjątków. Kod, którego nie rozumiesz, to dług, który spłacisz w najgorszym możliwym momencie.
2. Gdzie żyje stan i kto go zmienia? Szukaj pól statycznych, mutowalnych singletonów i miejsc, w których kilka klas modyfikuje te same dane. W ASP.NET Core zwróć też uwagę na czas życia serwisów w DI - singleton, który trzyma mutowalny stan albo DbContext, to proszenie się o kłopoty przy równoległych żądaniach.
3. Czy testy sprawdzają zachowanie, czy tylko świecą na zielono? Assert.NotNull jako jedyna asercja, testy sprawdzające mocki, brak przypadków brzegowych - to sygnały ostrzegawcze.
4. Co się stanie, kiedy coś pójdzie nie tak? Wyjątek, timeout, brak sieci. Czy wyjątki nie są po cichu połykane w pustym catch? Czy jest CancellationToken? Czy wywołania zewnętrznych usług mają timeout?
5. Bezpieczeństwo. Walidacja wejścia, autoryzacja (nie tylko czy endpoint ma [Authorize], ale też czy użytkownik ma dostęp do tego konkretnego zasobu) i sekrety poza kodem - w User Secrets, zmiennych środowiskowych albo Key Vault, a nie w appsettings.json w repozytorium.
6. Czy nazwy mówią prawdę o tym, co ten kod naprawdę robi? Metoda GetUser, która przy okazji tworzy użytkownika, albo ValidateOrder, która zapisuje zamówienie do bazy, to klasyka kodu, który przeszedł przez kilka "szybkich poprawek".
Zapisz sobie tę listę albo wydrukuj i przyklej obok monitora. Po kilku tygodniach zadawanie tych pytań wchodzi w nawyk.
AI Cię nie zastąpi. AI Cię pomnoży
I teraz najważniejsza myśl całego artykułu: AI to nie jest zamiennik programisty. AI to mnożnik.
Jeśli mnożysz solidne fundamenty - architekturę, testy, nawyk czytania kodu - dostajesz przewagę, jakiej ta branża jeszcze nie widziała. Jeśli mnożysz chaos, dostajesz dokładnie ten sam chaos. Tylko szybciej i w większych ilościach.
Mój eksperyment pokazał obie strony tego równania. W fazie miesiąca miodowego, kiedy nie czytałem kodu, AI mnożyło chaos - aż do serwisu na 900 linii i losowych bugów. Po 3 dniach inżynierii, kiedy decyzje znowu podejmował człowiek, zaczęło mnożyć porządek.
Mnie AI nie zastąpiło. AI mnie przyspieszyło. Ale tylko dlatego, że miało co mnożyć.
Ściąga: vibe coding vs świadoma praca z agentem
Wszystkie lekcje z eksperymentu w jednej tabeli:
| Obszar | Vibe coding | Świadoma praca z agentem |
| Prompty | długie elaboraty, "zrób całą funkcję" | krótkie prompty, 1 konkretne zadanie naraz |
| Historia zmian | commit od czasu do czasu | częste commity, powrót jednym poleceniem |
| Kod od AI | przyjmowany, dopóki "działa" | review każdego diffa przed przyjęciem |
| Bugi | agent dokleja kolejnego ifa | refaktoryzacja na bieżąco |
| Architektura | wynika z kolejnych promptów | decyzje podejmujesz Ty, AI klepie |
| Testy | AI wymyśla i pisze, nikt nie czyta | kryteria Ty, kod AI, review Ty |
| Efekt | AI mnoży chaos | AI mnoży fundamenty |
Podsumowanie
Po 4 tygodniach, w których agent AI napisał 100% kodu mojej aplikacji, mój wniosek jest prosty: fundamenty inżynierii oprogramowania właśnie stały się cenniejsze, nie tańsze. Narzędzia piszą kod coraz szybciej - a ktoś musi wiedzieć, czy ten kod jest dobry.
W skrócie:
• Faza 1 to magia: prototyp w kilka dni zamiast kilku tygodni, boilerplate pisze się sam.
• Faza 2 to rachunek: losowe bugi, serwis na 900 linii, sync-over-async, zero CancellationToken i stan zmieniany z 10 miejsc.
• AI nie projektuje, tylko uzupełnia wzorce i łata bugi kolejnymi ifami. Decyzje architektoniczne są po Twojej stronie.
• Testy od AI trzeba czytać. Test, którego nie przeczytałeś, nie istnieje.
• Workflow: małe kroki, częste commity, review każdego diffa, refaktoryzacja na bieżąco i kryteria testów od Ciebie.
• Checklista 6 pytań przed każdym merge'em kodu od AI.
• AI to mnożnik: pomnoży Twoje fundamenty albo Twój chaos.
Jeśli chcesz od razu przełożyć ten workflow na praktykę, mam coś dla Ciebie. Przygotowałem darmowy pakiet: 25 gotowych promptów do pracy z AI dla programistów C#/.NET - do refaktoryzacji, testów, debugowania, code review i legacy. Czyli dokładnie do tych zadań, przy których agent bez dobrych instrukcji najczęściej idzie na skróty. Pobranie zajmuje minutę, a potrafi oszczędzić godziny.
Odbierzesz go tutaj: modestprogrammer.pl/25-promptow. Zostawiasz maila i pakiet przychodzi od razu. Przy okazji dołączasz do społeczności ponad 10 000 programistów: raz w tygodniu jeden konkretny mail o .NET i AI, bez lania wody. Zero spamu - wypisujesz się jednym kliknięciem.
Powodzenia w pracy z agentem - i czytaj kod, zanim wejdzie na produkcję.