Является ли хорошей практикой использование исключений для потока управления в Ruby или Ruby on Rails? - PullRequest
14 голосов
/ 19 января 2011

Я читаю Agile Web Development с Rails (4-е изд.) И обнаружил следующий код

class ApplicationController < ActionController::Base
  protect_from_forgery

  private

  def current_cart
    Cart.find(session[:cart_id])
  rescue ActiveRecord::RecordNotFound
    cart = Cart.create
    session[:cart_id] = cart.id
    cart
  end
end

Поскольку я являюсь Java-разработчиком, мое понимание этой части кода более или менееследующее:

private Cart currentCard(){
  try{
    return CartManager.get_cart_from_session(cartId)
  }catch(RecordNotFoundEx e){
    Cart c = CartManager.create_cart_and_add_to_session(new Cart())
    return c;    
  }
}

Меня поразило то, что обработка исключений используется для управления нормальным потоком приложений (отсутствие Cart - совершенно нормальное поведение при первом посещении приложения Depot).

Если кто-то берет книгу по Java, они говорят, что это очень плохая вещь, и на то есть веская причина: обработка ошибок не должна использоваться в качестве замены для управляющих операторов, это вводит в заблуждение тех, кто читаетcode.

Есть ли веская причина, по которой такая практика оправдана в Ruby (Rails)?Это обычная практика в Ruby?

Ответы [ 3 ]

9 голосов
/ 19 января 2011

Rails никоим образом не последовательны в использовании исключений. find вызовет исключение, если объект не найден, но для сохранения вы можете выбрать, какое поведение вы хотите. Наиболее распространенная форма это:

if something.save
  # formulate a reply
else
  # formulate an error reply, or redirect back to a form, or whatever
end

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

Существует веская причина, по которой find вызывает исключение, однако: если исключение RecordNotFound всплывает до верхнего уровня, это вызовет рендеринг страницы 404. Поскольку вы обычно не ловите эти исключения вручную (редко вы видите rescue ActiveRecord::RecordNotFound в приложении Rails), вы получаете эту функцию бесплатно. Однако в некоторых случаях вы хотите что-то сделать, когда объект не существует, и в этих случаях вы должны поймать исключение.

Я не думаю, что термин «лучшая практика» на самом деле означает что-либо, но, по моему опыту, исключения не используются для управления потоком в Ruby больше, чем в Java или любом другом языке, который я использовал. Учитывая, что в Ruby нет проверенных исключений, вы имеете дело с исключениями гораздо реже.

В конце концов, дело до интерпретации. Поскольку наиболее распространенный вариант использования для find - это получение объекта для его отображения и то, что URL для этого объекта будет создан приложением, вполне может быть исключительное обстоятельство, что объект не может быть найден. Это означает, что либо приложение генерирует ссылки на несуществующие объекты, либо пользователь вручную редактировал URL-адрес. Также может быть так, что объект был удален, но ссылка на него все еще существует в кеше или через поисковую систему, я бы сказал, что это тоже исключительное обстоятельство.

Этот аргумент применяется к find при использовании, как в вашем примере, то есть с идентификатором. Существуют другие формы find (включая множество find_by_* вариантов), которые на самом деле ищут, и они не вызывают исключений (а затем в Rails 3 есть where, который заменяет многие из вариантов использования find в рельсах 2).

Я не хочу сказать, что использование исключений в качестве управления потоком - хорошая вещь, просто не обязательно неправильно, что find вызывает исключения, и что ваш конкретный вариант использования не является распространенным случаем.

3 голосов
/ 19 января 2011

Для конкретного случая использования вы можете просто сделать

def current_cart
  cart = Cart.find_or_create_by_id(session[:cart_id])
  session[:cart_id] = cart.id
  cart
end

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

0 голосов
/ 18 октября 2013

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

def current_cart
  @current_cart ||= begin
    unless cart = Cart.find_by_id(session[:cart_id])
      cart = Cart.create
      session[:cart_id] = cart.id
    end
    cart
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...