10 помилок в Ruby / Ruby on Rails, які легко виправити

Програмісти роблять помилки в коді. Деякі просто дратують (помилки, не програмісти — прим. перекладача), інші можуть бути небезпечними для програми.
Представляю вам свою добірку з 10 типових помилок розробників Ruby/RoR і порад про те, як їх уникати. Сподіваюся, вони допоможуть вам скоротити час налагодження коду.

1. Подвійне заперечення і складні умови.

if !user.nil?
# ...
end

unless user.blank?
# ...
end

unless user.active? || address.confirmed?
# ...
end

Подвійні заперечення складні для читання. Кожен раз я витрачаю кілька секунд для того, щоб розпарсити умова. Використовуйте Rails API і пишіть user.present? замість !user.blank?..

Я також рідко вважаю застосування unless виправданим, особливо з множинними умовами. Як швидко ви зрозумієте, в якому випадку цю умову буде виконано?

unless user.active? || address.confirmed?
...
end


2. Використання save замість save! без перевірки значення, що повертається.

user = User.new
user.name = "John"
user.save

В чому проблема даного коду? Ви не дізнаєтеся про помилку збереження, якщо вона відбудеться. Ні єдиного сліду не залишиться у ваших логах, і ви витратите багато часу, намагаючись розібратися: «Чому ж у базі даних немає пользоветелей?».
Якщо ви впевнені, що дані завжди коректні і запис повинна зберігатися — використовуйте bang методи (save!, create! і т. д.). Використовуйте save, тільки якщо ви перевіряєте обчислене значення:

if user.save
...
else
...
end


3. Використання self без необхідності.

class User
attr_accessor :first_name
attr_accessor :last_name

def display_name
"#{self.first_name} #{self.last_name}"
end
end

У разі self.first_name достатньо написати first_name, і результат не зміниться. Але це скоріше питання стилю, і ніяких негативних наслідків за собою не несе. Однак пам'ятайте, що self необхідно використовувати при присвоювання значень.

self.first_name = "John"


4. Проблема N+1 запиту.

posts = Post.limit(10)
posts.each do |post|
do_smth post.user
end

Якщо ви подивитеся в логи при виконанні даного коду, то побачите, що виконується 11 запитів до бази даних: один на вибірку постів і по одному на кожного користувача. Рейки не в змозі вгадати ваші бажання і зробити запит на вивантаження відразу 10 користувачів (можливо, навіть разом з постами). Для того, щоб надіслати Рейки в потрібну сторону, можна використовувати метод includes (детальніше тут).

5. Логічне поле з трьома значеннями.

Передбачається, що логічне поле може мати тільки два значення — true або false, чи не так? А як щодо nil? Якщо ви забули вказати значення за замовчуванням і додати опцію null: false у вашій міграції, вам доведеться мати справу з трьома булевими операторами значеннями true, false and nil. В результаті маємо такий код:

# новий пост, не опублікований і не неопублікований
if post.published.nil?
# ...
end

# пост опублікований
if post.published
# ...
end

# пост неопублікований
unless post.published
# ...
end

Якщо вам потрібно обробляти три різних значення — використовуйте текстове поле.

6. Втрачені записи після видалення.

Розглянемо простий приклад:

class User < ActiveRecord::Base
has_many :posts
end

class Post < ActiveRecord::Base
belongs_to :user
validates_presence_of :user
end

Якщо ми видалимо користувача, то пов'язані з ним пости перестануть бути коректними (що, швидше за все, призведе до помилок у додатку). У цьому випадку необхідно обробляти видалення пов'язаних об'єктів:

class User < ActiveRecord::Base
has_many :posts, dependent: :delete
end


7. Використання коду програми в міграціях.

Припустимо, що у вас є наступна модель:

class User < ActiveRecord::Base
ACTIVE = "after_registration"
end

і ви хочете додати в неї поле points (окуляри). Для цього ви створюєте міграцію. Ви також хочете встановити значення поля points для існуючих користувачів рівним 10. Для цього ви пишіть в міграції:

User.where(status: User::ACTIVE).update_all(points: 10)

Запускаєте міграцію, і — Ура! — все спрацювало: існуючі користувачі мають по 10 очок. Час йде, код змінюється, і ось ви вирішуєте видалити константу User::ACTIVE. З цього моменту ваші міграції не працюють, ви не можете запустити міграції з нуля (ви отримаєте помилку uninitialized constant User::ACTIVE).

Порада: не використовуйте код додатка всередині міграцій. Використовуйте, наприклад, тимчасові Rake завдання для зміни наявних даних.

Примітка перекладача:
На мій погляд, набагато зручніше використовувати тимчасові міграції, для зміни значень. Особливо при викатці змін на production: ймовірність забути запустити rake аж ніяк не дорівнює нулю, а міграції за вас запустить Capistrano.
А чистити тимчасові міграції можна після релізу.

8. Всюдисущий puts.

Залишився після налагодження puts засмічує логи і висновок під час прогону тестів. Використовуйте Rails.logger.debug, який дозволить фільтрувати логи в залежності від обраного рівня.

Примітка перекладача:

А краще навчитеся користуватися дебагером. При цьому не забувайте видаляти binding.pry перед комітом.

9. Забуваємо про map.

Я часто бачу такий код:

users = []
posts.each do |post|
users << post.user
end

Це як раз той випадок, коли потрібно використовувати map: лаконічно і идиоматично.

users = posts.map do |post|
post.user
end

# а я б написав так (прим. перекладача)
users = posts.map(&:user)


10. Не використовуємо Hash#fetch.

name = params[:user][:name]

Що тут не так? Даний код викличе виключення (NoMethodError: undefined method `[]' for nil:NilClass), якщо в хэше не буде ключа user. Якщо ви розраховуєте на те, що ключ повинен бути хэше, використовуйте Hash#fetch:

name = params.fetch(:user)[:name]

Тоді ви отримаєте більш осмислене виняток:

This will give you a meaningful exception - KeyError: key not found: :user


Замість висновку.

А які у вас улюблені помилки?

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.