Posted over 5 years ago. Visible to the public. Linked content.

Notification

Replacing exceptions with Notification in validations

A common way to approach validation is to run series of checks on some data. If any of these checks fails, you raise an exception with an error message.
I have a couple of problems with this approach. Firstly I'm not happy with using exceptions for something like this. Exceptions signal something outside the expected bounds of behavior of the code in question. But if you're running some checks on outside input, this is because you expect some messages to fail - and if a failure is expected behavior, then you shouldn't be using exceptions.
The second problem with code like this is that it fails with the first error it detects, but usually it's better to report all errors with the incoming data, not just the first. That way a client can choose to display all errors for the user to fix in a single interaction.

My preferred way to deal with reporting validation issues like this is the Notification pattern. A notification is an object that collects errors, each validation failure adds an error to the notification. A validation method returns a notification, which you can then interrogate to get more information.

When to use this refactoring

Exceptions are a very useful technique for handling exceptional behaviour and getting it away from the main flow of logic. This refactoring is a good one to use only when the outcome signaled by the exception isn't really exceptional, and thus should be handled through the main logic of the program. The example I'm looking at here, validation, is a common case of that.
Whether to use exceptions for a particular task is dependent on the context. So, as the prags go on to say, reading from a file that isn't there may or may not be an exception depending on the circumstances. If you are trying to read a well known file location, such as /etc/hosts on a unix system, then it's likely you can assume the file should be there, so throwing an exception is reasonable. On the other hand if you are trying to read a file from a path that the user has typed in on the command-line, then you should expect that it's likely the file isn't there, and should use another mechanism - one that communicates the unexceptional nature of the error.

There is a case when it may be sensible to use exceptions for validation failures. This would be situations where you have data that you expect to have already been validated earlier in processing, but you want to run the validation checks again to guard against a programming error letting some invalid data slip through.

Example

Copy
require 'date' class BookingRequest def initialize(number_of_seats:, date:) @number_of_seats = number_of_seats @date = date end def check raise ArgumentError, validation.error_message if validation.has_errors? end def validation note = Notification.new validate_date note validate_number_of_seats note note end private def validate_date(note) return note.add_error 'date is missing' if date.nil? begin parsed_date = Date.parse date rescue Exception => exp note.add_error 'Invalid format for date', exp return end note.add_error 'date cannot be before today' if parsed_date < Date.today end def validate_number_of_seats(note) return note.add_error 'number of seats cannot be null' if number_of_seats.nil? note.add_error 'number of seats must be positive' if number_of_seats < 1 end attr_reader :number_of_seats, :date end
Copy
require 'set' class Notification def initialize @errors = Set.new end def add_error(message) add_error message, nil end def add_error(message, exception) @errors.add Error.new(message, exception) end def has_errors? !@errors.empty? end def error_message @errors.map(&:message).join(', ') end private class Error def initialize(message:, cause:) @message = message @cause = cause end end end

Owner of this card:

Avatar
Alexander M
Last edit:
over 5 years ago
by Alexander M
Tags:
Software-Architecture
Posted by Alexander M to Ruby and RoR knowledge base
This website uses short-lived cookies to improve usability.
Accept or learn more