How to Write Elixiry Ruby - Result Object
Tags: ruby, elixir, objectorientedprogramming, designpattern
Published: 2020-01-08 00:15:22

---
title: How to Write Elixiry Ruby - Result Object
published: true
date: 2020-01-08 00:00:00 UTC
tags: ruby, elixir, object-oriented programming, design pattern
canonical_url: https://yiming.dev/blog/2020/01/06/how-to-write-elixiry-ruby-result-object
---

<section id="content" role="main">
    <div id="outline-container-sec-" style="padding-top: 70px">
        <div></div>
            <div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li>
<a href="#org0989e7a">Why do we need Result Objects?</a>
<ul>
<li><a href="#org4458d29">Returning <code>nil</code>: The Billion Dollar Mistake</a></li>
<li><a href="#org426ca1e">Raising exceptions: The Million Dollar Mistake</a></li>
</ul>
</li>
<li><a href="#org0f729c1">Tagged Tuple: Modeling Errors in Elixir</a></li>
<li>
<a href="#orgcdc2ea6">Result Objects: Introducing an extra level of indirection</a>
<ul>
<li><a href="#orga894f7e"><code>OpenStruct</code>: Tagged Tuple in Ruby</a></li>
<li><a href="#orgc67b5dc"><code>Struct</code>: Don't Repeat Yourself</a></li>
<li><a href="#org921b6df">Result Object: Adding More Behaviours to a <code>Struct</code></a></li>
</ul>
</li>
<li><a href="#org1a6a2ba">Result Struct in Elixir?</a></li>
<li><a href="#org7d12dbd">Summary: Modeling Business Error as Data/Objects</a></li>
</ul>
</div>
</div>
<blockquote>
<p>
<a href="http://sasamat.xen.prgmr.com/michaelochurch/wp/2013/04/03/gervais-macleod-21-why-does-work-suck/">"A language that doesn't affect the way you think about programming is not worth knowing." - Alan J. Perlis</a>
</p>
</blockquote>

<p>
Elixir/Erlang certainly affected the way I think about programming.
Specifically, how to handle inevitable failure/error cases in my program.
In Elixir/Erlang, <a href="https://yiming.dev/blog/2019/08/16/use-return-value-to-defer-decisions/">error cases can be easily modeled as tagged tuples</a>: <code>{:error, reason}</code>.
But in a more Object-Oriented language like Ruby, it's hard to model error as a lightweight data structure like Elixir tuples.
After some trial-and-error, I've found a great way to model errors in OO languages: Result Object.
</p>

<div id="outline-container-org0989e7a">
<h2 id="org0989e7a">Why do we need Result Objects?</h2>
<div id="text-org0989e7a">
<p>
Before explaining what is Result Object and how to write it, it's necessary to know why do we need it.
Error handling is inevitable in every programming language.
Take parsing CSV for example:
</p>
<ul>
<li>The file might be missing.</li>
<li>The header might be invalid.</li>
<li>The row might not match the header.</li>
</ul>
<p>
Our program cannot always run in a happy path.
We have to handle errors one way or another.
</p>

<p>
Two methods I used to handle errors in Ruby are:
</p>
<ol>
<li>returning <code>nil</code> or <code>false</code>.</li>
<li>raising an exception.</li>
</ol>

<p>
Let's see why these two methods are not enough for us.
</p>
</div>

<div id="outline-container-org4458d29">
<h3 id="org4458d29">Returning <code>nil</code>: The Billion Dollar Mistake</h3>
<div id="text-org4458d29">
<p>
Before learning Elixir, I used to return <code>nil</code> or <code>false</code> to indicate some error happens in Ruby:
</p>
<div>
<pre>def parse_csv(path)
  if file_exists?(path)
    file = read(path)
    parse(file)
  else
    nil
  end
end

# client
if results = parse_csv
  # do something
else
  # assuming file read failed
end
</pre>
</div>
<p>
It worked okay when the requirements were simple.
But it quickly broke down when another error needs to be handled.
</p>
<div>
<pre>def parse_csv(path)
  if file_exists?(path)
    file = read(path)
    rows = parse(file)
    if valid_header?(rows)
      # return results
    else
      # what to return here?
      # nil is already used
    end
  else
    nil
  end
end
</pre>
</div>
<p>
When there are multiple error cases, a simple <code>nil</code> or <code>false</code> cannot tell the client what really goes wrong.
</p>

<p>
More importantly, if the client doesn't handle <code>nil</code> and use <code>nil</code> as a normal return value, then a <code>NoMethodError</code> would be raised and the whole application would crash.
Countless bugs are produced due to this reason.
That's why <a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare">null (nil) references is called The Billion Dollar Mistake</a>.
We need a better way to handle error cases than <code>nil</code>!
</p>
</div>
</div>
<div id="outline-container-org426ca1e">
<h3 id="org426ca1e">Raising exceptions: The Million Dollar Mistake</h3>
<div id="text-org426ca1e">
<p>
If <code>nil</code> is not enough to tell what error happened, we can raise different exceptions, right?
</p>
<div>
<pre>def parse_csv(path)
  file = read!(path)
  parse!(file)
end

private

def read!(path)
  raise NoFileError unless file_exists?(path)
  # ...
end

def parse!(file)
  raise InvalidHeadersError unless valid_headers?
  # ...
end
</pre>
</div>
<p>
It worked okay at first glance.
Happy path is separated from sad paths.
The code is much simpler and straight-forward now!
</p>

<p>
But if we dig deeper, using exceptions has tremendous costs:
</p>
<ol>
<li>Raising/Catching exceptions have a performance penalty.</li>
<li>
<p>
Method API is now unpredictable.
</p>

<p>
We cannot see all the possible returns from a method.
</p>
<ul>
<li>"Are there any exceptions being raised beside the normal return value?"</li>
<li>"Are there any other exceptions being raised in these private methods?"</li>
<li>"Are there any other exceptions being raised in other object methods that are used in this method?"</li>
</ul>
</li>

<li>
<p>
Adding behaviours to exception class is hard.
</p>

<p>
Exception classes are inherited from <code>StandardError</code>.
So they have some default methods: <code>backtrace</code>, <code>cause</code>, etc.
If we add more domain-related methods to it (e.g. <code>reason</code>), is it still an <code>StandardError</code> class?
</p>
</li>
</ol>

<p>
All the costs above are fixable.
We can optimize the exception raising/catching performance so it won't be an issue.
We can declare all the exceptions raised by a method (like Java).
We can add a base class (inherited from <code>StandardError</code>) and inherit this base class to reuse behaviours.
</p>

<p>
But the fundamental flaw of using exceptions to handle sad paths is unsolvable:
<b>using exceptions mixes <i>fatal exceptions</i> with <i>domain errors</i>.</b>
</p>

<p>
Every program needs to handle fatal exceptions: syntax error, divided by zero, and so on.
But the errors we are handling here are domain errors: missing files, invalid inputs, and so on.
</p>

<p>
They need to be handled differently.
For fatal exceptions, the program may crash and notify a developer to fix the bug that leads to the exception.
For domain errors, the program may need to recover from the error and notify the user for a better user experience.
</p>

<p>
Raising exception like <code>StandardError</code> is the only solution for fatal exceptions for now.
That's why almost every programming language has this feature.
If we use exceptions for business errors, it's polluting the purity of fatal exceptions.
And both developers and operators would be confused when a non-fatal exception is raised on production.
</p>

<p>
So we need our own way to handle business errors.
And different languages have different specific ways to do it.
</p>
</div>
</div>
</div>

<div id="outline-container-org0f729c1">
<h2 id="org0f729c1">Tagged Tuple: Modeling Errors in Elixir</h2>
<div id="text-org0f729c1">
<p>
Let's first see how errors are modeled in Elixir.
And we'll see how it inspired me to find Result Object.
In Elixir, a function would <a href="https://yiming.dev/blog/2019/08/16/use-return-value-to-defer-decisions/">indicate its success/failure by returning a tagged tuple</a>:
</p>
<div>
<pre>def parse_csv(path) do
  if {:ok, file} = read_file(path) do
    # ...
    {:ok, results}
  else
    {:error, reason}
  end
end

# client
case pase_csv() do
  {:ok, results} -&gt;
    # ...
    nil

  {:error, reason} -&gt;
    nil
    # ...
end
</pre>
</div>
<ul>
<li>
<code>{:ok, results}</code> means the operation succeeded, callee can safely assume the <code>results</code> is correct.</li>
<li>
<code>{:error, reason}</code> means the operation failed, and callee can know what caused the error based on the value of <code>reason</code>.</li>
</ul>

<p>
So now, when I work in a Ruby project, I want to write similar code to handle errors more gracefully.
But simply returning an array won't work elegantly as Elixir does:
</p>
<div>
<pre>def parse_csv(path)
  if read(path)
    [:ok, results]
  else
    [:error, reason]
  end
end

# client
case parse_csv(path)
when [:ok, results] # results won't be bind to the return value
  # ...
when [:error, reason]
  # ...
end
</pre>
</div>

<p>
Before Ruby 2.7, we don't have pattern matching to write Elixiry <code>case</code> conditions.
What shall we do instead?
Is there a Object-Oriented way to modeling errors?
</p>
</div>
</div>
<div id="outline-container-orgcdc2ea6">
<h2 id="orgcdc2ea6">Result Objects: Introducing an extra level of indirection</h2>
<div id="text-orgcdc2ea6">
<blockquote>
<p>
<a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering">We can solve any problem by introducing an extra level of indirection. - Fundamental theorem of software engineering</a>
</p>
</blockquote>
<p>
To get the similar result as tagged tuple in Elixir, I start with a lightweight approach.
Then I gradually add more behavior and refactor it.
Finally I landed with a more complicated design pattern: Result Object.
</p>
</div>
<div id="outline-container-orga894f7e">
<h3 id="orga894f7e">
<code>OpenStruct</code>: Tagged Tuple in Ruby</h3>
<div id="text-orga894f7e">
<p>
My first try is to wrap the return value in an <code>OpenStruct</code>:
</p>
<div>
<pre>def parse_csv(path)
  if happy_path
    OpenStruct.new({valid: true, data: results})
  else
    # sad path
    OpenStruct.new({valid: false, reason: :file_missing})
  end
end

# client
result = parse_csv(path)
if result.valid
  p result.data
else
  case result.reason
  when :file_missing
    # ...
  end
end
</pre>
</div>

<p>
As you can see above, <code>OpenStruct</code> works just like tuples in Elixir.
We can create them and use them in a cheap and easy way.
And all the possible return values of a method is clearer than raising exceptions.
</p>
</div>
</div>
<div id="outline-container-orgc67b5dc">
<h3 id="orgc67b5dc">
<code>Struct</code>: Don't Repeat Yourself</h3>
<div id="text-orgc67b5dc">
<p>
As more and more <code>OpenStruct</code> objects are created, they start to attract behaviors.
The validation logic is a natural fit to be put in these objects, so I don't need to set <code>:valid</code> to <code>true/false</code> manually.
</p>
<div>
<pre>Result = Struct.new(:path) do
  attr_reader :data, :reason

  def valid?
    if happy_path
      @data = ...
    else
      @reason = ...
    end
  end
end

# client
result = Result.new(path)
if result.valid?
  p result.data
else
  case result.reason
  when :file_missing
    # ...
  end
end
</pre>
</div>

<p>
Defining <code>Struct</code> classes like this helps us group <code>OpenStruct</code> objects.
So the happy result and the sad result for the same method are always bundled together.
And different methods' result objects won't be mixed (since they are all <code>OpenStruct</code> before).
</p>
</div>
</div>
<div id="outline-container-org921b6df">
<h3 id="org921b6df">Result Object: Adding More Behaviours to a <code>Struct</code>
</h3>
<div id="text-org921b6df">
<p>
Finally, when these <code>Struct</code> classes become larger, they can have their own classes.
And if needed, they can have their own validation DSL, similar to what <code>ActiveModel::Validations</code> does.
</p>
<div>
<pre>class Result
  validate_file_existence :path

  def initialize(path)
    # ...
    # validate here
  end

  def valid?
    # run_validations!
  end
end
</pre>
</div>

<p>
Most importantly, we can discover more domain concepts just by following the lead of Result Objects.
When I wrote the code to parse a CSV file and transform it into an <code>ActiveRecord</code> object, I discovered these classes along the way:
</p>
<ol>
<li>
<code>CSVWrapper</code> as a wrapper around Ruby's builtin <code>CSV</code> library.
It provides an API (<code>headers</code>, <code>each_row</code>) that's more suitable to our use cases.</li>
<li>
<code>CSVParser</code> to transform a CSV file to a enumerator that returns valid domain objects.</li>
<li>
<code>RowParser</code> to transform a CSV row to a valid domain objects.</li>
</ol>

<p>
To quickly summarize, business errors are cases we need to handle as normal happy paths.
And just like normal business logic, we handle them by modeling them as data structures like tagged tuples or objects/classes like Result Objects.
</p>
</div>
</div>
</div>
<div id="outline-container-org1a6a2ba">
<h2 id="org1a6a2ba">Result Struct in Elixir?</h2>
<div id="text-org1a6a2ba">
<p>
Following the path from <code>OpenStruct</code> to Result Object, I'm wondering if we need to define Result Struct in Elixir.
</p>
<div>
<pre>defmodule Result do
  defstruct [:valid, :data, :reason]

  def new(data) do
    # ...
    # initialize data or reason based on the data passed in
  end
end
</pre>
</div>
<p>
Result Struct seems to be an overkill in Elixir/Erlang.
</p>
<ol>
<li>Elixir already has a great pattern matching system that working with tagged tuples is simple yet powerful.</li>
<li>Tagged tuple is a more common way for handling errors in Elixir/Erlang.</li>
</ol>

<p>
But I think if the business logic gets more complicated, we can follow the same thinking process to extract more domain concepts, too.
</p>
</div>
</div>
<div id="outline-container-org7d12dbd">
<h2 id="org7d12dbd">Summary: Modeling Business Error as Data/Objects</h2>
<div id="text-org7d12dbd">
<p>
In the end, tagged tuple in Elixir and Result Object in Ruby are both an extra level of indirection.
They both wrap the original results and provide an additional responsibility (to say if the operation succeeded or not or if the data is valid or not).
Again, they both demonstrate the power of <a href="https://yiming.dev/blog/2019/08/16/use-return-value-to-defer-decisions/">modeling business knowledge as data</a>.
Hope this technique can help you tackle other hard business problems as well!
</p>
</div>
</div>

    </div>
</section>