Kategoria:TypeScript

Problem czytelności w TypeScript - jak go rozwiązać?

  • Czas potrzebny na przeczytanie:5 minut
  • Opublikowane:24 maja 2022

Serio, uwielbiam ten język. Nie wyobrażam sobie pracy w czystym JavaScripcie, ale jest kilka rzeczy, które mnie wkurzają w TS. Jedną z większych jest czytelność. Tak, coś co miało być zaletą, jest jednocześnie dużym problemem. Jak pisać czytelniejszy kod w TypeScript?

Czytelność

Zacznijmy z grubej rury i zobaczymy przykładową funkcję w Haskellu:

hasPath :: [(Int, Int)] -> Int -> Int -> Bool
hasPath [] x y  = x == y
hasPath xs x y
  | x == y      = True
  | otherwise   =
    let xs' = [ (n,m) | (n,m) <- xs, n /= x ]
    in
      or [ hasPath xs' m y | (n, m) <- xs, n == x ]

Nic nie ogarniasz? Nie musisz. Osoby, które są zaznajomione z tym językiem są "przyzwyczajone" do nazywania niektórych rzeczy np. w ten sposób: xs.

Zanim wszyscy popukamy się w głowę i wyjdziemy z pochodniami na ulicę, żeby przepędzić entuzjastów programowania funkcyjnego, wróćmy na nasze rodzime podwórko.

document.addEventListener('click', (e) => {});

JavaScript Developerzy są przyzwyczajeni do przekazywania e zamiast event i większość z nas nie ma z tym problemu. Dochodzimy więc do wniosku, że czytelność to tak naprawdę rzecz umowna, uzelażniona od pewnych przyzwyczajeń, nie jest uniwersalna.

W dalszej części artykułu skupiam się bardziej na przyjętych w środowisku JS/TS "dobrych praktykach" i niestosowaniu się do nich w pewnych częściach języka.

Typy generyczne

W codziennej pracy staramy się nazywać odpowiednio funkcje, obiekty i zmienne, tak, aby drugi programista wiedział co w danej linijce mieliśmy na myśli. W tym całym "czystym kodzie" zapominamy często o typach! Widoczne to jest najczęściej w typach generycznych, gdzie argument deklarujemy jako <T>. Ta praktyka została zapożyczona z C# i... po prostu tak zostało.

O ile w prostych przykładach możemy tego nie dostrzegać, tak przy bardziej zaawnsowanych, często warunkowych/mapowanych typach, problem znacząco się nasila...

Powyższe, skromne pięć linijek kodu może przyprawić niejednego programistę o ból głowy. Pomijając już samą "logikę" tego typu, to c'mon, kto w normalnych funkcjach nazywałby tak swoje argumenty? Prosta zmiana nazw i kod staje się nieco bardziej zrozumiały:

Oczywiście, jak to już bywa w naszej branży, od każdej reguły istnieją jakieś wyjątki, nic nie jest czarno-białe. Dla mnie takim "marginesem błędu" jest tworzenie ogólnych, często mapowanych typów. Zresztą wbudowane, pomocnicze typy zostały właśnie w ten sposób zaimplementowane przed twórców samego języka:

type Partial<T> = { [P in keyof T]?: T[P] | undefined };

Błędy

Niech pierwszy rzuci kamieniem ten, który nie złapał się za głowę widząc błąd z TypeScripta. Nie ma to, jak dostać na klatę errorem na pół ekranu...

Nieczytelny błąd kompilacji w TypeScript

Na przestrzeni lat powstała nawet niepisana (bardzo przydatna) porada dla początkujących - "Czytaj tylko ostatnią linijkę błędu". Na szczęście na rynku pojawiają się takie perełki jak TypeScript Error Translator, które przyjmują wypluty błąd i przekształcają go na zrozumiałą dla człowieka informację.

Przedstawienie działania TypeScript Error Translator, do pola tekstowego przekazujemy błąd kompilacji, a w wyniku otrzymujemy wyjaśnienie problemu w prostym do zrozumienia języku

Twórca tego narzędzia przygotował nawet specjalne rozszerzenie do VSCode, nic tylko brać, czystego złoto!

Prefixy

A na sam koniec mały, subiektywny bonusik. Otóż praktyką, którą również zapożyczyliśmy z innych języków jest dodawania I lub T do analogicznie interfejsów i typów. Czy programiści naoglądali się za dużo Impl w Javie, czy też po prostu uznali, że jednoznakowy prefix będzie dobrym materiałem na konwencję nazewniczą?

Części osób skojarzy się to na pewno z nieco prehistoryczną już notacją węgierską. Ta konwencja nazewnicza została oryginalnie wprowadzona, by adresować typ danego wyrażenia. C od klasy, A od klas abstrakcyjnych, s od ciągu znaków itp. Przekładając to na nasze, programistyczne dinozaury na pewno pamiętają $ z jQuery.

Kolejna sprawa, czyli lenistwo programistów. Nie ukrywajmy, nazywanie rzeczy w programowaniu nie należy do najłatwiejszych. Korzystanie z prefixów ułatwia bardzo sprawę, ale może warto czasem się na chwilę zatrzymać i pokminić nad lepszą nazwą? :)

Żeby nie było, że tylko hejtuję. Sam uciekam się często w pewnym sensie o odwróconą, wspomnianą Impl w Javie, tylko że w React, deklarując propsy:

interface ComponentProps {}

const Component = () => {};

Jednak to dla mnie trochę coś innego niż prosty prefix/postfix. W tym przypadku faktycznie mówimy, czym dany interfejs jest, nie uciekając się do przyzwyczajeń z I.

Chociaż wspomniana technika do mnie nie przemawia, to z konwencjami nazewniczymi jest trochę tak, że jakby zła ona nie była, to i tak ważniejsza jest konsekwencja z kodzie...

Podsumowanie

TypeScript, przecież miało być czytelniej... a wyszło jak zawsze, czyli tak średnio.

Na szczęście są na to sposoby i z małą pomocą programistów, niektóre problemy mogą odejść w zapomnienie.

Źródła

O autorze

Olaf Sulich

Olaf jest Frontend Developerem, blogerem i nosi rybacki kapelusz 🎩 Pisze o wszystkim co związane z frontendem, ale nie boi się backendu i designów 🦾 Ma głowę pełną pomysłów i nadzieję, że znajdziesz tutaj coś dla siebie!

Frontlive School

Wyróżnij się na rynku pracy, nie wydając ani złotówki!

Wchodzę

Dołącz do społeczności!

Bo w programowaniu liczą się ludzie

Wchodzę