feed

tag:x,2008-06-01:kind/x interblah.net 2023-12-13T16:42:41+00:00 Conversations are hard tag:interblah.net,2023-12-13:/conversations-are-hard 2023-12-13T16:42:41+00:00 2023-12-13T16:42:41+00:00 interblah.net <p>Sometimes I find talking to people very hard. Even people I like a lot. And I’ve been thinking about why.</p> <p><a href="https://www.quora.com/How-can-I-tell-someone-something-without-confronting-her-description/answer/Nicole-Gravagna">This (ugh) Quora answer</a> actually summarises it pretty well:</p> <blockquote> <p>Question askers navigate conversations by asking questions, getting responses, asking more questions, and hoping that they will get a question in return so that they can talk about themselves a little too. They will listen to openly shared stories, but they won’t openly share their own stories.</p> <p>Open sharers will choose topics about their own lives to share, and hope that the other person will do the same. They will answer questions, but won’t ask them.</p> </blockquote> <p>What I’ve noticed is that, while I <em>can</em> do “open sharing”, I am much more often a “question asker”. I listen attentively to your stories and I ask questions to help you talk more about whatever is interesting you at the moment.</p> <p>But what I’ve also noticed, is that it <em>really sucks</em> sometimes when I’m talking to an <em>open sharer</em>, even if I really like the person, when they <em>never</em> switch modes and ask any questions<sup id="fnref:fair" role="doc-noteref"><a href="http://interblah.net/#fn:fair" class="footnote">1</a></sup>. In those conversations, it feels like the onus is <em>always</em> on me to switch modes and volunteer some hopefully-interesting thought or anecdote if I want to feel like I’m anything other than my conversational partner’s therapist. Doing that time after time eventually feels like I’m pushing for attention, which is icky and exhausting. <strong>I would like to feel like I am interesting to you without having to beg for your attention</strong>.</p> <p>I don’t think that <em>open sharers</em> are bad people; it’s just how they’ve learned to interact, and when they talk with other <em>open sharers</em> then it works totally fine for them. But sometimes it feels like they are oblivious to the fact that not everyone is the same as them. They might not even realise they are an <em>open sharer</em>, since their mode is by default the one that “creates” conversation.</p> <p>Think about what kind of conversationalist you are. <em>Really</em> think about it. In your last conversation with your good friend, did you ask them any questions? Have a scroll up. When was the last time you asked them anything meaningful at all? Has it been a while? Guess what, buddy: you might be an <em>open sharer</em>.</p> <p>My suggestion to all you <em>open sharers</em> out there, if you actually care about how the person you are talking to feels: pay attention in your conversations, and if it feels like your partner hasn’t said much, then maybe ask them a fucking question or two.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:fair" role="doc-endnote"> <p>OK, to be fair, conversations with an <em>extreme</em> <em>question asker</em> can also be pretty unpleasant; even for another <em>question asker</em> they can feel like an interrogation. <a href="http://interblah.net/#fnref:fair" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Weeknotes 2041 tag:interblah.net,2023-02-05:/weeknotes-2041 2023-02-05T22:33:22+00:00 2023-02-05T22:33:22+00:00 interblah.net <h3 id="work">Work</h3> <p>A lot of my work at the moment involves chasing down bugs in an incredibly complicated publishing system which has multiple triggers (both internal and external) that make films available to watch on a variety of platforms at specific times in specific countries. When bugs creep in, films either stay up or don’t go up, and sometimes that annoys the wrong people. This week was one of those weeks, but involving a bug that was introduced about two years ago, and then fixed 8 months later, so calculating the true impact of the issue is pretty difficult. This is definitely why I got into software development.</p> <h3 id="not-work">Not work</h3> <p>Does this site now run on Ruby 3.1? I hope it does. I’ve been slowly working through a bunch of my other small applications, trying to update them to modern versions of Ruby (and even Rails), so that at the very least they remain deployable. This site is one of those small applications.</p> <h3 id="my-real-job">My real job</h3> <p>As the parent of an infant, I am now accustomed to exhaustion, and within that broad label, all the subtly-distinct flavours it comes in. This week has mostly been standard not-enough-contiguous-hours-of-unconsciouness tiredness. Number of thousand-yard stares while the child does not immediately need my attention: 5.</p> <h3 id="physical-health">Physical health</h3> <p>None of this is helped by having yet another cold of some kind. Which of course we all get, and so it’s a never ending cycle of minor illness and sleep distruption and nobody is feeling very jazzy or sparkly but at the same time, the kid is so damned <em>adorable</em> that it remains worth it.</p> <h3 id="mental-health">Mental health</h3> <p>Some rare adult socialisation in the form of a birthday party for another child (but yes, it still counts), and then Tom &amp; Nat coming over for lunch today, both of which were very excellent.</p> <h3 id="door">Door</h3> <p>Today I found a door in my hallway that I hadn’t noticed before, but I’m currently too tired to investigate further.</p> Connoisseur tag:interblah.net,2022-10-06:/connoisseur 2022-10-06T10:22:47+01:00 2022-10-06T10:22:47+01:00 interblah.net <p>Listen, here’s what I’m wondering: is it better to be able to appreciate subtle refinements and improvements of a thing, the finest examples of it, if it comes at the expense of enjoying the version of that thing that’s most commonly or easily available?</p> <p>If that “better” thing gives an increase of happiness of X, but the more commonly available instances – in their relative worseness – now gives a <em>decrease</em> of happiness of Y, then how often is the sum of X greater than the sum of Y?</p> <p>Is it better to really get into, say, wine, for all the increased enjoyment that those few really great wines can provide, if it means that you either need to spend a new multiple on acquiring that wine, or suffer increasing disappointment from the wines you can affort or are offered at friends houses?</p> <p>Or is it better – on the whole – to remain largely ignorant of what distinguishes a “great” wine from an “ok” wine, and just enjoy them all to a lesser but more consistently positive extent?</p> <p>It might not be wine, it could be chocolate, or coffee, or art, or pretty much anything else that you can develop a “taste” for. Are the newly-available highs of pleasure worth, overall, the lessened ability to tolerate the instances of that thing which do not match your new palate?</p> <p>This is what I’m wondering. Sound out in the comments<sup id="fnref:comments" role="doc-noteref"><a href="http://interblah.net/#fn:comments" class="footnote">1</a></sup></p> <p>The only clue I can offer is that it’s a literal nightmare figuring out how to spell “connoisseur”. I had to look it up to write this. Twice.</p> <p>The universe weaves clues into its ineffable fabric, I suppose.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:comments" role="doc-endnote"> <p>JK there are no comments here – literal LOL – nah, you go scream into whatever digital silo you prefer. <a href="http://interblah.net/#fnref:comments" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> This is for Patrick tag:interblah.net,2022-06-13:/this-is-for-patrick 2022-06-13T15:52:43+00:00 2022-06-13T15:52:43+00:00 interblah.net <p>This is for Patrick, who doesn’t subscribe to the RSS or Atom feeds.</p> <p>Instead he sets a reminder to check this website every week or two (or perhaps less, I forget), and – upon seeing no new posts – sets another reminder to do the same later that month, or year, or whenever.</p> <p>This is for Patrick, who has lost so many precious seconds checking this site for new posts, only to find nothing.</p> <p>I won’t be responsible for this anymore. I won’t burn his time and energy like electron potential on the bitcoin bonfire. I won’t be the source of that disappointment. My stale blog-type thing won’t be the reflection in that lone tear as it traces its melancholy path down his face. Not anymore.</p> <p>This post, Patrick… <em>buddy</em>… this is for <strong>you</strong>.</p> Don't cache ActiveRecord objects tag:interblah.net,2021-01-29:/don-t-cache-activerecord-objects 2021-01-29T15:52:43+00:00 2021-01-29T15:52:43+00:00 interblah.net <p>Hello, new Rails developer! Welcome to <strong>WidgetCorp</strong>. I hope you enjoyed orientation. Those old corporate videos are a hoot, right? Still, you gotta sit through it, we all did. But anyway, let’s get stuck in.</p> <p>So as you know, here at WidgetCorp, we are the manufacturer, distributor and direct seller of the worlds highest quality <strong>Widgets</strong>. None finer!</p> <p>These widgets are high-value items, it’s a great market to be in, but they’re also pretty complicated, and need to be assembled from any number of different combinations of <em>doo-hickeys</em>, <em>thingamabobs</em> and <em>whatsits</em>, and unless we have the right quantities and types of each, then a particular kind of widget may not actually be available for sale.</p> <p>(If you just came for the advice, you can <a href="http://interblah.net/#whats-going-on">skip this nonsense</a>.)</p> <p>But those are manufacturing details, and you’re a Rails developer, so all it really means for us is this: <strong>figuring out the set of <em>available</em> widgets involves some necessarily-slow querying and calculations, and there’s no way around it</strong>.</p> <p>In our typical Rails app, here’s the code we have relating to showing availble widgets:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># db/schema.rb</span> <span class="n">create_table</span> <span class="ss">:widgets</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:title</span> <span class="c1"># ...</span> <span class="k">end</span> <span class="c1"># app/models/widget.rb</span> <span class="k">class</span> <span class="nc">Widget</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="n">scope</span> <span class="ss">:available</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="c1"># some logic that takes a while to run</span> <span class="p">}</span> <span class="k">end</span> <span class="c1"># app/helpers/widget_helper.rb</span> <span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- app/views/widgets/index.html.erb --&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">available_widgets</span><span class="err">.</span><span class="na">each</span> <span class="na">do</span> <span class="err">|</span><span class="na">widget</span><span class="err">|</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;h2&gt;&lt;</span><span class="err">%=</span> <span class="na">widget</span><span class="err">.</span><span class="na">name</span> <span class="err">%</span><span class="nt">&gt;&lt;/h2&gt;</span> <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">link_to</span> <span class="err">'</span><span class="na">Buy</span> <span class="na">this</span> <span class="na">widget</span><span class="err">',</span> <span class="na">widget_purchase_path</span><span class="err">(</span><span class="na">widget</span><span class="err">)</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span> </code></pre></div></div> <h3 id="its-so-slow">It’s so slow!</h3> <p>The complication of these widgets isn’t our customer’s concern, and we don’t want them to have to wait for even a second before showing them the set of available widgets that might satisfy their widget-buying needs. But the great news is that the widget component factory only runs at midnight, and does all its work instantaneously<sup id="fnref:fast" role="doc-noteref"><a href="http://interblah.net/#fn:fast" class="footnote">1</a></sup>, so this set is the same for the whole day.</p> <p>So what can we do to avoid having to calculate this set of available widgets every time we want to show the listing to the user, or indeed use that set of widgets anywhere else in the app?</p> <p>You guessed it, you clever Rails developer: <strong>we can cache the set of widgets</strong>!</p> <p>For the purposes of this example, let’s add the caching in the helper:<sup id="fnref:view-caching" role="doc-noteref"><a href="http://interblah.net/#fn:view-caching" class="footnote">2</a></sup></p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"available-widgets"</span><span class="p">,</span> <span class="ss">expires_at: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">)</span> <span class="k">do</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>Bazinga! Our website is now faster than a <em>ZipWidget</em>, which – believe me – is one of the fastest widgets that we here at WidgetCorp sell, and that’s saying something.</p> <p>Anyway, great work, and I’ll see you tomorrow.</p> <h3 id="the-next-day">The next day</h3> <p>Welcome back! I hope you enjoyed first night in the company dorm! Sure, it’s cosy, but we find that the reduced commute times helps us maximise employee efficiency, and just like they said in those videos, say it with me: “An efficient employee is… a…”</p> <p>… more impactful component in the overall WidgetCorp P&amp;L, that’s right. But listen to me, wasting precious developer seconds. Let’s get to work.</p> <p>So corporate have said they want to store the <em>colour</em> of the widget, because it turns out that some of our customers want to coordinate their widgets, uh, aesthetically I suppose. Anyway, I don’t ask questions, but it seems pretty simple, so let’s add ourselves the column to the database and show it on the widget listing:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># db/schema.rb</span> <span class="n">create_table</span> <span class="ss">:widgets</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:title</span> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:colour</span> <span class="c1"># ...</span> <span class="k">end</span> </code></pre></div></div> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- app/views/widgets/index.html.erb --&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">available_widgets</span><span class="err">.</span><span class="na">each</span> <span class="na">do</span> <span class="err">|</span><span class="na">widget</span><span class="err">|</span> <span class="err">%</span><span class="nt">&gt;</span> <span class="nt">&lt;h2&gt;&lt;</span><span class="err">%=</span> <span class="na">widget</span><span class="err">.</span><span class="na">title</span> <span class="err">%</span><span class="nt">&gt;&lt;/h2&gt;</span> <span class="nt">&lt;p&gt;</span>This is a <span class="nt">&lt;</span><span class="err">%=</span> <span class="na">widget</span><span class="err">.</span><span class="na">colour</span> <span class="err">%</span><span class="nt">&gt;</span> widget<span class="nt">&lt;/p&gt;</span> <span class="c">&lt;!-- ... --&gt;</span> <span class="nt">&lt;</span><span class="err">%</span> <span class="na">end</span> <span class="err">%</span><span class="nt">&gt;</span> </code></pre></div></div> <p>… aaaaaaaand <em>deploy</em>.</p> <p>Great! You are a credit to the compa <strong>WHOA</strong> hangon. The site is down. <strong>THE SITE IS DOWN</strong>.</p> <p>Exceptions. Exceptions are pouring in.</p> <p>“Undefined method <code>colour</code>”? What? We ran the migrations right? I’m sure we did. I saw it happen. Look here, I’m running it in the console, the attribute is there. What’s going on? Oh no, the red phone is ringing. You answer it. No, I’m telling you, <em>you</em> answer it.</p> <h2 id="whats-going-on">What’s going on</h2> <p>The reason we see exceptions after the deployment above is that the ActiveRecord objects in our cache are fully-marshalled ruby objects, and due to the way ActiveRecord dynamically defines attribute access, those objects only know about the columns and attributes of the <code>Widget</code> class <em>at the time they entered the cache</em>.</p> <p>And so here’s the point of this silly story: <strong><em>never</em> store objects in your cache whose structure may change over time</strong>. And in a nutshell, that’s pretty much <em>any</em> non-value object:</p> <ul> <li><code>ActiveRecord</code> objects can have attributes change via migrations</li> <li>other objects can change when gems are updated (including Rails), or even when you update the version of Ruby itself.</li> </ul> <h3 id="marshalling-data">Marshalling data</h3> <p>If you take a look at how Rails caching actually works, <a href="https://github.com/rails/rails/blob/6-1-stable/activesupport/lib/active_support/cache.rb#L587-L593">you can see that under the hood</a>, the data to be cached is passed to <code>Marshal.dump</code>, which turns that data into an encoded string.</p> <p>We can see what that looks like here:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">widget</span> <span class="o">=</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s1">'ZipWidget'</span><span class="p">)</span> <span class="err">$</span> <span class="n">data</span> <span class="o">=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">dump</span><span class="p">(</span><span class="n">widget</span><span class="p">)</span> <span class="c1"># \x04\bo:\vWidget\x16:\x10@new_recordF:\x10@attributeso:\x1EActiveModel::AttributeSet\x06;</span> <span class="c1"># \a{\aI\"\aid\x06:\x06ETo:)ActiveModel::Attribute::FromDatabase\n:\n@name@\b:\x1C</span> <span class="c1"># @value_before_type_casti\x06:\n@typeo:\x1FActiveModel::Type::Integer\t:\x0F@precision0</span> <span class="c1"># :\v@scale0:\v@limiti\r:\v@rangeo:\nRange\b:\texclT:\nbeginl-\t\x00\x00\x00\x00\x00\x00\x00\x80:\b</span> <span class="c1"># endl+\t\x00\x00\x00\x00\x00\x00\x00\x80:\x18@original_attribute0:\v@valuei\x06I\"\ntitle\x06;</span> <span class="c1"># \tTo;\n\n;\v@\x0E;\fI\"\x0EZipWidget\x06;\tT;\ro:HActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString\b;</span> <span class="c1"># \x0F0;\x100;\x11i\x01\xFF;\x170;\x18I\"\x0EZipWidget\x06;\tT:\x17@association_cache{\x00:\x11</span> <span class="c1"># @primary_keyI\"\aid\x06;\tT:\x0E@readonlyF:\x0F@destroyedF:\x1C@marked_for_destructionF:\x1E</span> <span class="c1"># @destroyed_by_association0:\x1E@_start_transaction_state0:\x17@transaction_state0:\x17</span> <span class="c1"># @inspection_filtero:#ActiveSupport::ParameterFilter\a:\r@filters[\x00:\n@maskU:</span> <span class="c1"># 'ActiveRecord::Core::InspectionMask[\t:\v__v2__[\x00[\x00I\"\x0F[FILTERED]\x06;\tT:$</span> <span class="c1"># @_new_record_before_last_commitT:\x18@validation_context0:\f@errorsU:\x18</span> <span class="c1"># ActiveModel::Errors[\b@\x00{\x00{\x00:\x13@_touch_recordT:\x1D@mutations_from_database0: </span> <span class="c1"># @mutations_before_last_saveo:*ActiveModel::AttributeMutationTracker\a;\ao;\b\x06;\a{\a</span> <span class="c1"># @\bo:%ActiveModel::Attribute::FromUser\n;\v@\b;\fi\x06;\r@\n;\x17o;\n\n;\v@\b;\f0;\r</span> <span class="c1"># @\n;\x170;\x180;\x18i\x06@\x0Eo;0\n;\v@\x0E;\fI\"\x0EZipWidget\x06;\tT;\r@\x11;\x17o;\n\t;</span> <span class="c1"># \v@\x0E;\f0;\r@\x11;\x170;\x18@\x10:\x14@forced_changeso:\bSet\x06:\n@hash}\x00F</span> </code></pre></div></div> <p>If you look closely in there, you can see some of the values (e.g. the value <code>ZipWidget</code>), but there’s plenty of other information about the specific structure and implementation of the ActiveRecord instance – particularly about the model’s understanding of the database – that’s encoded into that dump.</p> <p>You can revive the object by using <code>Marshal.load</code>:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">cached_widget</span> <span class="o">=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># =&gt; #&lt;Widget id: 1, title: "ZipWidget"&gt;</span> </code></pre></div></div> <p>And that works great, until you try and use any <em>new</em> attributes that might exist in the database. Let’s add the <code>colour</code> column, just using the console for convenience:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">.</span><span class="nf">add_column</span> <span class="ss">:widgets</span><span class="p">,</span> <span class="ss">:colour</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">default: </span><span class="s1">'beige'</span> </code></pre></div></div> <p>We can check this all works and that our widget in the database gets the right value:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">colour</span> <span class="c1"># =&gt; 'beige'</span> </code></pre></div></div> <p>But what if we try and use that same widget, but from the cache:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">cached_widget</span> <span class="o">=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># =&gt; #&lt;Widget id: 1, title: "ZipWidget"&gt;</span> <span class="err">$</span> <span class="n">cached_widget</span><span class="p">.</span><span class="nf">colour</span> <span class="no">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span> <span class="mi">1</span><span class="p">:</span> <span class="n">from</span> <span class="p">(</span><span class="n">irb</span><span class="p">):</span><span class="mi">14</span> <span class="no">NoMethodError</span> <span class="p">(</span><span class="n">undefined</span> <span class="nb">method</span> <span class="sb">`colour' for #&lt;Widget id: 1, title: "ZipWidget"&gt;) </span></code></pre></div></div> <p>Boom. The cached instance thinks it already knows everything about the schema that’s relevant, so when we try to invoke a method from a schema change, we get an error.</p> <h3 id="workarounds">Workarounds</h3> <p>The only ways to fix this are</p> <ul> <li>clearing your cache, losing all the benefits of it until it’s populated again</li> <li>reloading the object after it’s retrieved from the cache, again losing many of the benefits of it being cached</li> <li>or by adding behaviour to anything that <em>calls</em> <code>Widget#colour</code>, to handle the raised exception if it’s missing.</li> </ul> <p>Not great.</p> <p>But there’s a better solution, which is avoiding this issue in the first place.</p> <h2 id="store-ids-not-objects">Store IDs, not objects</h2> <p>Sometimes it is useful to take a step back from the code and think about what we are trying to acheive. We want to quickly return the set of available widgets, but calculating that set takes a long time. However, there’s a difference between <em>calculating</em> the set, and <em>loading</em> that set from the database. Once we know which objects are a part of the set, actually loading those records is likely to be pretty fast – databases are pretty good at those kinds of queries.</p> <p>So we don’t actually need to cache the fully loaded objects; we just need to cache something that lets us quickly load the <em>right</em> objects – their unique <code>id</code>s.</p> <p>Here’s my proposed fix for WidgetCorp:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="n">ids</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'available-widgets'</span><span class="p">,</span> <span class="ss">expires_at: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">)</span> <span class="k">do</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span> <span class="k">end</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">ids: </span><span class="n">ids</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>Yes, it’s less elegant that neatly wrapping the slow code in a cache block, but the alternative is a world of brittle cache data and deployment fragility and pain. If a new column is added to the <code>widgets</code> table, nothing breaks because we’re not caching anything but the IDs.</p> <h2 id="bonus-code-defend-your-app-from-brittle-cache-objects">Bonus code: defend your app from brittle cache objects</h2> <p>It can be easy to forget this when you’re building new features. This is the kind of thing that only bites in production, and it can happen years after the unfortunate cache entered use.</p> <p>So the best way of making sure to avoid it, is to have something in your CI build that automatically checks for these kinds of records entering your cache.</p> <p>In your <code>config/environments/test.rb</code> file, find the line that controls the cache store:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/environments/test.rb</span> <span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:null</span> </code></pre></div></div> <p>and change it to this:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/environments/test.rb</span> <span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="no">NullStoreVerifyingAcceptableData</span><span class="p">.</span><span class="nf">new</span> </code></pre></div></div> <p>And in <code>lib</code>, create this class:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># lib/null_store_verifying_acceptable_data.rb</span> <span class="k">class</span> <span class="nc">NullStoreVerifyingAcceptableData</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Cache</span><span class="o">::</span><span class="no">NullStore</span> <span class="k">class</span> <span class="nc">InvalidDataException</span> <span class="o">&lt;</span> <span class="no">Exception</span><span class="p">;</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">write_entry</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">entry</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">)</span> <span class="n">check_value</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="nf">value</span><span class="p">)</span> <span class="kp">true</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">check_value</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">)</span> <span class="o">||</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Relation</span><span class="p">)</span> <span class="k">raise</span> <span class="no">InvalidDataException</span><span class="p">,</span> <span class="s2">"Tried to store </span><span class="si">#{</span><span class="n">value</span><span class="p">.</span><span class="nf">class</span><span class="si">}</span><span class="s2"> in a cache. We cannot do this because \ the behaviour/schema may change between this value being stored, and \ it being retrieved. Please store IDs instead."</span> <span class="k">elsif</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span> <span class="o">||</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Set</span><span class="p">)</span> <span class="n">value</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">check_value</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">}</span> <span class="k">elsif</span> <span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="n">value</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">flatten</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">check_value</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>With this in place, when your build runs any test that exercises behaviour that ends up trying to persist ActiveRecord objects into the cache will raise an exception, causing the test to fail with an explanatory message.</p> <p>You can extend this to include other classes if you have other objects that may change their interface over time.</p> <h3 id="double-bonus-tests-for-nullstoreverifyingacceptabledata">Double bonus: tests for <code>NullStoreVerifyingAcceptableData</code></h3> <p>How can we be confident that the new cache store is actually going to warn us about behaviours in the test? What if it never ever raises the exception?</p> <p>When I’m introducing non-trivial behaviour into my test suite, I like to test <em>that</em> too. So here’s some tests to sit alongside this new cache store, so we can be confident that we can actually rely on it.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'test_helper'</span> <span class="k">class</span> <span class="nc">NullStoreVerifyingAcceptableDataTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span> <span class="n">setup</span> <span class="k">do</span> <span class="vi">@typical_app_class</span> <span class="o">=</span> <span class="no">Widget</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises exception when we try to cache an ActiveRecord object'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache a relation'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache an array of ActiveRecord objects'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="p">[</span><span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">]</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache a Set of ActiveRecord objects'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span><span class="p">([</span><span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">])</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'raises an exception when we try to cache a Hash that contains ActiveRecord objects'</span> <span class="k">do</span> <span class="n">assert_raises_when_caching</span> <span class="p">{</span> <span class="p">{</span><span class="ss">value: </span><span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">}</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'does not raise anything when caching an ID'</span> <span class="k">do</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">random_key</span><span class="p">)</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span> <span class="k">end</span> <span class="nb">test</span> <span class="s1">'does not raise anything when caching an array of IDs'</span> <span class="k">do</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">random_key</span><span class="p">)</span> <span class="p">{</span> <span class="vi">@typical_app_class</span><span class="p">.</span><span class="nf">first</span><span class="p">(</span><span class="mi">5</span><span class="p">).</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">assert_raises_when_caching</span><span class="p">(</span><span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="n">assert_raises</span> <span class="no">NullStoreVerifyingAcceptableData</span><span class="o">::</span><span class="no">InvalidDataException</span> <span class="k">do</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">random_key</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">random_key</span> <span class="no">SecureRandom</span><span class="p">.</span><span class="nf">hex</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>If you extend the cache validator to check for other types of objects, you can add tests to make sure those changes work as you expect.</p> <h3 id="triple-bonus-code-what-if-you-already-have-cached-objects">Triple-bonus code: what if you already have cached objects?</h3> <p>My <em>real</em> fix for WidgetCorp would actually try to mitigate this issue while still respecting the (possibly broken) objects still in the cache:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">WidgetHelper</span> <span class="k">def</span> <span class="nf">available_widgets</span> <span class="n">ids_or_objects</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'available-widets'</span><span class="p">,</span> <span class="ss">expires_at: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">)</span> <span class="k">do</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">available</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span> <span class="k">end</span> <span class="n">ids</span> <span class="o">=</span> <span class="k">if</span> <span class="n">ids_or_objects</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">present?</span> <span class="o">&amp;&amp;</span> <span class="n">ids_or_objects</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">)</span> <span class="n">ids_or_objects</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:id</span><span class="p">)</span> <span class="k">else</span> <span class="n">ids_or_objects</span> <span class="k">end</span> <span class="no">Widget</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">id: </span><span class="n">ids</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>This allows us to still use the ActiveRecord instances that are cached, while storing any new data in the cache as just IDs. Once this code has been running for a day, all of the existing data in the cache will have expired and the implementation can be changed to <a href="http://interblah.net/#store-ids-not-objects">the simpler one</a>.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:fast" role="doc-endnote"> <p>Pocket dimension, closed timelike curves, above your paygrade, don’t worry about it! <a href="http://interblah.net/#fnref:fast" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:view-caching" role="doc-endnote"> <p>You might imagine we could get around all this by caching the view, and indeed in this case, it wouldn’t raise the same problems, but in a typically mature Rails application we end up using cached data in other places outside of view generation, so the overall point is worth making. Still, award yourself 1 gold credit. <a href="http://interblah.net/#fnref:view-caching" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Two thoughts about maintainable software tag:interblah.net,2020-11-06:/two-thoughts-about-maintainable-software 2020-11-06T14:00:00+00:00 2020-11-06T14:00:00+00:00 interblah.net <p>I really enjoyed <a href="https://www.maintainable.fm/episodes/glenn-vanderburg-dont-ask-for-small-things">the most recent episode</a> of <a href="http://interblah.net/robby-russell">Robby Russell</a>’s podcast “<a href="https://www.maintainable.fm">Maintainable</a>”, in which <a href="https://twitter.com/glv">Glenn Vanderburg</a><sup id="fnref:glenn" role="doc-noteref"><a href="http://interblah.net/#fn:glenn" class="footnote">1</a></sup> talks about capturing explanations about code and systems, along with managing technical debt. It’s a great episode, and I highly recommend pouring it into your ears using whatever software you use to gather pods.</p> <p>The whole thing is good, but there were two specific points brought up fairly early in their conversation that got me thinking. What a great excuse to write a post here, and make me feel less guilty about potentially <em>never</em> publishing the Squirrel Simulator 2000 post I had promised.</p> <p>I know; I’m sorry. Anyway, here’s some tangible words instead of that <em>tease</em>. Enjoy!</p> <h2 id="documenting-the-why">Documenting the why</h2> <p>At the start of the conversation, Robby asks Glenn what he thinks makes “maintainable” software, and part of Glenn’s response is that there should be comments or documents that explain <em>why</em> the system is designed the way it is.</p> <blockquote> <p>Sometimes things will be surprisingly complex to a newcomer, and it’s because you tried something simple and you learned that that wasn’t enough. Or sometimes, things will be surprisingly simple, and it’s good to document that “oh, well we thought we needed it to be more complicated, but it works fine for [various] reasons.”</p> </blockquote> <p>Robby goes on to ask how developers might get better at communicating the “why” – should it be by pointing developers at tickets, or user stories somewhere, or it should be using comments in the code, or maybe some other area where things get documented? And without wanting to directly criticise Glenn’s answer, it brought to mind something that I have come to strongly believe during my career writing software so far: the right place to explain the <em>why</em> is the <strong>commit message</strong>.</p> <h3 id="but-why-not-comments">But why not comments?</h3> <p>I’ve lost count of the number of times that I’ve found a comment in code and realised it was wrong. We just aren’t great at remembering to keep them updated, and sometimes it’s not clear when a change in the implementation will reduce the accuracy of the comment.</p> <h3 id="but-its-already-fully-explained-in-the-user-storybasecamp-threadtrello-card">But it’s already fully explained in the user story/Basecamp thread/Trello card…</h3> <p>I’ve also lost count of the number of times that a commit message contains little more than a link, be it to a defunct Basecamp project, or a since-deleted Trello card, or am irretrievable Lighthouse ticket, or whatever system happened to be in use ten years ago, but is almost certainly archived or gone now, even if the service is still running.</p> <p>And even if we are lucky and that external service <em>is</em> still running, often these artefacts are discussions where a feature or change or bug is explored and understood and evolved. The initial description of a feature or a bug might not reflect the version we are building right now, so do we really want to ask other developers and our future selves to have to go back to that thread/card/ticket and re-synthesise all those comments and questions, every time they have a question about this code?</p> <h3 id="put-it-in-the-commit-message">Put it in the commit message</h3> <p>Comments rot. External systems, over the timescales of a significant software product, are essentially ephemeral. For software to be truly maintainable, it needs to carry as much of the explanation of the why <em>along with it</em>, and where the code itself cannot communicate that, the only place were an explanation doesn’t rot is where it’s completely bound to the implementation at that momment in time. And that’s exactly what the commit message is.</p> <p>So whenever I’m writing a commit, I try and capture as much of the “why” as I can. Yes, I write <a href="https://github.com/norman/friendly_id/commit/4bd4300035b5c250aeb2e5feec4c2feb9bcf2a19">multi-paragraph</a> <a href="https://github.com/gtd/validation_scopes/commit/2c9139af94239444575e1f413b9303badd149eb6">commit</a> <a href="https://github.com/rails/rails/commit/d44b628ad0be4791180eb70863cfe5392ec0f177">messages</a>. The first commit for a new feature typically includes an explanation of why we’re building it at all, what we hope it will do and so on. Where locally I’ve tried out a few approaches before settling on the one I prefer, I try and capture those other options (and why they weren’t selected) inside the commit message for the approach I <em>did</em> pick.</p> <p>Conversely, if I am looking at a line of code or a method and I’m not sure about the <em>why</em> of it, I reach for <code>git blame</code>, which immediately shows me the last time it was touched, and (using <a href="https://magit.vc">magit</a> or any good IDE) I can easily step forward and back and understand the origin and evolution of that code – as long as we’ve taken the time to capture the <em>why</em> for each of those changes. And this applies just as much to the code that I wrote as it does to other developers. <em>Future-me</em> often has no idea why I made certain choices – sometimes I can barely remember writing the code at all!</p> <p>So anyway, in a nutshell, when you’re committing code, try to imagine someone sitting beside you and asking “why?” a few times, and capture what your explanation would be in your commit message.</p> <p><em>BONUS TIP</em>: finding that your “why” explanation is a bit too long? That’s probably a signal that you need to make smaller commits, and take smaller steps implementing your feature. I’ve never regretted taking smaller steps, even though I recognise sometimes the desire to “just get it done” can be strong.</p> <h2 id="using-tests-as-todos">Using tests as TODOs</h2> <p>A little later, the conversation turns to the merits of “TODO” comments in the code, and how to handle time-based issues where code needs to exist but perhaps only for a certain amount of time in its current form, before it need to be revisited. It’s not unusual for startups to need to get something into production quickly, even if they <em>know</em> that they are spending technical debt in doing so. Glenn correctly points out that it can be hard to make sure that comments like that get attention in the future when they should.</p> <blockquote> <p>The problem is that when you put them in there, there’s no good way to make sure you pay attention when the time comes [to address the debt]</p> </blockquote> <p>I spend a fair bit of my professional time working with a startup that has a very mature (13 years old) Rails codebase, and more often than not, moving quickly involves <em>disabling</em> certain features temporarily, as well as adding new ones. For example, on occasion we might need to disable the regular newsletter mechanism and background workers while the company runs a one-off marketing campaign. To achieve this, we need to disable or modify some parts of the system temporarily, but ensure that we put them back in place once the one-off campaign is completed.</p> <p>In situations like this, I have found that we can use the tests themselves to remind us to re-enable the regular code. Here’s a very simple test helper method:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">skip_until</span><span class="p">(</span><span class="n">date_as_string</span><span class="p">,</span> <span class="n">justification</span><span class="p">)</span> <span class="n">skip</span><span class="p">(</span><span class="n">justification</span><span class="p">)</span> <span class="k">unless</span> <span class="no">Date</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">date_as_string</span><span class="p">).</span><span class="nf">past?</span> <span class="k">end</span> </code></pre></div></div> <p>Becase we have tests that check the behaviour of our newsletter system under normal conditions, when we disable the implementation temporarily, these tests will naturally fail, but we can use this helper to avoid the false-negative test failures in our continuous-integration builds, <em>without</em> having to delete the tests entirely and then remember to re-add them later:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NewsletterWorkerTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span> <span class="n">should</span> <span class="s2">"deliver newsletter to users"</span> <span class="k">do</span> <span class="n">skip_until</span><span class="p">(</span><span class="s1">'2020-12-01'</span><span class="p">,</span> <span class="s1">'newsletters are disabled while Campaign X runs'</span><span class="p">)</span> <span class="c1"># original test body follows</span> <span class="k">end</span> <span class="c1"># other tests </span> <span class="k">end</span> </code></pre></div></div> <p>This way, we get a clear signal from the build when we need to re-enable something, without the use of any easy-to-ignore comments at all.</p> <h2 id="not-all-comments">Not all comments?</h2> <p>I’m not completely opposed to all comments, but it’s become very clear to me that they aren’t the best way to explain either what some code does, or why it exists. If we can use code itself to describe <em>why</em> something exists, or is disabled – and then remind us about it, if appropriate – then why not take advantage of that?</p> <p>And if there’s only one thing you take away from all the above, let it be this: <strong>take the time to write good commit messages</strong>, as if you are trying to answer the questions you could imagine another developer asking if they were sat beside you. You’ll save time in code reviews, and you’ll save time in the future, since you’ll be able to understand the context of that code, the choices and tradeoffs that were made while writing it – whe <em>why</em> of it – much faster than otherwise.</p> <p>Here’s a great talk that further illustrates the value of good commit messages, and good commit hygiene in general: <a href="https://tekin.co.uk/2019/02/a-talk-about-revision-histories">A Branch in Time</a> by <a href="https://tekin.co.uk">Tekin Süleyman</a>.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:glenn" role="doc-endnote"> <p>I met Glenn once, at a dinner during the <a href="http://interblah.net/rubyfools-redux">Ruby Fools</a> conference (such a long time ago now). He said some very nice things to me over that dinner, which I’ve never forgotten. So thanks for that, Glenn! <a href="http://interblah.net/#fnref:glenn" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> LGTM tag:interblah.net,2020-07-28:/a-few-thoughts-on-code-review 2020-07-28T22:52:00+01:00 2020-07-28T22:52:00+01:00 james <p>There are a number of techniques that remote teams can use to collaborate, but the main one <em>the client</em><sup id="fnref:client" role="doc-noteref"><a href="http://interblah.net/#fn:client" class="footnote">1</a></sup> relies on is code review via pull requests.</p> <p>Code review on pull requests can help catch potential issues, but it’s not as good as actual TDD (or any other conscientous testing practice) because once implementation is written, it’s hard to evaluate what aspects of it are intended behaviour and which are side effects, or even bugs.</p> <p>Code review on pull requests can be good for getting a second opinion on a specific implementation, but they’re not as good as pairing, where ideas and approaches can be discussed and evaluated before any investment is made, and hard-to-spot edge cases can be identified before they are committed into code.</p> <p>Code review on pull requests is good for communicating between developers about what everyone is working on, but they’re not as good as actual discussion, ideally documented, because without conscious effort to capture context – the <em>why</em> this is being built, and <em>why</em> in this particular way – in either the tests, the commit messages, the pull request description, or ideally <strong>all three</strong>, it’s often up to the reviewer to try and reverse-engineer the intent and original goals of the piece of work. It’s easy to underestimate how much effort that reverse-engineering can be.</p> <p>Code review on pull requests is good for avoiding unending, ambigious conversations about what <em>might</em> work to solve a problem, because it’s always easier to measure the value of something concrete than something hypothetical, but by the time the review happens there’s already investment in that particular solution and that particular implementation of that solution. I see the same thing with MVPs, which should be built quickly without particular care, then learned from and finally discarded, but instead are built quickly and without particular care, but then put into production to become the foundation for future work… but I’m digressing; the point is that we developers tend to believe that extant code has more value than it really does, and the momentum of that particular approach can override any fundamental questions<sup id="fnref:approach" role="doc-noteref"><a href="http://interblah.net/#fn:approach" class="footnote">2</a></sup> that a code review might raise.</p> <p><em>But</em>… getting good at testing requires effort, and pairing requires a lot more energy than hacking solo, and thoughtfully documenting takes time, and development zen-like detachment from the fruits of our labour doesn’t come naturally either. And all these things require practice, and who has time to practice when there are features to ship!</p> <p>So we do code reviews on pull requests. LGTM<sup id="fnref:lgtm" role="doc-noteref"><a href="http://interblah.net/#fn:lgtm" class="footnote">3</a></sup> :(</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:client" role="doc-endnote"> <p>At the moment, I spend most of my active development time working remotely for a single client. It’s a Rails project that’s been running for approximately 13 years, with around 5 developers of varying skill levels actively contributing to the backend, where I spend most of my time. <a href="http://interblah.net/#fnref:client" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:approach" role="doc-endnote"> <p>“Did you consider, maybe, not implementing this at all?” <a href="http://interblah.net/#fnref:approach" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:lgtm" role="doc-endnote"> <p>“Looks Good To Me”, the hallmark sign that a reviewer has not even checked out and run the code, let along read it carefully and thought about more than syntax. <a href="http://interblah.net/#fnref:lgtm" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> I am an omakase chef tag:interblah.net,2020-07-20:/i-am-an-omakase-chef 2020-07-20T22:32:00+01:00 2020-07-20T22:32:00+01:00 interblah.net <p>I run a restaurant with a single seat, and a single sitting, normally around 10pm.</p> <p>There is only one item on my menu.</p> <p>At that time, my sole patron arrives and sits, watching me while I set to work, my hands moving slowly and deliberately with the practice of hundreds of prior repetitions.</p> <p>The pill is carefully broken in half, and the remainder stashed back in it’s blister.</p> <p>A small piece of mouldable savoury putty is selected, and carefully shaped around the pill, such that it fully surrounds it, but with the pill off-center, leaving a larger amount of putty at one end.</p> <p>A Dreamie<sup id="fnref:dreamies" role="doc-noteref"><a href="http://interblah.net/#fn:dreamies" class="footnote">1</a></sup> is selected, and is carefully split, and one half is held back in the hand while the other is delicately moulded into the waiting putty, such that the whole can now sit with the Dreamies-half on top, sitting on the putty with the pill hidden directly beneath it. This completes the dish. It is a Dreamie nigiri.</p> <p>The small morsel is finally placed in front of the diner, who considers it briefly, then eats, and then leaves.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:dreamies" role="doc-endnote"> <p>The singular of “<a href="https://www.dreamiestreats.co.uk/">Dreamies</a>” is basically unknowable. <a href="http://interblah.net/#fnref:dreamies" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Back on the wagon tag:interblah.net,2020-07-10:/back-on-the-wagon 2020-07-10T11:38:08+01:00 2020-07-10T11:38:08+01:00 interblah.net <p>Hey, it’s been a while. Sorry about that.</p> <p>For the past few months I’ve increasingly felt the tug to express thoughts and such, but while most of the world now does that on Twitter, as with Facebook I don’t feel like I can participate on there without condoning some of the awful aspects of social media.</p> <p>So I’m resurrecting this site, in the hopes that I can write a bit more here to scratch that itch.</p> <p>Obviously there was no way I could possibly resume writing here without a total overhaul of the design and backend. And then having spent days on it, learning <a href="http://tachyons.io">Tachyons</a> in the process, I flicked back to the previous design and, y’know what, it actually wasn’t that bad. But anyway, we’re here now.</p> <p>Speaking of Tachyons, I did read an interesting article about <a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/">CSS utility classes and “separation of concerns”</a> that has finally helped me understand the motivations that have driven front-end developers away from “semantic” CSS. As primarily a backend developer, I struggle to shake the dream of writing simple markup for a “thing” and then having different CSS render it appropriately in whatever contexts it might appear, but this helped me understand the journey. I want to re-read that article, because I have a hunch that the destination it describes isn’t actually that far away from the “semantic” one I hope for, just that the CSS might end up being dynamically composed from utility classes by a pre-processor. Maybe.</p> <p>Some random thoughts I’ve had on return to this site and the codebase behind it:</p> <ul> <li>At the start of this resurrection process, I wondered why I ever bothered writing my own software to run it, but as I got stuck in, once again I enjoyed the simpicity and flexibility that <a href="http://interblah.net/vanilla">Vanilla</a> affords. Building little components like the sidebar on the front page is easy.</li> <li>During the redesign, I wanted to avoid as much hand-written CSS as possible; I just want something that automatically provides a consistent and clean typographic layout with decent line heights and readability. I’m not sure I would call what I’ve achieved “beautiful”, but it’s not awful.</li> <li>There is a <em>lot</em> of outdated content here. The pages about how this site runs are way out of date (it’s now in a Docker container under <a href="http://interblah.net/dokku">Dokku</a> on a VPS, and has been for years). I have now switched to <a href="http://interblah.net/doom-emacs">Doom Emacs</a>, which is similar to <a href="http://interblah.net/spacemacs">Spacemacs</a>. Beyond that, the last real writing is six years old, or more, and I don’t entirely recognise many of those opinions as mine anymore. :shrug:!</li> <li><em>Note to self: figure out some way of getting emojis to render like GitHub and Slack.</em></li> <li>A bunch of snips refer to commenting, which I turned off a long time ago. I should really remove references to that, and potentially embrace <em>webmentions</em> like <a href="https://jamesmead.org/blog/2020-06-27-indieweb-ifying-my-personal-website">James</a> did recently. Or I could allow commenting via Twitter? Or even from/to Mastodon via ActivityPub?</li> </ul> <p>Anyway, I’m back. Or am I? I think I am. I hope I am.</p> <p>I’m back.</p> Getting Started with Spacemacs tag:interblah.net,2018-09-20:/getting-started-with-spacemacs 2018-09-20T15:17:14+00:00 2018-09-20T15:17:14+00:00 interblah.net <p>I wanted to capture what I like about my editor of choice at the moment. If you’re at all interested in modal editing, or are an experienced Vim user looking for a change, I think there’s a chance you might enjoy Spacemacs. I’ve written this to try and capture what I like about it, and some of the things I wish I’d known when I got started. Hopefully it’s useful to some of you!</p> <p><img src="http://interblah.net/images/spacemacs/spacemacs.png" alt="Spacemacs" /></p> <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> <h2 id="table-of-contents">Table of Contents</h2> <ul> <li><a href="http://interblah.net/#my-history-with-text-editors">My history with text editors</a></li> <li><a href="http://interblah.net/#why-spacemacs">Why Spacemacs?</a> <ul> <li><a href="http://interblah.net/#modal-editing">Modal editing</a> <ul> <li><a href="http://interblah.net/#-but-only-if-you-want-it">… but only if you want it</a></li> </ul> </li> <li><a href="http://interblah.net/#conventionality-and-discoverability-via-which-key">Conventionality and discoverability via <code>which-key</code></a> <ul> <li><a href="http://interblah.net/#discovering-new-functions-and-key-bindings">Discovering new functions and key bindings</a></li> <li><a href="http://interblah.net/#layers-and-conventionality">Layers and conventionality</a></li> </ul> </li> <li><a href="http://interblah.net/#magit">Magit</a></li> </ul> </li> <li><a href="http://interblah.net/#so-getting-started">So: Getting started</a> <ul> <li><a href="http://interblah.net/#installation">Installation</a> <ul> <li><a href="http://interblah.net/#ripgrep">Ripgrep</a></li> <li><a href="http://interblah.net/#getting-spacemacs">Getting Spacemacs</a></li> </ul> </li> <li><a href="http://interblah.net/#configuring-your-installation">Configuring your installation</a> <ul> <li><a href="http://interblah.net/#layers">Layers</a></li> <li><a href="http://interblah.net/#other-settings">Other settings</a></li> </ul> </li> <li><a href="http://interblah.net/#youre-a-vimmer-now-arry">You’re a vimmer now, ‘arry</a></li> <li><a href="http://interblah.net/#editing-files-and-projects">Editing files and projects</a></li> </ul> </li> <li><a href="http://interblah.net/#cheat-sheet">Cheat Sheet</a> <ul> <li><a href="http://interblah.net/#modal-editing-1">Modal editing</a></li> <li><a href="http://interblah.net/#general">General</a></li> <li><a href="http://interblah.net/#files-projects-and-buffers">Files, projects and buffers</a></li> <li><a href="http://interblah.net/#windows">Windows</a></li> <li><a href="http://interblah.net/#searching">Searching</a></li> <li><a href="http://interblah.net/#editing">Editing</a></li> <li><a href="http://interblah.net/#development-specific-stuff">Development specific stuff</a></li> <li><a href="http://interblah.net/#git">Git</a></li> <li><a href="http://interblah.net/#spacemacs-itself">Spacemacs itself</a></li> <li><a href="http://interblah.net/#documentation-and-help">Documentation and help</a></li> </ul> </li> <li><a href="http://interblah.net/#help-im-stuck">Help, I’m Stuck!</a></li> <li><a href="http://interblah.net/#other-notes">Other Notes</a> <!-- markdown-toc end --></li> </ul> <h2 id="my-history-with-text-editors">My history with text editors</h2> <p>Probably every 18 months or so, I seem to get an itch about my current editor, and look around for something different. Often it’s because something is behaving slowly, or I can’t get a feature to work. My editors-of-choice over the last 15 years have been, roughly:</p> <ul> <li><a href="https://macromates.com">TextMate</a>, because I saw it in the original Rails demo and loved how simple yet powerful it was, particularly the plugins. I have license #12 for TextMate. I used TextMate for a long time.</li> <li><a href="https://www.sublimetext.com">Sublime Text</a>, because I saw multiple cursors support. I wasn’t thrilled to switch to Python-based plugins, but knew enough to figure most stuff out.</li> <li><a href="https://atom.io">Atom</a>, because SublimeText had started to show weird slow-down that I couldn’t resolve, even by wiping my configuration.</li> <li>Sublime Text again, because Atom started <em>really</em> slowly, and the previous slowdown seemed to have resolved itself.</li> <li><a href="https://www.vim.org">Vim</a>, because a bunch of other people had switched recently and I wanted to see what all the fuss was about. Prior to this I’d only tried Vim a few times and really hated modal editing, but this stint was around 6 months and finally did whatever brain reconfiguration was required to make modal editing make sense to me. Probably I just learned enough of the movement keys to finally feel able to step around files without feeling like I was pecking at the keyboard.</li> <li>Atom (again), because they’d focussed a lot on speed recently, and I felt overwhelmed by all the arcane commands and key combinations I needed to learn to use most of the Vim plugins, and because I found <em>exploring</em> projects in Vim to require a lot of energy for me. Sometimes I want to just be able to use a mouse to click around a project structure!</li> <li><a href="https://github.com/syl20bnr/spacemacs">Spacemacs</a>, because Atom was still relatively slow to launch, and I had hit an annoying edge case in the project search where searching within bundled gems didn’t work smoothly.</li> </ul> <p>I’m sure all of the issues I experienced were largely <em>my</em> fault, rather than any problem with any of these editors, and I’m totally certain that any frustrations I experienced with any of them could be resolved with enough time and patience. However, when I’m sitting down to work on a project that already requires a lot of though, I don’t want to have to give up a lot of that energy to my editor; I need to be able to get it to do what I would like, with a minimum of energy, headscratching, or waiting for a spinny icon to finish.</p> <h2 id="why-spacemacs">Why Spacemacs?</h2> <p>Here’s what I really like about Spacemacs:</p> <h3 id="modal-editing">Modal editing</h3> <p>Much as I was <a href="http://interblah.net/vi">initially resistant</a> to it, putting all that work into using Vim for half a year did finally sink in, and I can pretty happily jump around within a line, or up and down blocks of code, without having to think too hard about it. I don’t know if I would entirely recant my rant, but the simple fact is that I’ve made the sacrifice and my brain has already been trained, so I might as well accept it. I don’t think I’ll ever be zealatous about the benefits of keeping your hands on the keyboard or anything like that, but I can say that once you <em>do</em> get a handle on movement and text manipulation commands, it doesn’t feel too bad.</p> <h4 id="but-only-if-you-want-it">… but only if you want it</h4> <p>Sometimes though, as a beginner, it’s just faster to use the mouse, or the arrow keys. And Spacemacs totally supports that. If I’m in a bad mood and don’t want to learn the modal way to do something, I can drag and select text, or jump the cursor around, and it just works. I can click on pretty much anything, from files and directories in the file tree, to links in a markdown document, and they do what you’d expect.</p> <p>It’s this graceful support for non-modal mechanisms that makes it easy to stick with a modal editor through the rough times and the smooth.</p> <h3 id="conventionality-and-discoverability-via-which-key">Conventionality and discoverability via <code>which-key</code></h3> <p>The biggest barrier to me sticking with Vim was feeling like “I know there’s a way to do this better” but having to enter the depths of the Google mines to figure out how. Vim is super-extensible and customisable, but the flip-side of that is that everyone has their own setup and key-bindings and so until you are an expert, you end up with a cobbled together configuration of stuff you’ve found on the internet. As I said above, I’m <em>sure</em> that eventually you would get a handle on this and be able to smooth any rough edges, but I think it’s a significant barrier for people who are already trying to internalise a whole new way of editing.</p> <p>With Spacemacs, the <em>main</em> way you invoke commands is by hitting the spacebar. Press that nice big giant button and after about half a second, a menu appears with a whole bunch of options about what you might press next, with descriptions of what those are.</p> <p><img src="http://interblah.net/images/spacemacs/menu.png" alt="The menu, invoked via the spacebar" title="Hit space to access the power!" /></p> <p>The commands are organised into sections, all based roughly on mnemonic groupings of what those functions do. So, if you want to do something with a file, chances are that it’s somewhere under the <code>f</code> section. If you want to change the project you’re working on, maybe try <code>p</code>. Want to search for something? Chances are it’ll be under <code>s</code>. Now these mnemonics aren’t perfect (“text” stuff is under <code>x</code> because <code>t</code> was used for “toggles”), but the descriptions are all <em>right there</em>, so the more you look at the menu, the more you internalise which section is which.</p> <p>Once you get more comfortable, you often don’t need to look at the menu at all, because the combinations become internalised. I don’t need to look at the menu when I want to rename a file, because seeing the menu again and again has helped me learn that it’s <code>SPC f R</code>. Checking the git status of the project is <code>SPC g s</code>. What Spacemacs and <code>which-key</code> do really well is support the learning process, and get you to a point where you can invoke all these commands quickly.</p> <h4 id="discovering-new-functions-and-key-bindings">Discovering new functions and key bindings</h4> <p>But it gets even better than that, because you don’t even need to dig around all the the menus until you find the command you were hoping for. If you have an inkling that there should be a more efficient way of, say, making some text uppercase without having to delete and re-type it, you can easily search <em>all of the commands</em> in the editor by hitting <code>SPC SPC</code> and then making a few guesses about what the command name might be. I hit <code>SPC SPC</code> and type “upper”, but none of those look right, so let’s try “upcase”, and bingo, a handful of functions that I might want to use.</p> <p><img src="http://interblah.net/images/spacemacs/upper.gif" alt="Exploring the functions in Spacemacs" /></p> <p>I can invoke one by hitting <code>RET</code>, but the menu also tells me what (if any) key conbination would run that function automatically, so if I end up doing this enough times, I’ll probably start trying that combo, and eventually not need to look it up ever again. Every instance of this helps teach me.</p> <p>Showing the keybindings also shows me where in the menu I would’ve found that key, which over time also gives me clues about where to search for something the next time (“upcase region” is <code>SPC x U</code>, so perhaps there are other text case changing functions under <code>SPC x</code>…).</p> <p>It also shows me when there are even faster ways of invoking the function using the “evil” bindings, in this case <code>g U</code>. Perhaps I’ll try that out, and if I try it enough times, it gets into my muscle memory, and I get faster.</p> <h4 id="layers-and-conventionality">Layers and conventionality</h4> <p>Learning how the menus are organised also leads to what I mean by “convenionality”. But before I explain this, I need to touch on how Spacemacs is organised. Rather than having you install lots of small Emacs packages (what you might consider plugins), Spacemacs gathers collections of related packages into what it calls “layers”. Every Spacemacs layer comes with a set of key bindings, which the community as a whole has developed together, gathering all of the features and functions of the packages within that layer together. So when you install the <a href="http://spacemacs.org/layers/+lang/ruby/README.html" title="Documentation for the Ruby layer in Spacemacs">“ruby” layer</a>, you get keybindings to run tests, to invoke bundler, to do simple refactorings, to use your preferred version manager, and so on, all ready for you to use and explorable via the menu and function search.</p> <p>This is not to say that you can’t change things – you totally can! – but you don’t need to. There are similar distributions for other editors that enable similar “sensible” configurations, so this is not to say that Spacemacs is any better than, say, <a href="https://github.com/carlhuda/janus">Janus</a> for vim. but for any of these powerful editors, having <em>something</em> that provides this kind of support is really great.</p> <h3 id="magit">Magit</h3> <p>From what I can tell, a lot of the love for Emacs comes from people talking about how <code>org-mode</code> is amazing and can do anything and it’s the greatest thing in the world, and that it’s worth switching to Emacs just to be able to harness the awesome power of <code>org</code><sup id="fnref:borg" role="doc-noteref"><a href="http://interblah.net/#fn:borg" class="footnote">1</a></sup>. That might be true. I have not used it. I might discover how to use it tomorrow and achieve a similar text-based nirvana.</p> <p>As a programmer though, there’s a different tool which has me <em>totally</em> sold on Emacs, and that’s <a href="https://magit.vc" title="Magit, the git package for Emacs">Magit</a>, a package for working with the Git version control system.</p> <p>I used to be a command-line Git jockey. I’d use <code>git add .</code> and <code>git commit -m "Blah blah"</code>, and even run interactive rebasing and patch-based adding from the command line. But if you care about creating a great git history, <a href="https://vimeo.com/280579162#t=1017s" title="The relevant section from a great talk by Tekin Suleyman">using git from the command line is working against you</a>.</p> <p>Magit makes it really easy for me to pick files to stage, or even just pick a few lines from those files, and commit just those changes, writing a great commit message while I do, right in my editor.</p> <p><img src="http://interblah.net/images/spacemacs/magit.gif" alt="Using Magit in spacemacs" /></p> <p>I really love Magit. With it can I do pretty much anything I knew how to do at the command line, and more, and it encourages me to write the best commit messages I can, and to keep my history tidy too. As far as I’m concerned, Spacemacs is a pretty decent editor with nice ways of teaching and reinforcing how to efficiently edit text, but Magit is a superpower.</p> <h2 id="so-getting-started">So: Getting started</h2> <p>The title of this post is “getting started”, so I’m going to try to share what I did to get going with Spacemacs, in the hope it helps you get started quickly, and see some of the nice features that I’ve been enjoying.</p> <p>As a caveat, let me say that I use a Mac, and I’m a Ruby developer using Git, so these notes are from that background, rather than a comprehensive set of instructions that will work for everyone.</p> <h3 id="installation">Installation</h3> <p>Installing Spacemacs is pretty simple. Firstly, you’ll need Emacs. There are a few packages available via Homebrew:</p> <ul> <li><code>emacs</code></li> <li><code>emacs-plus</code></li> <li><code>emacs-mac</code></li> </ul> <p>I use <code>emacs-mac</code> because it has patched which enable pixel-level scrolling, and it can also be configured to run without a title bar, which I like because I tend to run Emacs maximised (but not “full screen” since that means something else these days in MacOS).</p> <p><code> brew tap railwaycat/emacsmacport &amp;&amp; brew install emacs-mac --with-no-title-bars --with-spacemacs-icon </code></p> <p>One significant caveat of installing without title bars is that you can’t then drag the window around using your mouse. This can be a real pain, but if you <em>always</em> run Emacs maximised, it doesn’t matter. Unless you know you want this, I’d suggest installing without that option. You can use <code>brew info emacs-mac</code> to see all the installation options if you like.</p> <h4 id="ripgrep">Ripgrep</h4> <p>While we are at it, also install <code>ripgrep</code>, the super fast search tool that understands version control systems like git:</p> <p><code> brew install ripgrep </code></p> <p>Spacemacs will then use this tool to do fast searching. Next, install Spacemacs according to their instructions:</p> <h4 id="getting-spacemacs">Getting Spacemacs</h4> <p>I would encourage you to just follow <a href="https://github.com/syl20bnr/spacemacs">the instructions on the Spacemacs site</a>, but the gist is this:</p> <p><code> git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d </code></p> <p>Currently there’s a general recommendation to use the “develop” branch of the Spacemacs repository, since the “master” branch hasn’t seen much attention in a while. I find this a little odd, but that’s how they like to run the project I guess. So:</p> <p><code> cd ~/.emacs.d &amp;&amp; git checkout develop </code></p> <p>Next, start up Emacs! You should see the Spacemacs home screen, and you’ll be prompted to answer a number of questions about how you’d like Spacemacs to be set up. I’d suggest you pick “Evil” as the editing mode, unless you know you don’t want to try to use the Vim-like modal system. Also, pick the “standard” distribution, rather than the minimalist one. Or at least, that’s what I did.</p> <p>Now Spacemacs will set to downloading a whole bunch of packages and installing them, and this will take a few minutes. This is a perfect time <a href="http://spacemacs.org/doc/QUICK_START.html">read the Quickstart documentation</a> while you <a href="https://www.youtube.com/watch?v=QMZhD_ksmDY">drink your weak lemon drink</a>.</p> <h3 id="configuring-your-installation">Configuring your installation</h3> <p>Once all the packages have been installed, you’ll be presented with a simple help screen. Read it – it’s going to cover the basics fairly well. If you like, you can dive straight into editing files (take a look at the Cheat Sheet below which might help), but personally, I always like to see what I can tweak when I work with a new editor so once you’re done reading that, hit <code>SPC f e d</code> to open your personal configuration file. This is where all of the installed layers are declared and configured. I’m going to share the settings that I’ve used, but by all means tinker.</p> <h4 id="layers">Layers</h4> <p>Here’s roughly what my layer configuration looks like:</p> <p><pre><div class="code common_lisp"><span class="nv">javascript</span> <span class="nv">coffeescript</span> <span class="nv">yaml</span> <span class="p">(</span><span class="nv">osx</span> <span class="ss">:variables</span> <span class="nv">osx-use-option-as-meta</span> <span class="no">nil</span><span class="p">)</span> <span class="nv">helm</span> <span class="p">(</span><span class="nv">auto-completion</span> <span class="ss">:variables</span> <span class="nv">auto-completion-tab-key-behavior</span> <span class="ss">'complete</span><span class="p">)</span> <span class="nv">better-defaults</span> <span class="nv">emacs-lisp</span> <span class="nv">neotree</span> <span class="nv">git</span> <span class="nv">github</span> <span class="nv">markdown</span> <span class="nv">org</span> <span class="p">(</span><span class="nv">shell</span> <span class="ss">:variables</span> <span class="nv">shell-default-height</span> <span class="mi">30</span> <span class="nv">shell-default-position</span> <span class="ss">'bottom</span><span class="p">)</span> <span class="p">(</span><span class="nv">version-control</span> <span class="ss">:variables</span> <span class="nv">version-control-diff-side</span> <span class="ss">'left</span> <span class="nv">version-control-global-margin</span> <span class="no">t</span><span class="p">)</span> <span class="p">(</span><span class="nv">ruby</span> <span class="ss">:variables</span> <span class="nv">ruby-version-manager</span> <span class="ss">'chruby</span> <span class="nv">ruby-deep-indent-paren</span> <span class="no">nil</span><span class="p">)</span> <span class="p">(</span><span class="nv">ruby-on-rails</span> <span class="ss">:variables</span> <span class="nv">feature-use-chruby</span> <span class="no">t</span><span class="p">)</span> <span class="nv">html</span> <span class="nv">dash</span></div></pre></p> <p>The lines which aren’t wrapped in parentheses just use whatever defaults exist for the layer. Those that <em>are</em> (e.g. <code>osx</code> or <code>shell</code>) have been configured by me a little. Looking at the layer documentation will explain those settings, but the interesting parts there are that I’ve “unconfigured” the right alt key as the Emacs “meta” key, because I need that key to type <code>#</code> characters.</p> <p>I’ve also set the Ruby version manager to be “chruby”, but you might need something else. If you want to stick with the defaults, just delete <code>:variables</code> and everything after it, and remove the parentheses.</p> <h4 id="other-settings">Other settings</h4> <p><code>dotspacemacs-maximised-at-startup t</code></p> <p>I tend to run Emacs full screen, so I want it to start that way.</p> <p><code>dotspacemacs-line-numbers t</code></p> <p>Always show line numbers in every file</p> <p><code>dotspacemacs-whitespace-cleanup 'changed</code></p> <p>For any lines that I have edited, make sure there are no trailing whitespace characters, but otherwise leave other lines untouched. I find this is the best compromise between keeping whitespace in check, but not having commits full of unrelated whitespace changes just because I touched a file that had some.</p> <p>That’s it for the Spacemacs options, but you can configure other stuff inside the <code>dotspacemacs/user-config</code> function. Here’s a few things that I have:</p> <p><pre><div class="code common_lisp"><span class="c1">;; Use two spaces as the tab width</span> <span class="p">(</span><span class="nv">setq-default</span> <span class="nv">tab-width</span> <span class="mi">2</span><span class="p">)</span> <span class="c1">;; Make the modeline look good on Mac OS X</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">powerline-default-separator</span> <span class="ss">'utf-8</span><span class="p">)</span> <span class="c1">;; Make the right alt key available as meta in case that's useful</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">ns-right-option-modifier</span> <span class="ss">'meta</span><span class="p">)</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">mac-right-option-modifier</span> <span class="ss">'meta</span><span class="p">)</span> <span class="c1">;; When saving using cmd-s, exit back into normal mode so that other vim-ish commands just work</span> <span class="p">(</span><span class="nv">global-set-key</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"H-s"</span><span class="p">)</span> <span class="p">(</span><span class="k">lambda</span> <span class="no">nil</span> <span class="p">(</span><span class="nv">interactive</span><span class="p">)</span> <span class="p">(</span><span class="nv">evil-force-normal-state</span><span class="p">)</span> <span class="p">(</span><span class="nv">call-interactively</span> <span class="p">(</span><span class="nv">key-binding</span> <span class="s">"^X^S"</span><span class="p">))))</span> <span class="c1">;; Note that the "^X^S" string above actually needs to be the proper `ctrl-x ctrl-s` characters, which I can't embed in my blog. Best copy them directly from my dotfiles at https://raw.githubusercontent.com/lazyatom/dotfiles/spacemacs-develop/dotfiles/spacemacs</span> <span class="c1">;; Make cmd-t open the project file finder, like other editors</span> <span class="p">(</span><span class="nv">evil-define-key</span> <span class="ss">'normal</span> <span class="ss">'global</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"H-t"</span><span class="p">)</span> <span class="ss">'helm-projectile-find-file</span><span class="p">)</span> <span class="c1">;; Include underscore in word movement commands for Evil mode</span> <span class="p">(</span><span class="nb">dolist</span> <span class="p">(</span><span class="nv">mode-hook</span> <span class="o">'</span><span class="p">(</span><span class="nv">ruby-mode-hook</span> <span class="nv">enh-ruby-mode-hook</span> <span class="nv">python-mode-hook</span> <span class="nv">js2-mode-hook</span> <span class="nv">haml-mode-hook</span> <span class="nv">web-mode-hook</span><span class="p">))</span> <span class="p">(</span><span class="nv">add-hook</span> <span class="nv">mode-hook</span> <span class="nf">#'</span><span class="p">(</span><span class="k">lambda</span> <span class="p">()</span> <span class="p">(</span><span class="nv">modify-syntax-entry</span> <span class="nv">?_</span> <span class="s">"w"</span><span class="p">))))</span> <span class="c1">;; don't align if statements to the if</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">ruby-align-to-stmt-keywords</span> <span class="o">'</span><span class="p">(</span><span class="k">if</span> <span class="nv">begin</span> <span class="nb">case</span><span class="p">))</span> <span class="c1">;; set fill-column with for git commits to recommended width</span> <span class="p">(</span><span class="nv">add-hook</span> <span class="ss">'git-commit-mode-hook</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">()</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">fill-column</span> <span class="mi">70</span><span class="p">)))</span> <span class="c1">;; Follow symlinks to files without complaining</span> <span class="p">(</span><span class="k">setq</span> <span class="nv">vc-follow-symlinks</span> <span class="no">t</span><span class="p">)</span></div></pre></p> <p>OK, enough configuration. Let’s actually start editing some stuff!</p> <h3 id="youre-a-vimmer-now-arry">You’re a vimmer now, ‘arry</h3> <p>First thing’s first, you’re going to need to get used to modal editing. Covering that is <em>way</em> outside of the scope of this article, but here’s some basics.</p> <p>You’ll start in “normal” mode, which is for moving around, and for manipulating text without actually typing new stuff (so, like, moving lines, copying and pasting and deleting and so on).</p> <p>If you want to enter some text, you’ll need to enter “insert” mode. There are a few ways to do this, but one is just hitting “i”. Then you can type as much as you like, and hit <code>ESC</code> when you are done to get back to “normal” mode.</p> <p>To move the cursor around in normal mode, the vimmish way is by using the <code>hjkl</code> keys, which correspond to left, down, up and right. Left and right are pretty obvious, but I used to often get confused which of <code>j</code> and <code>k</code> was up or down. The best way I’ve heard of to remember this is that <code>j</code> looks a little bit like a down arrow, if you can stretch your imagination to that. Bear that in mind and it’ll soon become second nature.</p> <p>There is a “lot” more to editing using Vim-like commands, and you’ll spend most of your time doing that, so if you’re not very experienced, I would suggest trying out one of the various vim tutorials. There’s even one built in to Spacemacs (<code>SPC h T</code>)!</p> <h3 id="editing-files-and-projects">Editing files and projects</h3> <p>Spacemacs uses a package called <a href="https://projectile.readthedocs.io">projectile</a> to manage projects, but it can be a little unclear how to actually get started with this. Here’s a simple way to get into it.</p> <p>You can browse the list of projects that Projectile knows about with <code>SPC p p</code>. However, when you get started, this list will almost certainly be empty. Projectile considers a project as any directory that’s under version control. Projects don’t really exist until Projectile discovers them, and the best way of doing that is by opening a file within that directory.</p> <p>Hit <code>SPC f f</code> to start browsing for a file to open. You can start typing and hit <code>TAB</code> to autocomplete directory and file names, or use <code>ctrl-h</code> to jump up to parent directories to move around quickly. Once you’ve found the file, hit <code>RET</code> to open the file.</p> <p>Depending on the type of the file, you <em>might</em> be prompted to add a new layer to get all the features for that type of file; it doesn’t really matter what you choose at this point.</p> <p>Once the file is open, if you hit <code>SPC p p</code> again, assuming you’ve opened a file that was somewhere under version control, you should now see the project in the list, and you can get back to this project easily now.</p> <p>When you <em>do</em> select a project using <code>SPC p p</code>, you’ll next be prompted to pick a file from that project. For a long time, this confused me, particularly as I moved from one project to the other, and back, in a single editing session. However, this is normal – Spacemacs is just asking which buffer or file from that project that you want to start on.</p> <h2 id="cheat-sheet">Cheat Sheet</h2> <p>These are all the commands that I wish I’d known when I got started. It’s also very worth getting a grounding in Vim, including the keys I listed nearer the top of this article; here I’m just including the Spacemacs-specific commands that cover the basics.</p> <p>Spacemacs includes <em>many</em> other commands, and likely some of them will be better in certain situations than the ones I’ve listed. These are just the basic ones that kept me moving.</p> <h3 id="modal-editing-1">Modal editing</h3> <p>These are the basic commands that I use all the time:</p> <ul> <li><code>hjkl</code> for movement, although I will pretty happily use arrow keys to move around a little when I’m in insert mode</li> <li><code>w</code> and <code>b</code> (and <code>&lt;num&gt;w</code> and so on) to skip around words at a time</li> <li><code>x</code> to delete the character under the cursor</li> <li><code>r&lt;char&gt;</code> to replace the character under the cursor with a new one</li> <li><code>f&lt;char&gt;</code> to skip to a specific character (and to a far lesser extent, <code>&lt;num&gt;f&lt;char&gt;</code>, but the time I spend visually counting occurrences almost always outweighs just doing <code>f&lt;char&gt;</code> a few times)</li> <li><code>{</code> and <code>}</code> to jump up and down blocks of code. Basically, it moves to the next blank line, but if you’ve formatted your code nicely, more often than not this is the next function</li> <li><code>cw</code> and <code>ct&lt;char&gt;</code> to change the current work, or change to the given character (e.g. given <code>some_variable_name</code>, with my cursor at the start I might type <code>ct_</code> to edit just the <code>some</code> part)</li> <li><code>dw</code> and <code>dd</code> to delete words and lines</li> <li><code>o</code> to add a new blank line below this one and enter insert mode</li> <li><code>p</code> to paste whatever I’ve recently deleted somewhere else</li> <li><code>A</code> to go to the end of the line and enter insert mode (“Append”)</li> <li><code>^</code> and <code>$</code> to skip around between the start and end of lines</li> <li><code>gg</code> and <code>G</code> to move to the top and bottom of the file respectively</li> <li><code>ve</code> and <code>v$</code> to select the word or line, ready to do something with it (maybe <code>y</code> to copy it…)</li> <li>Getting used to hitting escape as often as possible to leave insert mode<sup id="fnref:savingfiles" role="doc-noteref"><a href="http://interblah.net/#fn:savingfiles" class="footnote">2</a></sup>.</li> </ul> <p>There’s a bunch more that I do know and use, and I am <em>certain</em> that I could be using different commands to do a lot of stuff more efficiently, but I think it was really internalising the commands above that got me through the transition from feeling massively slowed down to reasonably efficient at editing and moving text around.</p> <p>All of these are Vi/Vim commands, but Spacemacs implements them using a package called “evil”, which according to all the reckons on the internet that I’ve read, is hands down the most complete Vim emulation layer available. What this means is that any advice or tricks my Vim-using friends might have, I can also use too, and that’s pretty neat.</p> <h3 id="general">General</h3> <p><code>f d</code> - in rapid succession, will quit out of most menus/commands/minibuffers back into normal mode</p> <p><code>ctrl-g</code> - tell Emacs to quit something that’s taking too long</p> <p><code>SPC q q</code> - quit Emacs</p> <p><code>SPC q R</code> - restart Emacs</p> <p><code>q</code> - if you’re in a non-file window, chances are if you hit <code>q</code>, it’ll close that window. Give it a try.</p> <h3 id="files-projects-and-buffers">Files, projects and buffers</h3> <p><code>SPC f f</code> - browse for files and open them</p> <p><code>SPC f r</code> - browse for a recently-opened file</p> <p><code>SPC f R</code> - rename the current file</p> <p><code>SPC f D</code> - delete the current file</p> <p><code>SPC p p</code> - show list of projects to choose (you will be prompted to open a file in that project afterwards)</p> <p><code>SPC p f</code> - file a file within the current project</p> <p><code>SPC p b</code> - show a list of all open buffers for this project (buffers are sort of like “open files”)</p> <p><code>SPC b b</code> - show a list of all open buffers</p> <p><code>SPC b d</code> - delete a buffer, which basically means “close this file, I don’t want to see it anymore”</p> <p><code>SPC TAB</code> - switch back to the previous buffer</p> <p><code>SPC f t</code> - toggle open a file tree (use <code>shift-k</code> to go up to parent directories in it)</p> <h3 id="windows">Windows</h3> <p><code>SPC w /</code> - split a window vertically</p> <p><code>SPC w s</code> - split a window horizontally</p> <p><code>SPC 1/2/3...9</code> - jump to window 1 to 9 (you should see the window numbers in the little mode line)</p> <p><code>SPC 0</code> - jump to the file tree (if it’s open; might actually open it if it isn’t)</p> <p><code>SPC w d</code> - close the current window</p> <p><code>SPC w m</code> - make the current window the only window</p> <h3 id="searching">Searching</h3> <p><code>SPC /</code> - search every file in the project</p> <p><code>SPC s s</code> - search (“swoop”) in the current buffer</p> <p><code>SPC *</code> - search the current project for the symbol/word under the cursor</p> <p><code>*</code> - enter a powerful search “mode” for the symbol/word under the cursor</p> <h3 id="editing">Editing</h3> <p><code>SPC i k/j</code> - insert a blank line above or below the cursor without entering insert mode</p> <p><code>SPC ; ;</code> - comment out a line of code (although now I know about <code>g c c</code> and all the other Vim-ish <code>g c</code> commands)</p> <h3 id="development-specific-stuff">Development specific stuff</h3> <p><code>, t b</code> - run all the tests/specs in this file</p> <p><code>, t t</code> - run the test/spec at the cursor</p> <p><code>SPC c d</code> - close the test window (“c” is for compilation)</p> <p><code>SPC c k</code> - quit the current test run</p> <p><code>SPC p a</code> - switch to the alternate file for this one (e.g. from controller to controller test, and back)</p> <h3 id="git">Git</h3> <p><code>SPC g s</code> - open git status. Then hit <code>?</code> to see the keys that Magit understands. Note that case is important. Hit <code>q</code> to close the Magit buffer.</p> <h3 id="spacemacs-itself">Spacemacs itself</h3> <p><code>SPC f e d</code> - open your configuration file</p> <p><code>SPC f e R</code> - reload your configuration after editing that file</p> <p><code>SPC f e D</code> - compare your configuration to the default one. This is super useful when you’ve updated Spacemacs, since new configuration options become available. When you do this, you enter an “ediff” mode; hit <code>?</code> to see what keys do, but the basics are: press <code>n</code> and <code>p</code> to move between changes, then <code>a</code> or <code>b</code> to pick which option to use, and <code>q</code> to quit</p> <p><code>SPC SPC</code> - search for a function by name, and run it. This is a <em>great</em> way of getting a sense of what Emacs/Spacemacs can do</p> <h3 id="documentation-and-help">Documentation and help</h3> <p>Emacs and Spacemacs come with a huge amount of documentation built in, which is extremely useful when you’re trying to figure out how to do something.</p> <p><code>SPC h l</code> - search for a layer and then open its documentation, which will show all its configuration options and key bindings</p> <p><code>SPC h d f</code> - search for a function and show its documentation</p> <p><code>SPC h d v</code> - search for a variable and show its documentation, and current and default values</p> <p><code>SPC h d p</code> - search for a package and show information about it</p> <p><code>SPC h d k</code> - then enter any combination of keys, and Spacemacs will tell you what function they are bound to and what it will do.</p> <h2 id="help-im-stuck">Help, I’m Stuck!</h2> <p>As with any complicated bit of software, it’s possible to get stuck in a menu or lost in some options, and want to back out. Sometimes key bindings you expect to work, will not, because you’ve ended up in one of the “system buffers” that Emacs uses to store command output or something else. Don’t worry – it happens to everyone.</p> <p>The best way I’ve found to re-orient myself is to hit <code>SPC f r</code> or <code>SPC b r</code> to open the recent file or buffer list, and then get back to a file that I was working on. From there, I can normally get back to moving around my project like I expected. Worst case, just quit (<code>SPC q q</code>) and open Emacs again.</p> <h2 id="other-notes">Other Notes</h2> <p>Spacemacs works just as well in the console version of Emacs as the GUI one, so you can happily sync your configuration to remote machines and use everything you’ve learned when editing files on remote servers. And take a look at “Tramp” if you’re interested in another Emacs superpower involving remote files!</p> <p>Even though I’ve been using Spacemacs for the best part of a year now, I only recently actually read through the documentation (<code>SPC h SPC RET</code> inside Spacemacs, or <a href="http://spacemacs.org/doc/DOCUMENTATION.html">here</a>). There’s a lot of good stuff in there, particularly about the <code>evil</code> keybindings, and things like managing layouts and workspaces. Once you’ve gotten to grips with some of the basics, I highly recommend just browsing it, and then coming back to it every now and again, to see what jumps out in an “ah! I can use <em>that</em>” kind of way.</p> <p>I hope this has been useful, either if you’re curious about Spacemacs, or are playing around with it. Thanks for your time!</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:borg" role="doc-endnote"> <p>Perhaps, you might say… joining the b<code>org</code>? <em>Groan</em>. <a href="http://interblah.net/#fnref:borg" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:savingfiles" role="doc-endnote"> <p>With Spacemacs I actually have it set up so I can hit <code>cmd-s</code> to both save the file <em>and</em> enter normal mode, though I recently also learned about <code>fd</code> and am trying to practice that. <a href="http://interblah.net/#fnref:savingfiles" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div>