Copilot pisze kod szybciej ode mnie. Claude pisze kod szybciej ode mnie. I powiem szczerze: już zawsze będą szybsze. A mimo to w każdym projekcie jest kod, który wciąż piszę ręcznie. Litera po literze.
Nie, nie jestem dinozaurem, który tęskni za programowaniem bez internetu. Używam AI codziennie i oszczędza mi ono godziny pracy. Ale przez ostatnie lata wypracowałem sobie prostą listę: 4 rodzaje kodu, których nie oddaję AI. Nie z sentymentu, tylko dlatego, że w tych miejscach szybkość generowania przestaje mieć znaczenie, a zaczyna się liczyć coś zupełnie innego: zrozumienie i odpowiedzialność.
W tym artykule pokażę Ci, co to za kod i dlaczego akurat ten. Zaczniemy uczciwie - od miejsc, w których AI jest ode mnie po prostu lepsze. Potem zobaczysz błąd, który AI popełniło w kodzie liczącym pieniądze. Błąd, który przeszedłby code review w większości zespołów. A na koniec dostaniesz prosty test - 2 pytania, które zadaję sobie, zanim w ogóle napiszę prompt - matrycę decyzyjną "AI czy klawiatura?" i 3 zasady, które możesz wdrożyć od jutra.
Uczciwie: gdzie AI mnie miażdży
Zanim powiem, czego nie oddaję AI, najpierw uczciwie. Bez tego cały ten tekst byłby marudzeniem starego seniora.
Jest kod, przy którym AI jest ode mnie lepsze. Nie "trochę szybsze" - lepsze. I korzystam z tego bez żadnych wyrzutów sumienia. Oto 4 przykłady.
Boilerplate. DTO i mapowania, konfiguracja, rejestracje w kontenerze DI, migracje EF, setny CRUD w karierze. Ten kod wygląda prawie identycznie w każdym projekcie, a AI widziało go miliony razy. Ja piszę go w godzinę, AI w minutę - i wynik jest ten sam. Oddając tę robotę modelowi, nie tracę żadnej wiedzy, bo w tym kodzie po prostu jej nie ma.
Testy jednostkowe. Szkielet testu, układ Arrange-Act-Assert, dane testowe - to AI robi w sekundy. A co najlepsze: podrzuca przypadki brzegowe, o których sam bym nie pomyślał. Pusta lista, null, wartość ujemna, duplikaty. Traktuję AI jak generator scenariuszy, a sam decyduję, które z nich zostają.
Kod jednorazowy. Skrypt, który przemieli plik CSV i pójdzie do kosza. Prototyp na jedno demo. No i regexy - nikt przy zdrowych zmysłach nie powinien pisać regexów ręcznie :) Ten kod żyje godziny, nie lata. Szkoda na niego mojego czasu.
Tłumacz do cudzego kodu. Mój ulubiony przykład. Wklejasz 800 linii legacy i pytasz: "co to robi?". Dostajesz 5 zdań po ludzku. Onboarding w nowym projekcie, który kiedyś zajmował tygodnie, teraz zajmuje dni. Rewelacja.
Na takie zadania (testy, refaktoryzacja, praca z legacy) mam zresztą gotowe, sprawdzone prompty - wrócę do nich na końcu artykułu.
No dobra. Skoro jest tak dobrze, to po co w ogóle pisać cokolwiek ręcznie? Zamiast teoretyzować, pokażę Ci to na kodzie.
Eksperyment: znikający grosz
Poprosiłem AI o banalną rzecz:
"Napisz w C# funkcję, która dzieli kwotę faktury na równe raty."
Prościzna, prawda? Junior napisałby to w 3 minuty. Po 4 sekundach generowania dostałem coś takiego:
public static decimal[] SplitInvoice(decimal total, int count)
{
var installment = Math.Round(total / count, 2);
return Enumerable.Repeat(installment, count).ToArray();
}Dzielimy kwotę przez liczbę rat, zaokrąglamy do 2 miejsc po przecinku i zwracamy tablicę. Zanim przeczytasz dalej, zatrzymaj się na chwilę i przyjrzyj się temu kodowi. Widzisz problem?
Wygląda świetnie, prawda? Na pierwszy rzut oka wszystko gra. Kompiluje się bez ostrzeżeń. Czysty, idiomatyczny C#. Szczerze? Sam pewnie przepuściłbym to na code review, gdybym się spieszył. No to policzmy.
100 zł, 3 raty. 100 / 3 = 33,333..., czyli po zaokrągleniu 33,33 zł. Suma 3 rat:
33,33 + 33,33 + 33,33 = 99,99 zł
Gdzie jest grosz? Zniknął. Klient zapłaci wszystkie raty, a faktura nigdy nie zostanie rozliczona do zera.
"To tylko grosz". Jasne. Ale wyobraź sobie średnią firmę, która wystawia 50 000 faktur miesięcznie. Suma rat nie zgadza się z kwotą faktury na każdej fakturze, której kwota nie dzieli się równo na raty. Księgowość się nie spina, ktoś ręcznie szuka groszy, robią się korekty, reklamacje, audyt. Koszt tego błędu liczy się w dniach pracy ludzi, nie w groszach.
Poprawka: 2 linijki. Ostatnia rata nie jest liczona z dzielenia, tylko domyka różnicę do pełnej kwoty:
public static decimal[] SplitInvoice(decimal total, int count)
{
var installment = Math.Round(total / count, 2);
var result = Enumerable.Repeat(installment, count).ToArray();
result[^1] = total - installment * (count - 1); /* ostatnia rata domyka różnicę */
return result;
}Dla 100 zł i 3 rat dostajesz teraz 33,33 + 33,33 + 33,34 = 100,00 zł. Innym poprawnym podejściem jest rozłożenie brakujących groszy na pierwsze raty (33,34 + 33,33 + 33,33). Który wariant wybrać, to już decyzja biznesowa, a nie techniczna - i to dobra wskazówka, że mamy do czynienia z kodem, którego nie warto generować bezrefleksyjnie.
2 linijki. Ale musisz wiedzieć, że ich brakuje. A żeby to wiedzieć, musiałeś kiedyś ten problem przeżyć albo ten kod naprawdę przeczytać, a nie tylko przescrollować.
Test, który łapie ten błąd. Kiedy już wiesz, czego szukać, łatwo zabezpieczyć się testem. Zwróć uwagę, że ten test nie sprawdza wartości poszczególnych rat, tylko niezmiennik, który zawsze musi być prawdziwy: suma rat równa się kwocie faktury.
[Fact]
public void SplitInvoice_SumOfInstallments_EqualsTotal()
{
var installments = SplitInvoice(100m, 3);
Assert.Equal(100m, installments.Sum());
}Na oryginalnym kodzie od AI ten test od razu się wywali (99,99 zamiast 100). Tyle że dobry test pilnuje tego, co w domenie najważniejsze. A żeby wiedzieć, co jest najważniejsze, trzeba tę domenę rozumieć.
Bonus dla dociekliwych: Math.Round. W tym samym kodzie siedzi jeszcze jedna pułapka. Math.Round w .NET domyślnie zaokrągla "bankowo" (MidpointRounding.ToEven), czyli wartości leżące dokładnie w połowie zaokrągla do najbliższej parzystej. 2,5 robi się 2, a nie 3. A 0,125 przy 2 miejscach po przecinku robi się 0,12, a nie 0,13. Jeśli w Twojej domenie obowiązuje zaokrąglanie "szkolne", podaj tryb jawnie:
var installment = Math.Round(total / count, 2, MidpointRounding.AwayFromZero);AI nie myli się często. AI myli się pewnie. To jest sedno całego artykułu. Nie dostaniesz ostrzeżenia, nie dostaniesz "nie jestem pewien". Dostaniesz elegancki, przekonujący kod. A kod, który wygląda dobrze, jest groźniejszy niż kod, który wygląda źle - bo tego drugiego przynajmniej się boisz.
Dlatego mam swoją listę: 4 rodzaje kodu, które piszę ręcznie, nawet jeśli AI zrobiłoby to 10 razy szybciej. Ta lista powstała po kilkunastu latach w projektach. I po kilku bolesnych wtopach.
Kod #1: serce biznesu
Co to jest. Kod, który JEST Twoim produktem. Cenniki, rabaty, prowizje i rozliczenia, reguły domenowe - wszystko, za co klient płaci. To zwykle jakieś 5-10% całego kodu w projekcie. Ale to właśnie te procenty zarabiają pieniądze.
Dlaczego ręcznie? Bo to tu mieszkają wszystkie przypadki brzegowe: promocje, które się łączą, wyjątki, "klient specjalny". Rabat dla stałego klienta i promocja na kategorię - sumują się czy mnożą? A co, jeśli klient ma indywidualny cennik? Odpowiedzi na takie pytania nie ma w żadnym publicznym repozytorium, bo są tylko w Twojej firmie.
Jak sam napiszesz taki kod, masz go w głowie - debugujesz w minuty, nie w godziny. Funkcja dzieląca fakturę na raty to zresztą podręcznikowy przykład: jej błąd nie leżał w składni, tylko w zrozumieniu, jak działają pieniądze.
I nie oszukujmy się: płacą Ci właśnie za rozumienie tej logiki. To Twoja przewaga na rynku, a nie przykry obowiązek, który warto jak najszybciej komuś oddać.
Kod #2: pieniądze i bezpieczeństwo
Co to jest. Logowanie, sesje i tokeny, uprawnienia (czyli kto co widzi), płatności, dane osobowe. Kod, w którym pojedynczy błąd kosztuje najwięcej.
Dlaczego ręcznie? Prosta matematyka: koszt błędu jest tu wielokrotnie większy niż te 2 godziny, które zaoszczędzi Ci AI. Pamiętaj też, na czym AI się uczyło - na publicznym internecie. A publiczny internet to nie jest wzorzec bezpiecznego kodu. Pełno w nim przykładów pisanych tak, "żeby działało", a nie tak, żeby było bezpiecznie.
W uprawnieniach wystarczy jeden brakujący warunek i masz wyciek danych. Zobacz:
[HttpGet("faktury/{id}")]
public async Task<IActionResult> GetInvoice(int id)
{
var invoice = await _db.Invoices.FindAsync(id);
if (invoice is null)
return NotFound();
/* brakuje sprawdzenia, czy faktura należy do zalogowanego klienta */
return Ok(invoice);
}Kod się kompiluje, endpoint działa, testy "happy path" przechodzą. A każdy zalogowany użytkownik może podejrzeć cudzą fakturę, zmieniając id w adresie. Tu nie ma "prawie dobrze". Kod bezpieczeństwa, który działa w 99% przypadków... nie działa.
Kod #3: kod, którego jeszcze nie rozumiem
Co to jest. Ten punkt jest ważny szczególnie wtedy, gdy dopiero wchodzisz do branży. Pierwsza styczność z nową biblioteką albo technologią, wzorzec, który znasz tylko z teorii, fragment systemu, w którym nigdy nie grzebałeś.
Dlaczego ręcznie? Bo jest jedna brutalna prawda: nie zrobisz dobrego review kodu, którego sam nie umiałbyś napisać. Skąd masz wiedzieć, że AI się myli, skoro nie wiesz, jak wygląda "dobrze"? Kod od AI w nieznanej technologii zawsze wygląda wiarygodnie - bo nie masz go z czym porównać.
Złota zasada. Dlatego moja zasada brzmi: pierwszy raz ręcznie, każdy kolejny z AI. Pierwszy raz konfigurujesz nową bibliotekę? Napisz to sam, z dokumentacją otwartą obok. Za drugim razem możesz już spokojnie wygenerować podobny kod - i od razu zobaczysz, gdzie AI poszło na skróty. To nie jest strata czasu, tylko inwestycja w każde przyszłe review tego typu kodu.
Kod #4: granice i kontrakty
Co to jest. Publiczne interfejsy, kontrakty API (czyli to, co widzą inne zespoły), granice między modułami. Kodu jest tu mało, ale zostaje na lata.
Dlaczego ręcznie? Bo pisanie to myślenie. Kiedy sam nazywasz metodę i sam decydujesz, co jest parametrem, a co konfiguracją, Twój mózg wykonuje robotę projektową. Dobre nazwy nie przychodzą z autouzupełniania. Przy jednej sygnaturze metody odpowiadasz na całą serię pytań:
• czy metoda przyjmuje int, czy własny typ identyfikatora,
• czy przy braku danych zwraca null, rzuca wyjątek, czy zwraca wynik z informacją o błędzie,
• czy jest asynchroniczna i przyjmuje CancellationToken,
• co jest częścią kontraktu, a co szczegółem implementacji, którego nikt z zewnątrz nie powinien widzieć.
Interfejs to nie składnia, tylko decyzja. Zmienisz go - i wszyscy, którzy go używają, muszą zmienić się razem z Tobą. AI podpowie Ci składnię. Ale decyzję i tak musisz podjąć Ty.
Ukryty koszt: dług kompetencyjny
Jest jeszcze jeden powód, dla którego to wszystko ma znaczenie. Powód, o którym mało kto mówi, bo nie widać go w żadnym sprincie.
Dług kompetencyjny. Tak to nazywam. Znasz dług techniczny? Dług kompetencyjny działa tak samo, tylko rośnie w Twojej głowie, a nie w repozytorium. Każdy fragment kodu, który zaakceptowałeś bez zrozumienia, to mała pożyczka. Nic nie kosztuje... dziś. Spłacasz ją przy pierwszej awarii. Z odsetkami.
Test 3:00 w nocy. Mam na to swój test. Produkcja leży, klient dzwoni, Ty patrzysz w logi.
• Kod pisany ręcznie masz w głowie jak mapę. Wiesz, gdzie szukać, zanim otworzysz pierwszy plik.
• Kod tylko zaakceptowany to obcy teren. Debugujesz po omacku cudzy pomysł.
Który wolisz debugować o 3 w nocy? No właśnie.
Nie chodzi przy tym o to, żeby każdą linijkę pisać samemu. Chodzi o to, żeby rozumieć każdą linijkę, za którą odpowiadasz. Część kodu możesz spokojnie wygenerować i przeczytać. Ale tam, gdzie samo przeczytanie nie wystarczy, żeby go naprawdę zrozumieć, pisanie ręczne jest najtańszym sposobem, żeby tego długu nie zaciągać.
Jak decydować: 2 pytania przed promptem
Dobra, ale jak to stosować w praktyce? Nie będziesz przecież przy każdej linijce robić narady. Ja zadaję sobie 2 pytania - zajmuje to dosłownie 3 sekundy.
1. Jak długo ten kod będzie żył? Godziny, miesiące, lata? Kod jednorazowy rządzi się innymi prawami niż kod, z którym firma będzie żyła dekadę.
2. Czy umiem napisać go sam? Tu odpowiedz szczerze, nie życzeniowo - bo od tego zależy, czy w ogóle jesteś w stanie ocenić, co dostałeś.
Matryca: AI czy klawiatura? Z tych 2 pytań wychodzi prosta matryca 2x2:
| Długość życia kodu | Umiem napisać go sam | Nie umiem napisać go sam |
| Kod żyje krótko | AI - akceptuj śmiało | AI - od tego jest, nie ucz się na siłę jednorazowych skryptów |
| Kod żyje długo | AI, ale review linijka po linijce - czytasz jak swój kod | RĘCZNIE - albo najpierw się uczysz |
W ostatnim polu lądują właśnie 4 rodzaje kodu, o których pisałem wyżej. Przy nich "umiem" musi znaczyć naprawdę umiem, a nie "mniej więcej wiem, co to robi".
Kilka przykładów, jak to działa w praktyce:
• Skrypt, który raz przemieli CSV-kę: żyje krótko, rozumiesz go - AI, bez wyrzutów sumienia.
• Regex do jednorazowej migracji danych: żyje krótko, a regexów nikt nie umie na pamięć - też AI.
• Kolejny CRUD w projekcie, który będzie żył latami: żyje długo, ale robiłeś to setki razy - AI, plus uważne review.
• Dzielenie faktury na raty: żyje długo i dotyka pieniędzy - piszesz sam albo czytasz każdą linijkę tak, jakbyś ją sam napisał. Wtedy znikający grosz nie ma szans.
3 zasady do wdrożenia od jutra
Na koniec 3 zasady, które możesz wdrożyć dosłownie od jutra:
1. Pierwszy raz ręcznie, każdy kolejny z AI. Nowa biblioteka, nowy wzorzec, nowy fragment systemu - najpierw piszesz sam. Potem możesz delegować, bo wiesz, jak wygląda "dobrze".
2. Przy krytycznym kodzie odwróć role. Ty piszesz, AI robi review. AI świetnie wyłapuje braki i literówki, a decyzje zostają u Ciebie. Wystarczy prosty prompt, np.: "Zrób code review tej metody. Szukaj przypadków brzegowych, błędów zaokrągleń i brakujących walidacji. Nie przepisuj kodu - wypunktuj problemy."
3. Nie akceptuj kodu, którego nie obronisz. Jeśli nie umiesz wytłumaczyć go koledze na code review, to nie jest jeszcze Twój kod.
Podsumowanie
AI pisze szybciej ode mnie i już tak zostanie. Nie zamierzam się z nim ścigać. Ale nikt nie płaci Ci za szybkość wciskania Tab. Płacą Ci za rozumienie systemu - i za to, że bierzesz za niego odpowiedzialność.
Zbierzmy wszystko w jednym miejscu:
• AI wygrywa przy boilerplate, testach, kodzie jednorazowym i tłumaczeniu cudzego kodu - korzystaj z tego bez wyrzutów sumienia.
• AI nie myli się często, ale myli się pewnie. Kod, który wygląda dobrze, jest groźniejszy niż kod, który wygląda źle - jak funkcja, która gubiła grosz na każdej racie, a wyglądała wzorowo.
• Ręcznie piszę 4 rodzaje kodu: serce biznesu, pieniądze i bezpieczeństwo, kod, którego jeszcze nie rozumiem, oraz granice i kontrakty.
• Dług kompetencyjny nic nie kosztuje dziś, ale spłacasz go przy pierwszej awarii - z odsetkami.
• 2 pytania przed promptem: jak długo ten kod będzie żył i czy umiesz napisać go sam.
Nie namawiam Cię do rezygnacji z AI - to najlepsze narzędzie, jakie trafiło w ręce programistów od czasów IDE. Chodzi tylko o to, żebyś wiedział, kiedy jesteś pilotem, a kiedy pasażerem.
A jeśli chcesz mądrze oddawać AI tę robotę, którą oddawać warto, przygotowałem coś dla Ciebie. Darmowy PDF: 25 promptów AI dla programistów C#/.NET. Gotowe, sprawdzone prompty do Claude Code i Copilota - refaktoryzacja, testy, debugowanie, code review i praca z legacy. Czyli dokładnie te obszary, w których AI miażdży. Kopiujesz, wklejasz i widzisz różnicę w 5 minut.
Odbierzesz go tutaj: modestprogrammer.pl/25-promptow. Zostawiasz maila i PDF przychodzi od razu. Przy okazji dołączasz do społeczności ponad 10 000 programistów .NET: raz w tygodniu jeden konkretny mail o .NET i AI, plus rabaty na moje kursy - zawsze jako pierwszy i zawsze taniej. Zero spamu.
Pierwszy raz ręcznie, każdy kolejny z AI. Powodzenia!