Powszechnym trendem, który zaobserwowałem w projektach Elm, jest to, że Elm prowadzi Cię w stronę prostych rozwiązań. Przekonałem się, że sprawdza się to nawet w przypadku osób, które dopiero zaczynają przygodę z Elm. Prowadziłem coaching zespołu w środowisku korporacyjnym, adoptując Elma do nowego projektu. Zbudowali poprzedni projekt z AngularJS i naprawdę mieli z nim problemy. A raczej nie zmagali się z tym. Framework zaakceptuje każde rozwiązanie, jakie wymyślą. Kiedy rozpoczęli współpracę z Elmem, ustalenie, „w jaki sposób przechowywać dane wejściowe z pola wejściowego w moim modelu”, mogło zająć im nieco więcej czasu. Ale ponieważ Elm nie pozwolił im na użycie tymczasowych hacków, ich podejście było zupełnie inne. Zamiast kopiować i wklejać kod ze StackOverflow, który używa zmiennej globalnej lub poprawia DOM, „tylko po to, aby na razie działał”, Elm zmusił ich do znalezienia rozwiązania bez polegania na jakichkolwiek hackach. W rezultacie kilka miesięcy później zamiast walczyć z niestabilną bazą kodu, której zmiany trudno jest zmienić, pracowano z niezwykle niezawodnym kodem. Mogli łatwo i bez strachu wprowadzać zmiany. Różnica polegała na nocy i dniu.

Niedawno miałem podobne doświadczenie, które ilustruje, jak Elm prowadzi cię w stronę prostoty. Jestem autorem pakietu Elm dla bezpiecznych typów zapytań GraphQL, dillonkearns/elm-graphql (zobacz moją ostatnią dyskusję w Elm Conf, aby dowiedzieć się więcej). Zacząłem otrzymywać tę samą prośbę o funkcję od kilku różnych użytkowników. To była rozsądna prośba, ale kiedy się pojawiła, wyjaśniłem wyzwania techniczne i znaczenie przeprojektowania w celu jej obsługi.

Wyzwanie: Unikalne klucze

Zapytania GraphQL otrzymują odpowiedzi JSON, przy czym odpowiedzi mają ten sam „kształt” co zapytanie. Więc to zapytanie:

{
  currentUser {
    avatar
  }
}

zwróci JSON w ten sposób:

{
  "currentUser": {
    "avatar": "https://avatar.com/user.jpg"
  }
}

Jedynym zastrzeżeniem jest to, że jeśli zażądasz wielu pól o tej samej nazwie i różnych argumentach, musisz podać aliasy pól, aby wyraźnie powiedzieć GraphQL, pod jakim kluczem ma przekazać te dane.

{
  currentUser {
    avatar(size: 12)
    avatar(size: 48)
  }
}
# ERROR - Field 'avatar' has an argument conflict: is size 12 or 48?

Powyższe zapytanie powoduje błąd, ponieważ jest niejednoznaczne.

dillonkearns/elm-graphql obsługuje szczegóły niskiego poziomu tworzenia i deserializacji zapytań GraphQL, dzięki czemu 1) można używać funkcji języka Elm wysokiego poziomu zamiast skomplikowanej biblioteki oraz 2) może zagwarantować, że każde utworzone zapytanie będzie prawidłowe. Dlatego biblioteka potrzebuje sposobu, aby uniknąć tworzenia takich niejednoznacznych i nieprawidłowych zapytań.

Oryginalne rozwiązanie

Mój poprzedni algorytm zapewniający, że w razie potrzeby pola miały aliasy, polegał na zwiększaniu licznika i używaniu tej liczby dla każdego aliasu pola.

Stosując to podejście, moja biblioteka wygenerowałaby zapytanie takie jak to:

{
  currentUser {
    avatar(size: 12)
    avatar2: avatar(size: 48)  # this alias tells GraphQL to return this data under the key "avatar2"
  }
}

To zadziałało całkiem dobrze. Ale kiedy utworzyłeś zestaw selekcji jak powyżej, chwytając małe i średnie awatary, konieczne było zbudowanie odpowiedniego „Dekodera Jsona” w oparciu o to, co wiedział w tym momencie (dekodery Json to sposób, w jaki Elm deserializuje JSON do wpisanych danych Elm) . Nie mogłem więc udostępnić interfejsu API umożliwiającego połączenie dwóch takich zbiorów wyboru, ponieważ spowodowałoby to zbudowanie dekodera w celu wyodrębnienia danych z jego aliasu avatar2. Gdybyśmy jednak połączyli inny zestaw pól zawierający awatara średniego i pełnowymiarowego, ich dekodery Json próbowałyby uzyskać te dane z pola o nazwie avatar i avatar2, podczas gdy zamiast tego powinny szukać ich w polach avatar3 i avatar4. Mógłbym rozwiązać ten problem, przeprojektowując kod w celu wygenerowania deserializatorów (dekoderów Jsona) w ostatniej chwili, ale oznaczałoby to utworzenie niestandardowej struktury danych, aby móc to opóźnić. Dodałoby to znacznie więcej złożoności i punktów awarii, w których mógłbym wprowadzić błąd.

Szukasz prostego podejścia

Naprawdę obawiałem się pomysłu zrobienia tego przeprojektowania. Jasne, było to wykonalne, ale wprowadzanie tak dużej złożoności w celu rozwiązania tak prostego problemu nie wydawało się właściwe. Chciałem przemyśleć projekt biblioteki, zarówno pod kątem jej publicznego interfejsu API, jak i implementacji niskiego poziomu. Często robię to w Elm. Gdybym rozwiązywał ten problem w innym języku, takim jak TypeScript, łatwo byłoby dodać jakiś stan globalny, aby zliczać, ile razy utworzono pola o określonej nazwie. Z wcześniejszych doświadczeń z tym podejściem wynika, że ​​prowadzi to do raportów o błędach, testów z fałszywie pozytywnymi i negatywnymi wynikami oraz do tego, że inne osoby dotykają kodu i nie rozumieją, skąd bierze się to dziwne zachowanie lub co muszą zrobić, aby go właściwie zachować. To jest problem ze stanem ukrytym. Bardzo przydatne jest szybkie rozwiązywanie problemów. Ale na dłuższą metę zaczynasz odczuwać prawdziwy koszt. Stan jest tak naprawdę tym, co sprawia, że ​​kod jest trudny do zrozumienia i zmiany, a czytanie/zmiana kodu to czynność, na której spędzamy większość godzin kodowania. Jeśli więc uda nam się zminimalizować stan, będzie to ogromna wygrana!

Mógłbym stworzyć jakiś stan w mojej aplikacji Elm, aby rozwiązać ten problem, ale musiałoby to być wyraźne i dlatego jest bardziej nudne niż w innych językach. Jest oczywiście wiele miejsc, w których potrzebujesz stanu w aplikacji Elm, ale jesteś bardziej wyczulony na to, ile masz stanu i ile miejsc zależy od tego stanu ze względu na jego jednoznaczność. Ostatecznie stwierdziłem, że jest to doskonały kompromis, choć może być frustrujący, gdy chcesz „po prostu sprawić, by coś działało szybko”. Jeśli chodzi o łatwość konserwacji, to wyraźne podejście opłaca się nawet w krótkim okresie.

Prostsze rozwiązanie

W końcu wpadłem na pomysł użycia aliasów pól opartych na skrótach:

{
  currentUser {
    avatarABC: avatar(size: 12)
    avatarXYZ: avatar(size: 48)
  }
}

Generując unikalny skrót na podstawie argumentów pola (wyobraź sobie, że ABC jest reprezentacją skrótu (size: 12)), masz gwarancję uniknięcia niejednoznacznych zapytań. I nie musisz wiedzieć, które pola są używane w otaczającym zapytaniu! To rozwiązanie jest znacznie prostsze i mniej podatne na błędy, gdyż wystarczy podać dane pole, aby określić jego alias. Nie trzeba podglądać jego rodzeństwa ani martwić się, czy później zyska więcej rodzeństwa. Możesz przeczytać więcej o szczegółach ostatecznej implementacji w tym numerze Githuba.

Konkluzja

Dlaczego więc jest to tak ważne? Jedną z kluczowych cech, których szukam w języku, frameworku, technice kodowania itp., jest to, czy zmierza on w kierunku prostoty. Na przykład, jeśli refaktoryzacja za pomocą języka lub frameworka jest żmudna lub podatna na błędy, zrobisz to rzadziej. Jeśli wydaje się kuloodporny i łatwy jak w Elm, kod będzie miał tendencję do bycia łatwiejszym w utrzymaniu. Z biegiem czasu bazy kodów Elm mają silną tendencję do posiadania stanu minimalnego. A mniej stanu oznacza mniej ruchomych części, tańsze zmiany i mniej błędów.

Oferuję bezpłatne wstępne rozmowy z Elmem. Napisz do mnie, jeśli Twój zespół chce dowiedzieć się więcej o Elmie! Lub podziel się komentarzem, jeśli masz podobne doświadczenia z językiem lub frameworkiem, który poprowadził Cię do prostszego rozwiązania.