Skip to main content

Why failures and errors? Why not just failure or error?

Failure and errors have different fallbacks

Consider the following user story.

As a doctor I would like to see a list of the patient's allergies.

The list is received from the third-party API.

Sometimes a controller for this task may be implemented as follows.

controllers/allergies_controller.rb
class AllergiesController < ApplicationController
# ...
def index
# `response` is a `Hash`.
response = ApiClient.get(
"/allergies",
params: {patient_id: allowed_patient_id}
)

@allergies = response.dig(:data, :allergies)
end
end

And the corresponding view.

views/allergies/index.html.erb
<h2>Allergies</h2>

<% if @allergies.present? %>
<% @allergies.each do |allergy| %>
<p>
<div>
Name: <%= allergy[:name] %>
</div>
<div>
Symptoms: <%= allergy[:symptoms].join(", ") %>
</div>
</p>
<% end %>
<% else %>
<span>
Patient has no allergies.
</span>
<% end %>

The code may look innocent from the first point of view, but the devil is in the details.

In a case, the API response has an unexpected format, response.dig(:data, :allergies) returns nil.

Then @allergies.present? is evaluated as false in the view.

As a consequence, the "Patient has no allergies" string is displayed in the doctor's browser.

Just try to imagine what may happen when a doctor blindly relies on the page text 😬?

Unfortunately, a code with similar problems sometimes may be written even by senior developers 😢.

That is the main reason of why Convenient Service differentiate failures and errors.

Sure, the lib still can NOT 100% guarantee the complete elimination of the issue.

However, it encourages the users to think about fallbacks and split the failures and errors from the beginning.

The curiosity or the confusion that people feel when they find it difficult to decide whether to use a failure or an error works as a provocation for them to open and read this article.

This way they become aware of the issue and share it with others more frequently.

Refactoring using Convenient Service is the following:

controllers/allergies_controller.rb
class FetchAllergies
include ::ConvenientService::Standard::Config
# ...
def result
return error("API response does not have `data` key") unless response.has_key?(:data)
return error("API response data does not have `allergies` key") unless response[:data].has_key?(:allergies)

return failure("Patient `#{allowed_patient_id}` has no allergies") if response[:data][:allergies].empty?

success(allergies: response[:data][:allergies])
end

private

def response
# `response` is a `Hash`.
@response ||= ApiClient.get("/allergies", params: {patient_id: allowed_patient_id})
end
end
controllers/allergies_controller.rb
class AllergiesController < ApplicationController
# ...
def index
@fetch_allergies_result = FetchAllergies.result(allowed_patient_id: allowed_patient_id)
end
end
views/allergies/index.html.erb
<h2>Allergies</h2>

<% if @fetch_allergies_result.success? %>
<% @fetch_allergies_result.data[:allergies].each do |allergy| %>
<p>
<div>
Name: <%= allergy[:name] %>
</div>
<div>
Symptoms: <%= allergy[:symptoms].join(", ") %>
</div>
</p>
<% end %>
<% elsif @fetch_allergies_result.failure? %>
<span>
Patient has no allergies.
</span>
<% else # @fetch_allergies_result.error? %>
<span>
No allergies information available.
</span>
<div>
Try to refresh the page after a while.
<div>
<div>
If the issue persists please contact the support.
</div>
<% end %>

The code is more verbose, but that is the price of reliability.

Using the service goal resolution terminology, this is what we have as a summary.

  • @fetch_allergies_result.success? means that a patient 100% has allergies (positive service goal resolution).

    That is why we freely display them.

  • @fetch_allergies_result.failure? means that a patient 100% has NO allergies (negative service goal resolution).

    So we are confident in the "Patient has no allergies" text.

  • @fetch_allergies_result.error? means that even after service invocation, we still don't know whether a patient has allergies or not (no service goal resolution at all).

    Due to that, we are telling the truth that "No allergies information available" instead of the false-negative resolution that we had before the separation of failure and errors.