Kod Buggy Rails: 10 najczęstszych błędów popełnianych przez programistów Rails

Opublikowany: 2022-03-11

Ruby on Rails („Rails”) to popularna platforma open source, oparta na języku programowania Ruby, która ma na celu uproszczenie i usprawnienie procesu tworzenia aplikacji internetowych.

Railsy są zbudowane na zasadzie konwencji nad konfiguracją. Mówiąc najprościej, oznacza to, że Railsy domyślnie zakładają, że ich doświadczeni programiści będą postępować zgodnie ze „standardowymi” konwencjami najlepszych praktyk (w przypadku takich rzeczy jak nazewnictwo, struktura kodu itd.), a jeśli to zrobisz, wszystko będzie działać dla Ciebie „auto -magicznie” bez konieczności określania tych szczegółów. Chociaż ten paradygmat ma swoje zalety, nie jest też pozbawiony pułapek. Przede wszystkim „magia”, która dzieje się za kulisami w ramach, może czasami prowadzić do fałszerstw, zamieszania i „co do cholery się dzieje?” rodzaje problemów. Może mieć również niepożądane konsekwencje w zakresie bezpieczeństwa i wydajności.

W związku z tym, chociaż Railsy są łatwe w użyciu, nie jest też trudno je nadużywać. Ten samouczek omawia 10 typowych problemów Rails, w tym sposoby ich unikania i problemy, które powodują.

Powszechny błąd nr 1: Umieszczanie zbyt dużej ilości logiki w kontrolerze

Railsy są oparte na architekturze MVC. W społeczności Rails od jakiegoś czasu rozmawiamy o grubym modelu, chudym kontrolerze, ale kilka ostatnich aplikacji Railsowych, które odziedziczyłem, naruszyło tę zasadę. Bardzo łatwo jest przenieść logikę widoku (która lepiej mieści się w programie pomocniczym) lub logikę domeny/modelu do kontrolera.

Problem polega na tym, że obiekt kontrolera zacznie naruszać zasadę pojedynczej odpowiedzialności, czyniąc przyszłe zmiany w bazie kodu trudnymi i podatnymi na błędy. Ogólnie rzecz biorąc, jedyne typy logiki, które powinieneś mieć w swoim kontrolerze, to:

  • Obsługa sesji i plików cookie. Może to również obejmować uwierzytelnianie/autoryzację lub jakiekolwiek dodatkowe przetwarzanie plików cookie, które musisz wykonać.
  • Wybór modelu. Logika znajdowania właściwego obiektu modelu na podstawie parametrów przekazanych z żądania. Idealnie powinno to być wywołanie pojedynczej metody find ustawiającej zmienną wystąpienia, która ma być później użyta do renderowania odpowiedzi.
  • Zażądaj zarządzania parametrami. Zbieranie parametrów żądania i wywoływanie odpowiedniej metody modelu w celu ich utrwalenia.
  • Renderowanie/przekierowywanie. Odpowiednio renderowanie wyniku (html, xml, json itp.) lub przekierowanie.

Chociaż to wciąż przesuwa granice zasady pojedynczej odpowiedzialności, jest to swego rodzaju absolutne minimum, którego wymaga od nas framework Rails w kontrolerze.

Powszechny błąd nr 2: umieszczanie zbyt dużej logiki w poglądzie

Gotowy do użycia silnik szablonów Rails, ERB, to świetny sposób na tworzenie stron ze zmienną treścią. Jednakże, jeśli nie będziesz ostrożny, możesz wkrótce otrzymać duży plik będący mieszanką kodu HTML i Ruby, który może być trudny w zarządzaniu i utrzymaniu. Jest to również obszar, który może prowadzić do wielu powtórzeń, co prowadzi do naruszenia zasad DRY (nie powtarzaj się).

Może się to objawiać na wiele sposobów. Jednym z nich jest nadużywanie logiki warunkowej w widokach. Jako prosty przykład rozważmy przypadek, w którym mamy dostępną metodę current_user , która zwraca aktualnie zalogowanego użytkownika. Często w plikach widoku pojawią się warunkowe struktury logiczne, takie jak ta:

 <h3> Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %> </h3>

Lepszym sposobem poradzenia sobie z czymś takim jest upewnienie się, że obiekt zwracany przez current_user jest zawsze ustawiony, niezależnie od tego, czy ktoś jest zalogowany, czy nie, oraz że odpowiada na metody użyte w widoku w rozsądny sposób (czasami określane jako null obiekt). Na przykład, możesz zdefiniować pomocnika current_user w app/controllers/application_controller w następujący sposób:

 require 'ostruct' helper_method :current_user def current_user @current_user ||= User.find session[:user_id] if session[:user_id] if @current_user @current_user else OpenStruct.new(name: 'Guest') end end

Umożliwiłoby to zastąpienie poprzedniego przykładu kodu widoku tym jednym prostym wierszem kodu:

 <h3>Welcome, <%= current_user.name -%></h3>

Kilka dodatkowych zalecanych najlepszych praktyk Railsów:

  • Używaj odpowiednio układów widoków i części, aby zawrzeć elementy, które powtarzają się na Twoich stronach.
  • Użyj prezenterów/dekoratorów, takich jak klejnot Draper, aby zawrzeć logikę budowania widoku w obiekcie Ruby. Następnie możesz dodać metody do tego obiektu, aby wykonać operacje logiczne, które w innym przypadku zostałyby umieszczone w kodzie widoku.

Powszechny błąd nr 3: Wstawianie zbyt dużej ilości logiki do modelu

Biorąc pod uwagę wskazówki, jak zminimalizować logikę w widokach i kontrolerach, jedynym miejscem, które pozostało w architekturze MVC, aby umieścić całą tę logikę, byłyby modele, prawda?

Cóż, nie do końca.

Wielu programistów Rails faktycznie popełnia ten błąd i kończy wszystko w swoich klasach modeli ActiveRecord , co prowadzi do plików mongo, które nie tylko naruszają zasadę pojedynczej odpowiedzialności, ale są także koszmarem konserwacji.

Funkcjonalności, takie jak generowanie powiadomień e-mail, łączenie się z usługami zewnętrznymi, konwersja do innych formatów danych i tym podobne, nie mają wiele wspólnego z podstawową odpowiedzialnością modelu ActiveRecord , który powinien robić niewiele więcej niż wyszukiwanie i utrwalanie danych w bazie danych.

Więc jeśli logika nie powinna trafiać do widoków, do kontrolerów i do modeli, to gdzie powinna iść?

Wprowadź zwykłe stare obiekty Ruby (PORO). Przy tak rozbudowanym frameworku, jak Rails, nowi programiści często niechętnie tworzą własne klasy poza frameworkiem. Jednak przeniesienie logiki z modelu do PORO jest często tym, co zalecił lekarz, aby uniknąć zbyt skomplikowanych modeli. Dzięki PORO możesz umieszczać takie rzeczy jak powiadomienia e-mail lub interakcje API w ich własnych klasach, zamiast umieszczać je w modelu ActiveRecord .

Mając to na uwadze, ogólnie rzecz biorąc, jedyna logika, która powinna pozostać w twoim modelu, to:

  • Konfiguracja ActiveRecord (tj. relacje i walidacje)
  • Proste metody mutacji do enkapsulacji aktualizacji kilku atrybutów i zapisywania ich w bazie danych
  • Uzyskaj dostęp do opakowań , aby ukryć informacje o modelu wewnętrznym (np. metoda full_name , która łączy pola first_name i last_name w bazie danych)
  • Wyrafinowane zapytania (tj. bardziej złożone niż proste find ); ogólnie rzecz biorąc, nigdy nie należy używać metody where ani żadnych innych metod budowania zapytań, poza samą klasą modelu
Powiązane: 8 pytań do wywiadu dotyczącego Essential Ruby on Rails

Powszechny błąd nr 4: Używanie ogólnych klas pomocniczych jako wysypiska śmieci

Ten błąd jest tak naprawdę konsekwencją błędu nr 3 powyżej. Jak wspomniano, framework Rails kładzie nacisk na nazwane komponenty (tj. model, widok i kontroler) frameworka MVC. Istnieją dość dobre definicje rodzajów rzeczy, które należą do klas każdego z tych komponentów, ale czasami możemy potrzebować metod, które wydają się nie pasować do żadnej z trzech.

Generatory Rails dogodnie budują katalog pomocników i nową klasę pomocniczą, aby pasowała do każdego nowego zasobu, który tworzymy. Jednak zbyt kuszące jest, aby zacząć upychać jakąkolwiek funkcjonalność, która formalnie nie pasuje do modelu, widoku lub kontrolera do tych klas pomocniczych.

Chociaż Railsy są z pewnością skoncentrowane na MVC, nic nie stoi na przeszkodzie, aby tworzyć własne typy klas i dodawać odpowiednie katalogi do przechowywania kodu dla tych klas. Gdy masz dodatkowe funkcje, zastanów się, które metody grupują się razem i znajdź dobre nazwy dla klas, które przechowują te metody. Korzystanie z kompleksowego frameworka, takiego jak Rails, nie jest wymówką, by pozwolić, aby dobre praktyki projektowania zorientowanego obiektowo odeszły na bok.

Powszechny błąd nr 5: używanie zbyt wielu klejnotów

Ruby i Rails są obsługiwane przez bogaty ekosystem klejnotów, które łącznie zapewniają niemal każdą możliwość, o której może pomyśleć programista. Jest to świetne do szybkiego tworzenia złożonej aplikacji, ale widziałem też wiele rozdętych aplikacji, w których liczba klejnotów w Gemfile aplikacji jest nieproporcjonalnie duża w porównaniu z oferowaną funkcjonalnością.

Powoduje to kilka problemów z Railsami. Nadmierne użycie klejnotów sprawia, że ​​rozmiar procesu Railsowego jest większy niż powinien. Może to spowolnić wydajność w produkcji. Oprócz frustracji użytkowników może to również skutkować potrzebą większych konfiguracji pamięci serwera i zwiększonymi kosztami operacyjnymi. Uruchamianie większych aplikacji Railsowych zajmuje również więcej czasu, co spowalnia rozwój i sprawia, że ​​testy automatyczne trwają dłużej (i z reguły powolne testy po prostu nie są uruchamiane tak często).

Pamiętaj, że każdy klejnot, który wniesiesz do swojej aplikacji, może z kolei być uzależniony od innych klejnotów, a te z kolei mogą być zależne od innych klejnotów i tak dalej. Dodanie innych klejnotów może zatem mieć efekt kumulacyjny. Na przykład dodanie klejnotu rails_admin przyniesie w sumie 11 więcej klejnotów, ponad 10% wzrost w stosunku do podstawowej instalacji Rails.

W chwili pisania tego tekstu, nowa instalacja Rails 4.1.0 zawiera 43 klejnoty w pliku Gemfile.lock . Jest to oczywiście więcej niż jest zawarte w Gemfile i reprezentuje wszystkie klejnoty, które kilka standardowych klejnotów Rails wprowadza jako zależności.

Ostrożnie zastanów się, czy dodatkowe koszty ogólne są warte zachodu, gdy dodajesz każdy klejnot. Jako przykład, programiści często od niechcenia dodają klejnot rails_admin , ponieważ zasadniczo zapewnia on ładny interfejs sieciowy do struktury modelu, ale tak naprawdę nie jest niczym więcej niż wyszukanym narzędziem do przeglądania baz danych. Nawet jeśli Twoja aplikacja wymaga administratorów z dodatkowymi uprawnieniami, prawdopodobnie nie chcesz dawać im surowego dostępu do bazy danych i lepiej byłoby, gdybyś opracował własną, bardziej uproszczoną funkcję administracyjną, niż dodając ten klejnot.

Częsty błąd nr 6: Ignorowanie plików dziennika

Podczas gdy większość programistów Railsów jest świadomych istnienia domyślnych plików dziennika dostępnych podczas rozwoju i produkcji, często nie zwracają wystarczającej uwagi na informacje w tych plikach. Podczas gdy wiele aplikacji w środowisku produkcyjnym korzysta z narzędzi do monitorowania dzienników, takich jak Honeybadger lub New Relic, ważne jest również, aby mieć oko na pliki dziennika przez cały proces tworzenia i testowania aplikacji.

Jak wspomniano wcześniej w tym samouczku, framework Rails robi dla ciebie wiele „magii”, szczególnie w modelach. Definiowanie skojarzeń w modelach bardzo ułatwia tworzenie relacji i udostępnianie wszystkiego swoim widokom. Cały kod SQL potrzebny do wypełnienia obiektów modelu jest generowany dla Ciebie. To wspaniale. Ale skąd wiesz, że generowany kod SQL jest wydajny?

Jednym z przykładów, na który często się natkniesz, jest problem zapytania N+1. Chociaż problem jest dobrze zrozumiany, jedynym realnym sposobem zaobserwowania jego występowania jest przejrzenie zapytań SQL w plikach dziennika.

Załóżmy na przykład, że masz następujące zapytanie w typowej aplikacji blogowej, w której będziesz wyświetlać wszystkie komentarze dla wybranego zestawu postów:

 def comments_for_top_three_posts posts = Post.limit(3) posts.flat_map do |post| post.comments.to_a end end

Kiedy spojrzymy na plik dziennika żądania, które wywołuje tę metodę, zobaczymy coś takiego jak poniżej, gdzie jedno zapytanie jest wykonywane, aby uzyskać trzy obiekty post, a następnie trzy kolejne zapytania, aby uzyskać komentarze każdego z tych obiektów:

 Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700 Processing by PostsController#some_comments as HTML Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]] Rendered posts/some_comments.html.erb within layouts/application (12.5ms) Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)

Możliwość szybkiego ładowania ActiveRecord w Railsach umożliwia znaczne zmniejszenie liczby zapytań, pozwalając z góry określić wszystkie asocjacje, które mają zostać załadowane. Odbywa się to poprzez wywołanie metody includes (lub preload ) na budowanym obiekcie Arel ( ActiveRecord::Relation ). Dzięki includes , ActiveRecord zapewnia, że ​​wszystkie określone asocjacje są ładowane przy użyciu minimalnej możliwej liczby zapytań; np:

 def comments_for_top_three_posts posts = Post.includes(:comments).limit(3) posts.flat_map do |post| post.comments.to_a end end

Po wykonaniu powyższego poprawionego kodu widzimy w pliku dziennika, że ​​wszystkie komentarze zostały zebrane w jednym zapytaniu zamiast w trzech:

 Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700 Processing by PostsController#some_comments as HTML Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3) Rendered posts/some_comments.html.erb within layouts/application (12.2ms) Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)

Dużo wydajniejszy.

To rozwiązanie problemu N+1 jest tak naprawdę tylko przykładem tego rodzaju nieefektywności, które mogą istnieć „pod maską” w twojej aplikacji, jeśli nie zwracasz odpowiedniej uwagi. Wniosek jest taki, że powinieneś sprawdzać pliki dziennika programowania i testowania podczas programowania, aby sprawdzić (i rozwiązać!) Nieefektywności w kodzie, który buduje twoje odpowiedzi.

Przeglądanie plików dziennika to świetny sposób na uzyskanie informacji o nieefektywności w kodzie i poprawienie ich przed przejściem aplikacji do środowiska produkcyjnego. W przeciwnym razie możesz nie być świadomy wynikającego z tego problemu z wydajnością Railsów, dopóki system nie zostanie uruchomiony, ponieważ zestaw danych, z którym pracujesz w fazie rozwoju i testów, jest prawdopodobnie znacznie mniejszy niż w środowisku produkcyjnym. Jeśli pracujesz nad nową aplikacją, nawet produkcyjny zestaw danych może zaczynać się jako mały, a aplikacja będzie wyglądać, jakby działała poprawnie. Jednak w miarę jak Twój produkcyjny zestaw danych rośnie, problemy Rails, takie jak ten, spowodują, że Twoja aplikacja będzie działać coraz wolniej.

Jeśli zauważysz, że twoje pliki dziennika są zatkane mnóstwem niepotrzebnych informacji, oto kilka rzeczy, które możesz zrobić, aby je wyczyścić (techniki tam działają zarówno w przypadku programowania, jak i dzienników produkcyjnych).

Powszechny błąd nr 7: Brak testów automatycznych

Ruby i Rails domyślnie zapewniają potężne możliwości testów automatycznych. Wielu programistów Railsów pisze bardzo wyrafinowane testy przy użyciu stylów TDD i BDD i korzysta z jeszcze potężniejszych frameworków testowych z takimi perełkami jak rspec i cucumber.

Pomimo tego, jak łatwo jest dodać automatyczne testowanie do aplikacji Railsowej, byłem bardzo niemile zaskoczony, jak wiele projektów odziedziczyłem lub do których dołączyłem, w których dosłownie nie było napisanych żadnych testów (lub w najlepszym razie bardzo niewiele) przez poprzednie zespół programistów. Chociaż toczy się wiele dyskusji na temat tego, jak wszechstronne powinny być twoje testy, jest całkiem jasne, że przynajmniej niektóre automatyczne testy powinny istnieć dla każdej aplikacji.

Zgodnie z ogólną zasadą, dla każdej akcji w kontrolerach powinien być napisany co najmniej jeden wysokopoziomowy test integracji. W pewnym momencie w przyszłości inni programiści Rails najprawdopodobniej będą chcieli rozszerzyć lub zmodyfikować kod lub zaktualizować wersję Ruby lub Rails, a ten framework testowy zapewni im jasny sposób weryfikacji, czy podstawowa funkcjonalność aplikacji jest pracujący. Dodatkową korzyścią tego podejścia jest to, że zapewnia przyszłym programistom jasne wytyczenie pełnego zestawu funkcji zapewnianych przez aplikację.

Częsty błąd nr 8: Blokowanie połączeń z usługami zewnętrznymi

Zewnętrzni dostawcy usług Rails zwykle bardzo ułatwiają integrację swoich usług z Twoją aplikacją za pomocą klejnotów, które otaczają ich API. Ale co się stanie, jeśli Twoja usługa zewnętrzna przestanie działać lub zacznie działać bardzo wolno?

Aby uniknąć blokowania tych wywołań, zamiast wywoływania tych usług bezpośrednio w aplikacji Railsowej podczas normalnego przetwarzania żądania, powinieneś przenieść je do jakiegoś rodzaju usługi kolejkowania zadań w tle, jeśli to możliwe. Niektóre popularne perełki używane w aplikacjach Rails do tego celu to:

  • Opóźniona praca
  • Resque
  • Sidekiq

W przypadkach, w których delegowanie przetwarzania do kolejki zadań w tle jest niepraktyczne lub niewykonalne, należy upewnić się, że aplikacja ma wystarczającą obsługę błędów i przepisy dotyczące przełączania awaryjnego w tych nieuniknionych sytuacjach, gdy usługa zewnętrzna przestanie działać lub wystąpią problemy . Powinieneś także przetestować swoją aplikację bez usługi zewnętrznej (być może usuwając serwer, na którym znajduje się Twoja aplikacja z sieci), aby sprawdzić, czy nie powoduje to żadnych nieprzewidzianych konsekwencji.

Powszechny błąd nr 9: małżeństwo z istniejącymi migracjami baz danych

Mechanizm migracji bazy danych Railsów pozwala na tworzenie instrukcji do automatycznego dodawania i usuwania tabel i wierszy bazy danych. Ponieważ pliki zawierające te migracje są nazywane w sposób sekwencyjny, można je odtwarzać od początku czasu, aby przenieść pustą bazę danych do tego samego schematu co produkcja. Jest to zatem świetny sposób na zarządzanie szczegółowymi zmianami w schemacie bazy danych aplikacji i uniknięcie problemów Rails.

Chociaż z pewnością działa to dobrze na początku twojego projektu, w miarę upływu czasu proces tworzenia bazy danych może zająć trochę czasu i czasami migracje zostają niewłaściwie umieszczone, wstawione niewłaściwie lub wprowadzone z innych aplikacji Rails korzystających z tego samego serwera bazy danych.

Railsy tworzą reprezentację aktualnego schematu w pliku o nazwie db/schema.rb (domyślnie), który jest zwykle aktualizowany podczas migracji bazy danych. Plik schema.rb można wygenerować nawet wtedy, gdy nie ma migracji, uruchamiając zadanie rake db:schema:dump . Częstym błędem Railsów jest sprawdzanie nowej migracji w repozytorium źródłowym, ale nie odpowiednio zaktualizowanego pliku schema.rb .

Gdy migracje wymknęły się spod kontroli i trwają zbyt długo lub nie tworzą już poprawnie bazy danych, programiści nie powinni obawiać się wyczyszczenia starego katalogu migracji, zrzucenia nowego schematu i kontynuowania od tego miejsca. Skonfigurowanie nowego środowiska programistycznego wymagałoby wtedy rake db:schema:load zamiast rake db:migrate , na którym polega większość programistów.

Niektóre z tych kwestii są również omówione w Przewodniku po szynach.

Powszechny błąd nr 10: sprawdzanie poufnych informacji w repozytoriach kodu źródłowego

Framework Rails ułatwia tworzenie bezpiecznych aplikacji odpornych na wiele rodzajów ataków. Część z tego osiąga się za pomocą tajnego tokena do zabezpieczenia sesji z przeglądarką. Mimo że ten token jest teraz przechowywany w config/secrets.yml i ten plik odczytuje token ze zmiennej środowiskowej dla serwerów produkcyjnych, poprzednie wersje Railsów zawierały token w config/initializers/secret_token.rb . Plik ten często omyłkowo zostaje zaewidencjonowany w repozytorium kodu źródłowego wraz z resztą aplikacji, a gdy tak się stanie, każdy, kto ma dostęp do repozytorium, może teraz łatwo narazić wszystkich użytkowników aplikacji .

Dlatego powinieneś upewnić się, że plik konfiguracyjny repozytorium (np. .gitignore dla użytkowników git) wyklucza plik z Twoim tokenem. Twoje serwery produkcyjne mogą następnie pobrać swój token ze zmiennej środowiskowej lub z mechanizmu, takiego jak ten, który zapewnia gem dotenv.

Podsumowanie samouczka

Rails to potężny framework, który ukrywa wiele brzydkich szczegółów niezbędnych do zbudowania solidnej aplikacji internetowej. Chociaż to sprawia, że ​​tworzenie aplikacji webowych w Railsach jest znacznie szybsze, programiści powinni zwracać uwagę na potencjalne błędy w projektowaniu i kodowaniu, aby upewnić się, że ich aplikacje są łatwo rozszerzalne i konserwowalne w miarę ich rozwoju.

Deweloperzy muszą również zdawać sobie sprawę z problemów, które mogą sprawić, że ich aplikacje będą wolniejsze, mniej niezawodne i mniej bezpieczne. Ważne jest, aby przestudiować strukturę i upewnić się, że w pełni rozumiesz kompromisy dotyczące architektury, projektu i kodowania, które wprowadzasz w trakcie procesu tworzenia, aby zapewnić wysoką jakość i wysoką wydajność aplikacji.

Powiązane: Jakie są zalety Ruby on Rails? Po dwóch dekadach programowania używam Rails