Ruby 2.3: zamrożone literały łańcuchowe i operator bezpiecznej nawigacji
Ruby 2.3.0 ląduje w Boże Narodzenie z dwiema zmianami, które będziesz pisać codziennie — pragmą frozen-string-literal i operatorem bezpiecznej nawigacji &. — plus dig, did_you_mean i więcej. Praktyczny przegląd.
Ruby 2.3.0 przybyło w Boże Narodzenie, jak to ma w zwyczaju, i to wydanie pełne jest małych funkcji, które faktycznie będziesz pisać. Tam, gdzie ostatnie parę wydań było głównie o garbage collectorze pracującym ciężej za kulisami, 2.3 zmienia kod, który piszesz — nowy operator, nowa pragma na poziomie pliku, parę naprawdę przydatnych metod i przyjaźniejsze doświadczenie, gdy coś idzie nie tak. Oto praktyczny przegląd.
Operator bezpiecznej nawigacji: &.
To nagłówek, zapożyczony z C# i Groovy, gdzie od dawna zarabia na swoje. „Samotny
operator” &. woła metodę tylko, jeśli odbiorca nie jest nil, zwracając nil
zamiast wybuchać:
# przedtem — obronny taniec
name = user && user.profile && user.profile.name
# Ruby 2.3
name = user&.profile&.name
Jeśli user jest nil, całe wyrażenie zwiera się do nil zamiast podnosić
NoMethodError: undefined method 'profile' for nil. To zabija jeden z
najczęstszych, brzydkich wzorców w Ruby — łańcuch strażników && albo wywołania
try(:method), po które sięgają programiści Rails. Czyta się czysto i mówi dokładnie
to, co znaczy: „zawołaj to, jeśli jest na czym zawołać”.
Słowo ostrożności, bo łatwo go nadużyć: &. to narzędzie do przypadków, gdzie nil
jest prawowitą, oczekiwaną wartością (opcjonalna asocjacja, konfiguracja, której może
nie być). To nie sposób na zatuszowanie nil-i, które nigdy nie powinny wystąpić —
jeśli wartość będąca nil wskazuje błąd, chcesz, by NoMethodError go ujawnił, a nie
&. połknął go w cichy nil, który zawodzi myląco trzy warstwy dalej. Używaj go do
obsługi oczekiwanej nieobecności, nie do ukrywania nieoczekiwanej.
Zamrożone literały łańcuchowe: pragma i przyszłość
Drugą dużą zmianą jest pragma frozen_string_literal — magiczny komentarz na górze
pliku, który czyni każdy literał łańcuchowy w nim zamrożonym (niezmiennym):
# frozen_string_literal: true
GREETING = "hello" # ten łańcuch jest zamrożony — nie da się go zmutować
GREETING << " world" # => RuntimeError: can't modify frozen String
Są dwa powody, dla których to ma znaczenie. Pierwszy to wydajność: w Ruby każdy literał łańcuchowy normalnie alokuje zupełnie nowy obiekt za każdym razem, gdy jest ewaluowany, więc literał w gorącej pętli tworzy śmieci przy każdym przejściu. Zamrożony literał może być pojedynczym cache’owanym, współdzielonym, niezmiennym obiektem — mniej alokacji, mniejsza presja GC. Dla literałów używanych jako klucze hashy i stałe na gorących ścieżkach to realna oszczędność. Drugi to poprawność: niezmiennych łańcuchów nie da się przypadkowo zmutować przez jakiś odległy kod trzymający referencję, co usuwa kategorię błędów typu spooky-action-at-a-distance.
W 2.3 jest to opt-in per plik przez pragmę i to wyraźnie kierunek podróży —
długoterminowym planem jest, by zamrożone literały łańcuchowe stały się domyślne w
przyszłym Ruby. Przyjęcie pragmy teraz zabezpiecza twoje pliki na przyszłość i zarabia
korzyść wydajnościową już dziś. Jedna korekta: gdzie naprawdę potrzebujesz mutowalnego
łańcucha, zaalokuj go jawnie przez String.new albo +"..." (unarny plus zwraca
mutowalną kopię).
dig: bezpieczne sięganie w zagnieżdżone struktury
Hash#dig i Array#dig są małe, ale będziesz ich używać nieustannie. Przechodzą
zagnieżdżoną strukturę sekwencją kluczy, zwracając nil w chwili, gdy brakuje
któregokolwiek poziomu, zamiast podnosić błąd:
data = { user: { address: { city: "Kraków" } } }
data.dig(:user, :address, :city) # => "Kraków"
data.dig(:user, :company, :name) # => nil (bez błędu na brakującym :company)
# vs stare, kruche:
data[:user][:company][:name] # => NoMethodError na nil[:name]
To idea bezpiecznej nawigacji zastosowana do struktur danych i jest darem niebios dla
głęboko zagnieżdżonych hashy, które dostajesz z parsowania odpowiedzi JSON API, gdzie
któregokolwiek poziomu może brakować. Jedno wywołanie zastępuje stertę sprawdzeń &&
albo łańcuchów fetch.
did_you_mean: literówki dostają podpowiedź
Wygrana w jakości życia dla wszystkich: gem did_you_mean jest teraz dołączony i
włączony domyślnie, więc błędnie napisana nazwa metody lub zmiennej wraca z sugestią
zamiast gołego błędu:
NoMethodError: undefined method `lenght' for "hello":String
Did you mean? length
To drobiazg oszczędzający realny czas, zwłaszcza dla nowicjuszy, i nic cię nie kosztuje.
Mniejsze dodatki
Jeszcze kilka wartych wiedzy:
Enumerable#grep_vto odwrotnośćgrep— wybierz wszystko, co nie pasuje:lines.grep_v(/^#/)odrzuca linie komentarzy.Comparable#clampogranicza wartość do zakresu:score.clamp(0, 100)zamiast zagnieżdżonych wywołańmin/max.Hash#to_procpozwala hashowi działać jako blok wyszukiwania:ids.map(&lookup_hash)mapuje każde id przez hash.- Narzędzia deprecjacji „zamrożonych łańcuchów” (
--enable-frozen-string-literali tryb debug) pomagają znaleźć kod, który by się zepsuł pod przyszłym zachowaniem frozen-by-default.
Czy aktualizować?
Tak — i nietypowo, to wydanie, którego wartość pokazuje się w twoim edytorze, nie
tylko w metrykach. Operator bezpiecznej nawigacji i dig posprzątają kod obsługi nil,
który piszesz codziennie; pragma frozen_string_literal zarabia korzyść wydajnościową
i przygotowuje cię na przyszły domyślny wybór; a did_you_mean po cichu czyni każdy
komunikat błędu bardziej pomocnym. Nic z tego nie jest ryzykowne do przyjęcia — &.,
dig i przyjemności są czysto addytywne, a pragma zamrożonych łańcuchów jest opt-in
per plik, więc możesz wdrażać ją stopniowo.
Wzorzec ostatnich wydań Ruby trwa: runtime wciąż robi się szczuplejszy, a język wciąż
dostaje małe, ostre usprawnienia ergonomii, które czynią codzienny kod czystszym. 2.3
to szczególnie miła ich porcja — rodzaj wydania, gdzie miesiąc po aktualizacji
user&.profile&.name i data.dig(...) już wydają się, jakby zawsze tam były.