Przejdź do treści
Maciej Kotlarz21 lipca 202612 min czytaniaAI

Iteracyjne doprecyzowywanie i refaktoryzacja wygenerowanego kodu

Iteracyjne doprecyzowywanie i refaktoryzacja wygenerowanego kodu
Obraz wygenerowany lub zmodyfikowany przez AI
Jak dopracowywać kod generowany przez AI? Od prototypu do implementacji gotowej do przeglądu: refaktoryzacja, logika biznesowa i styl projektu.

Iteracyjne doprecyzowywanie i refaktoryzacja wygenerowanego kodu#

To druga część artykułu – jeśli nie czytałeś jeszcze pierwszej („Jak AI zmienia sposób pisania kodu”), warto zacząć właśnie od niej. Tam przyglądaliśmy się ogólnej zmianie podejścia do tworzenia oprogramowania z pomocą AI, a tutaj skupimy się na praktyce: jak iteracyjnie doprecyzowywać prompt, poprawiać i refaktoryzować wygenerowany kod, żeby z prototypu zrobić coś naprawdę użytecznego.

Iteracyjne doprecyzowywanie i refaktoryzacja wygenerowanego kodu#

Pierwsza wersja kodu wygenerowana przez AI prawie nigdy nie jest wersją finalną. W praktyce najlepiej traktować ją jako szkic rozwiązania, a nie gotową implementację produkcyjną. Model potrafi w kilka sekund stworzyć działający fragment kodu, jednak dopiero kolejne kroki nadają mu odpowiednią jakość.

Największą wartość przynosi iteracyjne dopracowywanie rozwiązania. Zamiast oczekiwać perfekcyjnego kodu już w pierwszej odpowiedzi, znacznie skuteczniejsze jest podejście oparte na małych, kontrolowanych krokach: analiza, poprawa, ponowna generacja i kolejna ocena.

Dobrym sposobem myślenia o modelu AI jest traktowanie go jak bardzo szybkiego współpracownika. Taki „partner” potrafi natychmiast zaproponować rozwiązanie, ale odpowiedzialność za jego jakość nadal spoczywa na programiście. To człowiek decyduje o:

  • jakości implementacji
  • zgodności z architekturą systemu
  • poprawności logiki biznesowej
  • utrzymywalności kodu

Model przedstawia propozycję. Programista podejmuje decyzje.

W praktyce iteracyjna praca z kodem generowanym przez AI wygląda najczęściej tak:

  • generujemy pierwszą wersję rozwiązania
  • analizujemy ją pod kątem poprawności i czytelności
  • prosimy model o konkretne poprawki
  • ponownie oceniamy efekt

Każda iteracja przybliża kod do poziomu jakości oczekiwanego w projekcie.

Pierwsza iteracja: działający szkic#

Pierwsza implementacja wygenerowana przez model zwykle spełnia podstawowe wymagania funkcjonalne. Kod się kompiluje, logika działa, a scenariusz testowy można uruchomić.

Jednocześnie taka implementacja często ma kilka typowych wad:

  • jest bardziej rozbudowana niż to konieczne
  • nie zawsze jest stylistycznie spójna
  • może odbiegać od konwencji projektu
  • bywa trudna do rozwijania

Dlatego pierwszym krokiem po wygenerowaniu kodu powinna być spokojna analiza. Na tym etapie nie chodzi jeszcze o gruntowną przebudowę rozwiązania, lecz o zrozumienie jego struktury.

Warto sprawdzić między innymi:

  • czy implementacja rzeczywiście realizuje wymagania
  • czy nazwy zmiennych i metod są zrozumiałe
  • czy metoda nie wykonuje zbyt wielu zadań naraz
  • czy struktura kodu jest logiczna

Dopiero po takiej ocenie można przejść do właściwej refaktoryzacji.

Poprawianie czytelności kodu#

Jednym z najczęstszych problemów kodu generowanego przez AI jest przeciętna czytelność. Kod zazwyczaj działa poprawnie, ale jego struktura bywa mniej elegancka niż w dopracowanej implementacji.

Często pojawiają się bardzo ogólne nazwy zmiennych albo imperatywny styl w miejscu, gdzie projekt preferuje bardziej deklaratywne podejście.

Przykładowa metoda wygenerowana w pierwszej iteracji może wyglądać tak:

Snippet
csharp
public List<string> GetActiveUserEmails(List<User> users)
{
    var result = new List<string>();
    foreach (var u in users)
    {
        if (u.IsActive && !string.IsNullOrEmpty(u.Email))
        {
            result.Add(u.Email);
        }
    }
    return result;
}

Kod jest poprawny, ale w wielu projektach .NET preferuje się użycie LINQ, które pozwala zapisać tę samą logikę krócej i bardziej deklaratywnie.

Po refaktoryzacji metoda może wyglądać tak:

Snippet
csharp
public List<string> GetActiveUserEmails(IEnumerable<User> users)
{
    return users
        .Where(user => user.IsActive && !string.IsNullOrWhiteSpace(user.Email))
        .Select(user => user.Email)
        .ToList();
}

Różnice wydają się niewielkie, ale są istotne:

  • metoda przyjmuje IEnumerable<User> zamiast List<User>
  • logika filtrowania jest bardziej przejrzysta
  • kod jest krótszy
  • styl odpowiada typowym wzorcom LINQ

W większych projektach takie drobne zmiany znacząco poprawiają czytelność całej bazy kodu.

Ulepszanie nazw i intencji#

Kolejnym etapem iteracyjnego dopracowywania kodu jest poprawa nazewnictwa. Modele językowe często generują nazwy technicznie poprawne, ale mało precyzyjne.

Typowe przykłady to:

  • data
  • result
  • item
  • value

Takie nazwy nie zawsze dobrze oddają znaczenie zmiennej lub obiektu w kontekście domeny biznesowej.

Podczas pracy z AI można łatwo poprosić o:

  • bardziej opisowe nazwy zmiennych
  • dopasowanie nazewnictwa do języka domeny
  • skrócenie zbyt długich identyfikatorów

Przykładowe polecenie może być bardzo proste:

"Zrefaktoryzuj kod tak, aby nazwy zmiennych były bardziej opisowe i zgodne z konwencją domenową projektu."

Takie zmiany wydają się drobne, ale w praktyce znacząco zwiększają czytelność kodu dla całego zespołu.

Doprecyzowanie logiki biznesowej#

Pierwsza wersja kodu zwykle implementuje tylko podstawową logikę. W rzeczywistej aplikacji pojawiają się jednak dodatkowe wymagania.

Najczęściej trzeba uwzględnić między innymi:

  • walidację danych
  • obsługę wyjątków
  • logowanie
  • przypadki brzegowe

Rozważmy prostą metodę pobierającą dane z API:

Snippet
csharp
public async Task<UserDto> GetUserAsync(int id)
{
    var response = await _httpClient.GetAsync($"/users/{id}");
    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<UserDto>(json);
}

Kod działa, ale brakuje w nim kilku istotnych elementów:

  • sprawdzenia kodu odpowiedzi HTTP
  • obsługi błędów
  • walidacji danych

Po pierwszej iteracji metoda może wyglądać lepiej:

Snippet
csharp
public async Task<UserDto?> GetUserAsync(int id)
{
    var response = await _httpClient.GetAsync($"/users/{id}");
    if (!response.IsSuccessStatusCode)
    {
        return null;
    }
    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<UserDto>(json);
}

W kolejnych iteracjach można dodatkowo dodać:

  • logowanie błędów
  • mechanizm retry
  • obsługę CancellationToken
  • walidację danych z API

Stopniowe ulepszanie implementacji jest zwykle znacznie skuteczniejsze niż próba wygenerowania perfekcyjnego kodu w jednym kroku.

Refaktoryzacja zbyt rozbudowanych metod#

Przy bardziej złożonych poleceniach AI potrafi wygenerować metody, które wykonują zbyt wiele operacji naraz.

Jedna metoda może na przykład:

  • pobierać dane z bazy
  • filtrować wyniki
  • mapować DTO
  • wykonywać dodatkową logikę biznesową

Taki kod działa, ale jest trudny w utrzymaniu i testowaniu.

Dobrym rozwiązaniem jest poproszenie modelu o podział metody zgodnie z zasadą pojedynczej odpowiedzialności.

Na przykład:

"Podziel tę metodę na mniejsze zgodnie z zasadą single responsibility."

Model często potrafi sensownie wydzielić osobne elementy:

  • pobieranie danych
  • transformację danych
  • walidację
  • właściwą logikę biznesową

Dzięki temu kod staje się czytelniejszy i łatwiejszy do testowania.

Dopasowanie do stylu projektu#

Każdy projekt posiada własne konwencje dotyczące stylu kodu. Mogą one obejmować:

  • nazewnictwo klas i metod
  • strukturę serwisów
  • sposób wstrzykiwania zależności
  • organizację katalogów

Model AI nie zna tych zasad, dopóki ich nie zobaczy. Dlatego bardzo skuteczną techniką jest przekazanie fragmentu istniejącego kodu jako wzorca.

Przykładowe polecenie:

"Poniżej znajduje się przykładowy serwis z naszego projektu. Dostosuj wygenerowany kod tak, aby był zgodny z tym stylem."

Po kilku takich iteracjach model zaczyna generować kod znacznie lepiej dopasowany do konwencji projektu.

Iteracyjne prompty zamiast jednego dużego polecenia#

Częstym błędem jest próba wygenerowania idealnego rozwiązania za pomocą jednego bardzo rozbudowanego promptu.

W praktyce dużo lepiej sprawdza się podejście iteracyjne.

Przykładowy proces może wyglądać tak:

  1. wygeneruj prostą implementację
  2. popraw czytelność kodu
  3. dodaj obsługę błędów
  4. dopasuj kod do architektury projektu
  5. uprość logikę i nazewnictwo

Każdy krok jest niewielki i łatwy do oceny. Dzięki temu znacznie łatwiej kontrolować jakość kodu.

Przejście od prototypu do kodu gotowego do przeglądu#

Jednym z kluczowych etapów pracy z kodem generowanym przez AI jest przejście od szybkiego prototypu do implementacji, którą można bez obaw wysłać do przeglądu kodu.

Kod prototypowy i kod produkcyjny mają zupełnie inne cele. W prototypie najważniejsza jest szybkość i możliwość sprawdzenia pomysłu. W kodzie produkcyjnym liczą się przede wszystkim:

  • czytelność
  • stabilność
  • testowalność
  • zgodność z architekturą systemu

Dlatego przed utworzeniem pull requesta warto przejść przez kilka kroków porządkowych.

Pierwszym z nich jest uproszczenie struktury kodu. Jeśli metoda jest zbyt długa lub wykonuje kilka różnych operacji, należy ją podzielić na mniejsze elementy.

Drugim krokiem jest dopracowanie nazewnictwa. Dobrze dobrane nazwy klas i metod często eliminują potrzebę dodatkowych komentarzy, ponieważ sama struktura kodu jasno pokazuje intencję.

Kolejnym etapem jest sprawdzenie obsługi błędów oraz przypadków brzegowych. Kod, który działa tylko w idealnym scenariuszu, bardzo szybko powoduje problemy w środowisku produkcyjnym.

Następnie warto zadbać o testy jednostkowe. Modele AI mogą pomóc także w tym obszarze. Wystarczy przekazać implementację metody i poprosić o wygenerowanie testów obejmujących typowe scenariusze oraz przypadki graniczne.

Na końcu dobrze jest jeszcze raz przejrzeć kod pod kątem spójności z resztą projektu:

  • czy styl jest zgodny z istniejącymi klasami
  • czy struktura katalogów jest właściwa
  • czy zależności są wstrzykiwane w ten sam sposób co w innych miejscach

Po wykonaniu tych kroków kod przestaje być eksperymentalnym prototypem i staje się pełnoprawnym elementem systemu.

AI jako narzędzie refaktoryzacji#

AI nie jest przydatne wyłącznie przy tworzeniu nowego kodu. Bardzo dobrze sprawdza się również przy refaktoryzacji istniejącej implementacji.

Można poprosić model na przykład o:

  • uproszczenie złożonej logiki
  • zamianę pętli na LINQ
  • wydzielenie metod pomocniczych
  • poprawę nazw
  • dodanie komentarzy lub dokumentacji
  • przygotowanie testów jednostkowych

W praktyce oznacza to, że nawet starszy kod w projekcie można stopniowo ulepszać przy wsparciu modeli AI.

Najważniejsza zasada#

Najlepsze rezultaty pojawiają się wtedy, gdy traktujemy kod generowany przez AI jako punkt wyjścia, a nie jako gotowe rozwiązanie.

Proces pracy przypomina wtedy klasyczną refaktoryzację:

  • analizujemy kod
  • upraszczamy strukturę
  • poprawiamy czytelność
  • dopasowujemy do architektury
  • dodajemy obsługę błędów

Różnica polega na tym, że kolejne propozycje zmian mogą pojawiać się niemal natychmiast.

Dzięki temu programista może skupić się na decyzjach architektonicznych i jakości rozwiązania, zamiast poświęcać czas na ręczne przepisywanie oczywistych fragmentów kodu.

Od prototypu do kodu gotowego do przeglądu#

Pierwsza wersja kodu wygenerowana przez AI bardzo często przypomina działający prototyp. Funkcjonalność zwykle jest na miejscu, logika zazwyczaj ma sens, a aplikacja potrafi zrobić to, o co prosiliśmy. Problem polega na tym, że taki kod rzadko jest od razu gotowy do włączenia do repozytorium produkcyjnego.

Na tym etapie pracy zaczyna się coś, co można nazwać fazą porządkowania. Nie chodzi już o szybkie sprawdzanie pomysłu, tylko o doprowadzenie rozwiązania do formy, którą inni programiści będą w stanie łatwo zrozumieć, utrzymywać i rozwijać.

Innymi słowy: AI pomaga szybko zbudować pierwszy szkic rozwiązania, ale to programista zamienia ten szkic w kod, który naprawdę pasuje do projektu.

Od eksperymentu do struktury projektu#

Kod generowany podczas eksperymentów często trafia do jednego pliku, jednej klasy albo nawet jednej dużej metody. W fazie prototypowania to zupełnie normalne — liczy się szybkość i możliwość sprawdzenia pomysłu.

Jednak przed wysłaniem zmian do repozytorium warto zatrzymać się na chwilę i zadać sobie kilka prostych pytań:

  • czy odpowiedzialności są dobrze rozdzielone?
  • czy klasy robią jedną rzecz?
  • czy struktura plików pasuje do architektury projektu?

Jeśli odpowiedź na któreś z tych pytań brzmi „nie do końca”, to znak, że czas na refaktoryzację.

Najczęściej sprowadza się to do kilku typowych kroków:

  • przeniesienie logiki biznesowej z kontrolerów do serwisów
  • wydzielenie modeli danych
  • podział dużych metod na mniejsze
  • usunięcie tymczasowego kodu używanego w trakcie eksperymentów

To nie jest zmiana funkcjonalności — to zmiana formy, dzięki której kod zaczyna przypominać resztę projektu.

Przenoszenie logiki z kontrolera do serwisu#

Jednym z najczęstszych efektów pracy z AI jest kontroler, który robi zdecydowanie za dużo. Generowany kod często zawiera w nim:

  • wywołania HTTP
  • przetwarzanie danych
  • obsługę błędów
  • logikę biznesową

Dla prototypu to w porządku. Dla kodu produkcyjnego — już niekoniecznie.

W typowej aplikacji .NET kontroler powinien być raczej cienką warstwą odpowiedzialną za obsługę żądania HTTP. Cała właściwa logika powinna znajdować się w serwisach.

Przykładowo: podczas pierwszej iteracji AI mogło wygenerować kod pobierający dane z API bezpośrednio w kontrolerze. W kolejnym kroku warto wydzielić tę logikę do osobnej klasy.

Snippet
csharp
public class WeatherService
{
    private readonly HttpClient _httpClient;
    public WeatherService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    public async Task<WeatherDto?> GetWeatherAsync(string city)
    {
        var response = await _httpClient.GetAsync($"/weather?city={city}");
        if (!response.IsSuccessStatusCode)
        {
            return null;
        }
        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<WeatherDto>(json);
    }
}

Dzięki temu kontroler może wyglądać dużo prościej:

Snippet
csharp
[ApiController]
[Route("api/weather")]
public class WeatherController : ControllerBase
{
    private readonly WeatherService _weatherService;
    public WeatherController(WeatherService weatherService)
    {
        _weatherService = weatherService;
    }
    [HttpGet]
    public async Task<IActionResult> Get(string city)
    {
        var result = await _weatherService.GetWeatherAsync(city);
        if (result == null)
        {
            return NotFound();
        }
        return Ok(result);
    }
}

Teraz odpowiedzialności są wyraźnie rozdzielone:

  • kontroler obsługuje HTTP
  • serwis zajmuje się logiką pobierania danych

Taki podział znacznie ułatwia późniejsze testowanie i rozwijanie aplikacji.

Spójność stylu i konwencji#

Kod generowany przez AI bazuje na ogromnej liczbie przykładów z różnych projektów. Problem polega na tym, że każdy zespół ma własne konwencje — a AI nie zawsze trafi w nie idealnie.

Dlatego przed wysłaniem zmian do przeglądu warto sprawdzić kilka rzeczy.

Nazwy klas i metod#

Czy nazwy są zgodne z konwencją projektu? Czy są wystarczająco opisowe? Czy nie ma skrótów, które mogą być niejasne dla innych osób?

Na przykład metoda o nazwie:

GetData()

prawdopodobnie powinna zostać zmieniona na coś bardziej konkretnego, np.:

GetWeatherAsync()

Mała zmiana, ale od razu poprawia czytelność.

Długość metod#

AI czasem generuje bardzo długie metody, które robią kilka rzeczy naraz. W takich sytuacjach warto poszukać fragmentów, które można wydzielić do osobnych metod.

Przykładowo:

  • parsowanie odpowiedzi
  • walidacja danych
  • logika przetwarzania

Każdy z tych elementów może być osobną metodą.

Obsługa błędów#

W kodzie generowanym przez AI obsługa błędów bywa uproszczona. Czasem pojawiają się puste bloki catch, czasem brak logowania błędów, a czasem metoda po prostu zwraca null.

Warto sprawdzić, czy sposób obsługi błędów jest zgodny ze standardami projektu. W wielu zespołach oznacza to na przykład:

  • używanie wyjątków domenowych
  • logowanie błędów
  • unikanie zwracania null, jeśli można użyć innego podejścia

Formatowanie kodu#

Na koniec zostaje rzecz najmniej spektakularna, ale bardzo ważna: formatowanie.

Automatyczne formatowanie (np. przez dotnet format albo ustawienia IDE) często wystarczy, aby kod od razu wyglądał jak reszta projektu.

To drobiazg, ale w przeglądzie kodu ma znaczenie — recenzent może skupić się na logice zamiast na stylu.

Usuwanie śladów eksperymentów#

Podczas pracy z AI często powstaje sporo kodu pomocniczego:

  • tymczasowe metody
  • dodatkowe warunki
  • fragmenty użyte tylko do testów
  • zakomentowane linie kodu

W trakcie eksperymentów są bardzo przydatne. Jednak w finalnej wersji zmiany powinny zniknąć.

Kod trafiający do repozytorium powinien wyglądać tak, jakby powstał w przemyślany sposób — nawet jeśli w rzeczywistości był wynikiem kilku iteracji z AI.

Dlatego przed wysłaniem pull requesta warto przejrzeć zmiany i zadać sobie jedno pytanie:

Czy ktoś, kto zobaczy ten kod po raz pierwszy, zrozumie go bez znajomości całego procesu jego powstawania?

Jeśli odpowiedź brzmi „tak”, to znak, że kod jest gotowy do przeglądu.

Czytelność ponad wszystko#

Najważniejszym celem na tym etapie nie jest sprytna optymalizacja ani maksymalna liczba abstrakcji. Najważniejsza jest czytelność.

Programista przeglądający kod widzi tylko końcowy efekt. Nie widzi promptów, prób, poprawek ani rozmów z AI. Dlatego struktura kodu powinna jasno pokazywać:

  • co robi dana klasa
  • za co odpowiada metoda
  • skąd pochodzą dane

Jeśli kod da się przeczytać niemal jak krótką historię działania aplikacji, to znaczy, że został dobrze przygotowany.

AI jako narzędzie do szybkiego prototypowania#

Najlepszy sposób myślenia o AI w procesie programowania jest dość prosty: to narzędzie do błyskawicznego tworzenia pierwszej wersji rozwiązania.

AI pomaga:

  • wygenerować początkową strukturę
  • przygotować przykładową implementację
  • szybko sprawdzić pomysł

Ale to programista odpowiada za ostatni krok — przekształcenie prototypu w kod, który naprawdę pasuje do architektury aplikacji i standardów zespołu.

Dopiero po tym etapie kod jest gotowy, aby trafić do przeglądu i stać się częścią projektu.

W praktyce oznacza to prostą zasadę: AI przyspiesza początek pracy, ale jakość końcowego kodu wciąż zależy od świadomej refaktoryzacji.

Linki zewnętrzne
Udostępnij
Zachowaj link lub przekaż artykuł dalej.
Newsletter

Dołącz do naszego stada

Krótko, konkretnie i bez szumu. Najnowsze artykuły oraz praktyczne materiały trafiają prosto do skrzynki.

Zapis oznacza akceptację Polityki prywatności.