Na przestrzeni lat pracując jako inżynier oprogramowania spotkałem się z niezliczoną ilością zagadkowych błędów i tajemniczych nieefektywności, które zdawały się przeczyć logice. Niedawno zetknąłem się z sytuacją tak uderzającą, że postanowiłem nazwać i zdefiniować zjawisko, które określiłem mianem Lemon Code Paradox (Paradoks Cytrynowego Kodu). Poniższy artykuł przybliża, czym jest Lemon Code (Cytrynowy Kod), jak do niego doszło oraz dlaczego paradoks, jaki on wywołuje, jest aż nadto znajomy doświadczonym programistom.
Czym jest Lemon Code 🍋
Lemon Code (Cytrynowy Kod) odnosi się do kodu wygenerowanego przez asystentów AI, który na pierwszy rzut oka działa poprawnie, lecz jest przesycony problemami związanymi z utrzymywalnością, czytelnością i spójną strukturą. To kod, który, mimo że funkcjonalnie działa, a czasami nawet imponuje szybkością dostarczenia, stanowi prawdziwy koszmar, gdy przychodzi do jego zrozumienia, debugowania i modyfikacji.
Geneza terminu „Lemon”
Nazwa czerpie inspirację z pojęcia „cytryny” używanego w kontekście produktów konsumenckich. W języku angielskim termin „lemon” oznacza wadliwy lub zawodny produkt, szczególnie w odniesieniu do samochodów używanych (np. kupno „lemon car” oznacza nabycie pojazdu, który z pozoru prezentuje się dobrze, lecz skrywa liczne ukryte wady).
W przypadku kodu generowanego przez AI problem często wynika z iteracyjnego charakteru jego tworzenia. Użytkownik angażuje się w wiele rund promptowania, stopniowo budując rozwiązanie, które kończy się niczym mozaika niekonsekwencji, długu technicznego i niespójności stylu, jednym słowem eklektyzm. Choć kod spełnia swoje bezpośrednie zadanie, często ukrywa swoją logikę za warstwami nieoptymalnych wzorców i nieoczekiwanych podejść.
Wprowadzenie do Lemon Code Paradox
Prawdziwy paradoks jest ironiczny: programiści, ze względu na swoje wysokie kwalifikacje, pozornie stanowią grupę, której najłatwiej przyjdzie zrozumienie i praca z kodem generowanym przez AI. Jednak w praktyce będą jedynymi ludźmi, którzy będą mieć z nim problemy. Nikt inny (zwykli ludzie) nawet nie podejmie się przeczytania czy modyfikacji takiego kodu.
W myśl tego paradoksu możemy przewidzieć, że dla zawodowych programistów wygenerowany kod może okazać się niespodziewanym ciężarem. Osoby niebędące programistami widzą tylko wynik i nie obchodzi ich, co jest pod spodem.
Dla tych z nas odpowiedzialnych za modyfikowanie i utrzymywanie kodu wyzwanie jest zbyt realne. Każdy obeznany programista doświadczający Paradoksu Cytrynowego Kodu staje przed dylematem. Z jednej strony kod robi to, co powinien. Z drugiej strony jego niezrozumiałość sprawia, że próba rozszerzenia lub refaktoryzacji jest trudna i czasochłonna. To rodzi pytania:
- Jeśli działa, dlaczego to naprawiać?
- Kod jest trudny do zrozumienia i utrzymania, ale czy powinienem zainwestować jeszcze więcej czasu w jego ręczne przepisywanie?
Moja przygoda z Cytrynowym Kodem 🍋
Niedawno musiałem dodać nową funkcję do mojej aplikacji - zadanie, które pierwotnie oszacowałem na 3-5 dni na podstawie moich wcześniejszych doświadczeń z podobnymi funkcjonalnościami. Funkcjonalność miała umiarkowaną złożoność i miała być spójna w implementacji z innymi podobnymi w projekcie. Zamiast podążać moim zwykłym sposobem pracy, postanowiłem użyć asystenta AI do wygenerowania autonomicznego kawałka kodu. Zadanie polegało na pobraniu danych z zewnętrznego API i odpowiednim ich przetworzeniu. Muszę podkreślić, że używam GenAI codziennie od ponad roku i uważam się za osobę dobrze obeznaną z AI 😉
Na początku cały proces wydawał się fascynujący. AI dostarczył działający kod w zaledwie kilka minut, nawet identyfikując niezbędne metody AWS API do osiągnięcia celu biznesowego. Szybko stworzyłem testy i zacząłem debugować skrypt z rzeczywistymi danymi. Wszystko szło gładko, dopóki nie pojawiły się pierwsze oznaki problemów: dziwne błędy pochodzące z asynchronicznej rekurencyjnej funkcji podczas przetwarzania dużych ilości danych. Te błędy nie były pokryte moimi testami i nagle uporządkowane rozwiązanie zaczęło przypominać labirynt.
Zanurzyłem się głęboko w kodzie, zabierając AI ze sobą w podróż, mając nadzieję, że może mi pomóc. Godziny zamieniły się w cały dzień, gdy próbowałem zrozumieć i zrefaktoryzować około 300 linii kodu Node.js. Kod był napisany w stylu tak różnym od mojego, że ręczne modyfikacje były ciężkie i podatne na błędy. Mimo moich intensywnych wysiłków i kilku próśb o wykonanie refaktoryzacji skierowanych do różnych asystentów AI (OpenAI, Q Developer, Perplexity z DeepSeek), żaden z nich nie zaproponował niczego sensownego.
To właśnie podczas tych frustrujących zmagań doznałem olśnienia, które pozwoliło mi zdefiniować Paradoks Cytrynowego Kodu. Stałem przed kodem, który prawie działał poprawnie, ale był tak nieprzejrzysty, że nie mogłem określić, dlaczego zachowywał się w ten sposób. Znalazłem się w dylemacie: czy powinienem wyrzucić cały kod i zacząć samodzielnie pisać go od nowa, czy powinienem kontynuować walkę z kodem takim, jaki jest? Postanowiłem go zatrzymać, stopniowo dodając więcej testów i refaktoryzując go do oddzielnych modułów i plików. Jednak tajemnicze błędy z asynchronicznej rekurencyjnej funkcji nadal występowały.
Na granicy utraty wiary w siebie, w Node.js i w cały świat, przypadkowo odkryłem prosty, ale druzgocący błąd - literówkę. Problematyczna rekurencyjna funkcja nie wywoływała samej siebie dla następnego rekursu, jak powinna; zamiast tego błędnie wywoływała inną podobnie nazwaną funkcję. To wyjaśnia, dlaczego działała poprawnie na małych zbiorach danych (pojedynczy page przy page’owaniu), ale zawodziła na dużych zbiorach danych zwracanych przez API (tysiące wywołań API, setki obiektów). Straciłem cały dzień na tę jedną literówkę.
Choć takie błędy pojawiają się w programowaniu od zarania dziejów tej branży, to wewnętrzna złożoność kodu tylko potęgowała problem. Trudno było obwiniać AI za samą literówkę – prawdopodobnie błąd został przeze mnie popełniony podczas kopiowania lub przearanżowania kodu. Niemniej jednak, niezdolność AI do generowania czystego i zrozumiałego kodu znacząco utrudniła diagnozę problemu. Co więcej, gdy poprosiłem o refaktoryzację, asystenci nie byli w stanie wykryć tego błędu.
Ironią losu, spędziłem więcej czasu na modyfikacjach i naprawianiu tego cytrynowego kodu wygenerowanego przez AI niż gdybym napisał tę funkcjonalność od zera samodzielnie. To doświadczenie doskonale uchwyciło Paradoks Cytrynowego Kodu: działające rozwiązanie,
które ma poplątany kod, i jest tak nieprzejrzyste, że nawet doświadczeni programiści z niechęcią myślą o jego przepisywaniu, mimo że wiedzą, iż może to być jedyny sposób na rozwiązanie fundamentalnych problemów.
Wnioski
- Wyjaśnialność jest kluczowa: Jeśli nie rozumiesz, jak działa wygenerowany kod, musisz go zmienić, aby był łatwy do zrozumienia dla ciebie i innych.
- Uważaj na black box: AI może być niesamowitym narzędziem do szybkiego prototypowania, ale gdy chodzi o utrzymywalność, kluczowe jest zrozumienie każdej części wygenerowanego kodu.
- Iteracyjne promptowanie ma swoje złe strony: Chociaż może prowadzić do działającego rozwiązania, może również skutkować mozaiką kodu, która uniemożliwia łatwe modyfikacje.
- Inwestuj w czytelność: Nawet jeśli kod “po prostu działa”, priorytetyzuj czytelność i spójność - szczególnie jeśli spodziewasz się długoterminowego utrzymywania go.
- Wprowadź refaktoryzację wcześnie: Nie czekaj, aż tajemnicze błędy zmuszą Cię do gruntownej analizy.
Paradoks Cytrynowego Kodu jest przestrogą dla nas, którzy integrujemy AI w naszych sposobach pracy programistycznej. Przypomina nam, że chociaż AI może przyspieszyć rozwój, nie jest substytutem starannego, rozważnego podejścia wymaganego do budowania utrzymywalnego oprogramowania.
Czy napotkałeś własną wersję Cytrynowego Kodu? Chętnie usłyszę Twoje doświadczenia i jak sobie z nimi poradziłeś.
Atrybucja: terminy Lemon Code i Lemon Code Paradox zostały nazwane i zdefiniowane przez Pawła Zubkiewicza (to ja) w lutym 2025 roku.