{
    "componentChunkName": "component---src-templates-post-page-js",
    "path": "/2019/error-handling-levels",
    "result": {"data":{"site":{"siteMetadata":{"title":"Solid Abstractions","siteUrl":"https://solidabstractions.com","twitterId":291334023,"author":{"fullName":"Julien Hartmann","profileHtml":"I am an open-source de­vel­op­er, for­mer IT con­sul­tant with a pas­sion for new tech­nol­o­gies. I be­lieve the role of an en­gi­neer is to em­pow­er peo­ple, by as­sem­bling sim­ple, re­fined de­signs.\n","links":[{"url":"https://github.com/spectras/","name":"github","title":"GitHub"},{"url":"https://stackoverflow.com/users/3212865/spectras","name":"stackoverflow","title":"StackOverflow"},{"url":"https://www.linkedin.com/in/julienhartmann/","name":"linkedin","title":"LinkedIn"}],"profilePicNode":{"original":{"src":"/static/profile-pic-301a9cbe7b572c3e7910c9717d2b3bcd.jpg"}},"url":"https://etherdream.org/about"}}},"markdownRemark":{"id":"3b698d86-6824-5b4a-a3f1-9581ce1a6ade","excerpt":"We cover the basics of correct error handling, so it is no longer an afterthought.\nIn previous post, we left some crucial questions unanswered, such as\n“What exactly makes an error recoverable?”. In this post, we dive into levels of abstraction and what they mean for error handling.","html":"<p>We cover the basics of correct error handling, so it is no longer an afterthought.\nIn previous post, we left some crucial questions unanswered, such as\n<i>“What exactly makes an error recoverable?”</i>.<br /></p>\n<p>In this post, we dive into levels of abstraction and what they mean for error handling.</p>\n<p>--- excerpt ---</p>\n<p>In <a href=\"error-handling-introduction\" class=\"internal\">previous post</a>, we defined an error as a\n<b>broken assumption</b>, then split those into <em>unexpected errors</em> and <em>expected errors</em>.\nEventually, we outlined a decision process, leaving some crucial questions unanswered, namely:</p>\n<ul>\n<li>What exactly makes an error recoverable?</li>\n<li>What is meant by exposing internals?</li>\n</ul>\n<p>We said the key to answering those is keeping <b>levels of abstraction</b> consistent,\nand that is the subject of this post.</p>\n<h2 id=\"levels-of-abstraction\">Levels of abstraction</h2>\n<p>A <a href=\"https://en.wikipedia.org/wiki/Abstraction_layer\" class=\"external\" rel=\"external noopener noreferrer\">level of abstraction</a> is a consistent\nview of a set of related concepts or components of an application.\nWe decompose that view into <b>contracts</b> that specify\nwhat each part does and what it requires. In software design, those contracts are usually\nformalized as <a href=\"https://en.wikipedia.org/wiki/Interface_(computing)\" class=\"external\" rel=\"external noopener noreferrer\">interfaces</a>.</p>\n<h3 id=\"until-it-fails\">Until it fails</h3>\n<p>Abstractions hide the innards of components, allowing\n<a href=\"https://en.wikipedia.org/wiki/Separation_of_concerns\" class=\"external\" rel=\"external noopener noreferrer\">separation of concerns</a> and\ninteroperability.\nThat is, they enable reasoning about and re-using components as black‑boxes, not caring\nabout their working details.</p>\n<p>For instance an http module might provide an abstraction of connecting to webservers to download\ndocuments and all of a sudden, we can <em>“fetch-that-url”</em> and not worry about how\nthe fetching works.</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-1\"><blockquote>\n<p><strong>http_fetch( <code class=\"language-text\">url</code> ) → <code class=\"language-text\">bytes</code></strong><br>\nRetrieve a document over HTTP.</p>\n<dl><dt>Arguments</dt><dd><p><strong>url:</strong> address of document to retrieve.<br></p></dd><dt>Returns</dt><dd><p>Document content as a <code class=\"language-text\">bytes</code> object.</p></dd></dl>\n</blockquote><figcaption><div class=\"figcaption-wrapper\">A very simplistic one-method http module interface.</div></figcaption></figure></div>\n<p>We might say we lie on top of that abstraction layer.</p>\n<p>Underneath, that http module probably only implements the logic that deals with the HTTP\nprotocol itself. For the actual sending and receiving of data, it will rely on other\nabstraction layers down the line. Maybe some encryption layer for SSL, some connection\npooling layer, a low-level networking layer, …</p>\n<p>Thus we compose our applications by <strong>stacking</strong> and <strong>juxtaposing</strong> abstraction layers,\niteratively decomposing our use case until we have all the details sorted out,\nor composing increasingly complex features until we can answer our use case.\nAs long as we keep those abstraction layers <strong>isolated</strong> from each other, communicating\nonly though their interfaces, everything is fine.</p>\n<p><b>Until it fails.</b></p>\n<p>Say the encryption layer in our example above fails to fulfill its contract\nbecause the peer rejected our certificate. What now?</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-2\"><img src=\"/files/fall-f80554c0d842f42384135cac3c4c5cd2.png\" alt=\"Abstraction layers breaking down\" srcset=\"\"><figcaption><div class=\"figcaption-wrapper\">Abstraction layers breaking down.</div></figcaption></figure></div>\n<h3 id=\"errors-are-part-of-abstraction\">Errors are part of abstraction</h3>\n<p>The answers lie in our design. When we model our layers of abstraction, we define interfaces\nthat formalize what our components can do and what they require.</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-3\"><img src=\"/files/contract-a44f57a27fb319ba08238a69aeda0a93.png\" alt=\"Design by contract\" srcset=\"/files/contract-1.5x-40f018ce01423b91b12ce2a13f80cd72.png 1.5x, /files/contract-2x-d3120045d54e69c8e6b70abb9d3d5bfb.png 2x\"><figcaption><div class=\"figcaption-wrapper\">Design by contract - single component view.</div></figcaption></figure></div>\n<p>Excluding straight bugs, this implicitly creates two realms for errors:</p>\n<dl><dt>Preconditions violations</dt><dd><p>The client/caller of the component fails to meet the requirements\n(eg: it provides an invalid URL).</p></dd><dt>Failures</dt><dd><p>Our component fails to fulfill its contract, either by failing to produce <b>side-effects</b>,\nor by failing to reach <b>postconditions</b>. At this stage, this is usually because of external\nconditions (eg: network is down).</p></dd></dl>\n<p>Preconditions violations are easy. The interface must simply state either that:</p>\n<ul>\n<li>\n<p>this is <strong>forbidden</strong> and such program is ill-formed (ie: this is a bug).\nWhich means this is <em>unexpected error</em> territory.</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">def</span> <span class=\"token function\">http_fetch</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token comment\"># force crash on interface violation</span>\n    <span class=\"token keyword\">assert</span> is_valid_url<span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span></code></pre></div>\n</li>\n<li>\n<p>or this is an <em>expected error</em> that will get reported. The interface then defines how.\nIn this example, it generates a python <code class=\"language-text\">ValueError</code> exception.</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">def</span> <span class=\"token function\">http_fetch</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> is_valid_url<span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">raise</span> ValueError<span class=\"token punctuation\">(</span><span class=\"token string\">\"invalid url\"</span><span class=\"token punctuation\">)</span></code></pre></div>\n</li>\n</ul>\n<div class=\"note\"><p>Mixing is possible: for instance the interface could define that passing an improper\n<code class=\"language-text\">url</code> type (eg: a number instead of a string) is a bug, while passing a string that\nholds an invalid url generates an error.</p></div>\n<p>Interesting errors are those belonging to the latter realm: the component we are designing\nfails to fulfill its contract.</p>\n<p>We must define this failure. As software designer, our job is to <b>create a language</b>.\nWe define interfaces, which are somewhat like things, bearing nouns: <em>HTTPConnector</em>,\n<em>CookieJar</em>, etc. And we define tasks, which are somewhat like verbs: <em>fetchDocument</em>,\n<em>saveCookies</em>, etc.</p>\n<p>As we do so, we pick the correct <strong>granularity</strong>, giving power and flexibility but not so\nmuch that simple tasks become a chore. Identifying all relevant interfaces,\ngrouping or splitting them correctly, and differentiating between\nimplementation details we hide and features we expose is an art.</p>\n<p>And that art applies to errors as well: it is our job as a designer to:</p>\n<ul>\n<li><b>identify</b> all possible reasons the component could fail to fulfill its contract for.</li>\n<li><b>group</b> them into error families that make sense being handled together, possibly\nhierachizing them for more complex abstractions.</li>\n<li><b>encapsulate</b> implementation details, ensuring errors do not expose the internals\nwe hid when we designed our interfaces.</li>\n</ul>\n<p>Together, those tasks create a language of errors.</p>\n<h3 id=\"speaking-in-errors\">Speaking in errors</h3>\n<h4 id=\"a-level-by-level-process\">A level-by-level process</h4>\n<p>An interesting consequence is that this language is specific to the abstraction layer we\nare designing. This means that:</p>\n<ul>\n<li>\n<p>what is an error at a level of abstraction <strong>might not be</strong> at levels <strong>above</strong>.<br>\nThe upper layer might expect that error and have a recovery process in place. For instance,\nthe interface specification of <code class=\"language-text\">http_fetch</code> function above might define that failure\nto connect shall be resolved by falling back to a cached copy. Thus, a connection error\ndoes not necessarily imply an <code class=\"language-text\">http_fetch</code> error.</p>\n<p>Hey, we just answered our first question:</p>\n<blockquote>\n<dl><dt>What makes an error recoverable?</dt><dd><p>➥ An error is recoverable, at a specific level of abstraction, if and <strong>only if</strong> that\nlevel defines a recovery process for that error.</p></dd></dl>\n</blockquote>\n</li>\n<li>what is an error at a level of abstraction <strong>might not be</strong> at levels <strong>below</strong>.<br>\nFor example, a low-level network layer cares about transporting messages from and end to\nthe other end. At the level, messages getting across are not an error. However, one\nof those messages could contain some HTTP error response, that <strong>will</strong> be interpreted\nas an error by the HTTP layer above.</li>\n</ul>\n<p>This is why error handling focuses a lot on abstraction boundaries: different abstraction\nlayers speak a different error language, so <strong>translation</strong> should happen when crossing boundaries.</p>\n<h4 id=\"translating-errors\">Translating errors</h4>\n<p>Summarizing our thoughts so far:</p>\n<blockquote>\n<dl><dt>What is meant by exposing internals?</dt><dd><p>➥ Errors are part of abstraction.<br>\n➥ The purpose of abstractions is to encapsulate and hide details.<br>\n➥ As a part of abstraction, errors contribute to encapsulation.</p></dd></dl>\n</blockquote>\n<p>In particular, when an error cannot be recovered from at a specific level of abstraction, we\nstill must ensure encapsulation is not broken. We do this by translating from lower-level\nerror language to the error language of current level.\nThat is, we <strong>convert</strong> the error.</p>\n<p>Namely, proper error conversion should:</p>\n<ul>\n<li>pick an error appropriate to our level of abstraction.</li>\n<li><strong>remove details</strong> from lower-level abstraction that are no longer relevant.</li>\n<li><strong>add context</strong> that the lower-level abstraction did not know of.</li>\n</ul>\n<p>For instance, assuming we designed our interface as a very simplistic one, hiding SSL usage,\nand we grouped all transfer-related errors under a generic <code class=\"language-text\">TransferError</code>, we could write:</p>\n<div class=\"figure-wrapper\"><figure id=\"fig-4\"><div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n    connection<span class=\"token punctuation\">.</span>do_handshake<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">except</span> SSLError <span class=\"token keyword\">as</span> exc<span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">raise</span> TransferError<span class=\"token punctuation\">(</span>\n        <span class=\"token string-interpolation\"><span class=\"token string\">f\"Handshake error while loading $</span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span>url<span class=\"token punctuation\">}</span></span><span class=\"token string\"> \"</span></span>\n        <span class=\"token string-interpolation\"><span class=\"token string\">f\"with connection $</span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span>connection_id<span class=\"token punctuation\">}</span></span><span class=\"token string\">: $</span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span>exc<span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">,</span>\n        url<span class=\"token operator\">=</span>url\n    <span class=\"token punctuation\">)</span> <span class=\"token keyword\">from</span> exc</code></pre></div><figcaption><div class=\"figcaption-wrapper\">Typical error conversion in python</div></figcaption></figure></div>\n<p>Our interface encapsulates the use of SSL as an implementation detail, therefore we cannot\nlet an <a href=\"https://docs.python.org/3/library/ssl.html#ssl.SSLError\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">SSLError</code></a> propagate.\nWe thus change the error type to one of those that client\ncode can expect from us. We don't forward working details of SSL handshake, but we add\nthe <code class=\"language-text\">url</code> as a context field, and ensure that, should the error be logged at some point,\nthe log will include the connection id.</p>\n<div class=\"note\"><p>We also use python error chaining\n<a href=\"https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement\" class=\"external\" rel=\"external noopener noreferrer\"><code class=\"language-text\">from</code></a>\nclause to ensure discarded details will be available for error investigation.\nThe handling of those discarded parts needs special care because,\neven though it's not relevant up the abstraction stack, they might be useful for debugging.</p></div>\n<h2 id=\"conclusion\">Conclusion</h2>\n<blockquote>\n<p><strong>A word about functions</strong></p>\n<p>As we close this part on levels of abstraction, we should note that at the smallest scope,\neven a single function:</p>\n<ul>\n<li>encapsulates a task, giving it a name and therefore defining <strong>vocabulary</strong>.</li>\n<li>has <strong>preconditions</strong>: predicates that must be true at beginning of the function.</li>\n<li>has <strong>postconditions</strong>: predicates that must be true at the end of the function.</li>\n</ul>\n<p>In fact, <b>every function is an abstraction</b>. And the principles we covered so far\napply to functions just as well as they apply to components.</p>\n</blockquote>\n<p>That was longer than I expected. Enough theory, in the <a href=\"error-handling-techniques\" class=\"internal\">next post</a>\nwe will see actual error handling mechanisms in code.</p>","fields":{"isPage":false,"slug":"/2019/error-handling-levels"},"frontmatter":{"title":"Error Handling part 2: Abstractions","classname":null,"date":"2019-02-28T00:00:00.000Z","formattedDate":"February 28, 2019","isoDate":"2019-02-28T00:00:00+00:00"},"headings":[{"value":"Levels of abstraction","depth":1},{"value":"Until it fails","depth":2},{"value":"Errors are part of abstraction","depth":2},{"value":"Speaking in errors","depth":2},{"value":"A level-by-level process","depth":3},{"value":"Translating errors","depth":3},{"value":"Conclusion","depth":1}],"image":{"original":{"src":"/static/fall-f80554c0d842f42384135cac3c4c5cd2.png"}},"series":{"name":"software","fullName":"software engineering","fields":{"slug":"/software"}},"tags":[{"name":"code","slug":"/tag/code"}]}},"pageContext":{"series":"software","slug":"/2019/error-handling-levels","previous":{"fields":{"slug":"/2019/error-handling-introduction"},"frontmatter":{"title":"Error Handling part 1: Introduction","series":"software"},"tags":[{"name":"code","slug":"/tag/code"}]},"next":{"fields":{"slug":"/2019/zero-cost-unique_ptr"},"frontmatter":{"title":"Zero-cost unique_ptr deleters","series":"software"},"tags":[{"name":"code","slug":"/tag/code"},{"name":"c++","slug":"/tag/c"}]},"seriesPrevious":{"fields":{"slug":"/2019/error-handling-introduction"},"frontmatter":{"title":"Error Handling part 1: Introduction","series":"software"},"tags":[{"name":"code","slug":"/tag/code"}]},"seriesNext":{"fields":{"slug":"/2019/zero-cost-unique_ptr"},"frontmatter":{"title":"Zero-cost unique_ptr deleters","series":"software"},"tags":[{"name":"code","slug":"/tag/code"},{"name":"c++","slug":"/tag/c"}]}}},
    "staticQueryHashes": ["1733002695","4006707078"]}