Programming and life. If you think programming IS life, seek professional help.
And that’s a Very Good Thing™ for both language communities. Yes, it’s good to know both languages and to use each where they make sense. But They’re Not The Same Thing. Obvious, yes?
Abe Voelker wrote a blog post in which he sang the praises of the Kleisli Gem to, in effect, let you write Haskell code in your Ruby. He started out by demonstrating a “naive” validation object for validating an uploaded image according to four criteria, verifying that the image:
In addition, he states a requirement for “a JSON-friendly version of the validation return value as the website is uploading the image via Ajax”.
Fine. Easy enough, without resorting to exotic “Reese’s Pieces” language concoctions.
The Gist copied at the end of this post is what I’d consider a first draft of a rewritten AvatarValidator
class. It’s a somewhat more readable version of the original; both are still
massive SRP violations. They do too much; they have leaky abstractions
through and through. But this is still an improvement:
The original code
made was clearly a classic
command pattern,
but implemented in a massive nested-if furball. You had to squint to see that it
was implementing a stepwise state machine, with subsequent states being
implemented as ever-more-deeply-nested if blocks.
In the first replacement,
the #validate method
(inside the guard clause) is a sequence of calls to private methods with
intention-revealing names that (should) make it easier to find what you’re
looking for, or to understand the overall process.
The original code
has inline, nested if statements whose else clauses each build and return
literal strings, more than doubling the length of the #validate method
(lines 10-33) compared with the new implementation.
Changes to the original output messages do not originate in a “single source of
truth”, which complicates
and adds risk to change; are you sure you’ve done everything and only what
you set out to do? (Remember, there are no tests yet.)
In the new code, any validation error detected will raise a RuntimeError whose
message encodesan identifier for the error, along with a single relevant data item. The
handler
for that RuntimeError calls a private method
that centralises all text strings for error messages, making it easy to change
later if needed (e.g, for i18n).
The original code
passes in an uploaded_file parameter, which is assigned to an instance variable
(at line 7). That ivar is then used, duck-type style, to find its size (by calling
the #size method at line 12) and by calling the path method of the return
object from uploaded_file’s #tempfile method at line 15).
In contrast, the first whack at a replacement AvatarValidator has a nested
class, AvatarValidator::UploadedFile,
which reports the #size (needed for validation) and produces a MiniMagick::Image
instance
of the data in the uploaded file (however that file was specified,
whether as a blob of data, a filename or an instance of AvatarValidator::UploadedFile itself).
AvatarValidator still has repetitive code. The internal validation-step methods (#verify_file_size, #validate_image_format, #verify_allowed_format, and (to a slightly lesser degree) #verify_image_dimensions) each follow the same pattern: bail out if we’re valid, or call #fail_with if we’re not.#verify_image_dimensions method, Assignment Branch Condition size for verify_image_dimensions is too high. [16.16/15] is one I have learned to take seriously. RuboCop’s default trigger value of 15 is in fact higher than what you should see if, say, you were following Sandi Metz’ rules.A new iteration of the rewrite will soon address these problems, and produce code that is neither a straw man to provide a convenient excuse to switch languages, nor code that would budge the WTF-O-Meter® at your team’s code review.
Recent Post
Read more