Strona główna / Artykuły / Gdy sztuczna inteligencja pisze Twoją aplikację React, ale pomija zasady czystego kodu

Gdy sztuczna inteligencja pisze Twoją aplikację React, ale pomija zasady czystego kodu

Dowiedz się o siedmiu nawykach pisania czystego kodu – DRY, zasada jednej odpowiedzialności, klauzule ochronne i inne – które często są łamane w kodzie React generowanym przez AI oraz jak je naprawić.

2427 słów

Klient niedawno przekazał nam projekt polegający na stworzeniu strony internetowej do zamawiania pizzy.

Początkowo zakres pracy wydawał się do opanowania.

Strona startowa.

Część z menu.

Różne kategorie pizzy.

Odrębne strony produktów.

Koszyk zakupów.

Proces płatności.

Ponadto panel administracyjny do zarządzania wszystkim w tle.

Pierwotne założenie brzmiało:

"To powinno pójść szybko."

Następnie podjęto decyzję o wykorzystaniu asystenta do kodowania opartego na sztucznej inteligencji.

Wtedy sprawy stały się ciekawe.

Pozwolenie sztucznej inteligencji na wykonanie większości kodowania

Projekt został zbudowany przy użyciu React.

Zamiast ręcznie wpisywać każdą linię kodu, wysyłano do sztucznej inteligencji instrukcje w celu wygenerowania poszczególnych elementów.

Instrukcja taka jak:

">Stwórz komponent karty pizzy."

natychmiast wygenerowała ten komponent.

Następnie:

"Stwórz koszyk zakupów."

Zrobione.

Następnie:

"Stwórz ekran płatności."

Również zrobione.

Następnie:

"Stwórz panel administracyjny pokazujący statystyki, zamówienia i informacje o klientach."

Dostarczone.

Szybkość była naprawdę imponująca.

Praca, która zwykle zajmowałaby godziny, była realizowana w ciągu minut.

W rezultacie otrzymałem:

  • Komponenty React
  • Formularze
  • Tabele danych
  • Widżety panelu administracyjnego
  • Integrację z API
  • Wskaźniki ładowania
  • Rozwiązywanie błędów
  • Layouty dostosowujące się do rozmiaru ekranu

A aplikacja rzeczywiście działała.

Wszystko wyglądało obiecująco.

Wydawało się, że znaleziono idealny proces pracy:

Idea → Prompt → Kod → Wdrożenie

Jednak brakowało jednego kluczowego kroku.

Przegląd tego, co zostało stworzone.

Klient nigdy nie zobaczył samego kodu

Z punktu widzenia klienta wszystko wyglądało doskonale.

Strona działała płynnie.

Panel sterowania funkcjonował prawidłowo.

Menü wyświetlało się poprawnie.

Zamówienia pojawiały się zgodnie z oczekiwaniami.

Wizualnie było to dopracowane.

Więc gdzie był haczyk?

Problem nie był widoczny z zewnątrz.

Pod spodem baza kodu stopniowo stawała się źródłem problemów z utrzymaniem.

Nie było to od razu oczywiste.

Jednak w miarę dodawania kolejnych funkcji zaczęło się pojawiać pewne wzorce.

Ciągle powracała ta sama myśl:

">Czekaj... czy nie zbudowano już czegoś niemal identycznego?"

Wtedy pojawiło się ważne uświadomienie:

Sztuczna inteligencja rzeczywiście potrafi tworzyć działający kod.

Jednak czysty kod nie jest czymś, co dostarcza automatycznie.

Problem nr 1: Powtarzająca się logika wszędzie

Okazało się, że niemal identyczna logika była rozproszona po całym aplikacji.

Například kilka oddzielnych funkcji obliczało sumy niezależnie.

Jedna z nich wyglądała tak:

const total = price * quantity;

W innych miejscach pojawiła się niemal identyczna wersja:

const orderTotal = price * quantity;

A w jeszcze innym pliku:

const cartAmount = price * quantity;

Wszystkie trzy w zasadzie wykonywały to samo obliczenie.

Technicznie nic nie było zepsute.

Jednak gdy tylko konieczne stało się zmianienie zasad cenowych, trzeba było odnaleźć i zaktualizować każdą z tych kopii.

Wtedy stał się jasny pierwszy zasadniczy cel czystego kodu.

1. DRY — Nie powtarzaj się

Każdy raz, gdy ta sama logika pojawia się wielokrotnie, należy zadać sobie pytanie:

Czy można to połączyć w jedno źródło?

Na przykład:

const calculateItemTotal = (price, quantity) => {
  return price * quantity;
};

Od tego momentu każda część aplikacji może wywoływać tę samą wspólną funkcję.

Celem nie jest wymuszanie możliwości ponownego użycia wszędzie.

Jest to po prostu sposób na usunięcie powtórzeń, które nie służą żadnemu celowi.

Problem nr 2: Jeden komponent robił wszystko

W pewnym momencie otwarto jeden z wygenerowanych plików React, aby przyjrzeć mu się bliżej.

Kontynuował rozwój.

Lina po linii.

Nieustannie się rozszerzał.

Ten jeden komponent był odpowiedzialny za:

  • Zapytania do API
  • Zarządzanie stanem formularza
  • Logikę walidacji
  • Widoczność modalu
  • Rysowanie tabeli
  • Filtrowanie wyników
  • Paginacja
  • Powiadomienia
  • Krótko mówiąc, jeden plik w zasadzie samodzielnie uruchamiał połowę aplikacji.

    Oczywistym wnioskiem było:

    ">Zachowanie tej struktury będzie trudne.

    Dlatego komponent został rozłożony na mniejsze części.

    Zamiast zachować tę strukturę:

    Orders.jsx
       ↓
    800+ lines
    

    została ona przekształcona w coś w rodzaju:

    Orders
     ├── OrderFilters
     ├── OrderTable
     ├── OrderRow
     ├── OrderModal
     └── useOrders
    

    To zmiany doprowadziły bezpośrednio do następnego zasady.

    2. Jedna odpowiedzialność

    Komponent lub funkcja powinny mieć jedno jasne zadanie.

    Jeśli nie możesz opisać, co robi komponent, w jednym zdaniu, istnieje duże prawdopodobieństwo, że jest on zbyt obciążony.

    Naprzимер:

    OrderTable wyświetla listę zamówień.

    To jest klarowny, skoncentrowany opis.

    Porównaj to z:

    OrderTable uzyskuje zamówienia, sprawdza uprawnienia użytkownika, oblicza łączną kwotę, uruchamia powiadomienia i wyświetla wszystko w tabeli.

    Taki rodzaj opisu to sygnał ostrzegawczy.

    Problem nr 3: Wtórne instrukcje if przejęły kontrolę nad kodem

    W pewnym momencie część logiki w projekcie wyglądała tak:

    if (user) {
      if (user.isActive) {
        if (user.isVerified) {
          // create order
        }
      }
    }
    

    Funkcjonowało to poprawnie.

    Jednak analiza tego kodu była męcząca.

    Dlatego zostało przepisane w ten sposób:

    if (!user) return;
    if (!user.isActive) return;
    if (!user.isVerified) return;
    
    // create order
    

    Ta wersja była o wiele łatwiejsza do zrozumienia na pierwszy rzut oka.

    Tutaj wchodzi w grę kolejny nawyk:

    3. Wczesne zwracanie wartości / klauzule ochronne

    Zamiast ukrywać podstawową logikę pod kilkoma warstwami wtórnych warunków, lepiej jest od razu zajmować się nieprawidłowymi przypadkami i wcześnie zakończyć wykonywanie kodu.

    Invalid?
       ↓
    Return
    
    Invalid?
       ↓
    ReturnEverything okay?
       ↓
    Do the actual work
    

    W rezultacie główna logika pozostaje prosta, czytelna i pozbawiona niepotrzebnego wcięcia.

    Problem nr 4: AI zasugerowała nowe komponenty dla elementów, które już istniały

    To błąd był nieco krępujący.

    Zadanie podane AI brzmiało:

    "Stwórz okno potwierdzenia do usuwania zamówienia."

    AI odpowiedziała, tworząc zupełnie nowy komponent.

    Prawie został on przyjęty bez żadnych pytań.

    Następnie szybkie przeszukanie projektu ujawniło coś ważnego.

    Komponent okna już istniał.

    Mógł zostać po prostu ponownie wykorzystany.

    To moment uświadomił coś ważnego:

    AI nie wie automatycznie, co już istnieje w danym kodzie.

    A nawet gdy system to wie, może i tak zdecydować się na stworzenie czegoś nowego zamiast ponownego wykorzystania tego, co już istnieje.

    Lepszym podejściem jest sformułowanie prośby w następujący sposób:

    "Zanim stworzysz nowy komponent, sprawdź istniejącą bazę kodu w poszukiwaniu elementów, które można ponownie wykorzystać."

    To doświadczenie wskazuje na kolejną zasadę:

    4. Wykorzystuj to, co już istnieje, zanim coś stworzysz

    Zanim dodasz:

    • nowy komponent
    • nową funkcję pomocniczą
    • nowy custom hook
    • nowego pomocnika API

    zadaj sobie pytanie:

    ">Czy to już istnieje w projekcie?"

    Czysta baza kodu nie polega na posiadaniu dziesiątek elementów nadających się do ponownego użycia.

    Polega na deweloperach, którzy wykorzystują to, co już jest dostępne, zamiast je potajemnie kopiować.

    Problem nr 5: Nazwy zmiennych były czasami okropne

    Kod wygenerowany przez AI pojawia się szybko.

    I łatwo jest zaakceptować nazwy zmiennych takie jak:

    const data = ...
    const result = ...
    const x = ...
    const temp = ...
    

    Te nazwy kompilują się bez problemów i nie powodują błędów.

    Ale po kilku miesiącach, do czego właściwie odnosi się data?

    Czy to dane związane z pizzą?

    Informacje o zamówieniach?

    Dane klientów?

    Czy coś związanego z panelami kontrolnymi?

    Dlatego w miarę rozwoju projektu nazewnictwo powinno być bardziej przemyślane.

    Zamiast pisać:

    const data = await getOrders();
    

    lepsza jest bardziej klarowna wersja:

    const orders = await getOrders();
    

    A zamiast:

    const x = users.filter(...);
    

    to czyta się o wiele lepiej:

    const activeUsers = users.filter(...);
    

    To prowadzi do kolejnej prostej zasady:

    5. Używaj nazw, które wyjaśniają kod

    Opisowe nazewnictwo często pozwala całkowicie uniknąć potrzeby komentarzy.

    Czytając coś takiego jak:

    const activeUsers = users.filter(
      (user) => user.isActive
    );
    

    Już sama treść mówi dokładnie, co się dzieje.

    Nie ma potrzeby dodawać:

    // Filter users who are currently active
    

    Kod mówi sam za siebie.

    Problem nr 6: Optymalizacja rzeczy, które nie wymagają optymalizacji

    W pewnym momencie w takim projekcie łatwo zacząć myśleć:

    "Ten kod wymaga lepszej optymalizacji."

    To często prowadzi do analizy takich rzeczy jak:

    useMemo()
    useCallback()
    

    i kilku innych trików związanych z wydajnością.

    Ale warto tu się zatrzymać.

    Optymalizacja nie jest automatycznie czymś dobrym.

    Weźmy prosty przykład obliczenia:

    const total = price * quantity;
    

    Nie ma powodu, by otaczać to jakimś skomplikowanym wzorcem optymalizacyjnym tylko dlatego, że istnieją takie narzędzia.

    Czynienie tego sprawiłoby jedynie, że kod będzie trudniejszy do zrozumienia.

    To wskazuje na kolejną lekcję:

    6. Optymalizuj tylko wtedy, gdy istnieje rzeczywisty problem

    Nie zabieraj się za optymalizację z powodu:

    "Ktoś w internecie powiedział, że ta technika jest szybsza."

    Zamiast tego najpierw zlokalizuj prawdziwy wąskie gardło.

    Czy komponent jest renderowany zbyt często?

    Czy wywołanie API jest wolne?

    Czy obliczenie jest rzeczywiście kosztowne?

    Czy rozmiar pliku jest zbyt duży?

    Czy zapytanie do bazy danych jest nieefektywne?

    Zidentyfikuj prawdziwy problem, zanim cokolwiek zmienisz.

    Dopiero wtedy powinna nastąpić optymalizacja.

    Czysty kod plus niepotrzebna optymalizacja równają się niepotrzebnej złożoności.

    Problem nr 7: Przestanie prośba AI o „naprawienie wszystkiego”

    To może być najważniejsza lekcja z takiego projektu.

    W pewnym momencie pojawia się pokusa, by po prostu powiedzieć:

    "Proszę po prostu uporządkować cały projekt dla mnie."

    Ale warto powstrzymać się od tego.

    Cóż takiego oznacza „uporządkowanie” w tym kontekście?

    SZI mogłaby odpowiedzieć, m.in. poprzez:

    • przenazwanie zmiennych i plików
    • przeorganizowanie struktury folderów
    • wprowadzenie nowych abstrakcji
    • połączenie funkcji ze sobą
    • usunięcie kodu, który uznaje za niepotrzebny
    • przebudowę architektury

    Część tych zmian rzeczywiście może pomóc.

    Inne mogą być bezużyteczne.

    A jeszcze inne mogą potajemnie wprowadzić błędy.

    Lepszy jest wolniejszy, etapowy podejście.

    Najpierw należy zapytać:

    "Proszę spojrzeć na ten projekt, ale jeszcze nic nie zmieniać."

    Następnie:

    "Proszę wskazać wszelką powtórnioną logikę."

    Potem:

    "Powiedz mi, które z tych duplikatów naprawdę warto poprawić."

    Dopiero potem należy przejrzeć sugestie.

    Następnie wprowadza się jedną zmianę.

    Potem przeprowadza się testy.

    To staje się siódmą zasadą:

    7. Zrozum przed refaktoryzacją

    Nigdy nie pozwól sztucznej inteligencji edytować kodu, którego wcześniej w pełni nie zrozumiałeś.

    Zacznij od analizy.

    Następnie upewnij się, że rozumiesz logikę.

    Potem zdecyduj, co robić.

    Następnie wprowadź zmianę.

    Potem sprawdź ją za pomocą testu.

    Co pokazuje strona z pizzą

    Ciekawe jest to, że klienci rzadko pytają:

    ">Czy twój kod jest czysty?"

    Zapytanie jest zazwyczaj prostsze:

    ">Czy strona działa?"

    I często tak jest.

    Mimo to deweloperzy mają odpowiedzialność wykraczającą poza tę pierwszą działającą wersję.

    Oprogramowanie rzadko pozostaje nieruchome po jego wypuszczeniu.

    W końcu klient wraca z prośbą:

    ">Czy możemy dodać śledzenie dostawy?"

    Następnie:

    ">Zróbmy kod rabatowy."

    Potem:

    ">Potrzebujemy obsługi wielu gałęzi."

    Następnie:

    ">Dodajmy konta dla personelu restauracji."

    Później:

    ">Chcielibyśmy mieć jakieś raporty."

    Niedługo później ta mała strona z pizzą przekształciła się w pełnowartościowy system.

    To właśnie w tym momencie czysty kod zaczyna usprawiedliwiać włożony wysiłek.

    Sztuczna inteligencja nie była winna

    Warto być w tym przypadku sprawiedliwym.

    Sztuczna inteligencja nie ponosi winy.

    Jest naprawdę cenny podczas takiego procesu tworzenia.

    Pomaga on w:

    • szybszym działaniu
    • testowaniu różnych podejść
    • zajmowaniu się powtarzalnymi zadaniami programistycznymi
    • wykrywaniu błędów
    • budowaniu komponentów
    • rozważaniu rozwiązań

    Prawdziwym problemem nie jest to, że sztuczna inteligencja generuje kod.

    Problemem jest to, że generowany kod czasami zostaje przyjęty bez wystarczająco dokładnej analizy.

    To dwa zupełnie różne problemy.

    Zmodyfikowany proces pracy przy programowaniu z użyciem AI

    Po takim projekcie sensowne jest zmianę podejścia.

    Proces może wyglądać mniej więcej w ten sposób:

    Understand the requirement
              ↓
    Explore existing code
              ↓
    Plan the solution
              ↓
    Ask AI for implementation
              ↓
    Review the generated code
              ↓
    Simplify
              ↓
    Test
              ↓
    Refactor if necessary
    

    Sztuczna inteligencja nadal zajmuje się dużą częścią faktycznego pisania kodu.

    Jednak większa część myślenia wraca do programisty.

    Wydaje się, że to właśnie w tym równowadze zmierza rozwój oprogramowania jako całości.

    Sztuczna inteligencja pisze kod, ale baza kodu nadal należy do ciebie

    To jest główny wniosek z całego tego doświadczenia.

    Gdy sztuczna inteligencja zaczyna tworzyć twój kod, łatwo jest założyć:

    "Sztuczna inteligencja to napisała, więc musi rozumieć, co robi."

    To założenie nie jest słuszne.

    Własność bazy kodu pozostaje przy tobie.

    To ty będziesz nią dalej zarządzać.

    To ty będziesz musiał znajdować błędy, gdy się pojawią.

    To ty będziesz musiał wyjaśnić, jak to działa, komuś innemu.

    To ty wrócisz do niej po kilku miesiącach i będziesz musiał ją ponownie zrozumieć.

    W pewnym momencie inny programista może otworzyć projekt i zastanowić się:

    ">Jaki był uzasadniony powód takiego podejścia?"

    Idealnie rzecz biorąc, sam kod powinien jasno przedstawić tę logikę.

    Siedem nawyków czystego kodu, których warto przestrzegać

    Podsumowując całe to doświadczenie, oto to, co zapadło mi w pamięć:

    1. DRY

    2. Jedna odpowiedzialność

    3. Wczesne zwracanie wartości

    4. Wykorzystuj to, co już istnieje, zanim stworzysz coś nowego

    5. Dobry nazewnictwo

    6. Optymalizuj na podstawie dowodów

    7. Zrozum najpierw, zanim refaktoryzujesz

    Sztuczna inteligencja może proponować modyfikacje, ale decyzja o tym, które z nich warto wdrożyć, należy do ciebie.

    Podsumowanie

    Gdy rozpoczynałem projekt strony z pizzą, zakładałem, że główną korzyścią z pomocy sztucznej inteligencji będzie:

    szybkość działania.

    Patrząc wstecz, doszedłem do wniosku, że istnieje większa korzyść.

    Sztuczna inteligencja uwalnia zasoby umysłowe na te aspekty rozwoju, które naprawdę wymagają osądu.

    Zamiast tracić energię na rutynowe zadania, możemy skierować tę energię na lepsze pytania:

    Czy to rzeczywiście właściwe podejście?

    Czy można to uprościć?

    Czy coś takiego już istnieje w projekcie?

    Czy ten projekt przetrwa po dodaniu nowej funkcji?

    Czy inny programista byłby w stanie to zrozumieć i kontynuować?

    To jest prawdziwa trudność w pracy z rozwojem wspomagany przez sztuczną inteligencję.

    Sztuczna inteligencja jest w stanie niemal natychmiast wygenerować setki linijek kodu.

    Potwierdzenie, że te linijki rzeczywiście zasługują na swoje miejsce w bazie kodu, nadal spoczywa na nas.

    Być może to jest zaktualizowana definicja czystego kodu, skoro sztuczna inteligencja jest już częścią procesu:

    To, że kod działa, to nie meta. Kluczowe jest umiejętność jego późniejszej konserwacji.

    Literatura pokrewna

  • Trzy patterny TypeScript, które ulepszają architekturę aplikacji React — Dowiedz się, w jaki sposób patterny Repository, Observer i Builder wykorzystują system typów TypeScript do tworzenia czystszych i łatwiejszych w utrzymaniu kodów w React i Next.js.