<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jasper Woudenberg</title>
    <description>The latest articles on DEV Community by Jasper Woudenberg (@jwoudenberg).</description>
    <link>https://dev.to/jwoudenberg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F17269%2Facd3c354-8e41-4263-8284-22ba9cba7c53.jpeg</url>
      <title>DEV Community: Jasper Woudenberg</title>
      <link>https://dev.to/jwoudenberg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jwoudenberg"/>
    <language>en</language>
    <item>
      <title>Haskell for the Elm Enthusiast</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Tue, 03 Aug 2021 15:40:01 +0000</pubDate>
      <link>https://dev.to/noredink/haskell-for-the-elm-enthusiast-ckm</link>
      <guid>https://dev.to/noredink/haskell-for-the-elm-enthusiast-ckm</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was co-authored by Michael Glass, Stöffel, and myself. It first first appeared on &lt;a href="https://blog.noredink.com/post/658510851000713216/haskell-for-the-elm-enthusiast" rel="noopener noreferrer"&gt;the NoRedInk blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Many years ago NRI adopted Elm as a frontend language. We started small with a disposable proof of concept, and as the engineering team increasingly was bought into Elm being a &lt;em&gt;much&lt;/em&gt; better developer experience than JavaScript more and more of our frontend development happened in Elm. Today almost all of our frontend is written in Elm.&lt;/p&gt;

&lt;p&gt;Meanwhile, on the backend, we use Ruby on Rails. Rails has served us well and has supported amazing growth of our website, both in terms of the features it supports, and the number of students and teachers who use it. But we’ve come to miss some of the tools that make us so productive in Elm: Tools like custom types for modeling data, or the type checker and its helpful error messages, or the ease of writing (fast) tests.&lt;/p&gt;

&lt;p&gt;A couple of years ago we started looking into Haskell as an alternative backend language that could bring to our backend some of the benefits we experience writing Elm in the frontend. Today some key parts of our backend code are written in Haskell. Over the years we’ve developed our style of writing Haskell, which can be described as very Elm-like (it’s also still changing!).&lt;/p&gt;

&lt;h2&gt;
  
  
  🌳 Why be Like Elm?
&lt;/h2&gt;

&lt;p&gt;Elm is a small language with great error messages, great documentation, and a great community. Together these make Elm one of the nicest programming languages &lt;em&gt;to learn&lt;/em&gt;. Participants in an &lt;a href="https://github.com/elmbridge/curriculum" rel="noopener noreferrer"&gt;ElmBridge&lt;/a&gt; event will go from knowing nothing of the language to writing a real application using Elm in 5 hours.&lt;/p&gt;

&lt;p&gt;We have a huge amount of Elm code at NoRedInk, and it supports some pretty tricky UI work. Elm scales well to a growing and increasingly complicated codebase. The compiler stays fast and we don’t lose confidence in our ability to make changes to our code. You can learn more about our Elm story &lt;a href="https://www.youtube.com/watch?v=DoA4Txr4GUs" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Unboxing Haskell
&lt;/h2&gt;

&lt;p&gt;Haskell shares a lot of the language features we like in Elm: Custom types to help us model our data. Pure functions and explicit side effects. Writing code without runtime exceptions (mostly).&lt;/p&gt;

&lt;p&gt;When it comes to ease of learning, Haskell makes different trade-offs than Elm. The language is much bigger, especially when including the many optional language features that can be enabled. It’s entirely up to you whether you want to use these features in your code, but you’ll need to know about many of them if you want to make use of Haskell’s packages, documentation, and how-tos. Haskell’s compiler errors typically aren’t as helpful as Elm’s are. Finally, we’ve read many Haskell books and blog posts, but haven’t found anything getting us from knowing no Haskell to writing a real application in it that’s anywhere near as small and effective as the &lt;a href="https://guide.elm-lang.org/" rel="noopener noreferrer"&gt;Elm Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏟️ When in Rome, Act Like a Babylonian
&lt;/h2&gt;

&lt;p&gt;Many of the niceties we’re used to in Elm we get in Haskell too. But Haskell has many additional features, and each one we use adds to the list of things that an Elm programmer will need to learn. So instead we took a path that many in the Haskell community took before us: limit ourselves to a subset of the language.&lt;/p&gt;

&lt;p&gt;There are many styles of writing Haskell, each with its own trade-offs. Examples include Protolude, RIO, the lens ecosystem, and many more. Our approach differs in being strongly inspired by Elm. So what does our Elm-inspired style of writing Haskell look like?&lt;/p&gt;

&lt;h3&gt;
  
  
  🍇 Low hanging fruit: the Elm standard library
&lt;/h3&gt;

&lt;p&gt;Our earliest effort in making our Haskell code more Elm-like was porting the Elm standard library to Haskell. We’ve open-sourced this port as a library named &lt;a href="https://hackage.haskell.org/package/nri-prelude" rel="noopener noreferrer"&gt;&lt;code&gt;nri-prelude&lt;/code&gt;&lt;/a&gt;. It contains Haskell counterparts of the Elm modules for working with &lt;code&gt;String&lt;/code&gt;s, &lt;code&gt;List&lt;/code&gt;s, &lt;code&gt;Dict&lt;/code&gt;s, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hackage.haskell.org/package/nri-prelude" rel="noopener noreferrer"&gt;&lt;code&gt;nri-prelude&lt;/code&gt;&lt;/a&gt; also includes a port of &lt;code&gt;elm-test&lt;/code&gt;. It provides everything you need for writing unit tests and basic property tests.&lt;/p&gt;

&lt;p&gt;Finally, it includes a GHC plugin that makes it so Haskell’s default &lt;code&gt;Prelude&lt;/code&gt; (basically its standard library) behaves like Elm’s defaults. For example, it adds implicit qualified imports of some modules like &lt;code&gt;List&lt;/code&gt;, similar to what Elm does.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎚️ Effects and the Absence of The Elm Architecture
&lt;/h3&gt;

&lt;p&gt;Elm is opinionated in supporting a single architecture for frontend applications, fittingly called The Elm Architecture. One of its nice qualities is that it forces a separation of application logic (all those conditionals and loops) and effects (things like talking to a database or getting the current time). We love using The Elm Architecture writing frontend applications, but don’t see a way to apply it 1:1 to backend development. In the F# community, they use the Elm Architecture for &lt;em&gt;some&lt;/em&gt; backend features (see: &lt;a href="https://safe-stack.github.io/docs/feature-clientserver-bridge/#when-to-use-elmishbridge" rel="noopener noreferrer"&gt;When to use Elmish Bridge&lt;/a&gt;), but it’s not generally applicable. We’d still like to encourage that separation between application logic and effects though, having seen some of the effects of losing that distinction in our backend code. Read our other post &lt;a href="https://blog.noredink.com/post/657392972659310592/pufferfish-please-scale-the-site" rel="noopener noreferrer"&gt; Pufferfish, please scale the site!&lt;/a&gt; if you want to read more about this.&lt;/p&gt;

&lt;p&gt;Out of many options we’re currently using &lt;a href="https://jaspervdj.be/posts/2018-03-08-handle-pattern.html" rel="noopener noreferrer"&gt;the handle pattern&lt;/a&gt; for managing effects. For each type of effect, we create a &lt;code&gt;Handler&lt;/code&gt; type (we added the extra &lt;code&gt;r&lt;/code&gt; in a typo way back and it has stuck around. Sorry). We use this pattern across our libraries for talking to outside systems: &lt;a href="https://hackage.haskell.org/package/nri-postgresql" rel="noopener noreferrer"&gt;&lt;code&gt;nri-postgresql&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://hackage.haskell.org/package/nri-http" rel="noopener noreferrer"&gt;&lt;code&gt;nri-http&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://hackage.haskell.org/package/nri-redis" rel="noopener noreferrer"&gt;&lt;code&gt;nri-redis&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://hackage.haskell.org/package/nri-kafka" rel="noopener noreferrer"&gt;&lt;code&gt;nri-kafka&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Without The Elm Architecture, we depend heavily on chaining permutations through a stateful &lt;code&gt;Task&lt;/code&gt; type. This feels similar to imperative coding: First, do A, then B, then C. Hopefully, when we’re later on in our Haskell journey, we’ll discover a nice architecture to simplify our backend code.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚚 Bringing Elm Values to Haskell
&lt;/h3&gt;

&lt;p&gt;One way in which Haskell is different from both Elm and Rails is that it is not particularly opinionated. Often the Haskell ecosystem offers multiple different ways to do one particular thing. So whether it’s writing an http server, logging, or talking with a database, the first time we do any of these things we’ll need to decide &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When adopting a Haskell feature or library, we care about&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smallness, e.g. introduce new concepts only when necessary&lt;/li&gt;
&lt;li&gt;how “magical” is it? E.g. How &lt;em&gt;surprising&lt;/em&gt; is it?&lt;/li&gt;
&lt;li&gt;How easy is it to &lt;em&gt;learn&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;how easy is it to &lt;em&gt;use?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;how comprehensible is the documentation?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;explicitness&lt;/em&gt; over *terseness (*but terseness isn’t implicitly bad).&lt;/li&gt;
&lt;li&gt;consistency &amp;amp; predictability&lt;/li&gt;
&lt;li&gt;“safety” (no runtime exceptions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the Haskell ecosystem provides an option that fits our Elm values, like with the handle pattern, and so we go with it. Other times a library has different values, and then the choice not to use it is easy as well. An example of this is lens/prism ecosystem, which allows one to write super succinct code, but is almost a language onto itself that one has to learn first.&lt;/p&gt;

&lt;p&gt;The hardest decisions are the ones where an approach protects us against making mistakes in some way (which we like) but requires familiarity with more language features to use (which we prefer to avoid).&lt;/p&gt;

&lt;p&gt;To help us make better decisions, we often &lt;strong&gt;try it both ways.&lt;/strong&gt; That is, we’re willing to build a piece of software with &amp;amp; without a complex language feature to ensure the &lt;em&gt;cost&lt;/em&gt; of the complexity is worth the benefit that the feature brings us.&lt;/p&gt;

&lt;p&gt;Another approach we take is making decisions locally. A single team might evaluate a new feature, and then demo it and share it with other teams after they have a good sense the feature is worth it. Remember: a super-power of Haskell is easy refactorability. Unlike our ruby code, going through and doing major re-writes in our Haskell codebase is often an hours-or-days-long (rather than weeks-or-months-long) endeavor. Adopting two different patterns simultaneously has a relatively small cost!&lt;/p&gt;

&lt;h3&gt;
  
  
  Case studies in feature adoption:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  🐘 Type-Check All Elephants
&lt;/h4&gt;

&lt;p&gt;One example where our approach is Elm-like in some ways but not in others is how we talk to the database. We’re using a GHC feature called quasiquoting for this, which allow us to embed SQL query strings directly into our Haskell code, like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{-# LANGUAGE QuasiQuotes #-}

module Animals (listAll) where

import Postgres (query, sql)

listAll :: Postgres.Handler -&amp;gt; Task Text (List (Text, Text))
listAll postgres =
  query postgres [sql|SELECT species, genus FROM animals|]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A library called &lt;a href="https://hackage.haskell.org/package/postgresql-typed" rel="noopener noreferrer"&gt;&lt;code&gt;postgresql-typed&lt;/code&gt;&lt;/a&gt; can test these queries against a real Postgres database and show us an error at compile time if the query doesn’t fit the data. Such a compile-time error might happen if a table or column we reference in a query doesn’t exist in the database. This way we use static checks to eliminate a whole class of potential app/database compatibility problems!&lt;/p&gt;

&lt;p&gt;The downside is that writing code like this requires everyone working with it to learn a bit about quasi quotes, and what return type to expect for different kinds of queries. That said, using some kind of querying library instead has a learning curve too, and query libraries tend to be pretty big to support all the different kinds of queries that can be made.&lt;/p&gt;

&lt;h4&gt;
  
  
  🔣 So Many Webserver Options
&lt;/h4&gt;

&lt;p&gt;Another example where we traded additional safety against language complexity is in our choice of webserver library. We went with &lt;code&gt;servant&lt;/code&gt; here, a library that lets you express REST APIs using types, like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Servant

data Routes route = Routes
  { listTodos ::
      route
        :- "todos"
        :&amp;gt; Get '\[JSON\] [Todo],
    updateTodo ::
      route
        :- "todos"
        :&amp;gt; Capture "id" Int
        :&amp;gt; ReqBody '[JSON] Todo
        :&amp;gt; Put '[JSON] NoContent,
    deleteTodo ::
      route
        :- "todos"
        :&amp;gt; Capture "id" Int
        :&amp;gt; Delete '[JSON] NoContent
  }
  deriving (Generic)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Servant is a big library that makes use of a lot of type-level programming techniques, which are pretty uncommon in Elm, so there’s a steep learning cost associated with &lt;em&gt;understanding&lt;/em&gt; how the type magic works. Using it without a deep understanding is reasonably straightforward.&lt;/p&gt;

&lt;p&gt;The benefits gained from using Servant outweigh the cost of expanded complexity. Based on a type like the one in the example above, the servant ecosystem can generate functions in other languages like Elm or Ruby. Using these functions means we can save time with backend-to-frontend or service-to-service communication. If some Haskell type changes in a backward-incompatible fashion we will generate new Elm code, and this might introduce a compiler error on the Elm side.&lt;/p&gt;

&lt;p&gt;So for now we’re using servant! It’s important to note that what we &lt;em&gt;want&lt;/em&gt; is compile-time server/client compatibility checking, and that’s why we swallow Servant’s complexity. If we could get the same benefit without the type-level programming demonstrated above, we would prefer that. Hopefully, in the future, another library will offer the same benefits from a more Elm-like API.&lt;/p&gt;

&lt;h2&gt;
  
  
  😻 Like what you see?
&lt;/h2&gt;

&lt;p&gt;We're running the libraries discussed above in production. Our most-used Haskell application receives hundreds of thousands of requests per minute without issue and produces hardly any errors.&lt;/p&gt;

&lt;p&gt;Code can be found at &lt;a href="https://github.com/NoRedInk/haskell-libraries" rel="noopener noreferrer"&gt;&lt;code&gt;NoRedInk/haskell-libraries&lt;/code&gt;&lt;/a&gt;. Libraries have been published to hackage and stackage. We'd love to know what you think!&lt;/p&gt;

</description>
      <category>haskell</category>
      <category>elm</category>
    </item>
    <item>
      <title>☄️ Pufferfish, please scale the site!</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Thu, 22 Jul 2021 07:43:08 +0000</pubDate>
      <link>https://dev.to/noredink/pufferfish-please-scale-the-site-o4p</link>
      <guid>https://dev.to/noredink/pufferfish-please-scale-the-site-o4p</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was co-authored by Michael Glass, Stöffel, and myself. It first first appeared on &lt;a href="https://blog.noredink.com/post/657392972659310592/pufferfish-please-scale-the-site" rel="noopener noreferrer"&gt;the NoRedInk blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We created Team Pufferfish about a year ago with a specific goal: &lt;strong&gt;&lt;em&gt;to avert the MySQL apocalypse&lt;/em&gt;&lt;/strong&gt;! The MySQL apocalypse would occur when so many students would work on quizzes simultaneously that even the largest MySQL database AWS has on offer would not be able to cope with the load, bringing the site to a halt.&lt;/p&gt;

&lt;p&gt;A little over a year ago, we forecasted our growth and load-tested MySQL to find out how much wiggle room we had. In the worst case (because we dislike apocalypses), or in the best case (because we like growing), we would have about a year’s time. This meant we needed to get going!&lt;/p&gt;

&lt;p&gt;Looking back on our work now, the most important lesson we learned was the importance of timely and precise feedback at every step of the way. At times we built short-lived tooling and process to support a particular step forward. This made us so much faster in the long run.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏔 Climbing the Legacy Code Mountain
&lt;/h2&gt;

&lt;p&gt;Clear from the start, Team Pufferfish would need to make some pretty fundamental changes to the Quiz Engine, the component responsible for most of the MySQL load. Somehow the Quiz Engine would need to significantly reduce its load on MySQL.&lt;/p&gt;

&lt;p&gt;Most of NoRedInk runs on a Rails monolith, including the Quiz Engine. The Quiz Engine is big! It’s got lots of features! It supports our teachers &amp;amp; students to do lots of great work together! Yay!&lt;/p&gt;

&lt;p&gt;But the Quiz Engine has some problems, too. A mix of complexity and performance-sensitivity has made engineers afraid to touch it. Previous attempts at big structural change in the Quiz Engine failed and had to be rolled back. If Pufferfish was going make significant structural changes, we would need to ensure our ability to be productive in the Quiz Engine codebase. Thinking we could &lt;em&gt;just do it&lt;/em&gt; without a new approach would be foolhardy.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ ️The Vengeful God of Tests
&lt;/h3&gt;

&lt;p&gt;We have mixed feelings about our test suite. It’s nice that it covers a lot of code. Less nice is that we don’t really know what each test is intended to check. These tests have evolved into complex bits of code by themselves with a lot of supporting logic, and in many cases, tight coupling to the implementation. Diving deep into some of these tests has uncovered tests &lt;em&gt;no longer covering any production logic at all&lt;/em&gt;. The test suite is large and we didn’t have time to dive deep into each test, but we were also reluctant to delete test cases without being sure they weren’t adding value.&lt;/p&gt;

&lt;p&gt;Our relationship with the Quiz Engine test suite was and still is a bit like one might have with an angry Greek god. We’re continuously investing effort to keep it happy (i.e. green), but we don’t always understand what we’re doing or why. Please don’t spoil our harvest and protect us from (production) fires, oh mighty RSpec!&lt;/p&gt;

&lt;p&gt;The ultimate goal wasn’t to change Quiz Engine functionality, but rather to reduce its load on MySQL. This is the perfect scenario for tests to help us! The test suite we want is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast&lt;/li&gt;
&lt;li&gt;comprehensive, and&lt;/li&gt;
&lt;li&gt;not dependent on implementation&lt;/li&gt;
&lt;li&gt;includes performance testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, that’s not the hand we were given:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The suite takes about 30 minutes to run in CI and even longer locally.&lt;/li&gt;
&lt;li&gt;Our QA team finds bugs that sneaked past CI in PRs with Quiz Engine changes relatively frequently.&lt;/li&gt;
&lt;li&gt;Many tests ensure that &lt;em&gt;specific queries&lt;/em&gt; are performed in a &lt;em&gt;specific order&lt;/em&gt;. Considering we might replace MySQL wholesale, these tests provide little value.&lt;/li&gt;
&lt;li&gt;And because a lot of Quiz Engine code is extremely performance-sensitive, there’s an increased risk of performance regressions only surfacing with real production load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fighting with our tests meant that even small changes would take hours to verify in tests, and then, because of unforeseen regressions not covered by the tests, take multiple attempts to fix, resulting in multiple-day roll-outs for small changes.&lt;/p&gt;

&lt;p&gt;Our clock is ticking! &lt;strong&gt;&lt;em&gt;We needed&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;to&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;iterate faster than that if we were going to&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;avert the apocalypse.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🐶 I have no idea what I’m doing 🧪
&lt;/h3&gt;

&lt;p&gt;Reading complicated legacy Rails code often raises questions that take surprising amounts of effort to answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this method dead code? If not, who is calling this?&lt;/li&gt;
&lt;li&gt;Are we ever entering this conditional? When?&lt;/li&gt;
&lt;li&gt;Is this function talking to the database?&lt;/li&gt;
&lt;li&gt;Is this function &lt;em&gt;intentionally&lt;/em&gt; talking to the database?&lt;/li&gt;
&lt;li&gt;Is this function only &lt;em&gt;reading&lt;/em&gt; from the database or also &lt;em&gt;writing&lt;/em&gt; to it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It isn’t even clear &lt;em&gt;what&lt;/em&gt; code was running. There are a few features of Ruby (and Rails) which optimize for &lt;em&gt;writing&lt;/em&gt; code over &lt;em&gt;reading&lt;/em&gt; it. We did our best to unwrap this type of code:&lt;/p&gt;

&lt;p&gt;Rails provides devs the ability to wrap functionality in hooks. &lt;code&gt;before_&lt;/code&gt; and &lt;code&gt;after_&lt;/code&gt; hooks let devs write setup and tear-down code once, then forget it. However, the existence of these hooks means calling a method might &lt;em&gt;also&lt;/em&gt; evaluate code defined in a different file, and you won’t know about it unless you explicitly look for it. &lt;em&gt;Hard to read!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Complicating things further is Ruby’s dynamic dispatch based on subclassing and polymorphic associations. Which &lt;code&gt;load_students&lt;/code&gt; am I calling? The one for &lt;code&gt;Quiz&lt;/code&gt; or the one for &lt;code&gt;Practice&lt;/code&gt;? They each implement the &lt;code&gt;Assignment&lt;/code&gt; interface but have pretty different behavior! And: they each have their own set of hooks🤦. Maybe it’s something completely different!&lt;/p&gt;

&lt;p&gt;And then there’s &lt;code&gt;ActiveRecord&lt;/code&gt;. &lt;code&gt;ActiveRecord&lt;/code&gt; makes it easy to write queries — a little too easy. It &lt;em&gt;doesn’t&lt;/em&gt; make it easy to know where queries are happening. It’s ergonomic that we can tell &lt;code&gt;ActiveRecord&lt;/code&gt; what we need, and let it figure how to fetch the data. It’s less nice when you’re trying to find out where in the code your queries are happening and the answer to that question is, “absolutely anywhere”. We want to know &lt;em&gt;exactly what queries&lt;/em&gt; are happening on these code paths. &lt;code&gt;ActiveRecord&lt;/code&gt; doesn’t help.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧵A rich history
&lt;/h3&gt;

&lt;p&gt;A final factor that makes working in Quiz Engine code daunting is the sheer size of the beast. The Quiz Engine has grown organically over many years, so there’s a lot of functionality to be aware of.&lt;/p&gt;

&lt;p&gt;Because the Quiz Engine itself has been hard to change for a while, APIs defined between bits of Quiz Engine code often haven’t evolved to match our latest understanding. This means understanding the Quiz Engine code requires not just understanding what it does today, but also how we thought about it in the past, and what (partial) attempts were made to change it. This increases the sum of Quiz Engine knowledge even further.&lt;/p&gt;

&lt;p&gt;For example, we might try to refactor a bit of code, leading to tests failing. But is this conditional branch ever reached in production? 🤷&lt;/p&gt;

&lt;h2&gt;
  
  
  Enough complaining. What did we do about it?
&lt;/h2&gt;

&lt;p&gt;We knew this was going to be a huge project, and huge projects, in the best case, are shipped late, and in the average case don’t ever ship. The only way we were going to have confidence that our work would ever see the light of day was by doing the riskiest, hardest, scariest stuff first. That way, if one approach wasn’t going to work, we would find out about it sooner and could try something new before we’d over-invested in a direction.&lt;/p&gt;

&lt;p&gt;So: where is the risk? What’s the scariest problem we have to solve? History dictates: The more we change the legacy system, the more likely we’re going to cause regressions.&lt;/p&gt;

&lt;p&gt;So our first task: cut away the part of the Quiz Engine that performs database queries and port this logic to a separate service. Henceforth when Rails needs to read or change Quiz Engine data, it will talk to the new service instead of going to the database directly.&lt;/p&gt;

&lt;p&gt;Once the legacy-code risk has been minimized, we would be able to focus on the (still challenging) task of changing where we store Quiz Engine data from single-database MySQL to something horizontally scalable.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⛏️ Phase 1: Extracting queries from Rails
&lt;/h3&gt;

&lt;h4&gt;
  
  
  🔪 Finding out where to cut
&lt;/h4&gt;

&lt;p&gt;Before extracting Quiz Engine MySQL queries from our Rails service, we first needed to know where those queries were being made. As we discussed above this wasn’t obvious from reading the code.&lt;/p&gt;

&lt;p&gt;To find the MySQL queries themself, we built some tooling: we monkey-patched &lt;code&gt;ActiveRecord&lt;/code&gt; to warn whenever an unknown read or write was made against one of the tables containing Quiz Engine data. We ran our monkey-patched code first in CI and later in production, letting the warnings tell us where those queries were happening. Using this information we decorated our code by marking all the reads and writes. Once code was decorated, it would no longer emit warnings. As soon as all the writes &amp;amp; reads were decorated, we changed our monkey-patch to not just warn but fail when making a query against one of those tables, to ensure we wouldn’t accidentally introduce new queries touching Quiz Engine data.&lt;/p&gt;

&lt;h4&gt;
  
  
  🚛 Offloading logic: &lt;strong&gt;Our first approach&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now we knew where to cut, we decided our place of greatest risk was moving a single MySQL query out of our rails app. If we could move a single query, we could move all of them. There was one rub: if we &lt;em&gt;did&lt;/em&gt; move all queries to our new app, we would add a lot of network latency. because of the number of round trips needed for a single request. Now we have a constraint: Move a single query into a new service, but with very little latency.&lt;/p&gt;

&lt;p&gt;How did we reduce latency?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get rid of network latency by getting rid of the network — we hosted the service in the same hardware as our Rails app.&lt;/li&gt;
&lt;li&gt;Get rid of protocol latency by using a dead-simple protocol: socket communication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We ended up building a socket server in Haskell that took data requests from Rails, and transformed them into a series of MySQL queries, which rails would use to fetch the data itself.&lt;/p&gt;

&lt;h4&gt;
  
  
  🛸 Leaving the Mothership: Fewer Round Trips
&lt;/h4&gt;

&lt;p&gt;Although co-locating our service with rails got us off the ground, it required significant duct tape. We had invested a lot of work building nice deployment systems for HTTP services and we didn’t want to re-invent that tooling for socket-based side-car apps. The thing that was preventing the migration was having too many round-trip requests to the Rails app. How could we reduce the number of round trips?&lt;/p&gt;

&lt;p&gt;As we moved MySQL query generation to our new service, we started to see this pattern in our routes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MySQL Read some data       ┐
Ruby  Do some processing   │ candidate 1 for
MySQL Read some more data  ┘ extraction
Ruby  More processing
MySQL Write some data      ┐
Ruby  Processing again!    │ candidate 2 for
MySQL Write more data      ┘ extraction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To reduce latency, we’d have to bundle reads and writes: In addition to porting reads &amp;amp; writes to the new service, we’d have to port the ruby logic &lt;em&gt;between&lt;/em&gt; reads and writes, which would be a lot of work.&lt;/p&gt;

&lt;p&gt;What if instead, we could &lt;em&gt;change&lt;/em&gt; the order of operations and make it look like this?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MySQL Read some data       ┐ candidate 1 for
MySQL Read some more data  ┘ extraction
Ruby  Do some processing
Ruby  More processing
Ruby  Processing again!
MySQL Write some data      ┐ candidate 2 for
MySQL Write more data      ┘ extraction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we’d be able to extract batches of queries to Haskell and leave the logic behind in Rails.&lt;/p&gt;

&lt;p&gt;One concern we had with changing the order of operations like this was the possibility of a request handler &lt;em&gt;first&lt;/em&gt; writing some data to the database, then reading it back again later. Changing the order of read and write queries would result in such code failing. However, since we now had a complete and accurate picture of &lt;em&gt;all&lt;/em&gt; the queries the Rails code was making, we knew (luckily!) we didn’t need to worry about this.&lt;/p&gt;

&lt;p&gt;Another concern was the risk of a large refactor like this resulting in regressions causing long feedback cycles and breaking the Quiz Engine. To avoid this we tried to keep our refactors as dumb as possible: Specifically: we mostly did a lot of inlining. We would start with something like this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class QuizzesControllller &amp;lt; ActionController
  def show
    quiz = load_quiz! # here are queries sometimes
    quiz_type = which_quiz(quiz) # and here other times
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and we would aggressively inline functions to surface where and why we were querying&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class QuizzesControllller &amp;lt; ActionController
  def show
    quiz = Quiz.find(quiz_id_param)
    quiz_type =
      if quiz.for_credit?
        :for_credit
      else
        load_practice_quiz_type
      end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and again, and again&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class QuizzesControllller &amp;lt; ActionController
  def show
    quiz = Quiz.find(quiz_id_param)
    quiz_type =
      if quiz.for_credit?
        :for_credit
      else
        how_much_fun = QuizForFun.find(quiz_id_param)
        if how_much_fun &amp;gt; 9000
          :super_saiyan
        else
          load_sub_syan_fun_type # TODO: inline me
        end
      end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These are refactors with a relatively small chance of changing behavior or causing regressions.&lt;/p&gt;

&lt;p&gt;Once the query was at the top level of the code it became clear when we needed data, and that understanding allowed us to push those queries to happen first.&lt;/p&gt;

&lt;p&gt;e.g. from above, we could easily push the previously obscured &lt;code&gt;QuizForFun&lt;/code&gt; query to the beginning:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class QuizzesControllller &amp;lt; ActionController
  def show
    quiz = Quiz.find(quiz_id_param)
    how_much_fun =
      if quiz.for_credit?
        nil
      else
        QuizForFun.find(quiz_id_param)
      end
    quiz_type = if quiz.for_credit?
        :for_credit
      elsif how_much_fun &amp;gt; 9000
        :super_saiyan
      else
        load_sub_syan_fun_type # TODO: inline me
      end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You might expect our bout of inlining to introduce a ton of duplication in our code, but in practice, it surfaced a lot of dead code and made it clearer what the functions we left behind were doing. That wasn’t what we set out to do, but still, nice!&lt;/p&gt;

&lt;h2&gt;
  
  
  👛 Phase 2: Changing the Quiz Engine datastore
&lt;/h2&gt;

&lt;p&gt;At this point all interactions with the Quiz Engine datastore were going through this &lt;strong&gt;&lt;em&gt;new&lt;/em&gt;&lt;/strong&gt; Quiz Engine service. Excellent! This means for the second part of this project, the part where we were actually going to avert the MySQL apocalypse, we wouldn’t need to worry about our legacy Rails code.&lt;/p&gt;

&lt;p&gt;To facilitate easy refactoring, we built this new service in Haskell. The effect was immediately noticeable. Like an embargo had been lifted, from this point forward we saw a constant trickle of small productive refactors get mixed in the work we were doing, slowly reshaping types to reflect our latest understanding. Changes we wouldn’t have made on the Rails side unless we’d have set aside months of dedicated time. Haskell is a great tool to use to manage complexity!&lt;/p&gt;

&lt;p&gt;The centerpiece of this phase was the architectural change we were planning to make: switching from MySQL to a horizontally scalable storage solution. But honestly, figuring the architecture details here wasn’t the most interesting or challenging portion of the work, so we’re just putting that aside for now. Maybe we’ll return to it in a future blog post (sneak peek: we ended up using Redis and Kafka). Like in step 1, the biggest question we had to solve was &lt;strong&gt;&lt;em&gt;“how are we going to make it safe to move forward quickly?”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One challenge was that we had left most of our test suite behind in Rails in phase one, so we were not doing too well on that front. We added Haskell test coverage of course, including many &lt;strong&gt;&lt;em&gt;golden result tests&lt;/em&gt;&lt;/strong&gt; which are worth a post on their own. Together with our QA team we also invested in our Cypress integration test suite which runs tests from the browser, thus integration-testing the combination of our Rails and Haskell code.&lt;/p&gt;

&lt;p&gt;Our most useful tool in making safe changes in this phase however was our production traffic. We started building up what was effectively a parallel Haskell service talking to Redis next to the existing one talking to MySQL. Both received production load from the start, but until the very end of the project only the MySQL code paths’ response values were used. When the Redis code path didn’t match the MySQL, we’d log a bug. Using these bug reports, we slowly massaged the Redis code path to return identical data to MySQL.&lt;/p&gt;

&lt;p&gt;Because we weren’t relying on the output of the Redis code path in production, we could deploy changes to it many times a day, without fear of breaking the site for students or teachers. These deploys provided frequent and fast feedback. Deploying frequently was made possible by the Haskell Quiz Engine code living in its own service, which meant deploys contained only changes by our team, without work from other teams with a different risk profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  🥁 So, did it work?
&lt;/h2&gt;

&lt;p&gt;It’s been about a month since we’ve switched entirely to the new architecture and it’s been humming along happily. By the time we did the official switch-over to the new datastore it had been running at full-load (but with bugs) for a couple of months already. Still, we were standing ready with buckets of water in case we overlooked something. Our anxiety was in vain: the roll-out was a non-event.&lt;/p&gt;

&lt;p&gt;Architecture, plans, goals, were all important to making this a success. Still, we think the thing most crucial to our success was continuously improving our feedback loops. Fast feedback (lots of deploys), accurate feedback (knowing &lt;strong&gt;all&lt;/strong&gt; the MySQL queries Rails is making), detailed feedback (lots of context in error reports), high signal/noise ratio (removing errors we were not planning to act on), lots of coverage (many students doing quizzes). Getting this feedback required us to constantly tweak and create tooling and new processes. But even if these processes were sometimes short-lived, they've never been an overhead, allowing us to move so much faster.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Helper modules are okay</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Fri, 12 Mar 2021 23:41:04 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/helper-modules-are-okay-1a9p</link>
      <guid>https://dev.to/jwoudenberg/helper-modules-are-okay-1a9p</guid>
      <description>&lt;p&gt;A module containing a loose collection of mostly unrelated functions, that would be my definition of a Helper module. The name &lt;code&gt;helpers&lt;/code&gt; kind of gives it away, it shows we weren't able to find a terse expression of the module's purpose.&lt;/p&gt;

&lt;p&gt;Maybe their haphazard construction makes helper modules a bit unlikeable. They don't have great designs. But I like them anyway, because I think they fill an important niche in the wider ecosystem that is a codebase. They allow us to remove duplication without doing any api design work, at least not yet.&lt;/p&gt;

&lt;p&gt;Api design is hard (&lt;a href="https://dev.to/jwoudenberg/api-design-for-code-quality-4aml"&gt;I wrote about this before&lt;/a&gt;), and doing a good job requires plenty of example usages, to test our design as we create it. When we first spot some duplication it is unlikely to already be so pervasive that enough data for a good design exists. Given insufficient data, &lt;a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction" rel="noopener noreferrer"&gt;duplication is better than a bad abstraction&lt;/a&gt;. And after duplication, adding a helper might be the next least-worst option.&lt;/p&gt;

&lt;p&gt;What happens next depends on usage of the helper. If it grows we might at some point design that abstraction using all the example use cases we now have. If usages remain constant we'll likely never look at the helper again. And if usage drops to a single callsite we can move the helper back to the module where it is called. One fewer helper, and one fewer layer of indirection!&lt;/p&gt;

</description>
      <category>apidesigne</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Informal work lists</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Tue, 09 Mar 2021 10:41:57 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/informal-work-lists-30j1</link>
      <guid>https://dev.to/jwoudenberg/informal-work-lists-30j1</guid>
      <description>&lt;p&gt;There are many ways I learn about new tasks. Email, Github, issue trackers, chat, and my memory all chip in. I have to copy these tasks over to a single place or I'll spend a lot of time worrying that I'm forgetting something.&lt;/p&gt;

&lt;p&gt;I think engineering teams can face a similar problem, tracking tasks and work in progress in many different places. Here are some examples of todo lists teams keep, that maybe aren't immediately recognizable as such.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The collection of &lt;code&gt;TODO&lt;/code&gt; comments in code pretty literally compromise a todo list.&lt;/li&gt;
&lt;li&gt;Skipped tests represent work in progress. The work will be finished either when the test is fixed and un-skipped, or removed.&lt;/li&gt;
&lt;li&gt;Each experimental feature flag represents some work in progress. Once the work is finished the flag can be removed again. (I realize there's other uses for feature flags that don't count as work in progress).&lt;/li&gt;
&lt;li&gt;Debug instrumentation running in production to help understand a specific issue of our code represent work in progress. Once the problem is understood the instrumentation can be removed again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The risk I see here is these are basically backlogs &lt;a href="https://dev.to/jwoudenberg/backlogs-aren-t-free-332d"&gt;with all the associated costs&lt;/a&gt;, but these costs aren't necessarily obvious. In fact its super easy to add an item of work in any of these categories. Removing an item of work can be much harder, especially if we are not or no longer the person with context on why the task was added.&lt;/p&gt;

&lt;p&gt;We can mitigate some of the risk using by adding some enabling constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI can reject PRs that contain &lt;code&gt;TODO&lt;/code&gt; comments. That way &lt;code&gt;TODO&lt;/code&gt; comments can be used as a reminder system while working on a PR, but any that remain when the PR is done will have to be moved into the team's backlog proper. Assuming folks take more care writing a backlog issue than a &lt;code&gt;TODO&lt;/code&gt; comment (this is true for me), this can force us to reflect on whether the thought represented by the &lt;code&gt;TODO&lt;/code&gt; is really worth turning into a ticket.
If you don't want to loose the ability to add &lt;code&gt;TODO&lt;/code&gt; comments in PRs but do like these needing to be added to the teams tracker, an alternative system is to enforce that &lt;code&gt;TODO&lt;/code&gt; comments need to include a link or issue number.&lt;/li&gt;
&lt;li&gt;Similar to &lt;code&gt;TODO&lt;/code&gt; comments, consider failing CI on skipped tests, or fail CI when skipped tests aren't accompanied by a link to a ticket.&lt;/li&gt;
&lt;li&gt;We can have a convention of adding comments to each feature flag, specifically explaining under what circumstances it will be flipped and when it can be removed. Answering these questions can help us figure out if adding a feature flag is the right move, and help future us or others remove the feature flag once it has served its purpose.&lt;/li&gt;
&lt;li&gt;Similar to documenting feature flags, a convention of commenting on debug instrumentation that answers the same two questions: when is this going to be used and when can we remove it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These small process changes can help us make more conscious decisions about whether to create future work for ourselves. And when we decide to go forward, set our future selves or others up better to complete the work, or decide to remove the task.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>Collaboration and Parallelization</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Fri, 26 Feb 2021 11:39:13 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/collaboration-and-parallelization-34le</link>
      <guid>https://dev.to/jwoudenberg/collaboration-and-parallelization-34le</guid>
      <description>&lt;p&gt;Collaborating on a task is great. Here's just some of the benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having more eyes on a problem helps us work faster. Another human might spot the problem causing a bug we missed, avoid unnecessary work, and help us avoid getting stuck in rabbit holes.&lt;/li&gt;
&lt;li&gt;Hearing more voices when making a decision improves the quality of that decision. The earlier we get those voices in, the less rework we have to do.&lt;/li&gt;
&lt;li&gt;By having more ears in the room we can bake context-sharing into the development process, and reduce the need for separate meetings, processes, and roles dedicated to alignment.&lt;/li&gt;
&lt;li&gt;Some of our hard work can be draining. Sharing painful tasks can help keep us motivated.&lt;/li&gt;
&lt;li&gt;Collaborating on tasks means we need fewer tasks in progress at the same time. This means our backlogs can be shorter, which has benefits of its own that &lt;a href="https://dev.to/jwoudenberg/backlogs-aren-t-free-332d"&gt;I wrote about previously&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think most people (including me) don't feel like collaborating on tasks 100% of the time. I need time for myself as well. Given the advantages above though, it seems worthwhile to continuously look for ways to collaborate as much as possible, as long as it's sustainable.&lt;/p&gt;

&lt;p&gt;This goal of collaborating more can be at odds with another popular goal: to parallelize work as much as possible.&lt;/p&gt;

&lt;p&gt;If we create a large backlog of tasks ready to pick up then we intentionally make the barrier to starting new work &lt;em&gt;very&lt;/em&gt; low. Starting new work takes less energy than gaining context on an in-progress task and supporting a colleague.&lt;/p&gt;

&lt;p&gt;When I created backlogs like that I think it was primarily out of fear that without sufficiently parallelized work someone on the team (myself included) might find themselves with nothing to do. Never mind I couldn't even imagine what it would be like not to know a single useful thing to do. Never mind that having some slack in our schedules for small refactoring tasks and the like makes us more productive rather than less. Any stretch of time between an individual running out of work and the team deciding a next step seemed unacceptable.&lt;/p&gt;

&lt;p&gt;There is a self-reinforcing feedback loop here: If we're used to picking up new work over helping finish in progress work then running out of new work is a big problem. By addressing the problem we further encourage ourselves to start new tasks.&lt;/p&gt;

&lt;p&gt;One way to break out of this loop is using a work in progress limit, a maximum amount of tasks the team can have in progress at the same time. When at the limit an in-progress task needs to be finished before a new one can started. Retro on what the biggest obstacles are on setting the limit lower and try to remove them. A work in progress limit will force us to work on fewer things at the same time without having to give up the curated backlog (which is scary!).&lt;/p&gt;

&lt;p&gt;Here's another self-reinforcing feedback loop incentivizing parallelization: As we practice collaborating on tasks our toolbox of collaboration strategies gets fuller. Until we have a full toolbox a lot of upcoming work can look unsuitable for collaboration, causing us to parallelize it preemptively and robbing ourselves of the opportunity to learn.&lt;/p&gt;

&lt;p&gt;We can break this loop by committing not to parallelizing preemptively. This means accept some amount of pain and trying to find collaboration strategies to address that pain. Maybe it doesn't work and you'll fall back to parallelizing anyway. That's fine. The important thing is to try collaboration first.&lt;/p&gt;

&lt;p&gt;How much do you collaborate on your team? How much do you parallelize work? Do you experience any tensions between the two?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>collaboration</category>
    </item>
    <item>
      <title>Backlogs Aren't Free</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Thu, 21 Jan 2021 18:25:34 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/backlogs-aren-t-free-332d</link>
      <guid>https://dev.to/jwoudenberg/backlogs-aren-t-free-332d</guid>
      <description>&lt;p&gt;I don't know what I would do without some sort of work list. If I couldn't jot down the occasional "I need to do X" thought for later, I'm pretty sure I wouldn't get any work done! So whether you call it a todo-list, backlog, roadmap, queue, inbox, or rug (for sweeping things under), I need one.&lt;/p&gt;

&lt;p&gt;Backlogs address a real need but also create a new issue: They grow. This might not seem a particularly big problem. Sure, it doesn't &lt;em&gt;feel&lt;/em&gt; great to have this list of a hundred things we want to do that doubles in size every year, but does it really &lt;em&gt;hurt&lt;/em&gt; anyone? Yes it does, says Donald G. Reinertsen in his amazing book &lt;a href="http://lpd2.com/" rel="noopener noreferrer"&gt;Principles of Product Development Flow&lt;/a&gt;. He introduces yet another term for backlog, product development inventory, and claims its cost is massively underestimated.&lt;/p&gt;

&lt;p&gt;Reinertsen identifies the following costs of product development inventory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Longer cycle time. Cycle time is the time between a task's conception and completion. That includes the time we spend working on a task but also the time it sits in a backlog waiting. The longer the backlog, the longer the wait.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increased risk. This follows from the previous point. Consider as an example the task to fix a bug in a piece of software. Waiting with a fix increases the damage the bug can do, gives the buggy code time to integrate with other code making it harder to change and fix, and gives time for context about the buggy code to seep away, increasing the cost of a fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More variability. When temporarily blocked from moving forward with one task a large backlog will always provide another one to work on instead. This creates a bias to having lots of things going on at the same time, which makes it hard to bring focus to those few tasks which are a bit harder than the rest. Such tasks can drag on and result in sudden unexpected delays.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More overhead. Backlogs require prioritizing. Prioritizing tasks requires (re)gaining context on what these tasks are about. If someone else requested work they'll appreciate the occasional status update about how that work is going. The larger the backlog, the more of this type of work we have to do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lower quality. If a single step to improve a product takes more time (longer cycle time), then our feedback cycle will be longer too. Reducing the quality of feedback will hurt quality itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Less motivation. This one is most intuitive to me. Seeing my todo-list shrink makes me feel good about myself. Seeing it grow not so much.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The upshot of all this: prefer shorter backlogs. Anyone in the market for a 'short backlog methodology' has their pick of gurus. Reinertsen presents a number techniques in &lt;a href="http://lpd2.com/" rel="noopener noreferrer"&gt;The Principles of Product Development Flow&lt;/a&gt;. But if you haven't chosen an approach yet, or if the approach you did pick isn't working for you, here's a backup option I quite like: throwing stuff away. Deleting tickets can be scary, but I find a long backlog scarier.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>Don't DRY, CARE!</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Sun, 10 Jan 2021 14:10:29 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/don-t-dry-care-3he9</link>
      <guid>https://dev.to/jwoudenberg/don-t-dry-care-3he9</guid>
      <description>&lt;p&gt;If you had to describe how you try to write code in a single acronym, what would it be? I thought a bit about it, and the best I could come up with is CARE. It stands for: Code Aspires to Resemble Explanation. Whichever way I'd explain the purpose of a body of code, I want my code to look like that explanation.&lt;/p&gt;

&lt;p&gt;I'm not talking about trying to make lines of code resemble grammatically correct English sentences. It's more about code and (hypothetical) explanation having a similar structure. For example, if your domain is writing utensils and your explanation clearly delineates different behaviors for pencils, ballpoints, and fineliners, do those categories enjoy high-level visibility in your code too? Or will you only come across these categories in the occasional conditional deep down in the implementation?&lt;/p&gt;

&lt;p&gt;There's more than one way to explain most things, and so it follows multiple people applying CARE to a problem might end up with very different results. I think that's fine.&lt;/p&gt;

&lt;p&gt;Here's some smells that might indicate code could be more CAREful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some terminology appears only in explanation or only in code.&lt;/li&gt;
&lt;li&gt;A single 'beat' in an explanation narrative requires coordination between multiple distinct bits of code.&lt;/li&gt;
&lt;li&gt;The existence of comments explaining &lt;em&gt;what&lt;/em&gt; is happening, suggesting the code doesn't explain itself well.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codequality</category>
      <category>dry</category>
    </item>
    <item>
      <title>Inline &amp; Extract</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Sat, 21 Nov 2020 08:48:49 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/inline-extract-i03</link>
      <guid>https://dev.to/jwoudenberg/inline-extract-i03</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                      extracting functions
                              --&amp;gt;

      MONOLITHIC                               FRAGMENTED
   small number of                           large number of
   large functions                           small functions

                              &amp;lt;--
                       inlining functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No matter what you consider the sweet spot for function size, maintaining it requires extracting and inlining to take place with equal frequency.&lt;/p&gt;

&lt;p&gt;In your day-to-day work, do you inline functions as often as you extract them?&lt;/p&gt;

</description>
      <category>inline</category>
      <category>extract</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Automatically OCR scanned PDFs in NixOS</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Sat, 14 Nov 2020 17:55:08 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/automatically-ocr-scanned-pdfs-in-nixos-25d</link>
      <guid>https://dev.to/jwoudenberg/automatically-ocr-scanned-pdfs-in-nixos-25d</guid>
      <description>&lt;p&gt;Luckily I'm receiving more and more letters by email these days, but I still get a fair amount of paper letters as well. These I scan and then throw away.&lt;/p&gt;

&lt;p&gt;To make it so I can find these documents back when I need them I run optical character recognition (OCR) on them after scanning. I can then use &lt;a href="https://www.mankier.com/1/pdfgrep" rel="noopener noreferrer"&gt;pdfgrep&lt;/a&gt; to search for a keyword in a directory of PDFs. That's so much easier than coming up with an organization scheme for these documents and applying it!&lt;/p&gt;

&lt;p&gt;Here is how it works: My scanner is able to upload scanned files to a directory on a small server I'm running. When the server notices a new PDF in this directory it runs optical character recognition on it and then moves it to a different directory containing all the PDFs I ever scanned.&lt;/p&gt;

&lt;p&gt;My scanner is a &lt;a href="https://www.brother-usa.com/products/ads1700w" rel="noopener noreferrer"&gt;Brother ADS-1700w&lt;/a&gt;. The server is the smallest &lt;a href="https://www.hetzner.com/cloud" rel="noopener noreferrer"&gt;Hetzner Cloud&lt;/a&gt; instance (CX11) and costs me 3 Euro's a month. I use the &lt;a href="https://ocrmypdf.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;OCRmyPDF&lt;/a&gt; to run optical character recognition. The server is running &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;NixOS&lt;/a&gt; and is deployed using &lt;a href="https://github.com/DBCDK/morph" rel="noopener noreferrer"&gt;morph&lt;/a&gt;. Finally, I'm using &lt;a href="https://healthchecks.io/" rel="noopener noreferrer"&gt;healthchecks.io&lt;/a&gt; to let me know when the setup breaks.&lt;/p&gt;

&lt;p&gt;Below is the annotated Nix code that makes the whole thing work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# A systemd path unit. Path units can be used to start&lt;/span&gt;
  &lt;span class="c"&gt;# other services when something happens on the file&lt;/span&gt;
  &lt;span class="c"&gt;# system, like a file being created.&lt;/span&gt;
  &lt;span class="nv"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ocrmypdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;# Enable this unit automatically when the server starts.&lt;/span&gt;
    &lt;span class="nv"&gt;wantedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"multi-user.target"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Start ocr-ing when there's new work."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;pathConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c"&gt;# Activate when files appear in the /data/scans-to-ocr&lt;/span&gt;
      &lt;span class="c"&gt;# directory. This is where our scanner should upload&lt;/span&gt;
      &lt;span class="c"&gt;# scanned files!&lt;/span&gt;
      &lt;span class="nv"&gt;DirectoryNotEmpty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/data/scans-to-ocr"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c"&gt;# If /data/scans-to-ocr does not exist, create it.&lt;/span&gt;
      &lt;span class="nv"&gt;MakeDirectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c"&gt;# The service that does the actual work of running OCR.&lt;/span&gt;
  &lt;span class="nv"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ocrmypdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Run ocrmypdf in /data/scans-to-ocr."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;serviceConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c"&gt;# Explain to systemd that this service is a script,&lt;/span&gt;
      &lt;span class="c"&gt;# not some long-running process it needs to keep&lt;/span&gt;
      &lt;span class="c"&gt;# alive. If the script exits after it's done that's&lt;/span&gt;
      &lt;span class="c"&gt;# fine, systemd will call it again if there's new&lt;/span&gt;
      &lt;span class="c"&gt;# work!&lt;/span&gt;
      &lt;span class="nv"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"oneshot"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c"&gt;# Now we define what to run when this service gets&lt;/span&gt;
      &lt;span class="c"&gt;# activated.&lt;/span&gt;
      &lt;span class="c"&gt;#&lt;/span&gt;
      &lt;span class="c"&gt;# We can pass `ExecStart` a single command to execute,&lt;/span&gt;
      &lt;span class="c"&gt;# but the work we want to do does not fit in a single&lt;/span&gt;
      &lt;span class="c"&gt;# command. Instead we let Nix create a shell script,&lt;/span&gt;
      &lt;span class="c"&gt;# then tell systemd to run that script.&lt;/span&gt;
      &lt;span class="nv"&gt;ExecStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt;
        &lt;span class="nv"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;writeShellScriptBin&lt;/span&gt; &lt;span class="s2"&gt;"go-ocr"&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          #!/usr/bin/env bash&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;          # Run our OCR logic in turn for each scanned file.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          for file in /data/scans-to-ocr/*; do&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # generates a standard file name containing the &lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # current date and some random characters.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            output="$(mktemp -u "/tmp/$(date +%Y%m%d)_XXX.pdf")"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;            # Run ocrmypdf on the scanned file.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # --output-type   don't generate PDF/A's. This&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 might fail, requiring manual&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 intervention.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # --rotate-pages  puts pages right-side-up&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # --skip-text     makes it so ocrmypdf skips&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 pages in the PDF that already&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 have text content, instead of&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 failing.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # --language      which languages OCR should&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 detect. A lot (all?) languages&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 seem to be available by&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 default. Run&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 `tesseract --list-langs`&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #                 to find out which.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ocrmypdf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/ocrmypdf \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              --output-type pdf \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              --rotate-pages \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              --skip-text \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              --language nld+eng \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              "$file" \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              "$output" \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              &amp;amp;&amp;amp; rm "$file" \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              &amp;amp;&amp;amp; mv "$output" /docs&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;            # Let healthchecks.io know whether ocrmypdf&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            # succeeded or failed.&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;curl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/curl --retry 3 \&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              https://hc-ping.com/YOUR_UUID/$?&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          done&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/go-ocr"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c"&gt;# For healthchecks.io to mark a check as healthy it needs&lt;/span&gt;
  &lt;span class="c"&gt;# to receive a periodic update, but we might not scan any&lt;/span&gt;
  &lt;span class="c"&gt;# documents for days on end. The CRON job below will ping&lt;/span&gt;
  &lt;span class="c"&gt;# healthchecks.io once an hour, but only if the&lt;/span&gt;
  &lt;span class="c"&gt;# /data/scans-to-ocr directory is empty, indicating&lt;/span&gt;
  &lt;span class="c"&gt;# ocrmypdf is doing work.&lt;/span&gt;
  &lt;span class="nv"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cron&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cron&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;systemCronJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0 * * * *      root    "&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"ls -1qA /data/scans-to-ocr/ | grep -q . "&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"|| curl -fsS -m 10 --retry 5 "&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"-o /dev/null https://hc-ping.com/YOUR_UUID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nixos</category>
      <category>nix</category>
      <category>ocr</category>
      <category>automation</category>
    </item>
    <item>
      <title>API design for code quality</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Sat, 31 Oct 2020 11:03:34 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/api-design-for-code-quality-4aml</link>
      <guid>https://dev.to/jwoudenberg/api-design-for-code-quality-4aml</guid>
      <description>&lt;p&gt;I so appreciate a thoughtful API. Working with a great API feels like using a super power: I'm productive, feeling good about the quality of the code I write, and just &lt;em&gt;happy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Designing a good API is hard work. Above all it requires data about different things people want to do with the API. The more the better. Such data provides an objective measure of how useful the API is.&lt;/p&gt;

&lt;p&gt;I'm often tempted to create internal API's, hoping it will bring productivity and joy to working in part of the code. This is hard when the small group working with the code don't produce enough data to support good API design.&lt;/p&gt;

&lt;p&gt;Which isn't to say we should give up on code quality, just that API design might often not be the right approach. When writing code used by a small group of people techniques like data modeling can achieve better results.&lt;/p&gt;

</description>
      <category>apidesign</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Writing RSpec tests for great debugging experiences</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Sun, 20 Sep 2020 14:52:20 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/writing-rspec-tests-for-great-debugging-experiences-1oak</link>
      <guid>https://dev.to/jwoudenberg/writing-rspec-tests-for-great-debugging-experiences-1oak</guid>
      <description>&lt;p&gt;The past couple of months I've worked a lot in a legacy codebase. We are lucky to have an extensive test suite which helps our efforts to make large changes immensely. At the same time working these tests has been frustrating. It's clear some failing tests provide better debugging experiences than others.&lt;/p&gt;

&lt;p&gt;My team has been working with code that has seen little development in a couple of years. Now that we return to it we need to onboard ourselves. Consider this post an alternative RSpec style guide, containing practices I will argue are beneficial for these archeologist-developers.&lt;/p&gt;

&lt;p&gt;There's other things you might optimize tests for though, so you might make different decisions and that's okay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do write the test description as a single string
&lt;/h2&gt;

&lt;p&gt;Which of these styles of writing a test description do you prefer?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a boat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"with a rudder"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"can steer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a boat with a rudder can steer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test description will be super important to our future selves. We'll need it to understand how we broke the test, how we may change the test without defeating its purpose, or when we may delete the test. It's harder to read the description if it's split up, more so if there's other code between the segments.&lt;/p&gt;

&lt;p&gt;RSpec has rules for writing test description segments. If we follow these rules RSpec can glue these sentence fragments into a full sentence nicely. But following these rules ensures the resulting test description is grammatically correct, not that the description is any good.&lt;/p&gt;

&lt;p&gt;The whole-sentence approach has a larger chance of delivering a coherent test description to our future selves intact. For starters, it's easier to write a good test description if we're not at the same time tasked to figure out how to reuse bits of it between tests. And secondly, if we're tweaking an existing test description we can do a better job if we can read the sentence in its entirety.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid &lt;code&gt;let&lt;/code&gt; bindings
&lt;/h2&gt;

&lt;p&gt;Can you tell whether this test will pass?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"taglines"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Slugs: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"the &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;adjective&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; frontier"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:adjective&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"slimiest"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;shared_examples_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"TNG intro"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:adjective&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"final"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"introduces"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Space, the final frontier."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sci-fi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Space, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:adjective&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"quietest"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it_behaves_like&lt;/span&gt; &lt;span class="s2"&gt;"TNG intro"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought up this not-so-great example to make a point, but it's not even &lt;em&gt;that&lt;/em&gt; bad. A similar test in a real suite intermingles with code from other test cases and might cover several files. That's way worse!&lt;/p&gt;

&lt;p&gt;The problem is that &lt;code&gt;let&lt;/code&gt; bindings are global variables. Global to a single test to be precise, but when we're debugging just the one test that's the same thing. I don't know any languages or frameworks that recommend extensive use of global variables, &lt;em&gt;except&lt;/em&gt; for test frameworks.&lt;/p&gt;

&lt;p&gt;I believe we should aspire for test code to have the same quality as production code and for that we need to apply the same practices. Most languages either disallow global variables entirely, warn you when you use them, or heavily discourage their use. Tests will be better for doing the same.&lt;/p&gt;

&lt;p&gt;We can use regular ruby variables instead of &lt;code&gt;let&lt;/code&gt; bindings, except those we can't pass between the test body and hooks like &lt;code&gt;before&lt;/code&gt;, and &lt;code&gt;after&lt;/code&gt;. That brings us to the next practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; hooks
&lt;/h2&gt;

&lt;p&gt;Let's look at a test using some hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:door&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Door&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;open_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;close_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"can go through a door"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;move_through_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose the &lt;code&gt;can go through a door&lt;/code&gt; test fails and we're investigating. First we try to figure out what the test does. That's doable in the example above, but harder in a real test suite where the pieces that make up a single test are far apart, separated by code used by other tests. We'll be scrolling through the larger suite, figuring out which bits of code the failing test uses, and trying to assemble these pieces in our minds.&lt;/p&gt;

&lt;p&gt;Often when I'm trying to assemble a mental model of a test this way my brain doesn't quite feel large enough to contain it all. I'm tempted to print the entire test suite, and use a marker on the lines that are relevant to the test I'm investigating. These would be the lines I'd mark in the example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:door&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Door&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;open_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
  &lt;span class="n"&gt;close_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"can go through a door"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;move_through_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hang on a moment, if we squint a bit that almost looks like a valid test. Let's clean that up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"can go through a door"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;door&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Door&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;open_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
  &lt;span class="n"&gt;move_through_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
  &lt;span class="n"&gt;close_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To me, this is a huge improvement over the test we had before. When a test written in this style fails I can skip the puzzle-solving phase and go straight to debugging.&lt;/p&gt;

&lt;p&gt;Suppose creating a door is a bit more involved, and we'd like to reuse the door creation logic in a couple of tests without repeating ourselves. In that case we can use a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"can go through a door"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;door&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_test_door&lt;/span&gt;
  &lt;span class="n"&gt;open_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
  &lt;span class="n"&gt;move_through_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
  &lt;span class="n"&gt;close_door&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"can knock on a door"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;door&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_test_door&lt;/span&gt;
  &lt;span class="n"&gt;knock_on&lt;/span&gt; &lt;span class="n"&gt;door&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_test_door&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Door&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;material: :wood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;locked: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But wait, this is splitting up the test code. Should we get our markers out again? I don't think so for two reasons. First, &lt;code&gt;create_test_door&lt;/code&gt; is explicitly called from the body of the test so that test body still gives a good summary of everything the test does. Second, the function we created has a self-descriptive name so we don't need to look at it's implementation until we have a question related to door creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do test whether your matchers are providing nice error messages
&lt;/h2&gt;

&lt;p&gt;Ideally the test description and error message are all we need understand what is broken in our code. A great error report allows us to move to figuring out &lt;em&gt;why&lt;/em&gt; the code is broken, and then fixing it.&lt;/p&gt;

&lt;p&gt;In practice error messages can be cryptic, requiring us to interpret them. Interpretation can be quick if we're familiar with the failing test, but we can't count on our future selves having that familiarity.&lt;/p&gt;

&lt;p&gt;In RSpec the choice of matcher has a big impact on error quality, and it's easy to make not-so-great choices. Take the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"George III and George IV are the same"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Monarch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:date_of_birth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:place_of_birth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:date_of_death&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:place_of_death&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:buried_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;george3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Monarch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"King of the United Kingdom of Great Britain and Ireland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"George"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"George William Frederich"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"III"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"4 June 1738"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Norfolk House, St James's Square, London, England"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"29 January 1820"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Windsor Castle, Windsor, Berkshire, England"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"St George's Chapel, Windsor Castle"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;george4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="no"&gt;Monarch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"King of the United Kingdom of Great Britain and Ireland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"George"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"George Augustus Frederich"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"IV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"12 August 1762"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"St James's Palace, London, England"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"26 June 1830"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Windsor Castle, Windsor, Berkshire, England"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"St George's Chapel, Windsor Castle"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;george3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;george4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test will fail with the following error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1) George III and George IV are the same
     Failure/Error: expect(george3).to eq(george4)

       expected: #&amp;lt;struct Monarch title="King of the United Kingdom of Great Britain and Ireland", first_name="George"...death="Windsor Castle, Windsor, Berkshire, England", buried_at="St George's Chapel, Windsor Castle"&amp;gt;
            got: #&amp;lt;struct Monarch title="King of the United Kingdom of Great Britain and Ireland", first_name="George"...death="Windsor Castle, Windsor, Berkshire, England", buried_at="St George's Chapel, Windsor Castle"&amp;gt;

       (compared using ==)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not great. The test fails because the expected and asserted values are not the same but the report makes it look like they are. This kind of error has sent me looking for the problem in entirely the wrong direction.&lt;/p&gt;

&lt;p&gt;Fixing it isn't entirely trivial either. I had to try a couple of improvements before finding one that worked.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;have_attributes&lt;/code&gt; instead of &lt;code&gt;eq&lt;/code&gt;: doesn't work with &lt;code&gt;Struct&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;Calling &lt;code&gt;.to_s&lt;/code&gt; on the monarchs before passing them to &lt;code&gt;eq&lt;/code&gt;: no improvement.&lt;/li&gt;
&lt;li&gt;Calling &lt;code&gt;.to_h&lt;/code&gt; on the monarchs before passing them to &lt;code&gt;eq&lt;/code&gt;: 🎉 a diff!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at another example. &lt;code&gt;eq&lt;/code&gt; sometimes produces bad errors, but &lt;code&gt;contain_exactly&lt;/code&gt; always produces bad errors. Take this test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"fruit salad contains the right ingredients"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Ingredient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:grams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;fruit_salad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="no"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mango"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pineapple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"coconut flakes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;recipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="no"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mango"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pineapple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"coconut flakes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruit_salad&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;contain_exactly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test fails with the error below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1) fruit salad contains the right ingredients
   Failure/Error: expect(fruit_salad).to contain_exactly(*recipe)

     expected collection contained:  ["#&amp;lt;struct Ingredient
name=\"coconut flakes\", grams=50&amp;gt;", "#&amp;lt;struct Ingredient
name=\"mango\", grams=400&amp;gt;", "#&amp;lt;struct Ingredient
name=\"pineapple\", grams=200&amp;gt;"]
     actual collection contained:    [#&amp;lt;struct Ingredient
name="mango", grams=400&amp;gt;, #&amp;lt;struct Ingredient
name="pineapple", grams=300&amp;gt;, #&amp;lt;struct Ingredient
name="coconut flakes", grams=50&amp;gt;]
     the missing elements were:      ["#&amp;lt;struct Ingredient
name=\"pineapple\", grams=200&amp;gt;"]
     the extra elements were:        [#&amp;lt;struct Ingredient
name="pineapple", grams=300&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test is failing because we added the wrong amount of pineapple, but it takes some effort to parse that out of the error report. &lt;code&gt;contain_exactly&lt;/code&gt; errors get worse as the complexity grows of the items in the arrays we're comparing.&lt;/p&gt;

&lt;p&gt;Instead of using &lt;code&gt;contain_exactly&lt;/code&gt; we might group the ingredients by name, check we have the right ingredients, then for each ingredient separately check we have the right amounts. That's more work up front for better error messages when tests fail, a trade-off.&lt;/p&gt;

&lt;p&gt;As things stand we have to fail our tests intentionally to learn what their error messages might look like, so writing RSpec tests with good errors takes commitment and experimentation. I don't have a style-guide like tip that will help test authors prevent poor matcher usage, but I do think there's a couple of things RSpec can improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve errors generated by matchers. For example, let &lt;code&gt;eq&lt;/code&gt; produce a diff.&lt;/li&gt;
&lt;li&gt;Remove matchers that cannot produce good error messages. For example: &lt;code&gt;contain_exactly&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Warn when we use a matcher in a way that will lead to poor error messages. For example, warn we pass &lt;code&gt;eq&lt;/code&gt; values of types for which it cannot produce good diffs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I've shown a couple of practices I believe improve the experience of debugging RSpec tests. It's interesting to note a lot of them come down to using plain Ruby language features over RSpec ones. What do you think of that? Would you miss these RSpec features? What do you like about them? I'd love to hear!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>rspec</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Using Shake Oracles</title>
      <dc:creator>Jasper Woudenberg</dc:creator>
      <pubDate>Fri, 15 May 2020 10:18:49 +0000</pubDate>
      <link>https://dev.to/jwoudenberg/shake-newcache-addoracle-and-addoraclecache-1khc</link>
      <guid>https://dev.to/jwoudenberg/shake-newcache-addoracle-and-addoraclecache-1khc</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/jwoudenberg/shake-rules-as-recipes-2el3"&gt;previous blogpost&lt;/a&gt; I argued for the use of the Shake build system, and for writing Shake rules like they are recipes. I also recommended the use of &lt;code&gt;newCache&lt;/code&gt;, &lt;code&gt;addOracle&lt;/code&gt;, and &lt;code&gt;addOracleCache&lt;/code&gt; in situations where it's tricky to do recipes. &lt;code&gt;newCache&lt;/code&gt;, &lt;code&gt;addOracle&lt;/code&gt;, and &lt;code&gt;addOracleCache&lt;/code&gt; are super useful, but they have imposing types, not the most helpful names, and they look similar, making it hard to know which one we need. In this post we're going to look at each in a little more detail. Let's start by briefly introducing these functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three functions with different uses
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;newCache&lt;/code&gt; allows us to memoize a function. A memoized function stores all the results it calculates. When a memoized function gets called with arguments it has seen before, it returns an earlier calculated result rather than repeating work. We can use &lt;code&gt;newCache&lt;/code&gt; to prevent duplicate work during a build but not across builds, because Shake throws away memoized results after each run.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;addOracle&lt;/code&gt; allows us to define dependencies that aren't files. Say that we want a rule to rebuild when the date changes. We can write some Haskell to get the current date and make it available as a dependency using &lt;code&gt;addOracle&lt;/code&gt;. These oracles run on every build because Shake needs to know if the values they return have changed, in which case the rules that depend on them need to be re-evaluated.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;addOracleCache&lt;/code&gt;, whatever its name implies, has a use case different from either &lt;code&gt;newCache&lt;/code&gt; or &lt;code&gt;addOracle&lt;/code&gt;. We can use it to create a rule that produces a Haskell value instead of a file. Suppose we have an expensive calculation, like recursively finding all the dependencies of a source file. We could define a regular build rule using &lt;code&gt;%&amp;gt;&lt;/code&gt; that performs the calculation and stores the result in a &lt;code&gt;sources.json&lt;/code&gt; file. Other rules could &lt;code&gt;need ["sources.json"]&lt;/code&gt;, decode its contents and use the result. &lt;code&gt;addOracleCache&lt;/code&gt; allows us to do the same thing without the encoding and decoding steps. Like other build rules and unlike &lt;code&gt;addOracle&lt;/code&gt;, a rule defined using &lt;code&gt;addOracleCache&lt;/code&gt; reruns if any of its dependencies changes.&lt;/p&gt;

&lt;p&gt;To summarize what these three functions do before looking at each in more detail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;newCache&lt;/code&gt; memoizes a function call within the current build.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addOracle&lt;/code&gt; defines dependencies that aren’t files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addOracleCache&lt;/code&gt; creates a rule that produces a Haskell value instead of a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's look at these in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use &lt;code&gt;newCache&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;newCache&lt;/code&gt; we can create a memoized function. This is a function that when called several times with the same argument will run just once. The memoized function stores the result of the first run for use in future calls.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;newCache&lt;/code&gt; has the following type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;newCache&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Eq&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="o"&gt;^^^^^^^^^^^^^&lt;/span&gt;            &lt;span class="o"&gt;^^^^^^^^^^^^^&lt;/span&gt;
             &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt;             &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;memoized&lt;/span&gt;
             &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;memoize&lt;/span&gt;               &lt;span class="n"&gt;function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we skip over the type constraints before the &lt;code&gt;=&amp;gt;&lt;/code&gt; first (we'll get to them in a moment!), then we can see that &lt;code&gt;newCache&lt;/code&gt; takes a function and then returns a memoized version of it. Wherever we were using the original function we can use the memoized version too, because they have the same type.&lt;/p&gt;

&lt;p&gt;To add memoization behavior Shake needs to know whether we called the function with a particular argument before. That requires comparing the input argument with input arguments from previous calls, and so we need to insist the input argument is of a type that allows such comparisons. Shake does this by requiring the input type to meet the &lt;code&gt;Eq&lt;/code&gt; and &lt;code&gt;Hashable&lt;/code&gt; constraints.&lt;/p&gt;

&lt;p&gt;One tricky situation where &lt;code&gt;newCache&lt;/code&gt; can help us out is when writing a rule for a command that produces more than one file, but where we don't want to hardcode which files it produces. Suppose we have a script &lt;code&gt;generate-schemas.sh&lt;/code&gt; that generates JSON schemas for common types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="s"&gt;"schemas/*.schema.json"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="kr"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;schemaSrcs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;getDirectoryFiles&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"elm/src/ApiTypes//*.elm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"generate-schemas.sh"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;schemaSrcs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt; &lt;span class="s"&gt;"generate-schemas.sh"&lt;/span&gt; &lt;span class="n"&gt;schemaSrcs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule above works but is pretty inefficient. &lt;code&gt;generate-schemas.sh&lt;/code&gt; creates all the schema files in a single run, but the rule above will rerun it every time we &lt;code&gt;need&lt;/code&gt; a different schema. Let's use &lt;code&gt;newCache&lt;/code&gt; to remove this duplication of work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;generateSchemasCached&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;newCache&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="nb"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;schemaSrcs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;getDirectoryFiles&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"elm/src/ApiTypes//*.elm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"generate-schemas.sh"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;schemaSrcs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt; &lt;span class="s"&gt;"generate-schemas.sh"&lt;/span&gt; &lt;span class="n"&gt;schemaSrcs&lt;/span&gt;

  &lt;span class="s"&gt;"schemas/*.schema.json"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="kr"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generateSchemasCached&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if more than one schema files is &lt;code&gt;need&lt;/code&gt;ed during a run, &lt;code&gt;generate-schemas.sh&lt;/code&gt; is ran just once. Later runs might run &lt;code&gt;generate-schema.sh&lt;/code&gt; again, because &lt;code&gt;newCache&lt;/code&gt; doesn't save results across builds. That's good though, because we might have deleted schema files in the interim.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use &lt;code&gt;addOracle&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Rules can signal they have a dependency on one or more files using &lt;code&gt;need&lt;/code&gt;. This causes the rule to rebuild if the file they depend on changes. But what if you'd like your rule to depend on the day of the week, or the version of a tool? Using &lt;code&gt;addOracle&lt;/code&gt; you can define such dependencies.&lt;/p&gt;

&lt;p&gt;Say we have a rule that creates a letter from a template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="s"&gt;"letter.txt"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;liftIO&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"letter.template"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt;
      &lt;span class="s"&gt;"templateer letter.template"&lt;/span&gt;
      &lt;span class="s"&gt;"--out"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s"&gt;"--options"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"date="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine we're working on this until late in the evening. The next day we want to post it, so we run Shake one more time to get today's date on there. Shake skips the update though because it doesn't know it should rebuild the letter when the date changes.&lt;/p&gt;

&lt;p&gt;If we had a file somewhere on our computer that always contained the current date then we could &lt;code&gt;need&lt;/code&gt; that. As it stands the current date is not a file, it's the result of a call to the (made up) &lt;code&gt;Date.current&lt;/code&gt; function. We can use &lt;code&gt;addOracle&lt;/code&gt; to turn it into a dependency.&lt;/p&gt;

&lt;p&gt;Let's start again by looking at &lt;code&gt;addOracle&lt;/code&gt;'s type. Notice how apart from the functions' constraints (the part of the type before the &lt;code&gt;=&amp;gt;&lt;/code&gt;) &lt;code&gt;addOracle&lt;/code&gt; has the exact same type as &lt;code&gt;newCache&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;addOracle&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RuleResult&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ShakeValue&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ShakeValue&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;^^^^^^^^^^^^^&lt;/span&gt;            &lt;span class="o"&gt;^^^^^^^^^^^^^&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;ShakeValue&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Typeable&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Binary&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;NFData&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like &lt;code&gt;newCache&lt;/code&gt; &lt;code&gt;addOracle&lt;/code&gt; takes a function and returns a function of the same type. This new function adds a dependency in any rule it gets called in. Shake requires a bunch of constraints on the function's argument type &lt;code&gt;q&lt;/code&gt; and return type &lt;code&gt;a&lt;/code&gt; to pull this off. Check out the &lt;a href="https://www.stackage.org/haddock/lts-15.11/shake-0.18.5/Development-Shake.html#t:ShakeValue" rel="noopener noreferrer"&gt;&lt;code&gt;ShakeValue&lt;/code&gt; documentation&lt;/a&gt; if you're interested in learning what these constraints are for. We'll see in a moment what &lt;code&gt;RuleResult q ~ a&lt;/code&gt; is about.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;addOracle&lt;/code&gt; to fix our letter templating rule like so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;currentDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;addOracle&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="kt"&gt;CurrentDate&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;liftIO&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;

  &lt;span class="s"&gt;"letter.txt"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;currentDate&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"letter.template"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt;
      &lt;span class="s"&gt;"templateer letter.template"&lt;/span&gt;
      &lt;span class="s"&gt;"--out"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s"&gt;"--options"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"date="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;Binary&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;NFData&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;RuleResult&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We made a similar change when we introduced &lt;code&gt;newCache&lt;/code&gt;: We extract into a function the lines we want to add special behavior to (memoization behavior in case of &lt;code&gt;newCache&lt;/code&gt;, dependency-tracking behavior now). We wrap our extracted function using the right helper, and use function this returns in our rule.&lt;/p&gt;

&lt;p&gt;What's different this time is that Shake requires each oracle created using &lt;code&gt;addOracle&lt;/code&gt; or &lt;code&gt;addOracleCache&lt;/code&gt; to have a unique type (we'll see why in a moment). We create one called &lt;code&gt;CurrentDate&lt;/code&gt; and use &lt;code&gt;Generic&lt;/code&gt; to generate all the instances Shake requires of the type. We also have to tell Shake that the oracle associated with the &lt;code&gt;CurrentDate&lt;/code&gt; input type always returns a &lt;code&gt;String&lt;/code&gt; result type (the return value of our imaginary &lt;code&gt;Date.current&lt;/code&gt; function).&lt;/p&gt;

&lt;h2&gt;
  
  
  Intermezzo: What's up with these boilerplate types?
&lt;/h2&gt;

&lt;p&gt;The reason oracles argument types need to be unique and the reason we for the &lt;code&gt;RuleResult q ~ a&lt;/code&gt; constraint is to support the &lt;code&gt;askOracle&lt;/code&gt; function in Shake's APIs. Using it our letter templating example looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;addOracle&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="kt"&gt;CurrentDate&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;liftIO&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;

  &lt;span class="s"&gt;"letter.txt"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;askOracle&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"letter.template"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt;
      &lt;span class="s"&gt;"templateer letter.template"&lt;/span&gt;
      &lt;span class="s"&gt;"--out"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s"&gt;"--options"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"date="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt; &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;Binary&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;NFData&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;RuleResult&lt;/span&gt; &lt;span class="kt"&gt;CurrentDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can pass &lt;code&gt;askOracle&lt;/code&gt; any of the types we have defined oracles for. Because all our oracles have unique input types &lt;code&gt;askOracle&lt;/code&gt; can figure out which one to call. And because we have explicitly defined the return types for each oracle input type using the &lt;code&gt;RuleResult&lt;/code&gt; type family the type checker knows the type of &lt;code&gt;askOracle SomeOracleType&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The whole thing is pretty magical, so I like to wrap up oracles in an API that exposes regular functions. As an example, we could wrap up the date oracle like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rules.Date&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;Development.Shake&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="k"&gt;qualified&lt;/span&gt; &lt;span class="nn"&gt;Date&lt;/span&gt;

&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;askOracle&lt;/span&gt; &lt;span class="kt"&gt;Current&lt;/span&gt;

&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;addOracle&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="kt"&gt;Current&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentOracle&lt;/span&gt;

&lt;span class="n"&gt;currentOracle&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="n"&gt;currentOracle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;liftIO&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;

&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;Current&lt;/span&gt; &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="kt"&gt;Current&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;Binary&lt;/span&gt; &lt;span class="kt"&gt;Current&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;NFData&lt;/span&gt; &lt;span class="kt"&gt;Current&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;RuleResult&lt;/span&gt; &lt;span class="kt"&gt;Current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our letter templating example could use this module like so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="kt"&gt;Rules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;

  &lt;span class="s"&gt;"letter.txt"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"letter.template"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt;
      &lt;span class="s"&gt;"templateer letter.template"&lt;/span&gt;
      &lt;span class="s"&gt;"--out"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s"&gt;"--options"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"date="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to use &lt;code&gt;addOracleCache&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Suppose we have a project that contains several Elm applications. The Elm applications share some modules between them. If we change an Elm module we'd like Shake to recompile just those projects that use the module. We could write a rule like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="s"&gt;"assets/*.elm.js"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kr"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filePattern&lt;/span&gt; &lt;span class="s"&gt;"assets/*.elm.js"&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
    &lt;span class="kr"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;.&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"elm"&lt;/span&gt;
    &lt;span class="kr"&gt;let&lt;/span&gt; &lt;span class="n"&gt;srcFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recursiveDependencies&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"elm.json"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;srcFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt; &lt;span class="s"&gt;"elm make --output"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;recursiveDependencies&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;FilePath&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;FilePath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;recursiveDependencies&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;direct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;directDependencies&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
  &lt;span class="n"&gt;recursive&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;traverse&lt;/span&gt; &lt;span class="n"&gt;recursiveDependencies&lt;/span&gt; &lt;span class="n"&gt;direct&lt;/span&gt;
  &lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mconcat&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;directDependencies&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;FilePath&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;FilePath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;directDependencies&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
  &lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="kt"&gt;Elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but it's not super efficient. For each Elm entrypoint it recalculates the full dependency tree. It would be nice if after calculating the dependency for a particular Elm module we could reuse that result in future builds, until any of the recursive dependencies of a module change.&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;recursiveDependencies&lt;/code&gt; looks a lot like a rule. The result it produces is a list of file paths corresponding to Elm modules. Its dependencies are the contents of those Elm modules, because a change in an Elm module might mean that its imports have changed, requiring a recalculation of the dependency tree. Let's use &lt;code&gt;addOracleCache&lt;/code&gt; to write it as a rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Rules&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;addOracleCache&lt;/span&gt; &lt;span class="n"&gt;recursiveDependencies&lt;/span&gt;

  &lt;span class="s"&gt;"assets/*.elm.js"&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kr"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filePattern&lt;/span&gt; &lt;span class="s"&gt;"assets/*.elm.js"&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
    &lt;span class="kr"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;.&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"elm"&lt;/span&gt;
    &lt;span class="kr"&gt;let&lt;/span&gt; &lt;span class="n"&gt;srcFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;askOracle&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RecursiveDependenciesFor&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"elm.json"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;srcFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd_&lt;/span&gt; &lt;span class="s"&gt;"elm make --output"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;recursiveDependencies&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;RecursiveDependencies&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;FilePath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;recursiveDependencies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RecursiveDependenciesFor&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;direct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;directDependencies&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
  &lt;span class="n"&gt;recursive&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;traverse&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;askOracle&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="kt"&gt;RecursiveDependenciesFor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;direct&lt;/span&gt;
  &lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mconcat&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;directDependencies&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;FilePath&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;FilePath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;directDependencies&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;readFile'&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="c1"&gt;-- This takes a dependency on `src`.&lt;/span&gt;
  &lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="kt"&gt;Elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;newtype&lt;/span&gt; &lt;span class="kt"&gt;RecursiveDependencies&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;RecursiveDependenciesFor&lt;/span&gt; &lt;span class="kt"&gt;FilePath&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;NFData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;RuleResult&lt;/span&gt; &lt;span class="kt"&gt;RecursiveDependenciesFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;FilePath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done! Now we'll cache the calculation of module dependencies between builds. One further possible optimization would be to use &lt;code&gt;addOracleCache&lt;/code&gt; to turn &lt;code&gt;directDependencies&lt;/code&gt; into a build rule as well. That way changes to Elm modules that don't touch imports won't trigger recalculation of a module's dependencies. Give it a try!&lt;/p&gt;

&lt;p&gt;It's worth emphasizing that although &lt;code&gt;addOracleCache&lt;/code&gt; has an identical type to &lt;code&gt;addOracle&lt;/code&gt;, it behaves quite differently. Remember that &lt;code&gt;addOracle&lt;/code&gt; is for defining dependencies. Shake runs &lt;code&gt;addOracle&lt;/code&gt; functions pre-emptively to check if their return values have changed. Had we used &lt;code&gt;addOracle&lt;/code&gt; here performance would be worse than the non-oracle-based version of the code we started with, because Shake would rerun it even if none of the Elm source files in the entire project had changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;I hope this post has been helpful in understanding when and how to use Shake's &lt;code&gt;newCache&lt;/code&gt;, &lt;code&gt;addOracle&lt;/code&gt;, and &lt;code&gt;addOracleCached&lt;/code&gt; functions. One final tip: make your oracle types nice and verbose because Shake uses them in its logs. It will make debugging oracles easier.&lt;/p&gt;

&lt;p&gt;That's it. Happy shaking!&lt;/p&gt;

</description>
      <category>build</category>
      <category>shake</category>
      <category>haskell</category>
    </item>
  </channel>
</rss>
