git » homepage.git » master » tree

[master] / md / TechWorks / authored / wondr / posts / assertions-and-postconditions.html

<html>
  <head>
    <title>Assertions and Postconditions - wondr</title>
    <link rel="stylesheet" href="../style.css"/>
    <link rel="stylesheet" href="../code_highlight.css"/>
    <link rel="alternate" type="application/atom+xml" href="../atom.xml"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  </head>
  <body class="page">
    <div class="site">
      <header id="header" class="header-container">
        <div class="site-header">
          <nav id="navmenu" aria-label="Main Menu">
            <ul class="main-menu">
              <li>
                <a href=".." aria-current="page">Index</a>
              </li>
            </ul>
          </nav>
          <div class="site-info">
            <h1 class="site-title title">Wondr</h1>
            <p class="site-description">Wondering about R</p>
          </div>
        </div>
      </header>
      <main class="main">
        <article lang="en" class="entry">
          <header class="entry-header">
            <div class="entry-info">
              <h1 class="entry-title title">Assertions and Postconditions</h1>
            </div>
            <div class="meta">
              <span class="posted-on">
                <svg class="icon" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true">
                  <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
                  <line x1="16" y1="2" x2="16" y2="6"></line>
                  <line x1="8" y1="2" x2="8" y2="6"></line>
                  <line x1="3" y1="10" x2="21" y2="10"></line>
                </svg>
                <span class="screen-reader">Posted on </span>
                <time class="date" datetime="2019-06-15T00:00:00Z">2019-06-15</time>
              </span>
              <span class="reading-time">
                <svg class="icon" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true">
                  <circle cx="12" cy="12" r="10"></circle>
                  <polyline points="12 6 12 12 15 15"></polyline>
                </svg>
                5 minute read
              </span>
            </div>
          </header>
          <div class="entry-content">
<p><a href="https://en.wikipedia.org/wiki/Assertion_(software_development)">Assertions</a> are a way to convey information about a program’s correct behavior to both humans and to the computer. <a href="https://en.wikipedia.org/wiki/Postcondition">Postconditions</a> are assertions about a function’s return value defined within that function. Base R supports them through a combination of <code>stopifnot()</code>, <code>on.exit()</code> and <code>returnValue()</code>.</p>
<blockquote>
<p>Note: <code>returnValue()</code> was introduced in R 3.2.0 but is marked experimental in its documentation, so the approach outlined in this post might not always be possible or desirable.</p>
</blockquote>
<p>Let’s start by looking at assertions, and go from there to an example motivating postconditions.</p>
<h2 id="assertions">Assertions</h2>
<p>Assertions are great and I use them all the time in most of the languages I use. Unlike comments, they actually run, and so are less likely to become stale. Unlike unit tests, they require no infrastructure and are exercised in the wild in addition to during development.</p>
<p>In some languages, like C with gcc, they can even be <a href="https://codeyarns.com/2015/03/16/how-to-disable-assert-in-gcc/">optionally disabled at compile-time in order to improve performance</a>.</p>
<p>In R, assertions are most easily written using <code>base::stopifnot()</code>, but there are <a href="https://www.r-bloggers.com/the-state-of-assertions-in-r/">a variety of packages</a> to ease their writing also.</p>
<p>Here’s a silly example, using <code>stopifnot()</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1"></a>make_odd &lt;-<span class="st"> </span><span class="cf">function</span>(n) {</span>
<span id="cb1-2"><a href="#cb1-2"></a>  <span class="kw">stopifnot</span>(n <span class="op">%%</span><span class="st"> </span><span class="dv">2</span> <span class="op">==</span><span class="st"> </span><span class="dv">0</span>)</span>
<span id="cb1-3"><a href="#cb1-3"></a>  n <span class="op">+</span><span class="st"> </span><span class="dv">1</span></span>
<span id="cb1-4"><a href="#cb1-4"></a>}</span></code></pre></div>
<p>Our <code>make_odd()</code> function here accepts a single argument, <code>n</code>, that should be numeric and should be even. If <code>n</code> is already odd, an error is produced:</p>
<pre><code>&gt; make_odd(11)
 Error in make_odd(11) : n%%2 == 0 is not TRUE</code></pre>
<p>But what if we wanted to make an assertion about the <em>return value</em> of <code>make_odd()</code> instead of about the argument <code>n</code>? That would be a postcondition.</p>
<p>The simplest way that comes to mind is something like this:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb3-1"><a href="#cb3-1"></a>make_odd &lt;-<span class="st"> </span><span class="cf">function</span>(n) {</span>
<span id="cb3-2"><a href="#cb3-2"></a>  ret &lt;-<span class="st"> </span>n <span class="op">+</span><span class="st"> </span><span class="dv">1</span></span>
<span id="cb3-3"><a href="#cb3-3"></a>  <span class="kw">stopifnot</span>(ret <span class="op">%%</span><span class="st"> </span><span class="dv">2</span> <span class="op">==</span><span class="st"> </span><span class="dv">1</span>)</span>
<span id="cb3-4"><a href="#cb3-4"></a>  ret</span>
<span id="cb3-5"><a href="#cb3-5"></a>}</span></code></pre></div>
<p>Unfortunately, this approach is impossible in a function that uses <code>return()</code> liberally. It’s doubly unfortunate because <a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity">complex</a> functions are exactly those that would benefit the most from statements about correct behavior.</p>
<p>Here’s an example of a complex function, <a href="https://github.com/rstudio/shinyloadtest/blob/7572a292e21d63a73dbf6de403e2537b72f65240/R/shiny-recorder.R#L165-L177">shouldIgnore</a>, from <a href="https://github.com/rstudio/shinyloadtest/">shinyloadtest</a>:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1"></a>shouldIgnore &lt;-<span class="st"> </span><span class="cf">function</span>(msg) {</span>
<span id="cb4-2"><a href="#cb4-2"></a>  canIgnore &lt;-<span class="st"> </span><span class="kw">c</span>(<span class="st">&#39;^a</span><span class="ch">\\</span><span class="st">[&quot;ACK.*$&#39;</span>, <span class="st">&#39;^</span><span class="ch">\\</span><span class="st">[&quot;ACK.*$&#39;</span>, <span class="st">&#39;^h$&#39;</span>)</span>
<span id="cb4-3"><a href="#cb4-3"></a>  <span class="cf">if</span> (<span class="kw">length</span>(<span class="kw">unlist</span>(stringr<span class="op">::</span><span class="kw">str_match_all</span>(msg, canIgnore))) <span class="op">&gt;</span><span class="st"> </span><span class="dv">0</span>) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb4-4"><a href="#cb4-4"></a>  parsed &lt;-<span class="st"> </span><span class="kw">parseMessage</span>(msg)</span>
<span id="cb4-5"><a href="#cb4-5"></a>  <span class="cf">if</span> (<span class="kw">length</span>(<span class="kw">intersect</span>(<span class="kw">names</span>(parsed), <span class="kw">c</span>(<span class="st">&quot;busy&quot;</span>, <span class="st">&quot;progress&quot;</span>, <span class="st">&quot;recalculating&quot;</span>))) <span class="op">&gt;</span><span class="st"> </span><span class="dv">0</span>) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb4-6"><a href="#cb4-6"></a>  <span class="cf">if</span> (<span class="kw">identical</span>(<span class="kw">names</span>(parsed), <span class="kw">c</span>(<span class="st">&quot;custom&quot;</span>))) {</span>
<span id="cb4-7"><a href="#cb4-7"></a>    customKeys &lt;-<span class="st"> </span><span class="kw">names</span>(parsed[[<span class="st">&quot;custom&quot;</span>]])</span>
<span id="cb4-8"><a href="#cb4-8"></a>    <span class="cf">if</span> (<span class="kw">isTRUE</span>(customKeys <span class="op">==</span><span class="st"> &quot;reactlog&quot;</span>)) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb4-9"><a href="#cb4-9"></a>  }</span>
<span id="cb4-10"><a href="#cb4-10"></a>  noop &lt;-<span class="st"> </span><span class="kw">list</span>(<span class="dt">errors =</span> <span class="kw">list</span>(), <span class="dt">values =</span> <span class="kw">list</span>(), <span class="dt">inputMessages =</span> <span class="kw">list</span>())</span>
<span id="cb4-11"><a href="#cb4-11"></a>  <span class="cf">if</span> (<span class="kw">identical</span>(parsed, noop)) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb4-12"><a href="#cb4-12"></a>  <span class="kw">return</span>(<span class="ot">FALSE</span>)</span>
<span id="cb4-13"><a href="#cb4-13"></a>}</span></code></pre></div>
<p>You can see from its name, <code>shouldIgnore()</code>, and from the sprinkling of <code>return(TRUE)</code> and <code>return(FALSE)</code> in its body, that this function is probably supposed to return a logical vector of length 1. It’s not immediately obvious that it always does, which is why my instinct is to call this function complex.</p>
<p>If it doesn’t, code higher up the <a href="https://en.wikipedia.org/wiki/Call_stack">call stack</a> might misbehave and produce errors that aren’t obviously a result of this particular function returning the wrong kind of value.</p>
<p>How can we mitigate the higher-than-normal risk associated with changing this function, such as by trying to simplify it?</p>
<h2 id="unit-tests">Unit Tests</h2>
<p>One thing we could do is write a unit test for a representative set of values for <code>msg</code>, the function’s only argument. However, those unit tests will only run during development. In the wild, a problematic value of <code>msg</code> might not produce an obvious error.</p>
<h2 id="postcondition">Postcondition</h2>
<p>Writing unit tests is rarely a bad idea, but it’s not necessarily the highest leverage idea. If we could make a runnable statement about correct return value within the function itself, we’d be able to produce a precise error during development <em>and</em> in the wild. We’d also add value to any existing unit tests that exercised <code>shouldIgnore()</code> at all, even those not written specifically to test just it.</p>
<p>A postcondition is also superior to checking the return value of <code>shouldIgnore()</code> wherever it’s called from, because it might be called from numerous and ever-changing places.</p>
<h2 id="postcondition-implementation">Postcondition Implementation</h2>
<p><code>on.exit()</code>, <code>stopifnot()</code>, and <code>returnValue()</code> are the necessary working pieces. They come together like this:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb5-1"><a href="#cb5-1"></a>make_odd &lt;-<span class="st"> </span><span class="cf">function</span>(n) {</span>
<span id="cb5-2"><a href="#cb5-2"></a>  <span class="kw">on.exit</span>(<span class="kw">stopifnot</span>(<span class="kw">returnValue</span>() <span class="op">%%</span><span class="st"> </span><span class="dv">2</span> <span class="op">==</span><span class="st"> </span><span class="dv">1</span>))</span>
<span id="cb5-3"><a href="#cb5-3"></a>  n <span class="op">+</span><span class="st"> </span><span class="dv">1</span></span>
<span id="cb5-4"><a href="#cb5-4"></a>}</span></code></pre></div>
<ol type="1">
<li><code>on.exit()</code> establishes an expression that should be run when the enclosing function, <code>make_odd()</code>, exits.</li>
<li><code>stopifnot()</code> is used as the expression to run</li>
<li><code>returnValue()</code> refers to the function’s return value, or the result of <code>n + 1</code></li>
</ol>
<p><code>shouldIgnore()</code> could be augmented using the same technique, to error if its return value was ever not a logical vector of length 1:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb6-1"><a href="#cb6-1"></a>shouldIgnore &lt;-<span class="st"> </span><span class="cf">function</span>(msg) {</span>
<span id="cb6-2"><a href="#cb6-2"></a></span>
<span id="cb6-3"><a href="#cb6-3"></a>  <span class="kw">on.exit</span>(<span class="kw">stopifnot</span>(<span class="kw">is.logical</span>(<span class="kw">returnValue</span>()) <span class="op">&amp;&amp;</span><span class="st"> </span><span class="kw">length</span>(<span class="kw">returnValue</span>()) <span class="op">==</span><span class="st"> </span><span class="dv">1</span>))</span>
<span id="cb6-4"><a href="#cb6-4"></a></span>
<span id="cb6-5"><a href="#cb6-5"></a>  canIgnore &lt;-<span class="st"> </span><span class="kw">c</span>(<span class="st">&#39;^a</span><span class="ch">\\</span><span class="st">[&quot;ACK.*$&#39;</span>, <span class="st">&#39;^</span><span class="ch">\\</span><span class="st">[&quot;ACK.*$&#39;</span>, <span class="st">&#39;^h$&#39;</span>)</span>
<span id="cb6-6"><a href="#cb6-6"></a>  <span class="cf">if</span> (<span class="kw">length</span>(<span class="kw">unlist</span>(stringr<span class="op">::</span><span class="kw">str_match_all</span>(msg, canIgnore))) <span class="op">&gt;</span><span class="st"> </span><span class="dv">0</span>) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb6-7"><a href="#cb6-7"></a>  parsed &lt;-<span class="st"> </span><span class="kw">parseMessage</span>(msg)</span>
<span id="cb6-8"><a href="#cb6-8"></a>  <span class="cf">if</span> (<span class="kw">length</span>(<span class="kw">intersect</span>(<span class="kw">names</span>(parsed), <span class="kw">c</span>(<span class="st">&quot;busy&quot;</span>, <span class="st">&quot;progress&quot;</span>, <span class="st">&quot;recalculating&quot;</span>))) <span class="op">&gt;</span><span class="st"> </span><span class="dv">0</span>) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb6-9"><a href="#cb6-9"></a>  <span class="cf">if</span> (<span class="kw">identical</span>(<span class="kw">names</span>(parsed), <span class="kw">c</span>(<span class="st">&quot;custom&quot;</span>))) {</span>
<span id="cb6-10"><a href="#cb6-10"></a>    customKeys &lt;-<span class="st"> </span><span class="kw">names</span>(parsed[[<span class="st">&quot;custom&quot;</span>]])</span>
<span id="cb6-11"><a href="#cb6-11"></a>    <span class="cf">if</span> (<span class="kw">isTRUE</span>(customKeys <span class="op">==</span><span class="st"> &quot;reactlog&quot;</span>)) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb6-12"><a href="#cb6-12"></a>  }</span>
<span id="cb6-13"><a href="#cb6-13"></a>  noop &lt;-<span class="st"> </span><span class="kw">list</span>(<span class="dt">errors =</span> <span class="kw">list</span>(), <span class="dt">values =</span> <span class="kw">list</span>(), <span class="dt">inputMessages =</span> <span class="kw">list</span>())</span>
<span id="cb6-14"><a href="#cb6-14"></a>  <span class="cf">if</span> (<span class="kw">identical</span>(parsed, noop)) <span class="kw">return</span>(<span class="ot">TRUE</span>)</span>
<span id="cb6-15"><a href="#cb6-15"></a>  <span class="kw">return</span>(<span class="ot">FALSE</span>)</span>
<span id="cb6-16"><a href="#cb6-16"></a>}</span></code></pre></div>
<h2 id="caveats">Caveats</h2>
<p>There are a couple things to be aware of if you use this approach.</p>
<p>First, <code>returnValue()</code> is marked experimental, and so the code where you use it might break in the future when you upgrade R.</p>
<p>Second, by default, <code>on.exit()</code> overwrites existing expressions. This can be changed by calling <code>on.exit()</code> with <code>add = TRUE</code>, but that’s a change you’d have to make to all <code>on.exit()</code> calls in your function subsequent to your postcondition.</p>
<h2 id="summary">Summary</h2>
<ol type="1">
<li><em>Assertions</em> are runnable statements about correct behavior</li>
<li><code>stopifnot()</code> is the easiest way to write assertions in R</li>
<li><code>on.exit()</code> and <code>returnValue()</code> can be used together with <code>stopifnot()</code> to write <em>postconditions</em>, or assertions about a function’s return value.</li>
</ol>
<p>Thanks for reading, I hope you enjoyed the post!</p>
<p>Thank you also to <a href="https://yihui.name/en/">Yihui Xie</a> for originally suggesting the approach to postconditions that I elaborate upon.</p>
          </div>
        </article>
        <article data-sblg-article="1" class="entry-content"></article>
      </main>
      <footer id="footer" class="footer-container">



        <div class="footer">

<div class="social">
  <nav aria-label="Social Menu">
    <ul class="social-menu"><li>
      <a href="mailto:alan@tailrecursion.com" >
          <span class="screen-reader">Send E-mail</span>
          <svg class="icon" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true">
  
  <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
  <polyline points="22,6 12,13 2,6"></polyline>
  
</svg>

        </a>
      </li><li>
        <a href="https://github.com/alandipert/wondr-blog" >
          <span class="screen-reader">Open Github account</span>
          <svg class="icon" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true">
  
  <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
  
</svg>

        </a>
      </li>

<li>
        <a href='https://twitter.com/alandipert' target='_blank' rel='noopener'>
          <span class='screen-reader'>Open Twitter account in new tab</span><svg class='icon' viewbox='0 0 24 24' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-hidden='true'>
  
  <path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"/>
  
</svg>
</a>
      </li>
    </ul>
  </nav>
</div>
        </div>
      </footer>
    </div>
  </body>
</html>