Ошибка в коде Rails: 10 самых распространенных ошибок, которые допускают разработчики Rails

Опубликовано: 2022-03-11

Ruby on Rails («Rails») — это популярная платформа с открытым исходным кодом, основанная на языке программирования Ruby, которая стремится упростить и оптимизировать процесс разработки веб-приложений.

Rails построен по принципу соглашения, а не конфигурации. Проще говоря, это означает, что по умолчанию Rails предполагает, что его опытные разработчики будут следовать «стандартным» передовым практикам (в таких вещах, как имена, структура кода и т. д.), и, если вы это сделаете, все будет работать для вас «автоматически». -волшебным образом» без необходимости указывать эти детали. Хотя эта парадигма имеет свои преимущества, она также не лишена и подводных камней. В частности, «волшебство», которое происходит за кулисами во фреймворке, иногда может приводить к обману, путанице и «что, черт возьми, происходит?» виды проблем. Это также может иметь нежелательные последствия в отношении безопасности и производительности.

Соответственно, несмотря на то, что Rails прост в использовании, им нетрудно злоупотребить. В этом руководстве рассматриваются 10 общих проблем Rails, в том числе способы их предотвращения и проблемы, которые они вызывают.

Распространенная ошибка №1: слишком много логики в контроллере

Rails основан на архитектуре MVC. В сообществе Rails мы уже некоторое время говорим о толстой модели и тонком контроллере, но несколько последних приложений Rails, которые я унаследовал, нарушили этот принцип. Слишком просто переместить логику представления (которую лучше разместить в помощнике) или логику домена/модели в контроллер.

Проблема в том, что объект контроллера начнет нарушать принцип единой ответственности, что сделает будущие изменения в кодовой базе сложными и подверженными ошибкам. Как правило, единственными типами логики, которые вы должны иметь в своем контроллере, являются:

  • Обработка сеансов и файлов cookie. Это может также включать аутентификацию/авторизацию или любую дополнительную обработку файлов cookie, которую вам необходимо выполнить.
  • Выбор модели. Логика поиска нужного объекта модели по параметрам, переданным из запроса. В идеале это должен быть вызов одного метода find, устанавливающего переменную экземпляра, которая будет использоваться позже для вывода ответа.
  • Управление параметрами запроса. Сбор параметров запроса и вызов соответствующего метода модели для их сохранения.
  • Рендеринг/перенаправление. Отрисовка результата (html, xml, json и т. д.) или перенаправление, в зависимости от обстоятельств.

Хотя это по-прежнему расширяет границы принципа единой ответственности, это своего рода абсолютный минимум, который требует от нас среда Rails в контроллере.

Распространенная ошибка № 2: слишком много логики в представлении

Готовый механизм шаблонов Rails, ERB, — отличный способ создавать страницы с переменным содержимым. Однако, если вы не будете осторожны, вы можете вскоре получить большой файл, представляющий собой смесь кода HTML и Ruby, которым будет сложно управлять и поддерживать. Это также область, которая может привести к большому количеству повторений, что приведет к нарушению принципов DRY (не повторяйтесь).

Это может проявляться по-разному. Одним из них является чрезмерное использование условной логики в представлениях. В качестве простого примера рассмотрим случай, когда у нас есть доступный метод current_user , который возвращает текущего пользователя, вошедшего в систему. Часто в файлах представлений появляются условные логические структуры, подобные этой:

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

Лучший способ справиться с чем-то подобным — убедиться, что объект, возвращаемый current_user , всегда установлен, независимо от того, вошел ли кто-то в систему или нет, и что он разумно отвечает методам, используемым в представлении (иногда называемым нулевым значением). объект). Например, вы можете определить хелпер current_user в app/controllers/application_controller следующим образом:

 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

Это позволит вам заменить предыдущий пример кода представления этой простой строкой кода:

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

Пара дополнительных рекомендуемых лучших практик Rails:

  • Правильно используйте макеты и части представления, чтобы инкапсулировать элементы, которые повторяются на ваших страницах.
  • Используйте презентаторы/декораторы, такие как гем Draper, для инкапсуляции логики построения представления в объекте Ruby. Затем вы можете добавить в этот объект методы для выполнения логических операций, которые в противном случае вы могли бы поместить в свой код представления.

Распространенная ошибка № 3: слишком много логики в модели

Учитывая руководство по минимизации логики в представлениях и контроллерах, единственным оставшимся местом в архитектуре MVC для размещения всей этой логики будут модели, верно?

Ну, не совсем.

Многие разработчики Rails на самом деле совершают эту ошибку и в конечном итоге вставляют все в свои классы модели ActiveRecord , что приводит к файлам mongo, которые не только нарушают принцип единой ответственности, но и являются кошмаром обслуживания.

Такие функции, как генерация уведомлений по электронной почте, взаимодействие с внешними службами, преобразование данных в другие форматы и т. п., не имеют ничего общего с основной обязанностью модели ActiveRecord , которая должна делать немногим больше, чем поиск и сохранение данных в базе данных.

Итак, если логика не должна идти в представлениях, и не должна идти в контроллерах, и не должна идти в моделях, то куда ей идти?

Введите простые старые объекты Ruby (PORO). С всеобъемлющим фреймворком, таким как Rails, новые разработчики часто неохотно создают свои собственные классы вне фреймворка. Однако перенос логики из модели в PORO часто является именно тем, что доктор прописал, чтобы избежать чрезмерно сложных моделей. С помощью PORO вы можете инкапсулировать такие вещи, как уведомления по электронной почте или взаимодействие с API, в их собственные классы, а не вставлять их в модель ActiveRecord .

Итак, имея это в виду, вообще говоря, единственная логика, которая должна оставаться в вашей модели, такова:

  • Конфигурация ActiveRecord (т. е. отношения и проверки)
  • Простые методы мутации для инкапсуляции обновления нескольких атрибутов и сохранения их в базе данных.
  • Доступ к оболочкам для сокрытия внутренней информации модели (например, метод full_name , который объединяет поля first_name и last_name в базе данных)
  • Сложные запросы (т.е. более сложные, чем простой find ); вообще говоря, вы никогда не должны использовать метод where или любые другие подобные ему методы построения запросов вне самого класса модели.
Связанный: 8 основных вопросов для интервью по Ruby on Rails

Распространенная ошибка № 4: Использование общих вспомогательных классов в качестве свалки

Эта ошибка на самом деле является своего рода следствием ошибки № 3 выше. Как уже говорилось, среда Rails уделяет особое внимание именованным компонентам (т. е. модели, представлению и контроллеру) среды MVC. Существуют довольно хорошие определения того, что относится к классам каждого из этих компонентов, но иногда нам могут понадобиться методы, которые, кажется, не вписываются ни в один из трех.

Генераторы Rails удобно создают вспомогательный каталог и новый вспомогательный класс для каждого нового ресурса, который мы создаем. Однако становится слишком заманчиво начать запихивать в эти вспомогательные классы любую функциональность, которая формально не вписывается в модель, представление или контроллер.

Хотя Rails, безусловно, ориентирован на MVC, ничто не мешает вам создавать собственные типы классов и добавлять соответствующие каталоги для хранения кода этих классов. Когда у вас есть дополнительная функциональность, подумайте, какие методы группируются вместе, и найдите хорошие имена для классов, содержащих эти методы. Использование всеобъемлющего фреймворка, такого как Rails, не является оправданием для отказа от передового опыта объектно-ориентированного проектирования.

Распространенная ошибка № 5: использование слишком большого количества драгоценных камней

Ruby и Rails поддерживаются богатой экосистемой драгоценных камней, которые в совокупности предоставляют практически любые возможности, которые может придумать разработчик. Это отлично подходит для быстрого создания сложного приложения, но я также видел много раздутых приложений, в которых количество драгоценных камней в Gemfile приложения непропорционально велико по сравнению с предоставляемой функциональностью.

Это вызывает несколько проблем с Rails. Чрезмерное использование драгоценных камней делает размер процесса Rails больше, чем он должен быть. Это может снизить производительность в производстве. Помимо разочарования пользователей, это также может привести к необходимости использования больших конфигураций памяти сервера и увеличению эксплуатационных расходов. Также требуется больше времени для запуска больших приложений Rails, что замедляет разработку и увеличивает время выполнения автоматических тестов (и, как правило, медленные тесты просто не запускаются так часто).

Имейте в виду, что каждый драгоценный камень, который вы добавляете в свое приложение, может, в свою очередь, иметь зависимости от других драгоценных камней, а те, в свою очередь, могут иметь зависимости от других драгоценных камней и так далее. Таким образом, добавление других драгоценных камней может иметь комбинированный эффект. Например, добавление rails_admin принесет в общей сложности 11 дополнительных драгоценных камней, что на 10 % больше, чем при базовой установке Rails.

На момент написания этой статьи новая установка Rails 4.1.0 включает 43 гема в файле Gemfile.lock . Это явно больше, чем включено в Gemfile и представляет собой все драгоценные камни, которые горстка стандартных драгоценных камней Rails привносит в качестве зависимостей.

Тщательно продумайте, стоит ли добавлять дополнительные накладные расходы при добавлении каждого драгоценного камня. Например, разработчики часто небрежно добавляют гем rails_admin , потому что он, по сути, обеспечивает хороший веб-интерфейс к структуре модели, но на самом деле это не более чем причудливый инструмент для просмотра базы данных. Даже если вашему приложению требуются пользователи-администраторы с дополнительными привилегиями, вы, вероятно, не захотите давать им необработанный доступ к базе данных, и вам будет лучше разработать собственную более упорядоченную функцию администрирования, чем добавлять этот драгоценный камень.

Распространенная ошибка № 6: игнорирование файлов журнала

Хотя большинство разработчиков Rails знают о файлах журналов по умолчанию, доступных во время разработки и производства, они часто не уделяют достаточного внимания информации в этих файлах. Хотя многие приложения используют инструменты мониторинга журналов, такие как Honeybadger или New Relic, также важно следить за файлами журналов на протяжении всего процесса разработки и тестирования приложения.

Как упоминалось ранее в этом руководстве, среда Rails делает для вас много «волшебства», особенно в моделях. Определение ассоциаций в ваших моделях позволяет очень легко установить отношения и сделать все доступным для ваших представлений. Весь SQL, необходимый для заполнения объектов вашей модели, генерируется для вас. Замечательно. Но откуда вы знаете, что сгенерированный SQL эффективен?

Один из примеров, с которым вы будете часто сталкиваться, называется проблемой запроса N+1. Хотя проблема хорошо изучена, единственный реальный способ увидеть, как она происходит, — просмотреть SQL-запросы в файлах журналов.

Скажем, например, у вас есть следующий запрос в типичном приложении блога, где вы будете отображать все комментарии для выбранного набора сообщений:

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

Когда мы посмотрим на файл журнала запроса, который вызывает этот метод, мы увидим что-то вроде следующего, где делается один запрос для получения трех объектов публикации, а затем выполняются еще три запроса для получения комментариев каждого из этих объектов:

 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)

Возможность активной загрузки ActiveRecord в Rails позволяет значительно сократить количество запросов, позволяя заранее указать все ассоциации, которые будут загружены. Это делается путем вызова метода preload (или includes ) для создаваемого объекта Arel ( ActiveRecord::Relation ). С помощью includes ActiveRecord гарантирует загрузку всех указанных ассоциаций с использованием минимально возможного количества запросов; например:

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

Когда приведенный выше исправленный код выполняется, мы видим в лог-файле, что все комментарии были собраны в один запрос вместо трех:

 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)

Гораздо эффективнее.

Это решение проблемы N+1 на самом деле предназначено только как пример неэффективности, которая может существовать «под капотом» в вашем приложении, если вы не уделяете ему должного внимания. Вывод здесь заключается в том, что вы должны проверять свои файлы журналов разработки и тестирования во время разработки, чтобы проверять (и устранять!) Неэффективность кода, который создает ваши ответы.

Просмотр файлов журналов — отличный способ узнать о неэффективности вашего кода и исправить их до того, как ваше приложение будет запущено в производство. В противном случае вы можете не знать о возникшей проблеме с производительностью Rails, пока ваша система не будет запущена, поскольку набор данных, с которым вы работаете при разработке и тестировании, вероятно, будет намного меньше, чем в рабочей среде. Если вы работаете над новым приложением, даже ваш производственный набор данных может начаться с малого, и ваше приложение будет выглядеть так, как будто оно работает нормально. Однако по мере роста вашего производственного набора данных подобные проблемы Rails будут заставлять ваше приложение работать все медленнее и медленнее.

Если вы обнаружите, что ваши файлы журналов забиты кучей информации, которая вам не нужна, вот некоторые вещи, которые вы можете сделать, чтобы очистить их (приведенные методы работают как для журналов разработки, так и для журналов производства).

Распространенная ошибка № 7: отсутствие автоматизированных тестов

Ruby и Rails по умолчанию предоставляют мощные возможности автоматизированного тестирования. Многие разработчики Rails пишут очень сложные тесты, используя стили TDD и BDD, и используют еще более мощные тестовые фреймворки с такими жемчужинами, как rspec и огурец.

Несмотря на то, как легко добавить автоматизированное тестирование в ваше приложение Rails, я был очень неприятно удивлен тем, сколько проектов я унаследовал или присоединился к которым буквально не было тестов, написанных (или, в лучшем случае, очень мало) предыдущими версиями. Команда разработчиков. Несмотря на то, что существует множество споров о том, насколько комплексным должно быть ваше тестирование, совершенно очевидно, что для каждого приложения должно существовать хотя бы какое -то автоматизированное тестирование.

Как правило, для каждого действия в ваших контроллерах должен быть написан хотя бы один высокоуровневый интеграционный тест. В какой-то момент в будущем другие разработчики Rails, скорее всего, захотят расширить или изменить код или обновить версию Ruby или Rails, и эта среда тестирования предоставит им четкий способ проверки того, что базовая функциональность приложения соответствует требованиям. работающий. Дополнительным преимуществом этого подхода является то, что он дает будущим разработчикам четкое представление о полном наборе функций, предоставляемых приложением.

Распространенная ошибка № 8: Блокировка вызовов внешних сервисов

Сторонние поставщики услуг Rails обычно упрощают интеграцию своих услуг в ваше приложение с помощью драгоценных камней, обертывающих их API. Но что произойдет, если ваша внешняя служба выйдет из строя или начнет работать очень медленно?

Чтобы избежать блокировки этих вызовов, вместо того, чтобы вызывать эти службы непосредственно в вашем приложении Rails во время обычной обработки запроса, вы должны по возможности переместить их в какой-либо фоновый сервис очередей заданий. Некоторые популярные драгоценные камни, используемые в приложениях Rails для этой цели, включают:

  • Отложенная работа
  • Спасение
  • Сидекик

В тех случаях, когда нецелесообразно или невозможно делегировать обработку очереди фоновых заданий, вам необходимо убедиться, что ваше приложение имеет достаточную обработку ошибок и средства аварийного переключения для тех неизбежных ситуаций, когда внешняя служба выходит из строя или испытывает проблемы. . Вы также должны протестировать свое приложение без внешней службы (возможно, удалив из сети сервер, на котором находится ваше приложение), чтобы убедиться, что это не приведет к каким-либо непредвиденным последствиям.

Распространенная ошибка № 9: брак с существующими миграциями баз данных

Механизм миграции базы данных Rails позволяет создавать инструкции для автоматического добавления и удаления таблиц и строк базы данных. Поскольку файлы, содержащие эти миграции, имеют последовательные имена, вы можете воспроизвести их с самого начала, чтобы привести пустую базу данных к той же схеме, что и рабочая. Таким образом, это отличный способ управлять детальными изменениями в схеме базы данных вашего приложения и избегать проблем с Rails.

Хотя это, безусловно, хорошо работает в начале вашего проекта, со временем процесс создания базы данных может занять довольно много времени, и иногда миграции теряются, вставляются не по порядку или вводятся из других приложений Rails, использующих тот же сервер базы данных.

Rails создает представление вашей текущей схемы в файле с именем db/schema.rb (по умолчанию), который обычно обновляется при выполнении миграции базы данных. Файл schema.rb можно создать даже при отсутствии миграций, запустив задачу rake db:schema:dump . Распространенной ошибкой Rails является проверка новой миграции в репозиторий с исходным кодом, но не соответствующий обновленный файл schema.rb .

Когда миграция выходит из-под контроля и выполняется слишком долго или база данных больше не создается должным образом, разработчики не должны бояться очистить старый каталог миграции, создать дамп новой схемы и продолжить работу с нее. В этом случае для настройки новой среды разработки потребуется rake db:schema:load , а не rake db:migrate , на который полагаются большинство разработчиков.

Некоторые из этих вопросов также обсуждаются в Руководстве по Rails.

Распространенная ошибка № 10: Проверка конфиденциальной информации в репозиториях исходного кода

Платформа Rails позволяет легко создавать безопасные приложения, невосприимчивые ко многим типам атак. Отчасти это достигается за счет использования секретного токена для защиты сеанса с браузером. Несмотря на то, что этот токен теперь хранится в config/secrets.yml , и этот файл считывает токен из переменной среды для рабочих серверов, прошлые версии Rails включали токен в config/initializers/secret_token.rb . Этот файл часто по ошибке помещается в репозиторий исходного кода вместе с остальной частью вашего приложения, и когда это происходит, любой, у кого есть доступ к репозиторию, теперь может легко скомпрометировать всех пользователей вашего приложения .

Поэтому вы должны убедиться, что ваш файл конфигурации репозитория (например, .gitignore для пользователей git) исключает файл с вашим токеном. Затем ваши рабочие серверы могут получить свой токен из переменной среды или из механизма, подобного тому, который предоставляет гем dotenv.

Завершение урока

Rails — это мощный фреймворк, который скрывает множество уродливых деталей, необходимых для создания надежного веб-приложения. Хотя это значительно ускоряет разработку веб-приложений на Rails, разработчикам следует обращать внимание на потенциальные ошибки проектирования и кодирования, чтобы убедиться, что их приложения легко расширяемы и ремонтопригодны по мере их роста.

Разработчики также должны знать о проблемах, которые могут сделать их приложения медленнее, менее надежными и менее безопасными. Важно изучить структуру и убедиться, что вы полностью понимаете компромиссы в архитектуре, дизайне и кодировании, которые вы делаете на протяжении всего процесса разработки, чтобы обеспечить высокое качество и высокую производительность приложения.

Связанный: Каковы преимущества Ruby on Rails? После двух десятилетий программирования я использую Rails