Punkt wyjścia: CRM napisany przez AI
Klient z branży handlowej zwrócił się do nas z prośbą o przejęcie wewnętrznego narzędzia typu CRM, wspierającego pracę zespołu handlowego. Aplikacja została wygenerowana w całości z pomocą AI (Claude): backend oraz frontend napisane w JavaScript, Node.js po stronie serwera, SQLite jako warstwa danych. W środowisku demonstracyjnym aplikacja wyglądała kompletnie — logowanie, listy klientów, formularze i podstawowe raporty działały zgodnie z oczekiwaniami. Zakres zlecenia został pierwotnie sformułowany jako wdrożenie gotowej aplikacji na serwer produkcyjny.
Przed akceptacją zakresu prac przeprowadziliśmy wstępny audyt kodu oraz zachowania aplikacji w różnych scenariuszach użycia. Wynik audytu istotnie zmienił zakres projektu.
Diagnoza: zakres faktycznie działającej funkcjonalności
Po przeprowadzeniu audytu stwierdziliśmy, że poprawnie obsługiwany jest wyłącznie podstawowy scenariusz użycia — i to z zastrzeżeniami. Odchylenia od tej ścieżki skutkowały nieobsłużonymi wyjątkami. Poniżej kluczowe ustalenia z audytu:
- Brak obsługi błędów i walidacji po stronie backendu — niepoprawne dane wejściowe (pusty formularz, nieoczekiwany typ danych, błąd sieci) powodowały awarię żądania
- Walidacja realizowana wyłącznie po stronie frontendu — backend akceptował dane w formie, w jakiej zostały przesłane, bez dodatkowej weryfikacji
- SQLite zakodowany na sztywno — ścieżka do pliku bazy umieszczona w kodzie źródłowym, bez mechanizmu konfiguracji
- Tryb diagnostyczny wbudowany w logikę aplikacji — flagi
DEBUGustawione na wartośćtruebezpośrednio w plikach źródłowych, bez możliwości przełączenia z poziomu konfiguracji - Pełne dane diagnostyczne w odpowiedziach HTTP — stack trace wyjątków dostępny dla użytkownika końcowego, co stanowi również problem bezpieczeństwa
- Brak migracji schematu bazy danych — struktura tabel istniała wyłącznie w pliku
.sqlitena stacji programistycznej, bez możliwości odtworzenia na innym środowisku - Sekrety w repozytorium kodu — klucze API, hasła oraz tokeny dostępowe zakomitowane w plikach konfiguracyjnych
- Pokrycie testowe ograniczone do jednego scenariusza — w repozytorium znajdował się wyłącznie skrypt weryfikujący najprostszą ścieżkę użycia
Uzasadnienie decyzji o przepisaniu backendu
Rozważaliśmy scenariusz refaktoryzacji istniejącego kodu jako opcję tańszą niż przepisanie od podstaw. Szczegółowa analiza wykazała jednak, że struktura źródeł nie pozwala na stopniowe wprowadzanie poprawek — kod nie posiadał punktów zaczepienia umożliwiających sensowną dekompozycję:
Brak separacji warstw
Logika biznesowa, warstwa dostępu do danych oraz obsługa HTTP znajdowały się w tych samych plikach. Pojedyncza funkcja łączyła parsowanie żądania, zapytania SQL i budowanie odpowiedzi.
Brak możliwości testowania
Zależności były wstrzykiwane bezpośrednio wewnątrz funkcji. Uniemożliwiało to podmianę bazy danych, zegara czy klienta HTTP na implementację testową — każdy test wymagałby uruchomienia pełnego środowiska.
Brak separacji środowisk
Aplikacja korzystała z tej samej konfiguracji niezależnie od środowiska. Nie istniał mechanizm pozwalający przełączyć połączenie z bazą, poziom logowania ani parametry diagnostyczne.
Niezgodność ze stackiem klienta
Infrastruktura docelowa klienta oparta jest o Nginx oraz PHP. Wdrożenie aplikacji Node.js wymagałoby dodatkowego komponentu w stacku operacyjnym i generowałoby koszty utrzymania.
Istotne ustalenie analizy: nawet w przypadku utrzymania platformy Node.js backend wymagałby napisania od podstaw, aby spełniać wymagania utrzymaniowe i rozwojowe. Przy porównywalnym nakładzie pracy wybraliśmy technologię zgodną ze stackiem klienta — PHP 8.5 i Symfony 8.0. Zgodność ze stackiem docelowym przekłada się na niższe koszty infrastruktury, spójność ze znanym klientowi środowiskiem operacyjnym oraz szerszą dostępność specjalistów do dalszego rozwoju.
Warstwa prezentacji: zakres zmian po stronie frontendu
Jakość warstwy frontendowej była zdecydowanie wyższa niż backendowej. Interfejs użytkownika został zrealizowany poprawnie — kod wygenerowany przez AI okazał się w tym obszarze gotowy do produkcyjnego wykorzystania po wprowadzeniu niewielkich korekt.
Elementy, które zachowaliśmy bez istotnych modyfikacji:
- Struktura komponentów oraz podział na widoki
- Obsługa stanów interfejsu — wskaźniki ładowania, stany puste, komunikaty powodzenia
- Układ, stylowanie oraz responsywność
- Walidacja formularzy po stronie klienta (jako pierwsza warstwa weryfikacji — drugą wprowadziliśmy po stronie backendu)
Wprowadzone zmiany miały charakter ograniczony: rozszerzenie obsługi błędów zwracanych przez API o czytelne komunikaty, drobne korekty walidacji oraz aktualizacja adresów punktów końcowych w związku z migracją backendu do Symfony.
Nowa architektura: PHP/Symfony z bazą MariaDB
Implementację poprzedziło zaprojektowanie warstwowej architektury aplikacji — etap, który w poprzedniej wersji projektu nie został zrealizowany. Kod wygenerowany przez AI pełnił funkcję działającego prototypu, lecz nie posiadał struktury pozwalającej na długoterminowe utrzymanie.
Warstwa kontrolerów
Kontrolery Symfony przyjmują żądania HTTP, walidują dane wejściowe z wykorzystaniem komponentu Symfony Validator i delegują realizację logiki do warstwy usług. Kontroler operuje wyłącznie na obiektach domenowych — nie posiada wiedzy o strukturze bazy danych.
Warstwa usług (service layer)
Logika biznesowa — reguły przypisywania klientów do opiekunów, etapy procesu sprzedażowego, obliczanie wskaźników — znajduje się w dedykowanych klasach usługowych. Każda z nich jest testowalna jednostkowo, bez konieczności uruchamiania bazy danych.
Warstwa dostępu do danych
Doctrine ORM wraz ze wzorcem repozytorium. Zapytania do bazy danych są skupione w repozytoriach właściwych dla konkretnego bytu (np. ClientRepository), co ułatwia ich lokalizację, modyfikację oraz optymalizację.
Migracje schematu
Doctrine Migrations — każda zmiana struktury bazy danych zapisana jest w dedykowanym pliku migracji pod kontrolą wersji. Odtworzenie schematu na dowolnym środowisku realizowane jest pojedynczą komendą.
Konfiguracja środowiska
Zmienne środowiskowe w plikach .env oraz profile Symfony (dev, prod, test). Sekrety pozostają poza repozytorium, tryb diagnostyczny wyłączany jest na środowisku produkcyjnym z poziomu zmiennych środowiskowych.
Warstwę danych przenieśliśmy z SQLite na MariaDB. SQLite stanowi odpowiednie rozwiązanie dla prototypów i aplikacji jednoużytkownikowych, jednak w systemie CRM wykorzystywanym jednocześnie przez wielu użytkowników ograniczenia związane z blokowaniem bazy oraz brak wsparcia dla części zapytań istotnie wpływały na stabilność i wydajność systemu.
Automatyczny deployment i gotowość produkcyjna
Proces wdrożenia w poprzedniej wersji aplikacji sprowadzał się do ręcznego skopiowania plików na serwer oraz uruchomienia procesu aplikacyjnego. Nie przewidziano mechanizmu migracji bazy danych, rozgrzewania cache ani możliwości wycofania zmian. Wprowadziliśmy w jego miejsce skryptowy proces wdrożeniowy, wykonujący każdą aktualizację w sposób atomowy:
- Budowa nowego wydania w katalogu
releases/<timestamp>/, niezależnym od aktualnie produkcyjnej wersji - Instalacja zależności (
composer install --no-dev) oraz rozgrzanie cache Symfony - Wykonanie migracji schematu bazy danych oczekujących na zastosowanie
- Przełączenie dowiązania symbolicznego
currentna nowe wydanie — operacja wykonywana atomowo - Kontrolowane przeładowanie procesu PHP-FPM
- Procedura wycofania zmian realizowana pojedynczą komendą — dowiązanie symboliczne zostaje przywrócone na poprzednie wydanie
Równolegle wprowadzone zostały elementy nieobecne w pierwotnej wersji projektu: rozdzielenie konfiguracji środowisk, wyłączenie trybu diagnostycznego na środowisku produkcyjnym z poziomu zmiennych środowiskowych, obsługa błędów prezentująca neutralny komunikat użytkownikowi końcowemu oraz przeniesienie sekretów do pliku .env.local poza repozytorium.
Weryfikacja scenariuszy użycia wykraczających poza podstawowy
Pokrycie testowe w pierwotnej wersji projektu ograniczało się do pojedynczego skryptu weryfikującego podstawową ścieżkę działania aplikacji. Przyjęliśmy bardziej systematyczne podejście do weryfikacji jakości:
Mapowanie scenariuszy użycia
Przygotowanie pełnej listy ścieżek użytkownika w oparciu o funkcjonalności aplikacji: scenariusze podstawowe, błędy walidacji, stany puste, wyjątki sieciowe oraz konflikty wynikające z współbieżnego dostępu.
Testy jednostkowe logiki biznesowej
Pokrycie testami PHPUnit warstwy usług — reguły przypisywania klientów, etapy lejka sprzedażowego, kalkulacja wskaźników. Każda usługa objęta testami ścieżek zasadniczych oraz przypadków brzegowych.
Testy integracyjne
Testy kontrolerów uruchamiane na dedykowanym środowisku testowym z rzeczywistą instancją MariaDB. Weryfikacji podlega pełny cykl przetwarzania żądania: kontroler, warstwa usług, repozytorium, baza danych, odpowiedź.
Weryfikacja manualna przypadków brzegowych
Scenariusze, których automatyzacja nie przynosi proporcjonalnych korzyści: pusty wynik wyszukiwania, utrata połączenia w trakcie zapisu, równoległa edycja tego samego rekordu. Weryfikacja wykonywana według udokumentowanej listy kontrolnej.
Rezultaty projektu
Aplikacja pierwotnie działała wyłącznie w warunkach demonstracyjnych. Po zakończeniu prac stanowi ona stabilne narzędzie pracy zespołu handlowego, gotowe do dalszego rozwoju funkcjonalnego oraz skalowania liczby użytkowników. Poniżej szacunkowe podsumowanie zakresu zmian:
pierwotnie: wyłącznie ścieżka podstawowa
backend, wdrożenie oraz testy
nieujawnionych w wersji demo
pierwotnie: wbudowany tryb diagnostyczny
Kluczowym efektem projektu, wykraczającym poza same wskaźniki ilościowe, jest przywrócenie kontroli nad procesem rozwoju aplikacji. Obecność testów automatycznych, migracji bazy danych oraz procedury wycofania zmian pozwala klientowi wprowadzać modyfikacje w sposób bezpieczny i kontrolowany — bez ryzyka regresji w obszarach niezwiązanych bezpośrednio z wprowadzaną zmianą.
Nasze podejście do AI: narzędzie pod nadzorem inżynierskim
Opisane powyżej problemy nie stanowią podstawy do ogólnego sceptycyzmu wobec narzędzi sztucznej inteligencji. W CodeRoll aktywnie korzystamy z AI w codziennej pracy — Claude Code stanowi podstawowe narzędzie wspierające pisanie kodu, Gemini wykorzystujemy do zastosowań o bardziej ogólnym charakterze. Istotą odpowiedzialnego wykorzystania tych narzędzi jest odpowiednie umiejscowienie ich w procesie wytwórczym.
- AI wspiera pracę programisty, nie zastępuje decyzji projektowych. Wykorzystujemy je do generowania kodu powtarzalnego, pierwszych wersji komponentów, szablonów testów oraz do eksploracji możliwych rozwiązań.
- Kod wygenerowany przez AI podlega recenzji inżynierskiej. Traktujemy go analogicznie do kodu napisanego przez programistę na wczesnym etapie rozwoju — wartościowego jako punkt wyjścia, wymagającego weryfikacji i często korekty przed włączeniem do projektu.
- Sztuczna inteligencja posiada ograniczoną zdolność oceny własnych rozwiązań. Wygenerowana aplikacja może działać poprawnie w scenariuszu demonstracyjnym, a jednocześnie zawierać istotne braki — brak walidacji danych wejściowych, sekrety w repozytorium, nieadekwatny dobór warstwy danych. Identyfikacja takich problemów wymaga doświadczenia produkcyjnego.
- Decyzje architektoniczne, kwestie bezpieczeństwa oraz gotowość produkcyjna pozostają w gestii inżyniera. AI dostarcza szkielet rozwiązania; kontekst biznesowy oraz wymagania utrzymaniowe definiuje zespół projektowy.
Kiedy warto rozważyć usługę „AI rescue”?
Poniższe sygnały wskazują, że aplikacja wygenerowana przez AI wymaga przejęcia przez doświadczony zespół inżynierski przed wdrożeniem produkcyjnym:
- Aplikacja działa w środowisku demonstracyjnym, lecz zawodzi przy pierwszych realnych użytkownikach — charakterystyczny objaw pokrycia ograniczonego do ścieżki podstawowej
- Wymagane jest wdrożenie produkcyjne, lecz brak zdefiniowanego procesu — brak procedury wdrożeniowej, migracji schematu bazy oraz konfiguracji środowisk
- Zastosowana warstwa danych jest nieadekwatna do docelowej infrastruktury — np. SQLite lub przechowywanie danych po stronie klienta, podczas gdy serwer docelowy wyposażony jest w MySQL, MariaDB lub PostgreSQL
- W aplikacji występują błędy, a ich diagnoza przekracza kompetencje osoby, która zleciła jej wygenerowanie — typowe w sytuacji, gdy projekt powstał bez udziału programisty
- Aplikacja działa wyłącznie w środowisku lokalnym autora — ścieżki zakodowane na sztywno, niezmapowane zależności systemowe
- Brak testów lub pokrycie ograniczone do scenariusza podstawowego — uniemożliwia bezpieczne wprowadzanie zmian funkcjonalnych
- Sekrety zakomitowane do repozytorium kodu — wymaga niezwłocznej rotacji kluczy oraz uporządkowania konfiguracji
Zakres prac realizowanych w ramach usługi
- Audyt kodu źródłowego oraz architektury wraz z raportem stanu projektu
- Przepisanie backendu lub jego części z dostosowaniem do wymagań produkcyjnych
- Zaprojektowanie warstw aplikacji, wprowadzenie testów oraz migracji schematu bazy
- Konfiguracja środowisk, skrypty wdrożeniowe oraz podstawowy monitoring
- Zabezpieczenie sekretów, walidacja danych wejściowych, obsługa błędów
- Dokumentacja scenariuszy testowych oraz procesu wdrożeniowego
Podsumowanie
Narzędzia oparte o sztuczną inteligencję znacząco obniżyły próg wejścia w tworzenie aplikacji — rozwiązania, których wytworzenie wymagało dotychczas zespołu programistów, mogą obecnie powstawać przy znacznie mniejszych zasobach. Jednocześnie obserwujemy wzrost liczby projektów stanowiących prototypy o pozornej gotowości produkcyjnej, niespełniających wymagań utrzymaniowych ani bezpieczeństwa.
Kod wygenerowany przez AI nie wymaga odrzucenia. W opisywanym projekcie warstwa prezentacji została zachowana w zdecydowanej większości — jakość kodu frontendowego okazała się satysfakcjonująca. Przepisania wymagał backend, nie ze względu na ograniczenia AI, lecz ze względu na charakter obszaru: warstwa serwerowa wymaga świadomych decyzji architektonicznych, których generator kodu samodzielnie nie podejmuje.
Wnioski wynikające z projektu:
- Aplikacje wygenerowane przez AI należy traktować jako prototypy; wdrożenie produkcyjne wymaga udziału inżyniera
- Warstwa frontendowa AI osiąga satysfakcjonującą jakość; warstwa backendowa wymaga weryfikacji inżynierskiej
- Stos technologiczny aplikacji powinien być dostosowany do infrastruktury klienta, a nie odwrotnie
- Gotowość produkcyjna obejmuje nie tylko kod aplikacji, lecz również proces wdrożeniowy, pokrycie testowe, konfigurację środowisk oraz monitoring
- Oszczędność czasu wynikająca z wykorzystania AI jest realna, lecz nie zwalnia z weryfikacji inżynierskiej
W przypadku projektu wygenerowanego przez AI, który nie spełnia oczekiwań produkcyjnych, zachęcamy do kontaktu. Umówienie darmowej konsultacji pozwoli nam ocenić stan kodu oraz określić, które elementy można zachować, a które wymagają gruntownych zmian.
Projekt wygenerowany przez AI wymaga przejęcia?
Świadczymy usługi w zakresie AI rescue — przejmujemy aplikacje wygenerowane przez sztuczną inteligencję i doprowadzamy je do jakości produkcyjnej. Zakres obejmuje architekturę, pokrycie testowe, procesy wdrożeniowe oraz bezpieczeństwo. Umów darmową konsultację, podczas której ocenimy stan Twojego projektu i przedstawimy rekomendacje dalszych działań.
Umów darmową konsultację