Strona główna / Artykuły / Projektowanie komponentów frontend w oparciu o odpowiedzialności, a nie o możliwość ponownego użycia

Projektowanie komponentów frontend w oparciu o odpowiedzialności, a nie o możliwość ponownego użycia

Dowiedz się, dlaczego organizowanie komponentów frontend według jasnych zasad przypisania własności i odpowiedzialności – zamiast maksymalnego powtarzania kodu – ułatwia izolację i konserwację zmian w funkcjach.

3171 słów

Frontend staje się łatwiejszy w utrzymaniu, gdy granice komponentów są określane na podstawie jasno zdefiniowanych obowiązków, a nie tego, ile kodu można teoretycznie udostępnić wspólnie.

Problem z naszym frontendem nigdy nie polegał na tym, że poszczególne komponenty stały się zbyt duże. Większość z nich była rzeczywiście kompaktowa.

Mieliśmy solidną bibliotekę ponownie używalnych przycisków, kart, okien modalnych, elementów tabeli, kontrolek formularza, hooków, narzędzi API, wspólnych schematów oraz uporządkowaną strukturę folderów. Patrząc na repozytorium, wszystko wyglądało dobrze zorganizowane. Ponowne użycie elementów było powszechne, oczywiste duplikaty rzadkie, a poziom abstrakcji nadawał bazie kodu wygląd dojrzałości.

Prawdziwy problem pojawiał się za każdym razem, gdy konieczna była zmiana jakiejś funkcjonalności.

Nawet niewielki wymóg mógł zmusić nas do jednoczesnego przeszukiwania kilku niepowiązanych ze sobą warstw: komponentu na poziomie ekranu, formularza o zastosowaniu ogólnym, logiki wspólnej zapakowanej w hooka, kodu sieciowego, okna dialogowego przeznaczonego do wielu scenariuszy użycia oraz schematu służącego do walidacji danych wejściowych. Komponent, który początkowo był wspólny, ponieważ dwa ekrany wyglądały podobnie, stopniowo gromadził flagi, właściwości callback, warunkowe reguły walidacji, alternatywne układy oraz specjalne przypadki dotyczące procesów pracy, o których w ogóle nie miał wiedzieć.

Kod był wielokrotnie używalny, jednak odpowiedzialność była rozproszona w całym systemie.

To, co ostatecznie uproszczyło frontend, to nie nowy schemat nazywania folderów, inna biblioteka do zarządzania stanem ani ścisły limit liczby linii na komponent. Była to zmiana w tym, co oczekiwaliśmy, że rzeczywiście reprezentuje granica komponentu.

Zamiast pytać tylko:

Czy ten komponent można ponownie wykorzystać w innym miejscu?

Zaczęliśmy się zastanawiać:

Jaka odpowiedzialność powinien obejmować ten komponent?

A drugie pytanie szybko stało się równie ważne:

Gdy ta odpowiedzialność musi ulec zmianie, ile niepowiązanego kodu zostaje wciągnięte razem z nią?

To pytanie przekształciło naszą architekturę w prostą hierarchię, w której warstwa środkowa pełni największą rolę.

Komponenty wielokrotnego użycia zajmowały się wyłącznie prezentacją. Komponenty funkcjonalne zawierały informacje o procesach biznesowych. Strony oraz inne granice kompozycji decydowały o tym, jak wszystkie elementy łączą się ze sobą.

Praca nad interfejsem użytkownika stała się łatwiejsza, nie dlatego, że usunęliśmy całą duplikację, ale dlatego, że zmiany na poziomie poszczególnych funkcji były bardziej skoncentrowane, a mniej części bazy kodu musiało ze sobą współpracować.

1. Ponowne wykorzystanie nie jest tym samym co prostota

„Unikaj duplikowania komponentów” to rada, która generalnie brzmi słusznie.

I często rzeczywiście tak jest.

Gdy dwa ekrany używają tego samego przycisku, pola wprowadzania danych lub okna modalnego, ujednolicenie ich implementacji zmniejsza niespójności i konieczność dodatkowej obsługi.

Problemy pojawiają się, gdy podobieństwo wizualne jest mylone z wspólną odpowiedzialnością.

Pomyśl o dwóch oddzielnych funkcjach, które obie wymagają okna potwierdzenia.

Pierwsza implementacja mogłaby wyglądać mniej więcej tak:

<ConfirmationDialog
  title="Delete project?"
  onConfirm={deleteProject}
/>

Następnie inny proces wymaga tego samego okna, tylko bez przycisku anulowania.

Trzeci przypadek wymaga specjalnego komunikatu ostrzegawczego.

Kolejny wymaga przeprowadzenia asynchronicznnej weryfikacji, zanim użytkownik będzie mógł potwierdzić.

Niedługo później komponent ewoluuje w coś takiego:

<ConfirmationDialog
  title="Delete project?"
  variant="danger"
  showCancel
  disableConfirm={isDeleting}
  customWarning={warning}
  onBeforeConfirm={validateDeletion}
  onConfirm={deleteProject}
  onSpecialAction={archiveInstead}
  useLegacyLayout={false}
/>

Technicznie rzecz biorąc, komponent nadal jest wykorzystywany wielokrotnie.

Jednak pod względem architektonicznym zaczął pełnić funkcję języka konfiguracji.

Każdy nowy proces pracy prosi o wspólny komponent o obsługę kolejnej wariacji. Staje się on bardziej elastyczny, ale ta elastyczność przychodzi kosztem ukrywania coraz większej ilości zachowań za rosnącym zestawem właściwości.

Dokładnie tutaj koszt abstrakcji odbiega od kosztu duplikacji.

Duplikacja daje się od razu zauważyć – dwa niemal identyczne komponenty znajdują się obok siebie w repozytorium, doskonale widoczne dla każdego, kto czyta kod.

Nieodpowiednio dobrane abstrakcje na pierwszy rzut oka wyglądają tanio. Ich prawdziwy koszt ujawnia się dopiero później, gdy funkcje, które obsługują, zaczynają rozwijać się w różnych kierunkach.

Duplikacja kosztuje cię coś, co można od razu zauważyć. Wybór niewłaściwej abstrakcji kosztuje cię coś, czego zauważasz dopiero wtedy, gdy „podobne” funkcje przestają zmieniać się synchronicznie.

To uświadomienie zmieniło sposób, w jaki ocenialiśmy możliwości ponownego użycia kodu.

Powtarzający się JSX nie wzbudzał już automatycznego podejrzenia. Ważniejsze stało się pytanie, czy dwa fragmenty kodu rzeczywiście dzielą tę samą odpowiedzialność, czy po prostu w danym momencie do siebie przypominają.

2. Zaczęliśmy projektować komponenty wokół odpowiedzialności

Najważniejszą zmianą architektoniczną było pozwolenie drzewu komponentów na odzwierciedlanie rzeczywistej odpowiedzialności zamiast dążenia do maksymalnego ponownego użycia kodu.

Weźmy jako przykład proces konfiguracji.

Każda warstwa w tym procesie istnieje z własnego, odrębnego powodu.

UserSettingsPage łączy tę funkcjonalność z resztą aplikacji. Może być świadoma routingu, układu strony lub tego, który konto jest obecnie edytowane.

UserSettingsForm przechowuje wiedzę o procesie. Rozumie, jakie dane należą do ustawień użytkownika, w jaki sposób pola są ze sobą powiązane, co oznacza wysłanie danych oraz jak błędy powinny być pokazywane użytkownikowi.

Button, Input i Checkbox w ogóle nie wiedzą, że istnieją ustawienia użytkownika. Są ponownie używalne właśnie dlatego, że ich zadania są rzeczywiście uniwersalne.

To środkowe warstwo funkcjonalności jest tą, którą zespoły najczęściej tracą jako pierwszą.

W interfejsie użytkownika, który zbyt agresywnie dąży do wielokrotnego wykorzystania elementów, deweloperzy często przechodzą bezpośrednio od strony do komponentów ogólnych, omijając wszelkie warstwy specyficzne dla danej funkcji.

Gdy tak się dzieje, zachowania biznesowe tracą wyraźne miejsce realizacji.

Zamiast tego przenikają one do konfiguracji.

Komponenty ogólne zaczynają wiedzieć, że jeden proces wymaga pola na nazwę użytkownika, podczas gdy inny go nie potrzebuje. Ogólne tabele zaczynają rozróżniać, które działania są dopuszczalne dla konkretnego typu obiektu domeny. Wspólny modalny interfejs przejmuje specjalne zachowania tylko dlatego, że jakiś proces akurat wyświetla jego treść wewnątrz modalu.

Warstwa przeznaczona do wielokrotnego wykorzystania stopniowo przyswaja wiedzę o biznesie, którą nigdy nie miała przenosić.

Projektowanie oparte na zasadzie odpowiedzialności odwraca tę presję.

Komponentom specyficznym dla danej funkcji daje się możliwość zrozumienia tej funkcji, którą obsługują.

Prymitywy wielokrotnego użycia są celowo traktowane tak, by nie wiedziały nic o cechach specyficznych dla danej funkcji.

To rozdzielenie ułatwia zrozumienie całej architektury, ponieważ każda warstwa musi przechowywać tylko mniejszą ilość kontekstu.

3. Komponenty do zadań specyficznych zdobyły swoje miejsce

Jednym z trudniejszych nawyków do przezwyciężenia było przekonanie, że komponent używany tylko w jednym miejscu stanowi wadę projektu.

Weźmy takie przykład:

<OrderCancellationDialog />

Samo nazwanie wskazuje, że ten komponent został stworzony do jednego procesu pracy.

Porównaj to z czymś takim:

<ConfirmationDialog />

To drugie nazwanie sugeruje większą możliwość wielokrotnego użycia.

A jednak anulowanie zamówienia może ostatecznie wymagać znacznie więcej niż zwykłej potwierdzenia.

Użytkownik może musieć wybrać powód anulowania. Na ekranie mogą pojawić się szczegóły zwrotu pieniędzy. Niektóre zamówienia mogą nie być podatne na anulowanie. Sprawdzanie uprawnień może się różnić. Komunikaty ostrzegawcze mogą ulegać zmianie w zależności od statusu realizacji. Sama akcja anulowania może mieć własne asynchroniczne zarządzanie błędami.

Gdy wszystko to umieścimy w ogólnej klasie ConfirmationDialog, wspólny komponent stopniowo staje się ekspertem od tego, co tak naprawdę oznacza anulowanie zamówienia.

Lepsza struktura zazwyczaj wygląda w ten sposób:

OrderCancellationDialog przejmuje kontrolę nad całym procesem.

Może ona posiadać informacje o uprawnieńach, powodach anulowania, statusie asynchronicznym, tekście zwrotu pieniędzy oraz stanach błędów, ponieważ wszystko to naturalnie należy do siebie.

Tymczasem elementy niższego poziomu pozostają wielokrotnie używalne właśnie dlatego, że w ogóle nie posiadają żadnej wiedzy o zamówieniach.

To podział zmienił liczbę podejmowanych decyzji dotyczących komponentów.

Nie było już potrzeby uzasadniania istnienia komponentu poprzez liczenie miejsc, w których został on importowany.

Możliwość ponownego użycia nie jest wymogiem dla każdego komponentu. Niektóre komponenty istnieją wyłącznie po to, by dać odpowiednie miejsce dla konkretnej części logiki biznesowej.

Komponent o pojedynczym zastosowaniu nadal może być przydatny, jeśli ułatwia znajdowanie, rozumienie i późniejszą modyfikację procesu pracy.

Taka lokalna przejrzystość często przeważa nad kosztem wprowadzania drugiego, niespowiązanego procesu pracy do wspólnej abstrakcji tylko dlatego, że interfejsy użytkownika wyglądają podobnie.

4. Logika biznesowa pozostała poza komponentami wyświetlania

To samo problemy z rozdziałem obowiązków pojawiły się ponownie w kwestiach obsługi danych i infrastruktury.

Komponent, który początkowo jest czysto wizualny, może stopniowo przejąć takie zadania jak pobieranie danych, odczytywanie parametrów URL, wykonywanie mutacji, uruchamianie powiadomień, obsługa nawigacji, zarządzanie pamięcią cache oraz śledzenie stanu procesu pracy.

Załóżmy ProjectTable, który ostatecznie zajmuje się tym wszystkim:

ProjectTable
 ├── fetch projects
 ├── read query parameters
 ├── filter projects
 ├── manage loading state
 ├── render rows
 ├── delete projects
 ├── show notifications
 └── navigate after actions

Nazwa nadal sugeruje „tabelę”.

Jednak komponent ten teraz rozumie znaczną część funkcjonalności.

To utrudnia jego ponowne użycie w innych miejscach, ponieważ ponowne wykorzystanie tej tabeli pociąga za sobą założenia dotyczące pobierania danych, mutacji, nawigacji, cacheowania oraz efektów ubocznych.

Struktura zorganizowana wokół konkretnych obowiązków mogłaby wyglądać bardziej w ten sposób:

Kod na poziomie funkcji decyduje o tym, jak wygląda „ładowanie”, w jaki sposób radzimy sobie z błędami, co dokładnie powoduje usunięcie danych, jakie filtry mają zastosowanie w tym konkretnym procesie pracy oraz czy użytkownik powinien zostać później przekierowany.

Sam ProjectTable skupia się wyłącznie na wyświetlaniu danych projektu i raportowaniu działań użytkownika.

To nie oznacza, że komponenty prezentacyjne muszą być pozbawione całej logiki.

Traktowanie tego jako sztywnej zasady tylko powoduje ponowne pojawienie się tego samego problemu w innej formie. Tabela może legalnie przechowywać lokalny stan interakcji, np. które wiersze są rozwinięte lub które kolumny są widoczne, ponieważ ten stan rzeczywiście należy do tabeli.

Lepszą zasadą kierującą jest:

Nie pozwól, by wiedza na poziomie infrastruktury rozprzestrzeniała się w górę drzewa komponentów dalej, niż faktycznie wymaga tego funkcja.

Celem nie jest całkowite usunięcie logiki z komponentów.

Celem jest umieszczenie logiki blisko tej odpowiedzialności, która nadaje jej sens.

5. Składanie elementów zamiast zmieniania opcji

Jedna z najbardziej widocznych zmian nastąpiła w momencie, gdy przestaliśmy odpowiadać na każde nowe wymaganie kolejną właściwością.

Komponenty ogólne mają tendencję do rozrastania się poprzez konfigurację.

Tabela może zaczynać się skromnie:

<DataTable rows={projects} columns={columns} />

Następnie wymagania się gromadzą:

<DataTable
  rows={projects}
  columns={columns}
  selectable
  sortable
  paginated
  editableRows
  showBulkActions
  enableExport
  customToolbar={toolbar}
  rowActions={rowActions}
  emptyState={emptyState}
  onSelectionChange={handleSelection}
/>

Żadna z tych właściwości sama w sobie nie jest nierozsądna.

Problemy pojawiają się, gdy komponent musi śledzić, które kombinacje właściwości są w ogóle ważne.

Czy wiersze mogą być jednocześnie edytowalne i wybieralne?

Czy akcje masowe powinny się pojawiać, jeśli eksport jest wyłączony?

Czy niestandardowa paska narzędzi zastępuje tę domyślną, czy stoi obok niej?

Czy paginacja jest obsługiwana po stronie klienta, czy przez serwer?

Każdy dodany parametr logiczny i funkcja zwrotna zwiększa liczbę stanów, które komponent generyczny musi uwzględnić.

Kompozycja przenosi część tej odpowiedzialności z powrotem na osobę wywołującą:

<Table>
  <TableToolbar>
    <ProjectFilters />
    <ExportButton />
  </TableToolbar>
<ProjectRows projects={projects} />
  <Pagination />
</Table>

Teraz to komponent nadrzędny decyduje, które elementy są faktycznie potrzebne w tym konkretnym procesie.

Podstawowe elementy tabeli nie muszą uwzględniać każdej specyficznej wersji produktu, jaką aplikacja mogłaby kiedyś stworzyć.

Konfiguracja wymaga, aby komponent przewidywał każdą możliwą kombinację. Kompozycja pozwala osobie wywołującej stworzyć tylko tę kombinację, która jest jej faktycznie potrzebna.

Kompozycja nie zapobiega automatycznie tworzeniu przez kogoś czegoś bezsensownego. Osoba wywołująca nadal może stworzyć nieprawidłową kombinację.

To, co się zmienia, jest inne: komponent współdzielony nie musi już samodzielnie kodować każdej specyficznej dla danego biznesu wersji.

Część konfiguracji nadal może być bez problemu zachowana. Na przykład wielokrotnie używany Button powinien obsługiwać spójne opcje takie jak rozmiar, stan wyłączenia czy wizualne podkreślenie.

Prawdziwe pytanie brzmi, czy dana właściwość reprezentuje kolejną wariację tej samej podstawowej funkcji, czy też po cichu zmusza jeden komponent do jednoczesnego obsługi kilku niepowiązanych zadań.

6. Jasna odpowiedzialność uproszcza decyzje dotyczące stanu

Większość problemów z stanem w interfejsie użytkownika na pierwszy rzut oka przypomina problemy z narzędziami.

Rozmowa zazwyczaj przeradza się w debatę na temat mechanizmów:

Czy to powinno znajdować się w stanie komponentu, Context, Zustand, Redux, URL czy w pamięci serwera?

W praktyce jednak wiele z tych problemów rozwiązuje się samo, gdy dowiadujemy się, kto faktycznie jest odpowiedzialny za dane zachowanie.

Stan ma tendencję do wzrostu, gdy prawa własności nie są jasne:

Modalny element zaczyna od stanu znajdującego się w jego obrębie.

Następnie inny komponent również musi go uruchomić, więc stan jest przenoszony.

Później potrzebuje go również pasek narzędzi znajdujący się daleko w drzewie struktury, więc trafia on do kontekstu.

Niedługo potem pojedynczy globalny magazyn przechowuje flagi widoczności modalnych elementów, nieukończone projekty formularzy, ustawienia filtrów tabeli, zapisane odpowiedzi API, aktywne wybory kart oraz różnorakie szczegóły z funkcji, które nie mają ze sobą nic wspólnego.

W tym momencie ludzie zazwyczaj obwiniają bibliotekę do zarządzania stanem za ten bałagan.

Jednak prawdziwym problemem zazwyczaj nie jest narzędzie, lecz kwestia prawa własności.

Gdy granice komponentów już odzwierciedlają rzeczywiste obowiązki, stan aplikacji ma naturalnie swoje odpowiednie miejsce przechowywania.

Cokolwiek użytkownik obecnie wpisuje do pola, może pozostać w tym samym polu.

Proces anulowania składający się z kilku kroków powinien znajdować się w ramach funkcji anulowania.

Wszystko, co jest pobierane z serwera, powinno trafiać tam, gdzie aplikacja zajmuje się kwestiami związanymi ze stanem serwera.

Filtr, który musi być przechowywany między różnymi ekranami lub móc być udostępniany za pomocą linku, najprawdopodobniej powinien znajdować się w adreście URL.

Stan, który jest faktycznie dzielony pomiędzy wieloma niepowiązanymi częściami aplikacji, może wymagać bardziej zcentralizowanego zarządzania nim.

Żadne z tych zasad nie sprawia, że decyzje dotyczące stanu aplikacji znikną, ale dostarczają ramy do ich podejmowania.

Gdy granice komponentów odzwierciedlają rzeczywiste odpowiedzialności, decydowanie o miejscu przechowywania stanu staje się znacznie prostsze.

To model mentalny przyniósł zespołowi więcej korzyści niż jakakolwiek „poprawna” biblioteka stanów.

7. Czasami duplikacja była lepszym rozwiązaniem

Najtrudniejsze do zaakceptowania w tym podejściu było to, że pewna ilość duplikacji jest w porządku, a nawet pożądana.

Załóżmy dwa formularze, które wyglądają niemal identycznie i dzielą mniej więcej 80 procent swojej struktury oraz kodu.

Pierwotnym odruchem jest natychmiastowe połączenie ich w jeden wspólny komponent.

Jednak te pozostałe 20 procent może zawierać zupełnie inną logikę biznesową.

Może jeden formularz sprawdza dane inaczej.

Może wysłanie danych w jednym formularzu wywołać inny ciąg działań niż w drugim.

Jeden może jedynie zapisywać szkic.

Drugi może uruchomić coś nieodwracalnego.

Pierwszy formularz z czasem prawdopodobnie stanie się bardziej złożony, podczas gdy drugi powinien pozostać prosty.

Próba wymuszenia użycia obu rozwiązań w jednej wspólnej implementacji zwykle skutkuje czymś, co przypomina labirynt warunków i specjalnych przypadków wbudowanych w jeden „używalny” komponent.

W takiej sytuacji zamiast duplikowanego JSX mamy do czynienia z podwójnym obciążeniem poznawczym. Każdy, kto pracuje z którymkolwiek z tych procesów, musi najpierw przeanalizować, w jaki sposób wspólny komponent chroni drugi, zanim będzie mógł bezpiecznie cokolwiek zmienić.

W takich przypadkach często lepiej jest po prostu zachować dwa oddzielne, nazwane elementy:

FeatureAForm
FeatureBForm

mimo że część podstawowego kodu się powtarza.

To nie jest argument za duplikowaniem jako zasadą ogólną.

Gdy rzeczywiście wspólna odpowiedzialność stanie się oczywista i stabilna, można ją wyodrębnić. Oba rozwiązania mogą ostatecznie korzystać z tych samych elementów wejściowych, narzędzi walidacji, otoczeń układu lub innych elementów niezależnych od konkretnego domeny.

Rzeczywista różnica dotyczy timingu, a nie zasady.

Zamiast uciekać się do abstrakcji w momencie, gdy dwie rzeczy wyglądają podobnie, sensowniej było czekać, aż wspólna granica rzeczywiście się sprawdzi z biegiem czasu.

Z tego wynikła zasada ogólna:

Lepiej kopiować prosty kod niż dzielić się logiką opartą na założeniach, które wciąż się rozwijają.

Zwykły skopiowany kod jest łatwy do zauważenia i znajduje się w jednym miejscu.

Z drugiej strony abstrakcja stworzona zbyt wcześnie może potajemnie połączyć kilka specyficznych dla danej funkcji założeń pod jedną pozornie uporządkowaną nazwą, ukrywając właśnie tę złożoność, której próbowano się pozbyć.

8. O czym tak naprawdę chodziło: utrzymywanie zmian w lokalnym zakresie

Ostatecznie stało się jasne, że całe to podejście wcale nie dotyczyło tego, jak duży lub wielokrotnie używalny jest komponent.

Chodziło o to, jak można wprowadzać zmiany w sposób zlokalizowany.

Pytanie, które warto zadawać za każdym razem, brzmiało:

Gdy zmieniamy jedną funkcjonalność, ile niezwiązanych z nią kodów musimy najpierw zrozumieć?

Projekt z większą liczbą wspólnych, uogólnionych komponentów może technicznie mieć mniej powtórzonych fragmentów kodu niż projekt składający się z większej liczby elementów specyficznych dla danej funkcjonalności.

Jednak nadal może to zmusić programistę do zapamiętywania znacznie większej części całego oprogramowania, aby móc dokonać bezpiecznej edycji.

Taki rodzaj kosztu rzadko pojawia się w żadnych metrykach dotyczących ponownego wykorzystania kodu.

Można mieć doskonale nadający się do ponownego użycia komponent, który mimo to tworzy kiepskie warunki do wprowadzania zmian.

Z drugiej strony komponent stworzony specjalnie do jednej funkcji i używany tylko w jednym miejscu może nadal poprawić utrzymywalność bazy kodu, po prostu dlatego, że utrzymuje ten jeden proces pracy w formie samodzielnej i łatwej do zrozumienia.

To okazało się najprecyzyjniejszym sformułowaniem całej idei:

Projektuj granice komponentów wokół lokalnego rozumowania, a nie wokół maksymalizacji możliwości ponownego użycia.

To jedno zasada łączy ze sobą wszystko inne.

Współdzielone elementy bazowe zasługują na swoje miejsce, ponieważ wzorce prezentacji rzeczywiście powtarzają się w całej bazie kodu.

Komponenty specyficzne dla danej funkcji pozostają takie, ponieważ procesy biznesowe zmieniają się z powodów, które rzadko kiedy są ze sobą powiązane.

Kompozycja zapobiega temu, by współdzielone komponenty musiały uczyć się każdej możliwej wariacji biznesowej.

Stan ustala się w pobliżu tego zachowania, które faktycznie nim zarządza.

A duplikacja staje się do przyjęcia dokładnie wtedy, gdy udostępnianie kodu mogłoby połączyć założenia, które i tak same się od siebie oddalają.

Frontend staje się prostszy nie dlatego, że każdy komponent stał się mniejszy lub bardziej wielokrotnie używalny, ale dlatego, że każdy z nich wymaga mniej od osoby, która go czyta.

Odpowiednia granica komponentu to taka, którą można wyjaśnić, gdy coś się zmienia

Kuszące jest ocenianie bazy kodu frontendu na podstawie powierzchownych wskaźników: intensywne wielokrotne wykorzystywanie komponentów, minimalna duplikacja, schludnie zorganizowane foldery, małe rozmiary plików. Te elementy nie są bez znaczenia, ale zaskakująco mało mówią o tym, co naprawdę dzieje się w momencie zmiany wymagań.

Gdzie właściwie należy ten element zachowania?

Który komponent jest odpowiedzialny za zrozumienie tej reguły biznesowej?

Gdzie powinien znajdować się ten konkretny element oprogramowania państwowego?

Jeśli zmieni się jakieś wymaganie, które pliki powinny logicznie zostać połączone?

Które części interfejsu użytkownika są rzeczywiście wielokrotnie używalne, a które powinny pozostać specyficzne dla jednej funkcji?

Wzorzec, który ostatecznie uproszczył ten interfejs użytkownika, nie był żadną nowatorską techniką abstrakcji.

Chodziło o to, by każda warstwa bazy kodu miała mniej rzeczy do śledzenia.

Wielokrotnie używalne elementy bazowe były odpowiedzialne za prezentację.

Komponenty funkcjonalne były odpowiedzialne za procesy pracy.

Grenice kompozycji decydowały o tym, jak funkcje ze sobą współpracują.

A komponenty specyficzne dla biznesu mogły pozostać dokładnie takimi, jakie były: specyficznymi.

Otrzymana baza kodu niekoniecznie miała mniej komponentów ani teoretycznie minimalną ilość duplikatów.

To, co faktycznie uzyskano, to baza kodu, w której programista mógł pracować nad pojedynczą funkcjonalnością bez konieczności najpierw zapamiętywania całej architektury frontendu.

Z twojego doświadczenia – czy większa liczba komponentów wielokrotnie używalnych lub jaśniejsze granice pomiędzy istniejącymi komponentami bardziej przyczyniły się do ułatwienia utrzymania frontendu?

Literatura pokrewna

  • Praktyczne porównanie wzorców struktury folderek w React — Wyjaśnia struktury projektów React oparte na funkcjach, warstwach i domenie oraz daje wskazówki dotyczące wyboru odpowiedniej struktury w miarę rozwoju aplikacji.
  • Projektowanie dla cache’u wstecznego/przódniego: kryteria dopuszczalności, restauracja i stan — Dowiedz się, jak cache przeglądarki wsteczny/przódni przywraca całe strony, co go cichо blokuje oraz jak obsługiwać przywrócony stan za pomocą funkcji pageshow i pagehide.