<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.5">Jekyll</generator><link href="https://msol.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://msol.io/" rel="alternate" type="text/html" /><updated>2026-05-14T10:53:39-07:00</updated><id>https://msol.io/feed.xml</id><title type="html">msol</title><subtitle>msol • a blog made of words</subtitle><author><name>Mike Solomon</name></author><entry><title type="html">Mac productivity tips for developers</title><link href="https://msol.io/blog/tech/work-more-efficiently-on-your-mac-for-developers/" rel="alternate" type="text/html" title="Mac productivity tips for developers" /><published>2019-06-15T04:50:13-07:00</published><updated>2019-06-15T04:50:13-07:00</updated><id>https://msol.io/blog/tech/work-more-efficiently-on-your-mac-for-developers</id><content type="html" xml:base="https://msol.io/blog/tech/work-more-efficiently-on-your-mac-for-developers/">&lt;h1 id=&quot;working-faster&quot;&gt;Working faster&lt;/h1&gt;

&lt;p&gt;Software developers spend hour after hour on their machines,
so it’s worth spending a little time improving common workflows now and then.&lt;/p&gt;

&lt;p&gt;The time you can spend on this is virtually unbounded,
but I have found there are a few tricks that many people miss—a
handful of high-value improvements that can go a long way without hours of investment.&lt;/p&gt;

&lt;p&gt;Some of these improvements require extra software,
(all of it free and open source)
and there’s a &lt;a href=&quot;#software&quot;&gt;section&lt;/a&gt; at the bottom to set that up when needed.&lt;/p&gt;

&lt;h2 id=&quot;1-the-hyper-key--transform-caps-lock-into-escape&quot;&gt;1: The “Hyper” key + transform Caps Lock into Escape&lt;/h2&gt;

&lt;p&gt;You know what would be great?
Having an extra modifier key open for whatever we want.&lt;/p&gt;

&lt;p&gt;We can make use of &lt;a href=&quot;http://stevelosh.com/blog/2012/10/a-modern-space-cadet/#hyper&quot;&gt;Steve Losh’s idea&lt;/a&gt; of emulating the extra “Hyper” key introduced
by the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space-cadet_keyboard&quot;&gt;Space Cadet keyboard&lt;/a&gt; by defining Hyper as control+option+command+shift.
Since no sane application will expect a user to hold all those keys at once,
we can effectively create a new modifier key.&lt;/p&gt;

&lt;p&gt;My primary use for Hyper is machine-global shortcut keys,
especially for window management with &lt;a href=&quot;#2-managing-windows-with-hammerspoon&quot;&gt;Hammerspoon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vim users often remap caps lock to Escape to save their pinky finger some pain. We are going to do that too–but only when tapped. That way, we can user Hyper and Escape on the same key, without them interfering.&lt;/p&gt;

&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First install &lt;a href=&quot;#karabiner-elements&quot;&gt;Karabiner Elements&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, install &lt;a href=&quot;karabiner://karabiner/assets/complex_modifications/import?url=https://msol.io/files/karabiner/hyper.json&quot;&gt;this karabiner configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alternatively, you can download &lt;a href=&quot;https://msol.io/files/karabiner/hyper.json&quot;&gt;this file&lt;/a&gt; and add it by hand to the complex modifications section of
&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/karabiner/karabiner.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that that’s done,
it’s time to set up some shortcuts that use Hyper in order to ease window management.&lt;/p&gt;

&lt;h2 id=&quot;2-managing-windows-with-hammerspoon&quot;&gt;2: Managing windows with Hammerspoon&lt;/h2&gt;

&lt;p&gt;Hammerspoon is a macOS swiss army knife.
I mostly use it for window management, not unlike other tools like Divvy or SizeUp.
It lets you arrange, resize, switch between, and open applications and windows on one or more monitors.&lt;/p&gt;

&lt;p&gt;By setting up a few basic commands in Hammerspoon,
switching between and resizing windows in macOS becomes much faster.
I have a simple set of related commands that are easy to learn and use,
but improve my daily efficiency manyfold.&lt;/p&gt;

&lt;p&gt;First, I have the 10-15 most common applications I use bound to &lt;a href=&quot;#1-the-hyper-key--transform-caps-lock-into-escape&quot;&gt;Hyper&lt;/a&gt; + a single key, such as Hyper+space to open my editor. Next, I have shortcuts for window movement and resizing for the active window: full-screen, two half-screens, four quarter-screens, one to cycle the application between monitors, and one to cycle between instances of the same application.&lt;/p&gt;

&lt;p&gt;Mastering these commands is enough to work extremely efficiently, but I have a few others included that I find useful as well.&lt;/p&gt;

&lt;h3 id=&quot;setup-1&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First &lt;a href=&quot;https://hammerspoon.org&quot;&gt;install Hammerspoon&lt;/a&gt;.
Then open up &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.hammerspoon/init.lua&lt;/code&gt; and add this:&lt;/p&gt;

&lt;noscript&gt;&lt;pre&gt;-- Mike Solomon @msol 2019

local log = hs.logger.new(&amp;#39;main&amp;#39;, &amp;#39;info&amp;#39;)
DEVELOPING_THIS = false -- set to true to ease debugging

HYPER = {&amp;#39;ctrl&amp;#39;, &amp;#39;shift&amp;#39;, &amp;#39;alt&amp;#39;, &amp;#39;cmd&amp;#39;}

-- App bindings
function setUpAppBindings()
  hyperFocusAll(&amp;#39;w&amp;#39;, &amp;#39;React Native Debugger&amp;#39;, &amp;#39;Simulator&amp;#39;, &amp;#39;qemu-system-x86_64&amp;#39;)
  hyperFocusOrOpen(&amp;#39;e&amp;#39;, &amp;#39;Notes&amp;#39;)
  hyperFocus(&amp;#39;i&amp;#39;, &amp;#39;IntelliJ IDEA&amp;#39;, &amp;#39;IntelliJ IDEA-EAP&amp;#39;, &amp;#39;Xcode&amp;#39;, &amp;#39;Android Studio&amp;#39;, &amp;#39;Atom&amp;#39;, &amp;#39;Code&amp;#39;)
  hyperFocusOrOpen(&amp;#39;a&amp;#39;, &amp;#39;Finder&amp;#39;)
  hyperFocusOrOpen(&amp;#39;x&amp;#39;, &amp;#39;Calendar&amp;#39;)
  hyperFocusOrOpen(&amp;#39;m&amp;#39;, &amp;#39;Messages&amp;#39;)
  hyperFocusOrOpen(&amp;#39;r&amp;#39;, &amp;#39;Slack&amp;#39;)
  hyperFocus(&amp;#39;t&amp;#39;, &amp;#39;Safari&amp;#39;)
  hyperFocusOrOpen(&amp;#39;;&amp;#39;, &amp;#39;iTerm2&amp;#39;)
  hyperFocusOrOpen(&amp;#39;s&amp;#39;, &amp;#39;OmniFocus&amp;#39;)
  hyperFocus(&amp;#39;f&amp;#39;, &amp;#39;Google Chrome&amp;#39;, &amp;#39;Firefox&amp;#39;)
  hyperFocusOrOpen(&amp;#39;space&amp;#39;, &amp;#39;Sublime Text&amp;#39;)
end

-- Window management
function setUpWindowManagement()
  hs.window.animationDuration = 0 -- disable animations
  hs.grid.setMargins({0, 0})
  hs.grid.setGrid(&amp;#39;2x2&amp;#39;)

  function mkSetFocus(to)
    return function() hs.grid.set(hs.window.focusedWindow(), to) end
  end

  local fullScreen = hs.geometry(&amp;quot;0,0 2x2&amp;quot;)
  local leftHalf = hs.geometry(&amp;quot;0,0 1x2&amp;quot;)
  local rightHalf = hs.geometry(&amp;quot;1,0 1x2&amp;quot;)
  local upperLeft = hs.geometry(&amp;quot;0,0 1x1&amp;quot;)
  local lowerLeft = hs.geometry(&amp;quot;0,1 1x1&amp;quot;)
  local upperRight = hs.geometry(&amp;quot;1,0 1x1&amp;quot;)
  local lowerRight = hs.geometry(&amp;quot;1,1 1x1&amp;quot;)

  hs.hotkey.bind(HYPER, &amp;#39;l&amp;#39;, mkSetFocus(fullScreen))
  hs.hotkey.bind(HYPER, &amp;#39;h&amp;#39;, mkSetFocus(leftHalf))
  hs.hotkey.bind(HYPER, &amp;quot;&amp;#39;&amp;quot;, mkSetFocus(rightHalf))
  hs.hotkey.bind(HYPER, &amp;quot;y&amp;quot;, mkSetFocus(upperLeft))
  hs.hotkey.bind(HYPER, &amp;quot;b&amp;quot;, mkSetFocus(lowerLeft))
  hs.hotkey.bind(HYPER, &amp;quot;u&amp;quot;, mkSetFocus(upperRight))
  hs.hotkey.bind(HYPER, &amp;quot;n&amp;quot;, mkSetFocus(lowerRight))

  hs.hotkey.bind(HYPER, &amp;quot;up&amp;quot;, hs.window.filter.focusNorth)
  hs.hotkey.bind(HYPER, &amp;quot;down&amp;quot;, hs.window.filter.focusSouth)
  hs.hotkey.bind(HYPER, &amp;quot;left&amp;quot;, hs.window.filter.focusWest)
  hs.hotkey.bind(HYPER, &amp;quot;right&amp;quot;, hs.window.filter.focusEast)
  -- hs.hotkey.bind(HYPER, &amp;quot;v&amp;quot;, hs.window.filter.focusNorth)
  -- hs.hotkey.bind(HYPER, &amp;quot;c&amp;quot;, hs.window.filter.focusSouth)
  -- hs.hotkey.bind(HYPER, &amp;quot;j&amp;quot;, hs.window.filter.focusWest)
  -- hs.hotkey.bind(HYPER, &amp;quot;p&amp;quot;, hs.window.filter.focusEast)
  hs.hotkey.bind(HYPER, &amp;quot;q&amp;quot;, hs.hints.windowHints)
  -- HYPER &amp;quot;d&amp;quot; -- Bound in Karabiner to Cmd+Tab (application switcher)
  -- HYPER &amp;quot;k&amp;quot; -- Bound in Karabiner to Cmd+` (next window of application)

  -- throw to other screen
  hs.hotkey.bind(HYPER, &amp;#39;o&amp;#39;, function()
    local window = hs.window.focusedWindow()
    window:moveToScreen(window:screen():next())
  end)
end

-- focus on the last-focused window of the application given by name, or else launch it
function hyperFocusOrOpen(key, app)
  local focus = mkFocusByPreferredApplicationTitle(true, app)
  function focusOrOpen()
    return (focus() or hs.application.launchOrFocus(app))
  end
  hs.hotkey.bind(HYPER, key, focusOrOpen)
end

-- focus on the last-focused window of the first application given by name
function hyperFocus(key, ...)
  hs.hotkey.bind(HYPER, key, mkFocusByPreferredApplicationTitle(true, ...))
end


-- focus on the last-focused window of every application given by name
function hyperFocusAll(key, ...)
  hs.hotkey.bind(HYPER, key, mkFocusByPreferredApplicationTitle(false, ...))
end


-- creates callback function to select application windows by application name
function mkFocusByPreferredApplicationTitle(stopOnFirst, ...)
  local arguments = {...} -- create table to close over variadic args
  return function()
    local nowFocused = hs.window.focusedWindow()
    local appFound = false
    for _, app in ipairs(arguments) do
      if stopOnFirst and appFound then break end
      log:d(&amp;#39;Searching for app &amp;#39;, app)
      local application = hs.application.get(app)
      if application ~= nil then
        log:d(&amp;#39;Found app&amp;#39;, application)
        local window = application:mainWindow()
        if window ~= nil then
          log:d(&amp;#39;Found main window&amp;#39;, window)
          if window == nowFocused then
            log:d(&amp;#39;Already focused, moving on&amp;#39;, application)
          else
            window:focus()
            appFound = true
          end
        end
      end
    end
    return appFound
  end
end


function maybeEnableDebug()
  if DEVELOPING_THIS then
    log.setLogLevel(&amp;#39;debug&amp;#39;)
    log.d(&amp;#39;Loading in development mode&amp;#39;)
    -- automatically reload changes when we&amp;#39;re developing
    hs.pathwatcher.new(os.getenv(&amp;#39;HOME&amp;#39;) .. &amp;#39;/.hammerspoon/&amp;#39;, hs.reload):start()
    hs.alert(&amp;#39;Hammerspoon config reloaded&amp;#39;)
    log:d(&amp;#39;Hammerspoon config reloaded&amp;#39;)
  end
end

function setUpClipboardTool()
  ClipboardTool = hs.loadSpoon(&amp;#39;ClipboardTool&amp;#39;)
  ClipboardTool.show_in_menubar = false
  ClipboardTool:start()
  ClipboardTool:bindHotkeys({
    toggle_clipboard = {HYPER, &amp;quot;p&amp;quot;}
  })
end

-- Main

maybeEnableDebug()
setUpAppBindings()
setUpWindowManagement()
setUpClipboardTool()
&lt;/pre&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/msolomon/db3ec8c1c7b2620b4ec242f15f042fa0.js&quot;&gt; &lt;/script&gt;

&lt;p&gt;Then run Hammerspoon (or “Reload Config” from the menu bar icon).&lt;/p&gt;

&lt;p&gt;The configuration file is pretty easy to read with a little effort.
If you set up the &lt;a href=&quot;#1-the-hyper-key--transform-caps-lock-into-escape&quot;&gt;Hyper key&lt;/a&gt;,
then this file should just work for you,
and I find these particular settings to be very useful.&lt;/p&gt;

&lt;h2 id=&quot;3-tap-shift-to-move-over-words&quot;&gt;3: Tap shift to move over words&lt;/h2&gt;

&lt;p&gt;I find that moving my cursor word-by-word is very useful even outside my text editor. OS X provides line-editing shortcuts similar to &lt;a href=&quot;https://cnswww.cns.cwru.edu/php/chet/readline/rltop.html&quot;&gt;Readline&lt;/a&gt;/&lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;, for example Control+a to jump to the beginning of a line and Control+e to jump to the end (which I recommend you use). Even better is to do this without a modifier key!&lt;/p&gt;

&lt;p&gt;Inspired by a &lt;a href=&quot;http://stevelosh.com/blog/2012/10/a-modern-space-cadet/#shift-parentheses&quot;&gt;similar idea about parens&lt;/a&gt; from Steve Losh, notice that tapping your shift key normally does nothing, and you rarely do so. Instead, why not tap left-shift to move one word to the left, and tap right-shift to move one word to the right?&lt;/p&gt;

&lt;p&gt;I find this very useful for moving short distances in text of all kinds, and it soon becomes second nature.&lt;/p&gt;

&lt;h3 id=&quot;setup-2&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First install &lt;a href=&quot;#karabiner-elements&quot;&gt;Karabiner Elements&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, install &lt;a href=&quot;karabiner://karabiner/assets/complex_modifications/import?url=https://msol.io/files/karabiner/shift.json&quot;&gt;this karabiner configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alternatively, you can download &lt;a href=&quot;https://msol.io/files/karabiner/shift.json&quot;&gt;this file&lt;/a&gt; and add it by hand to the complex modifications section of
&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/karabiner/karabiner.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;4-right-thumb-control-key&quot;&gt;4: Right-thumb control key&lt;/h2&gt;

&lt;p&gt;How often do you use the right-side command key? Never.&lt;/p&gt;

&lt;p&gt;Instead of wasting that key,
why not turn it into a control key?
Control is very useful if you spend time in the terminal
(&lt;a href=&quot;http://www.iterm2.com/&quot;&gt;iTerm2&lt;/a&gt; is great) so you can do e.g.
control+p to get the previous command,
or control+r to search previous commands.&lt;/p&gt;

&lt;p&gt;You press this key by curling your right thumb.
It may feel unnatural at first,
but before long the relief on your left pinky will be palpable.&lt;/p&gt;

&lt;h3 id=&quot;setup-3&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First install the excellent &lt;a href=&quot;#karabiner-elements&quot;&gt;Karabiner Elements&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;post-image&quot; title=&quot;Right-side control configuration screenshot&quot; alt=&quot;Command-R to Control-L configuration&quot; src=&quot;/img/mac-dev/command-r-to-control-l.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then open it and under “Simple Modifications” change the “From key” &lt;code class=&quot;highlighter-rouge&quot;&gt;right_command&lt;/code&gt; and the “To key” to &lt;code class=&quot;highlighter-rouge&quot;&gt;left_control&lt;/code&gt;. Done!&lt;/p&gt;

&lt;h2 id=&quot;5-ctrlw-deletes-the-previous-word&quot;&gt;5: Ctrl+W deletes the previous word&lt;/h2&gt;

&lt;p&gt;When you make a typing mistake, it is often faster to rewrite the entire last word than to repeatedly press backspace to erase the typo. I find this to be much more efficient overall. Emacs users will already be familiar with the choice of Ctrl+W.&lt;/p&gt;

&lt;h3 id=&quot;setup-4&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First install &lt;a href=&quot;#karabiner-elements&quot;&gt;Karabiner Elements&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, install &lt;a href=&quot;karabiner://karabiner/assets/complex_modifications/import?url=https://msol.io/files/karabiner/ctrlw.json&quot;&gt;this karabiner configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alternatively, you can download &lt;a href=&quot;https://msol.io/files/karabiner/ctrlw.json&quot;&gt;this file&lt;/a&gt; and add it by hand to the complex modifications section of
&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/karabiner/karabiner.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This works especially well with the &lt;a href=&quot;#4-right-thumb-control-key&quot;&gt;right thumb control key&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;6-a-better-shell-with-oh-my-zsh&quot;&gt;6: A better shell with &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh&quot;&gt;oh-my-zsh&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Most people use Bash for their shell.
Assuming you spend some time in the terminal,
you probably already know your way around Bash pretty well.
Sadly, Bash has limited capabilities,
especially for command completion.
Other shells, like &lt;a href=&quot;http://www.zsh.org/&quot;&gt;Zsh&lt;/a&gt;, offer more but can be unfamiliar.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh&quot;&gt;oh-my-zsh&lt;/a&gt;:
a layer of frosting on top of the powerful Z-shell that makes it Bash-compatible
and adds a self-updating system of &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins&quot;&gt;plugins&lt;/a&gt; and &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh/wiki/themes&quot;&gt;themes&lt;/a&gt;.
You will find your autocomplete much improved
as well as available niceties like changing directories by typing directory names without &lt;code class=&quot;highlighter-rouge&quot;&gt;cd&lt;/code&gt;,
and a very nice default prompt.&lt;/p&gt;

&lt;p&gt;Oh-my-zsh also opens the door to more powerful customizations,
and boasts an active community of people who have often already implemented features you wish your shell had.&lt;/p&gt;

&lt;h3 id=&quot;setup-5&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;First install Zsh. If you use &lt;a href=&quot;http://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;—which I hope you do—then simply
&lt;code class=&quot;highlighter-rouge&quot;&gt;brew install zsh&lt;/code&gt;.
You will also need Git (&lt;code class=&quot;highlighter-rouge&quot;&gt;brew install git&lt;/code&gt;).
Then just run the &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh#setup&quot;&gt;curl-to-shell command&lt;/a&gt; from oh-my-zsh’s setup instructions,
open up &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt; and enable a few &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins&quot;&gt;plugins&lt;/a&gt;
by adding them to the &lt;code class=&quot;highlighter-rouge&quot;&gt;plugins=( ... )&lt;/code&gt; array you’ll see in that file,
and you’re done!
Be sure to open a new terminal window to see your changes.&lt;/p&gt;

&lt;h2 id=&quot;7-syntax-highlighting-in-the-terminal&quot;&gt;7: Syntax highlighting in the terminal&lt;/h2&gt;

&lt;p&gt;I love syntax highlighting because it lets me catch errors in my code as I type.
Why not have the same thing for my bash commands in the terminal?&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;post-image&quot; title=&quot;Syntax highlighing on oh-my-zsh&quot; alt=&quot;Terminal syntax highlighting screenshot&quot; src=&quot;/img/mac-dev/terminal-syntax-highlighting.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This syntax highlighting makes valid commands yellow,
invalid commands red, highlights strings, and underlines valid file paths.&lt;/p&gt;

&lt;h3 id=&quot;setup-6&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;This one requires &lt;a href=&quot;#a-better-shell-with-oh-my-zshoh-my-zsh&quot;&gt;oh-my-zsh&lt;/a&gt; as seen in the previous tip.
Syntax highlighting is an oh-my-zsh plugin, but it doesn’t come bundled by default.&lt;/p&gt;

&lt;p&gt;Run the commands&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; ~/.oh-my-zsh/custom/plugins
git clone git://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;to install the plugin.
Then open up &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt; and add &lt;code class=&quot;highlighter-rouge&quot;&gt;zsh-syntax-highlighting&lt;/code&gt; to the end of the &lt;code class=&quot;highlighter-rouge&quot;&gt;plugins=( ... )&lt;/code&gt; array.
Open up a new terminal and enjoy your syntax highlighting!&lt;/p&gt;

&lt;h2 id=&quot;8-faster-key-repeat&quot;&gt;8: Faster key repeat&lt;/h2&gt;

&lt;p&gt;OS X defaults to a very slow key repeat rate,
but it also doesn’t let you lower it enough to please me in System Preferences.
This often comes in handy for moving short distances in text.&lt;/p&gt;

&lt;h3 id=&quot;setup-7&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;You can customize this by running commands in your Terminal.&lt;/p&gt;

&lt;p&gt;Here are the settings I use, that I got from somewhere online (fractional values do not work). You may need to log out and back in to see them applied.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;defaults write &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; InitialKeyRepeat &lt;span class=&quot;nt&quot;&gt;-int&lt;/span&gt; 10 &lt;span class=&quot;c&quot;&gt;# normal minimum is 15 (225 ms)&lt;/span&gt;
defaults write &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; KeyRepeat &lt;span class=&quot;nt&quot;&gt;-int&lt;/span&gt; 1 &lt;span class=&quot;c&quot;&gt;# normal minimum is 2 (30 ms)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;9-file-searching-aliases&quot;&gt;9: File-searching aliases&lt;/h2&gt;

&lt;p&gt;Many terminal users set up aliases to shorten common tasks,
such as &lt;code class=&quot;highlighter-rouge&quot;&gt;alias gc='git commit'&lt;/code&gt; so that the whole command doesn’t need to be typed.
You should of course set up custom aliases,
but there are two in particular that I find useful quite often.&lt;/p&gt;

&lt;p&gt;The first is &lt;code class=&quot;highlighter-rouge&quot;&gt;f&lt;/code&gt;,
which searches the current directory subtree for files with names containing a string (ignoring case).
&lt;code class=&quot;highlighter-rouge&quot;&gt;f png&lt;/code&gt; would find all PNG files in the current subtree,
as well as “PNGisMyFavorite.txt” and so forth.&lt;/p&gt;

&lt;p&gt;The second is &lt;code class=&quot;highlighter-rouge&quot;&gt;r&lt;/code&gt;,
which recursively greps the current directory subtree for files matching a pattern.
&lt;code class=&quot;highlighter-rouge&quot;&gt;r HTTP&lt;/code&gt; would grep for files containing that exact string,
while &lt;code class=&quot;highlighter-rouge&quot;&gt;r '&quot;http[^&quot;]*&quot;' -i&lt;/code&gt; would search for double-quoted strings starting with “http”, ignoring case.&lt;/p&gt;

&lt;h3 id=&quot;setup-8&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;We will actually implement these as Bash (or Zsh) functions or aliases
in &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.bashrc&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;.
Just add these lines anywhere in the file appropriate to your shell:&lt;/p&gt;

&lt;noscript&gt;&lt;pre&gt;400: Invalid request&lt;/pre&gt;&lt;/noscript&gt;
&lt;script src=&quot;https://gist.github.com/9453299.js&quot;&gt; &lt;/script&gt;

&lt;h1 id=&quot;software&quot;&gt;Software&lt;/h1&gt;

&lt;p&gt;Some of these improvements require software. Here are some of the most important.&lt;/p&gt;

&lt;h3 id=&quot;karabiner-elements&quot;&gt;&lt;a href=&quot;https://pqrs.org/osx/karabiner/&quot;&gt;Karabiner Elements&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Karabiner lets you rebind keys,
key combinations, trackpad gestures, set key delays, and more.
It allows customization through a GUI, or a configuration file which lives at &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/karabiner/karabiner.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;hammerspoon&quot;&gt;&lt;a href=&quot;https://hammerspoon.org&quot;&gt;Hammerspoon&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Hammerspoon is a macOS swiss army knife.
I mostly use it for window management, not unlike other tools like Divvy or SizeUp. It lets you arrange, resize, switch between, and open applications and windows on one or more monitors, among many other things.
It can be customized through a Lua file at  &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.hammerspoon/init.lua&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;oh-my-zsh&quot;&gt;&lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh&quot;&gt;Oh My Zsh&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Oh My Zsh makes it easy to get a great shell configuration, making your work in the terminal much better. It is community maintained and has many plugins.&lt;/p&gt;

&lt;h1 id=&quot;more-resources&quot;&gt;More resources&lt;/h1&gt;

&lt;p&gt;I tried to hit high-value improvements that I don’t think most people already have,
but there is a whole world of deeper customization out there.
Here are some links to resources I’ve found useful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jasonrudolph/keyboard&quot;&gt;Toward a more useful keyboard&lt;/a&gt;, Jason Rudolph&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stevelosh.com/blog/2012/10/a-modern-space-cadet/&quot;&gt;A Modern Space Cadet&lt;/a&gt;, Steve Losh&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/holman/dotfiles&quot;&gt;Holman does dotfiles&lt;/a&gt;, Zach Holman&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.hammerspoon.org/docs/index.html&quot;&gt;Hammerspoon documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pqrs.org/osx/karabiner/&quot;&gt;Karabiner Elements&lt;/a&gt; options&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mike Solomon</name></author><summary type="html">Working faster</summary></entry><entry><title type="html">Nietzsche would write clickbait</title><link href="https://msol.io/blog/thoughts/nietzsche-would-write-clickbait/" rel="alternate" type="text/html" title="Nietzsche would write clickbait" /><published>2016-01-08T07:24:05-08:00</published><updated>2016-01-08T07:24:05-08:00</updated><id>https://msol.io/blog/thoughts/nietzsche-would-write-clickbait</id><content type="html" xml:base="https://msol.io/blog/thoughts/nietzsche-would-write-clickbait/">&lt;div&gt;
  &lt;img class=&quot;post-image image-margins side-image&quot; src=&quot;/img/nietzsche.jpg&quot; width=&quot;200px&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;Nietzsche’s style is outrageous.&lt;/p&gt;

&lt;p&gt;He &lt;a href=&quot;https://www.gutenberg.org/files/39955/39955-h/39955-h.html#Sect_103&quot;&gt;denies morality and immorality&lt;/a&gt;. He criticizes popular religions like Christianity and Judaism, popular moral philosophies like utilitarianism, and popular virtues like modesty&lt;sup id=&quot;fnref:immodest&quot;&gt;&lt;a href=&quot;#fn:immodest&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;His language is evocative and exaggerated. As Higgins &amp;amp; Solomon&lt;sup id=&quot;fnref:higgins-solomon&quot;&gt;&lt;a href=&quot;#fn:higgins-solomon&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; point out:&lt;/p&gt;

&lt;blockquote class=&quot;display-table&quot;&gt;
  &lt;p&gt;… it is evident that he was willing to be misunderstood if that was the price of attracting our attention.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and go on to cite Nietzsche’s biblical tone in &lt;em&gt;Thus Spoke Zarathustra&lt;/em&gt; among other things.&lt;/p&gt;

&lt;p&gt;That sounds as close to clickbait as things got in the 19th century. And spreading new ideas, generating ad revenue—what’s the difference, anyway?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:immodest&quot;&gt;

      &lt;p&gt;&lt;a href=&quot;https://www.gutenberg.org/files/4363/4363-h/4363-h.htm&quot;&gt;gutenberg.org/files/4363/4363-h/4363-h.htm&lt;/a&gt;, § 265 &lt;a href=&quot;#fnref:immodest&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:higgins-solomon&quot;&gt;

      &lt;p&gt;p. xxv, Introduction to &lt;em&gt;Thus Spoke Zarathustra&lt;/em&gt;, Barnes &amp;amp; Noble 2005. ISBN 978-1-59308-278-9.&lt;/p&gt;

      &lt;p&gt;I have no relation to Robert C. Solomon. &lt;a href=&quot;#fnref:higgins-solomon&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Mike Solomon</name></author><summary type="html"></summary></entry><entry><title type="html">Dirt-cheap client-encrypted online backups with Raspberry Pi</title><link href="https://msol.io/blog/tech/dirt-cheap-client-encrypted-online-backups-with-raspberry-pi/" rel="alternate" type="text/html" title="Dirt-cheap client-encrypted online backups with Raspberry Pi" /><published>2016-01-07T01:01:51-08:00</published><updated>2016-01-07T01:01:51-08:00</updated><id>https://msol.io/blog/tech/dirt-cheap-client-encrypted-online-backups-with-raspberry-pi</id><content type="html" xml:base="https://msol.io/blog/tech/dirt-cheap-client-encrypted-online-backups-with-raspberry-pi/">&lt;p&gt;To be useful to me, backups must be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Stored in a reliable and offsite location&lt;/li&gt;
  &lt;li&gt;Readable only by me (client-side encryption)&lt;/li&gt;
  &lt;li&gt;Cheap&lt;/li&gt;
  &lt;li&gt;Automatic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can achieve all of these goals with a combination of tools:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://duplicity.nongnu.org/&quot;&gt;Duplicity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cloud.google.com/storage/docs/nearline&quot;&gt;Google Nearline&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.gnupg.org/&quot;&gt;GPG&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Raspberry Pi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been using this setup for about a year, and it costs me about $2 a month for about 100 GB of backups.&lt;/p&gt;

&lt;p&gt;The Raspberry Pi is very useful because it uses very little power and can backup my Network Attached Storage (NAS) automatically over the network.&lt;/p&gt;

&lt;h3 id=&quot;aside-backing-up-a-computer&quot;&gt;Aside: backing up a computer&lt;/h3&gt;

&lt;p&gt;You should strongly consider using &lt;a href=&quot;https://www.backblaze.com&quot;&gt;Backblaze&lt;/a&gt;&lt;sup id=&quot;fnref:bb-no-relation&quot;&gt;&lt;a href=&quot;#fn:bb-no-relation&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. It’s cheap, has no storage limit, and lets you use your own encryption key. It’s going to be the simplest and most reliable bet if you only need to backup the computers you use frequently, instead of your NAS.&lt;/p&gt;

&lt;h2 id=&quot;backing-up-your-nas-or-multiple-computers&quot;&gt;Backing up your NAS, or multiple computers&lt;/h2&gt;

&lt;p&gt;There are five basic steps we need to get backups running:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#set-up-google-nearline&quot;&gt;Set up the storage service: Google Nearline&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mounting-via-ssh&quot;&gt;Set up the connection to the files to be backed up: sshfs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#set-up-gpg-and-your-encryption-keys&quot;&gt;Set up encryption software: GPG&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#set-up-duplicity-and-the-backup-script-on-your-raspberry-pi&quot;&gt;Configure the backup software: Duplicity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#scheduling-backups-with-cron&quot;&gt;Run the backups on a schedule: cron&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;set-up-google-nearline&quot;&gt;Set up Google Nearline&lt;/h3&gt;

&lt;p&gt;Go to the Google &lt;a href=&quot;https://console.developers.google.com/project&quot;&gt;Developer’s Console&lt;/a&gt;, and sign up as necessary. You may need to enter billing information. Create a new project on that page, perhaps called &lt;code class=&quot;highlighter-rouge&quot;&gt;duplicity-backups&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Click on the &lt;code class=&quot;highlighter-rouge&quot;&gt;duplicity-backups&lt;/code&gt; project and click the hamburger menu (three lines) button in the upper left corner and select “Storage” under “Storage.” Press “Create bucket” and choose a name (perhaps “photos”) and select “Nearline” for “Storage class.”&lt;/p&gt;

&lt;p&gt;The last piece needed is access credentials for this storage bucket. Press Settings on the left, then click Interoperability. Create a new key, then copy down the Access Key and Secret shown. We will use these later.&lt;/p&gt;

&lt;h3 id=&quot;mounting-via-ssh&quot;&gt;Mounting via SSH&lt;/h3&gt;

&lt;p&gt;You will need to access the files you wish to backup (likely located on your NAS) over the network. I will assume that they are reachable via SSH. If they are not, you will need to mount them on the filesystem in a similar way (perhaps with NFS).&lt;/p&gt;

&lt;p&gt;Run &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo apt-get install sshfs&lt;/code&gt; to make mounting drives over SSH possible.&lt;/p&gt;

&lt;p&gt;It will be easier to connect to your NAS via SSH if you use passwordless authentication with a public/private key pair. Run &lt;code class=&quot;highlighter-rouge&quot;&gt;ssh-copy-id nasuser@mynashost&lt;/code&gt; to copy it, substituting in your actual NAS information.&lt;/p&gt;

&lt;p&gt;Run &lt;code class=&quot;highlighter-rouge&quot;&gt;mkdir ~/nas&lt;/code&gt; to create a place to mount your NAS directories.&lt;/p&gt;

&lt;p&gt;Add lines like this to &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/fstab&lt;/code&gt; so your Raspberry Pi can treat the remote host as a drive:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;root@mycroft:/my-nas/Photos /home/pi/nas/Photos fuse.sshfs      user,delay_connect,noauto,_netdev,uid&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000,gid&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000,idmap&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;user,allow_other,reconnect 0 0
root@mycroft:/my-nas/write /home/pi/nas/write fuse.sshfs      user,delay_connect,noauto,_netdev,uid&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000,gid&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000,idmap&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;user,allow_other,reconnect 0 0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And test it with &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo mount /home/pi/nas/Photos&lt;/code&gt;, verifying that the files appear in that directory as expected.&lt;/p&gt;

&lt;p&gt;I recommend that your create a second partition directory on your NAS (separate from that which you wish to back up) to store Duplicity’s local cache and log files. Otherwise, you are likely to quickly fill your Raspberry Pi’s local storage. This directory is called “write” in my examples. Run &lt;code class=&quot;highlighter-rouge&quot;&gt;touch /home/pi/nas/write/.useThisWriteDir&lt;/code&gt; after it is mounted to use with the script below.&lt;/p&gt;

&lt;h3 id=&quot;set-up-gpg-and-your-encryption-keys&quot;&gt;Set up GPG and your encryption keys&lt;/h3&gt;

&lt;p&gt;GPG is supported by Duplicity for encryption, and provides a high level of security.&lt;/p&gt;

&lt;p&gt;Run &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo apt-get install gnupg&lt;/code&gt;. Then run &lt;code class=&quot;highlighter-rouge&quot;&gt;gpg --gen-key&lt;/code&gt; and follow the prompts, choosing the defaults. Be sure to choose a long (ideally random) passphrase and to write it down (preferably in a password manager). You won’t be able to read your backups without the generated keys, so be sure to &lt;a href=&quot;/blog/tech/back-up-your-pgp-keys-with-gpg/&quot;&gt;back that up&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;We will need the fingerprint of the newly-generated key to tell Duplicity to use it. Run &lt;code class=&quot;highlighter-rouge&quot;&gt;gpg --fingerprint&lt;/code&gt; to see it. &lt;code class=&quot;highlighter-rouge&quot;&gt;gpg --fingerprint | grep pub | grep -P &quot;(?&amp;lt;=/)\\w{8} &quot;&lt;/code&gt; should highlight the 8-character fingerprint you require.&lt;/p&gt;

&lt;h3 id=&quot;set-up-duplicity-and-the-backup-script-on-your-raspberry-pi&quot;&gt;Set up &lt;a href=&quot;http://duplicity.nongnu.org/&quot;&gt;Duplicity&lt;/a&gt; and the backup script on your Raspberry Pi&lt;/h3&gt;

&lt;p&gt;These instructions assume you use &lt;a href=&quot;https://www.raspbian.org/&quot;&gt;Raspbian&lt;/a&gt;. They should be adaptable for use on other Linux (or Linux-like) systems.&lt;/p&gt;

&lt;p&gt;Install duplicity by running &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo apt-get install duplicity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simple backup script is needed to store credentials and run the backup. I store it along with the files I wish to backup (Photos), but you could do something more secure instead.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# stored in Photos, the directory I wish to back up&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SRC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/pi/nas/Photos
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DEST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gs://&amp;lt;your Google Cloud Storage bucket name&amp;gt;/Photos

&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FTP_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;password&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;your Google access key&amp;gt;
export GS_SECRET_ACCESS_KEY=&quot;&lt;/span&gt;&amp;lt;your Google secret&amp;gt;&lt;span class=&quot;s2&quot;&gt;&quot;

export KEY=&quot;&lt;/span&gt;&amp;lt;your GPG key fingerprint &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;8 characters&lt;span class=&quot;o&quot;&gt;)&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
export PASSPHRASE=&quot;&lt;/span&gt;&amp;lt;your GPG passphrase&amp;gt;&lt;span class=&quot;s2&quot;&gt;&quot;

# Locking is handled by cron. This has helped it restart after problems.
killall duplicity
find /home/pi/nas/write/.cache/duplicity/ | grep lockfile.lock | xargs rm

# make sure we're using the right write dir.
# remote mounting issues can otherwise cause problems
if [ ! -f /home/pi/nas/write/.useThisWriteDir ]; then
  echo &quot;&lt;/span&gt;write directory does not appear to be mounted&lt;span class=&quot;s2&quot;&gt;&quot;
  exit
fi

duplicity &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
  --sign-key &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$KEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
  --encrypt-key &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$KEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
  --log-file /home/pi/nas/write/duplicity.log &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
  --archive-dir /home/pi/nas/write/.cache/duplicity/ &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
  &quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SRC&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DEST&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; 2&amp;gt;&amp;amp;1 &amp;gt;&amp;gt; /home/pi/nas/write/duplicity-foreground.log&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Run the backup script on a small test directory to make sure it’s all set up properly.&lt;/p&gt;

&lt;h3 id=&quot;scheduling-backups-with-cron&quot;&gt;Scheduling backups with cron&lt;/h3&gt;

&lt;p&gt;The last step is to run this backup automatically. Cron can do this for us, and is built-in.&lt;/p&gt;

&lt;p&gt;We will use a &lt;a href=&quot;https://timkay.com/solo/&quot;&gt;simple perl script&lt;/a&gt; to keep things from running multiple times. Download it to &lt;code class=&quot;highlighter-rouge&quot;&gt;/home/pi&lt;/code&gt; and make it executable with &lt;code class=&quot;highlighter-rouge&quot;&gt;chmod u+x /home/pi/solo.pl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code class=&quot;highlighter-rouge&quot;&gt;crontab -e&lt;/code&gt; and add these lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# m h  dom mon dow   command&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-qs&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/home/pi/nas/write'&lt;/span&gt; /proc/mounts &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; /home/pi/solo.pl &lt;span class=&quot;nt&quot;&gt;-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3386 mount /home/pi/nas/write
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; /home/pi/nas/write &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; /home/pi/solo.pl &lt;span class=&quot;nt&quot;&gt;-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3386 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;umount &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; /home/pi/nas/write
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-qs&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/home/pi/nas/Photos'&lt;/span&gt; /proc/mounts &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; /home/pi/solo.pl &lt;span class=&quot;nt&quot;&gt;-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3385 mount /home/pi/nas/Photos
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; /home/pi/nas/Photos &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; /home/pi/solo.pl &lt;span class=&quot;nt&quot;&gt;-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3385 &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;umount &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; /home/pi/nas/Photos
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-qs&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/home/pi/Drive'&lt;/span&gt; /proc/mounts &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; /home/pi/solo.pl &lt;span class=&quot;nt&quot;&gt;-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3384 mount /home/pi/Drive
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; /home/pi/solo.pl &lt;span class=&quot;nt&quot;&gt;-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3383 sh /home/pi/nas/Photos/backup.sh &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;span class=&quot;c&quot;&gt;# optional: automatic reboots and software updates to keep things well-oiled&lt;/span&gt;
0 5 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; ssh mynasuser@mynas &lt;span class=&quot;s1&quot;&gt;'reboot'&lt;/span&gt;
0 5 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/2 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; /sbin/shutdown &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; +1
0 6 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;rpi-update
0 7 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get upgrade &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
0 10 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get autoremove &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get autoclean &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get clean &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will start a new backup as soon as the last completes. This works well for my use case, adjust as necessary.&lt;/p&gt;

&lt;p&gt;Be sure to test your backups to ensure you can restore in a disaster! &lt;code class=&quot;highlighter-rouge&quot;&gt;duplicity verify&lt;/code&gt; may help you here.&lt;/p&gt;

&lt;h2 id=&quot;future-improvements&quot;&gt;Future improvements&lt;/h2&gt;

&lt;p&gt;This could be improved with emails about failed backups, or when backups haven’t run for some time. The overall process could also be simpler. Ideas? Let me know in the comments and I can update the article with them!&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:bb-no-relation&quot;&gt;

      &lt;p&gt;I have no affiliation with Backblaze. &lt;a href=&quot;#fnref:bb-no-relation&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Mike Solomon</name></author><summary type="html">To be useful to me, backups must be:</summary></entry><entry><title type="html">Pro-style testing</title><link href="https://msol.io/blog/tech/pro-style-testing/" rel="alternate" type="text/html" title="Pro-style testing" /><published>2015-10-10T09:47:07-07:00</published><updated>2015-10-10T09:47:07-07:00</updated><id>https://msol.io/blog/tech/pro-style-testing</id><content type="html" xml:base="https://msol.io/blog/tech/pro-style-testing/">&lt;p&gt;If you write software professionally, you probably write automated tests. This is fantastic.&lt;/p&gt;

&lt;p&gt;But have you ever thought about how to leverage the experiences of other software engineers to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Write tests that are maximally likely to prevent bugs&lt;/li&gt;
  &lt;li&gt;Write tests that make locating and fixing the cause of a bug easy&lt;/li&gt;
  &lt;li&gt;Write as few (and as short and readable) tests as possible while achieving the above&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below are general guidelines to build a mental framework of what, how, and why to test in any language. There are also specific and hard-earned recommendations for and against a variety of possible testing strategies. Most recommendations come with links to more reading.&lt;/p&gt;

&lt;p&gt;If you want to test like a pro, read on. If you disagree with a recommendation or would like elaboration, leave a comment or send me an email. There is always room to improve.&lt;/p&gt;

&lt;h1 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#table-of-contents&quot; id=&quot;markdown-toc-table-of-contents&quot;&gt;Table of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#terminology&quot; id=&quot;markdown-toc-terminology&quot;&gt;Terminology&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#test-sizes&quot; id=&quot;markdown-toc-test-sizes&quot;&gt;Test sizes&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#properties-of-tests&quot; id=&quot;markdown-toc-properties-of-tests&quot;&gt;Properties of tests&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#general-principles-to-follow&quot; id=&quot;markdown-toc-general-principles-to-follow&quot;&gt;General principles to follow&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#test-one-behavior-per-test&quot; id=&quot;markdown-toc-test-one-behavior-per-test&quot;&gt;Test one behavior per test&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#test-each-behavior-once&quot; id=&quot;markdown-toc-test-each-behavior-once&quot;&gt;Test each behavior once&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#write-tests-that-provide-value-by-reducing-risk&quot; id=&quot;markdown-toc-write-tests-that-provide-value-by-reducing-risk&quot;&gt;Write tests that provide value by reducing risk&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#name-tests-to-describe-the-behavior-precisely&quot; id=&quot;markdown-toc-name-tests-to-describe-the-behavior-precisely&quot;&gt;Name tests to describe the behavior precisely&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#rework-code-until-it-is-easy-to-test&quot; id=&quot;markdown-toc-rework-code-until-it-is-easy-to-test&quot;&gt;Rework code until it is easy to test&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#watch-it-fail&quot; id=&quot;markdown-toc-watch-it-fail&quot;&gt;Watch it fail&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#tests-should-use-literals-where-possible&quot; id=&quot;markdown-toc-tests-should-use-literals-where-possible&quot;&gt;Tests should use literals where possible&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#leverage-the-type-system&quot; id=&quot;markdown-toc-leverage-the-type-system&quot;&gt;Leverage the type system&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mistakes-to-avoid&quot; id=&quot;markdown-toc-mistakes-to-avoid&quot;&gt;Mistakes to avoid&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#dont-write-change-detector-tests&quot; id=&quot;markdown-toc-dont-write-change-detector-tests&quot;&gt;Don’t write change-detector tests&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#dont-test-code-you-dont-own-in-small-or-medium-tests&quot; id=&quot;markdown-toc-dont-test-code-you-dont-own-in-small-or-medium-tests&quot;&gt;Don’t test code you don’t own in Small or Medium tests&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#red-flags-and-code-smells&quot; id=&quot;markdown-toc-red-flags-and-code-smells&quot;&gt;Red flags and code smells&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#concrete-tips&quot; id=&quot;markdown-toc-concrete-tips&quot;&gt;Concrete tips&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#aaa-test-structure&quot; id=&quot;markdown-toc-aaa-test-structure&quot;&gt;AAA test structure&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#write-the-assertion-first&quot; id=&quot;markdown-toc-write-the-assertion-first&quot;&gt;Write the assertion first&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#write-exactly-one-test-for-each-equivalence-class&quot; id=&quot;markdown-toc-write-exactly-one-test-for-each-equivalence-class&quot;&gt;Write exactly one test for each equivalence class&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#when-testing-state-changes-assert-before-as-well-as-after&quot; id=&quot;markdown-toc-when-testing-state-changes-assert-before-as-well-as-after&quot;&gt;When testing state changes, assert before as well as after&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#only-control-direct-dependencies-not-dependencies-of-dependencies&quot; id=&quot;markdown-toc-only-control-direct-dependencies-not-dependencies-of-dependencies&quot;&gt;Only control direct dependencies, not dependencies of dependencies&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#assert-on-boundaries-for-functions-accepting-a-contiguous-range-of-inputs&quot; id=&quot;markdown-toc-assert-on-boundaries-for-functions-accepting-a-contiguous-range-of-inputs&quot;&gt;Assert on boundaries for functions accepting a contiguous range of inputs&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#more-about-testing&quot; id=&quot;markdown-toc-more-about-testing&quot;&gt;More about testing&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#property-based-tests&quot; id=&quot;markdown-toc-property-based-tests&quot;&gt;Property-based tests&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#large-tests&quot; id=&quot;markdown-toc-large-tests&quot;&gt;Large tests&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#refactoring-tests&quot; id=&quot;markdown-toc-refactoring-tests&quot;&gt;Refactoring tests&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#reading-and-resources&quot; id=&quot;markdown-toc-reading-and-resources&quot;&gt;Reading and resources&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#more-terminology&quot; id=&quot;markdown-toc-more-terminology&quot;&gt;More terminology&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#box-colors&quot; id=&quot;markdown-toc-box-colors&quot;&gt;Box colors&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#subtypes-of-tests&quot; id=&quot;markdown-toc-subtypes-of-tests&quot;&gt;Subtypes of tests&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#good-resources&quot; id=&quot;markdown-toc-good-resources&quot;&gt;Good resources&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#short-but-valuable-reading&quot; id=&quot;markdown-toc-short-but-valuable-reading&quot;&gt;Short but valuable reading&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;terminology&quot;&gt;Terminology&lt;/h1&gt;

&lt;p&gt;Language around automated testing is often ambiguous and overloaded. I will use these terms:&lt;/p&gt;

&lt;h2 id=&quot;test-sizes&quot;&gt;Test sizes&lt;/h2&gt;

&lt;p&gt;Terms like “unit test” and “integration test” can mean different things to different people, so we will use test sizes &lt;a href=&quot;http://googletesting.blogspot.com/2010/12/test-sizes.html&quot;&gt;as defined by Google&lt;/a&gt;, recapped here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Small&lt;/strong&gt;: Usually called unit tests, Small tests are each extremely narrow in scope, run quickly, and test behavior in isolation.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Medium&lt;/strong&gt;: Sometimes called integration tests, Medium tests check interactions between layers and components.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Large&lt;/strong&gt;: Also called end-to-end or system tests, Large tests are very coarse-grained and often touch many components and make use of the network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Related reading:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2010/12/test-sizes.html&quot;&gt;Google definitions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/a/4904533&quot;&gt;from StackOverflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;properties-of-tests&quot;&gt;Properties of tests&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Fidelity&lt;/strong&gt;: A high­-fidelity test is sensitive to defects in the code under test: the test fails when the code is broken.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resilience&lt;/strong&gt;: A resilient test fails &lt;em&gt;only&lt;/em&gt; when the code under test is broken–refactoring won’t break it, and it is not flaky.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Precision&lt;/strong&gt;: A high-precision test tells you where the defect is. Ideally the exact line number and what differed from our expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;general-principles-to-follow&quot;&gt;General principles to follow&lt;/h1&gt;

&lt;h2 id=&quot;test-one-behavior-per-test&quot;&gt;Test one behavior per test&lt;/h2&gt;

&lt;p&gt;Each test should test one behavior. Many of your methods will have one behavior, so verify that behavior and as little else as possible (often nothing!). Asserting runtime invariants is okay, but usually there should be few assertions other than the primary expected behavior.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2013/08/testing-on-toilet-test-behavior-not.html&quot;&gt;Test Behavior, Not Implementation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2014/04/testing-on-toilet-test-behaviors-not.html&quot;&gt;Test Behaviors, Not Methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;test-each-behavior-once&quot;&gt;Test each behavior once&lt;/h2&gt;

&lt;p&gt;Testing the same thing more than once is a maintenance burden. Obviously you should not test the same behavior with two separate tests, but sometimes it is tempting to “cross-test” by adding an extra assertion in a related test. Avoid this, because it decreases the Precision when the test fails, and because it violates “Test one behavior per test.”&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2008/02/in-movie-amadeus-austrian-emperor.html&quot;&gt;Too Many Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;write-tests-that-provide-value-by-reducing-risk&quot;&gt;Write tests that provide value by reducing risk&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A test should reduce risk&lt;/em&gt;, or it is not providing any value.&lt;/p&gt;

&lt;p&gt;One way to check this is to ask “what class of bug could this test detect?” If there is no answer, there should be no test. You can rephrase as “what risk does this test help us avoid?”, and if there is no answer, you need no test.&lt;/p&gt;

&lt;p&gt;It works the other way too: think of what the risks (possible classes of bugs) are, and write the appropriate tests to detect them.&lt;/p&gt;

&lt;p&gt;One case that obviously provides value is a regression test: you’ve encountered a bug before, so it’s important to have a test to prevent it from reappearing in the future.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2014/05/testing-on-toilet-risk-driven-testing.html&quot;&gt;Risk-Driven Testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2007/02/tott-naming-unit-tests-responsibly.html&quot;&gt;Naming Unit Tests Responsibly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;name-tests-to-describe-the-behavior-precisely&quot;&gt;Name tests to describe the behavior precisely&lt;/h2&gt;

&lt;p&gt;Test names appear in test failures and in the code itself. If the names precisely describe the behavior being tested, readers do not need to read the test to understand what cases are covered and which aren’t, and failures become easier to debug and fix.&lt;/p&gt;

&lt;p&gt;Tests are often a good way to learn how an interface works, and clear test names can be useful to demonstrate an interface.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2014/10/testing-on-toilet-writing-descriptive.html&quot;&gt;Writing Descriptive Test Names&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;rework-code-until-it-is-easy-to-test&quot;&gt;Rework code until it is easy to test&lt;/h2&gt;

&lt;p&gt;You must test your code, so your code must be easy to test. If you write your code before your tests without keeping this in mind, you may not notice until you begin writing tests. When this happens, consider reworking your code to be more testable.&lt;/p&gt;

&lt;p&gt;If your test is long, your code may need to change to improve testability.&lt;/p&gt;

&lt;p&gt;These strategies can make your code more testable:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ensure your methods, classes, and modules each have only &lt;a href=&quot;http://programmers.stackexchange.com/a/32614&quot;&gt;one concern&lt;/a&gt;, &lt;a href=&quot;http://blog.codinghorror.com/curlys-law-do-one-thing/&quot;&gt;one job&lt;/a&gt;, one &lt;a href=&quot;http://butunclebob.com/ArticleS.UncleBob.SrpInRuby&quot;&gt;reason to change&lt;/a&gt;. Each should deal with one thin layer only, and rely on other methods/classes/modules to deal with other layers.&lt;/li&gt;
  &lt;li&gt;Reduce the number of dependencies. Are you sure the code under test has only has one reason to change?&lt;/li&gt;
  &lt;li&gt;Use &lt;a href=&quot;http://stackoverflow.com/a/140655&quot;&gt;dependency injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2008/12/static-methods-are-death-to-testability.html&quot;&gt;Avoid static methods&lt;/a&gt; (in Scala, look for companion objects and other objects)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html&quot;&gt;Avoid singletons&lt;/a&gt; (in Scala, look for companion objects and other objects)&lt;/li&gt;
  &lt;li&gt;Wrap unavoidable singletons or static methods (such as those provided by a library) in a simple class that can be injected as a dependency&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://engineering.monsanto.com/2015/07/28/avoiding-mocks/&quot;&gt;Inject small, single purpose methods that encapsulate dependencies&lt;/a&gt;–these are easy to test, and prevent overreliance on mocking&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;watch-it-fail&quot;&gt;Watch it fail&lt;/h2&gt;

&lt;p&gt;It’s tempting to write a test, see that it passes, and move on. But what if you made a mistake in your test? You probably don’t have tests for your test, so instead, break your code in a way the test should detect, &lt;em&gt;and run it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This avoids two classes of bugs:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Your test won’t detect the bug you thought it would (low Fidelity)&lt;/li&gt;
  &lt;li&gt;Your tests aren’t actually being run (it happens)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;tests-should-use-literals-where-possible&quot;&gt;Tests should use literals where possible&lt;/h2&gt;

&lt;p&gt;In production code, deduplication and flexibility are very important. Surprisingly, in tests, it is often better to duplicate and inline simple values and literals to reduce the likelihood of mistakes and to improve the direct readability of the test. Simple immutable objects shared across tests are also acceptable.&lt;/p&gt;

&lt;p&gt;For example, URLs strings should be literal values in tests instead of being constructed as URL objects. This sort of duplication is more readable, simpler, and less error prone. In exchange, it is very inflexible–but this is a better tradeoff in a test.&lt;/p&gt;

&lt;p&gt;See &lt;a href=&quot;http://googletesting.blogspot.com/2014/07/testing-on-toilet-dont-put-logic-in.html&quot;&gt;Don’t put logic in tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This does not apply as much to property checks, which should use generators where possible.&lt;/p&gt;

&lt;h2 id=&quot;leverage-the-type-system&quot;&gt;Leverage the type system&lt;/h2&gt;

&lt;p&gt;Careful use of statically type-checked languages render entire classes of Small tests unnecessary because the type checker can enforce certain guarantees. Availability of static typing features vary by language; make use of those that are available, and consider this when choosing a new language.&lt;/p&gt;

&lt;p&gt;Carefully choose the types of primitives so they enforce as many guarantees as possible. For instance, prefer an unsigned integer over a signed integer when a value cannot be negative; this eliminates the need for one test. This principle applies similarly to objects and other derived types. Consider introducing new types that can only be constructed with guarantees that will later be relied upon, this removes the need for checking these guarantees in the code relying on them. Consider refining interfaces to accept only values maximally verified by the type system.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/programming/comments/3myc9b/insider_oracle_has_lost_interest_in_java/cvjsoua&quot;&gt;A unit tester walks into a bar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;mistakes-to-avoid&quot;&gt;Mistakes to avoid&lt;/h1&gt;

&lt;h2 id=&quot;dont-write-change-detector-tests&quot;&gt;Don’t write change-detector tests&lt;/h2&gt;

&lt;p&gt;One way to test code is to duplicate some of the logic you are trying to test in the test itself, then assert that the results are equal.&lt;/p&gt;

&lt;p&gt;This only detects when your code changes, and cannot catch any bugs apart from “the code changed.” Such a test has low Fidelity and low Resilience. Such a test is a maintenance burden. Rewrite or delete.&lt;/p&gt;

&lt;p&gt;One common form of change-detection is a test that checks each step of the implementation. Test behavior instead.&lt;/p&gt;

&lt;p&gt;A very specific type of test that looks like (but is not) a useless change-detector can provide refactor/optimization safety (yet no value up until then): a test that reimplements the code under test and compares the outputs. This verifies that the underlying behavior has not changed. This type of test is easy to misapply. It is insufficient on its own. Prefer other types of tests when possible, perhaps simple property checks.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2015/01/testing-on-toilet-change-detector-tests.html&quot;&gt;Change-Detector Tests Considered Harmful&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;dont-test-code-you-dont-own-in-small-or-medium-tests&quot;&gt;Don’t test code you don’t own in Small or Medium tests&lt;/h2&gt;

&lt;p&gt;Tests should live in the same project as the code that they test, and should be maintained by the same people. This gives the owners freedom to refactor and make bug fixes as needed, provided their tests still pass. This lets the people best suited to test and maintain test code do so. This reduces your own maintenance burden. Note that it makes sense to test Adapter or other code that wraps a dependency.&lt;/p&gt;

&lt;p&gt;Most dependencies will be services or libraries. If you do not trust a dependency, consider contributing new tests to cover the cases they do not. If you still don’t trust a dependency, consider removing or replacing it. If you cannot contribute to a dependency directly, consider maintaining a patch, or if necessary, consider a fork. If you have a binary or service dependency that you cannot contribute to, eliminate, or trust, consider writing a &lt;strong&gt;separate&lt;/strong&gt; suite of tests to ensure it works as you expect. In &lt;em&gt;no case&lt;/em&gt; should you test an external dependency as a side effect of testing your own code in a Small or Medium test.&lt;/p&gt;

&lt;p&gt;Large tests may implicitly test external dependencies; this is to be expected. Even so, they should not &lt;em&gt;explicitly&lt;/em&gt; test external dependencies beyond, say, setting up connections.&lt;/p&gt;

&lt;h1 id=&quot;red-flags-and-code-smells&quot;&gt;Red flags and code smells&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;Long tests. Tests should generally be short and easy to follow. Arrange, act, assert (see AAA below)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2008/08/tott-sleeping-synchronization.html&quot;&gt;Sleeping&lt;/a&gt; (Thread.sleep, Future.sleep, sleep(), etc.). There are very few places this is actually what you want.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2013/05/testing-on-toilet-dont-overuse-mocks.html&quot;&gt;Many mocks&lt;/a&gt; (specifically mocking, not other test doubles). You may be testing the implementation too closely. The code under test may have too many dependencies, and it may have more than one concern.&lt;/li&gt;
  &lt;li&gt;The test &lt;a href=&quot;http://googletesting.blogspot.com/2014/07/testing-on-toilet-dont-put-logic-in.html&quot;&gt;generates&lt;/a&gt; nontrivial data. There may be bugs in the data generation code. Consider separating it out and testing it. Consider using a property check, which can help make this reusable. Consider breaking the code under test into multiple methods which can be tested on simpler data.&lt;/li&gt;
  &lt;li&gt;Tests with logic that also appears in the code under test. Is this a change-detector test?&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;concrete-tips&quot;&gt;Concrete tips&lt;/h1&gt;

&lt;p&gt;Hat-tip to &lt;a href=&quot;https://twitter.com/greenberg&quot;&gt;Ryan Greenberg&lt;/a&gt;, from whom I stole most of this section.&lt;/p&gt;

&lt;h2 id=&quot;aaa-test-structure&quot;&gt;AAA test structure&lt;/h2&gt;

&lt;p&gt;Many tests are easy to read if they are in the form: Arrange, Act, Assert. First Arrange the required objects, perform the Act you want to test, then Assert the results are as expected.&lt;/p&gt;

&lt;h2 id=&quot;write-the-assertion-first&quot;&gt;Write the assertion first&lt;/h2&gt;

&lt;p&gt;Think of test cases in terms of properties that must be true, then assert them. It may be easier to think of the assertion first, then write code to arrange objects and act on them.&lt;/p&gt;

&lt;h2 id=&quot;write-exactly-one-test-for-each-equivalence-class&quot;&gt;Write exactly one test for each equivalence class&lt;/h2&gt;

&lt;p&gt;For example, if the code is intended to work the same on any number of items in a sequence, you don’t need a test for 2 items, 3 items, and 4 items.&lt;/p&gt;

&lt;h2 id=&quot;when-testing-state-changes-assert-before-as-well-as-after&quot;&gt;When testing state changes, assert before as well as after&lt;/h2&gt;

&lt;p&gt;For example, if a method should increment a counter, assert that the counter value starts at what you expect before calling the method. This avoids certain bugs in tests.&lt;/p&gt;

&lt;h2 id=&quot;only-control-direct-dependencies-not-dependencies-of-dependencies&quot;&gt;Only control direct dependencies, not dependencies of dependencies&lt;/h2&gt;

&lt;p&gt;Only set up and rely on direct dependencies of what you are testing (possibly using a &lt;a href=&quot;http://googletesting.blogspot.com/2013/07/testing-on-toilet-know-your-test-doubles.html&quot;&gt;test double&lt;/a&gt; such as a stub, mock or fake), never dependencies of dependencies.&lt;/p&gt;

&lt;p&gt;For example, imagine:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We have a request handler &lt;code class=&quot;highlighter-rouge&quot;&gt;logValidRequests&lt;/code&gt; that validates a request &lt;code class=&quot;highlighter-rouge&quot;&gt;req&lt;/code&gt; by calling &lt;code class=&quot;highlighter-rouge&quot;&gt;validate(req)&lt;/code&gt; and then logs &lt;code class=&quot;highlighter-rouge&quot;&gt;req&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;logValidRequests&lt;/code&gt; won’t log &lt;code class=&quot;highlighter-rouge&quot;&gt;req&lt;/code&gt; when &lt;code class=&quot;highlighter-rouge&quot;&gt;validate&lt;/code&gt; returns &lt;code class=&quot;highlighter-rouge&quot;&gt;false&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;One way &lt;code class=&quot;highlighter-rouge&quot;&gt;req&lt;/code&gt; can be invalid is if it is all lowercase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should &lt;em&gt;not&lt;/em&gt; write your test by calling &lt;code class=&quot;highlighter-rouge&quot;&gt;logValidRequests&lt;/code&gt; with an all-lowercase &lt;code class=&quot;highlighter-rouge&quot;&gt;req&lt;/code&gt;. Instead, stub &lt;code class=&quot;highlighter-rouge&quot;&gt;validate&lt;/code&gt; to return &lt;code class=&quot;highlighter-rouge&quot;&gt;false&lt;/code&gt;, then assert that nothing is logged (and don’t forget other test cases!). This improves Resilience and Precision.&lt;/p&gt;

&lt;h2 id=&quot;assert-on-boundaries-for-functions-accepting-a-contiguous-range-of-inputs&quot;&gt;Assert on boundaries for functions accepting a contiguous range of inputs&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;def isBig(num: Long) = num &amp;gt; 100&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should test 100, but also 99 because it is at the boundary of the change. Even better, write a property test. Remember to test each equivalence class exactly once.&lt;/p&gt;

&lt;h1 id=&quot;more-about-testing&quot;&gt;More about testing&lt;/h1&gt;

&lt;h2 id=&quot;property-based-tests&quot;&gt;Property-based tests&lt;/h2&gt;

&lt;p&gt;Property-based tests (also called property checks) are a different way to think about testing. The basic idea is to assert that some law holds about the code under test, and then let the test framework generate test cases in an attempt to disprove the law. When it does so, it will try to find a minimal failing case to help you find your bug.&lt;/p&gt;

&lt;p&gt;It is worth writing property checks if you can, despite the initial learning curve. They allow you to declare laws and let the computer worry about coming up with cases that are likely to fail. They encourage writing reusable Generators that improve readability and reuse.&lt;/p&gt;

&lt;p&gt;Property-based tests are most useful in unit tests.&lt;/p&gt;

&lt;p&gt;In Scala there is &lt;a href=&quot;https://www.scalacheck.org/&quot;&gt;ScalaCheck&lt;/a&gt; and more can be found online.&lt;/p&gt;

&lt;h2 id=&quot;large-tests&quot;&gt;Large tests&lt;/h2&gt;

&lt;p&gt;Large tests are your last line of defense before production (or “reported by users”). Not all tests are equally useful at this level.&lt;/p&gt;

&lt;p&gt;Test “happy path” behavior. This makes sure that the system works end-to-end in the real environment. Depending on your setup, you may be able to run this in a staging environment as well as the production environment.&lt;/p&gt;

&lt;p&gt;Test for regressions in known high-level bugs. If you can write a Small or Medium test for this, prefer that instead. However, make sure each regression gets a test, and sometimes this means a Large test.&lt;/p&gt;

&lt;p&gt;Don’t attempt to test every way in which your system can fail. For example, if you have a suite of validations that are already tested in Small tests, do not repeat every test at the Large (or Medium) level. Instead, test one representative validation to ensure that the validations are wired in. Even better, test at the Medium level.&lt;/p&gt;

&lt;h2 id=&quot;refactoring-tests&quot;&gt;Refactoring tests&lt;/h2&gt;

&lt;p&gt;It can be hard to refactor your tests, because unlike your production code, you don’t have tests (for your tests).&lt;/p&gt;

&lt;p&gt;One good strategy is to refactor your test code after manually (and temporarily) breaking the production code. This gives you some confidence that your tests fail when they ought to fail (showing their level of Fidelity).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2007/04/tott-refactoring-tests-in-red.html&quot;&gt;Refactoring tests in the red&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;reading-and-resources&quot;&gt;Reading and resources&lt;/h1&gt;

&lt;h2 id=&quot;more-terminology&quot;&gt;More terminology&lt;/h2&gt;

&lt;h3 id=&quot;box-colors&quot;&gt;Box colors&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Black box&lt;/strong&gt;: Knows nothing of internals–testing the interface’s contract, not implementation&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;White box&lt;/strong&gt;: Testing the internals–testing the implementation, not the interface&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Grey box&lt;/strong&gt;: Testing interface’s contract as in black box, but sets up state beforehand with knowledge of internals&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;subtypes-of-tests&quot;&gt;Subtypes of tests&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Regression&lt;/strong&gt;: did a bug we fixed reappear?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: how fast is the code? is it fast enough?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Security/Privacy&lt;/strong&gt;: will this leak data or allow unwanted access?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Code quality&lt;/strong&gt;: does the code meet standards we can automatically (statically) measure?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Acceptance&lt;/strong&gt;: Does it conform to specifications?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stress&lt;/strong&gt;: How does it handle being put under increasing loads, up to failure?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;good-resources&quot;&gt;Good resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Google’s &lt;a href=&quot;http://googletesting.blogspot.com/&quot;&gt;Testing Blog&lt;/a&gt;, including &lt;a href=&quot;http://googletesting.blogspot.com/search/label/TotT&quot;&gt;Testing on the Toilet&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Clean Code&lt;/em&gt; by Robert C. Martin&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://misko.hevery.com/code-reviewers-guide/&quot;&gt;Writing testable code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;short-but-valuable-reading&quot;&gt;Short but valuable reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2014/03/testing-on-toilet-what-makes-good-test.html&quot;&gt;What makes a good test?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2013/08/testing-on-toilet-test-behavior-not.html&quot;&gt;Test behavior, not implementation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2014/05/testing-on-toilet-risk-driven-testing.html&quot;&gt;Risk-driven testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2014/05/testing-on-toilet-effective-testing.html&quot;&gt;Effective testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2013/07/testing-on-toilet-know-your-test-doubles.html&quot;&gt;Know your test doubles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://googletesting.blogspot.com/2013/03/testing-on-toilet-testing-state-vs.html&quot;&gt;Testing state vs. testing interactions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mike Solomon</name></author><summary type="html">If you write software professionally, you probably write automated tests. This is fantastic.</summary></entry><entry><title type="html">You’re probably wrong about caching</title><link href="https://msol.io/blog/tech/youre-probably-wrong-about-caching/" rel="alternate" type="text/html" title="You're probably wrong about caching" /><published>2015-09-05T05:22:40-07:00</published><updated>2015-09-05T05:22:40-07:00</updated><id>https://msol.io/blog/tech/youre-probably-wrong-about-caching</id><content type="html" xml:base="https://msol.io/blog/tech/youre-probably-wrong-about-caching/">&lt;blockquote&gt;
  &lt;p&gt;There are only two hard things in Computer Science: cache invalidation and naming things –Phil Karlton&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Caching is a great tool. Lots of useful data fits easily in memory–so cache it! Improve your latencies, ease the load on your database, reduce your hardware costs! Can you spell free lunch?&lt;/p&gt;

&lt;p&gt;Many of the costs of caching aren’t paid up-front. This makes caching seem very attractive–and to be clear, there are many situations where caching your data is a great option–but if you’re just looking to pick up some “quick wins,” caching is a bad place to start.&lt;/p&gt;

&lt;p&gt;I believe we, as software developers, have a very strong tendency to underestimate the complexities and issues caching brings along with it, &lt;em&gt;especially&lt;/em&gt; when we look at oh-so-seductive early results of exploratory caching atop our data sources. In turn, I believe we often cache before the benefits actually outweigh the costs.&lt;/p&gt;

&lt;h2 id=&quot;fine-why-do-you-think-caching-is-so-hard&quot;&gt;Fine, why do you think caching is so hard?&lt;/h2&gt;

&lt;p&gt;I’m glad you asked.&lt;/p&gt;

&lt;h3 id=&quot;reasoning-about-cached-data-is-harder&quot;&gt;Reasoning about cached data is harder&lt;/h3&gt;

&lt;p&gt;Caching fundamentally means that you no longer read from your source of truth. Whenever you see something unexpected in your data (say, while debugging during an incident), you now must ask “does this data match our source of truth?” Every read or write to a piece of cached data is subject to mismatch with the source of truth and this must often be taken into account when tracking down issues.&lt;/p&gt;

&lt;h3 id=&quot;a-new-class-of-perspective-bugs-are-possible-with-cached-data&quot;&gt;A new class of perspective bugs are possible with cached data&lt;/h3&gt;

&lt;p&gt;Not all data appears in the same way to all users. For example, a list of “Best Articles” on a news site might depend on which user is logged in. A classic caching mistake is caching these perspective-dependent values and serving them to users who should have a different perspective. This is avoidable enough once, but is easy to mistakenly introduce later on. This can lead to serious privacy or even security issues.&lt;/p&gt;

&lt;h3 id=&quot;reproducing-behavior-involving-caching-is-harder&quot;&gt;Reproducing behavior involving caching is harder&lt;/h3&gt;

&lt;p&gt;When you introduce caching, you also introduce a new layer in which behavior can differ from your expectations. New race conditions are possible where they weren’t before: items can expire from the cache when you don’t expect them to, which objects are cached depends on access patterns that can vary by time of day or other factors. This means that issues can appear, but it is not obvious how to reproduce them to assist in fixing them.&lt;/p&gt;

&lt;h3 id=&quot;access-pattern-changes-can-subtly-lower-cache-hit-rates-which-damages-performance&quot;&gt;Access pattern changes can subtly lower cache hit rates which damages performance&lt;/h3&gt;

&lt;p&gt;When access patterns change, so can performance. A special case of this can occur when data is fronted by a cache and access patterns change. As cache misses increase, latency increases and throughput can drop. However, traffic levels may stay the same, masking the cause, and potentially overloading the underlying data source. Like any issue, this can be dealt with, but it makes dealing with certain incidents more difficult.&lt;/p&gt;

&lt;h3 id=&quot;in-process-caching-in-garbage-collected-languages-increases-gc-pressure&quot;&gt;In-process caching in garbage-collected languages increases GC pressure&lt;sup id=&quot;fnref:gc-pressure&quot;&gt;&lt;a href=&quot;#fn:gc-pressure&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/h3&gt;

&lt;p&gt;This only applies to certain scenarios, like in-process caches on the JVM, but it serves as another example of how caching can introduce unexpected issues. In this case, large numbers of long-lived cached objects can get promoted into older generations of the garbage collector and increase both the run time of individual collections and the frequency at which they must happen.&lt;/p&gt;

&lt;h3 id=&quot;recovering-from-a-failed-cache-is-hard&quot;&gt;Recovering from a failed cache is hard&lt;/h3&gt;

&lt;p&gt;Caches can let you scale up your serving capacity past what your underlying data source could serve alone, which is one reason to cache. Unfortunately, when your cache machines go down (or are unreachable on the network, or unresponsive, or…) you cannot simply bring them back online, as all data stored in memory will already be lost. You must warm your caches by reading from the underlying store while still trying to serve production traffic. Often your only choice will be to deny all but a fraction of traffic, slowly ramping up the amount you serve as your caches warm.&lt;/p&gt;

&lt;h2 id=&quot;is-it-worth-it&quot;&gt;Is it worth it?&lt;/h2&gt;

&lt;p&gt;It depends; the tradeoffs are yours to choose between.&lt;/p&gt;

&lt;p&gt;But before you choose, consider that many of the downsides won’t manifest themselves right away. Don’t forget how easy it is to ignore these downsides and focus only on the benefits–the payoff is immediate, but the costs must be paid constantly throughout the cache’s lifetime.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:gc-pressure&quot;&gt;

      &lt;p&gt;Garbage collector (GC) pressure happens when your application (running on the JVM, CLR, V8, and other garbage-collected runtimes) allocates then releases memory for many objects. The runtime must occasionally collect this garbage by walking through your memory graph to determine which objects are still needed, and which can be freed.&lt;/p&gt;

      &lt;p&gt;When your application produces and discards many objects in a short amount of time–say, from an in-memory cache–the garbage collector needs to interrupt your running code and collect dead objects more frequently, and each collection can take longer. &lt;a href=&quot;#fnref:gc-pressure&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Mike Solomon</name></author><summary type="html">There are only two hard things in Computer Science: cache invalidation and naming things –Phil Karlton</summary></entry><entry><title type="html">Ruby on Rails: Don’t delete, tombstone</title><link href="https://msol.io/blog/tech/ruby-on-rails-dont-delete-tombstone/" rel="alternate" type="text/html" title="Ruby on Rails: Don't delete, tombstone" /><published>2015-08-18T11:32:38-07:00</published><updated>2015-08-18T11:32:38-07:00</updated><id>https://msol.io/blog/tech/ruby-on-rails-dont-delete-tombstone</id><content type="html" xml:base="https://msol.io/blog/tech/ruby-on-rails-dont-delete-tombstone/">&lt;p&gt;In many web apps, things need to be deleted (no way!). But actually deleting records from your database has some side effects that aren’t immediately obvious:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Undelete isn’t possible without additional work&lt;/li&gt;
  &lt;li&gt;In small, simple apps, you may not have any other record of deleted rows&lt;/li&gt;
  &lt;li&gt;You may be able to improve your product based on what has been deleted&lt;/li&gt;
  &lt;li&gt;Extra work is required to “reactivate” deleted users or other models with their former data attached&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One easy way to deal with this is with &lt;strong&gt;tombstones&lt;/strong&gt;. A tombstone is simply a column in a table that marks whether a given record has been deleted.&lt;/p&gt;

&lt;p&gt;This means that nearly all queries will need to filter based on this column to make sure it isn’t reading “deleted” (tombstoned) data. How annoying!&lt;/p&gt;

&lt;p&gt;Fortunately, in Rails it is easy to separate out this concern. We can define a mixin called &lt;code class=&quot;highlighter-rouge&quot;&gt;tombstoneable&lt;/code&gt; such that any ActiveRecord model that mixes it in will automatically filter out deleted records by default, and add some easy methods to query for tombstoned records as well.&lt;/p&gt;

&lt;h2 id=&quot;make-it-work&quot;&gt;Make it work&lt;/h2&gt;

&lt;p&gt;Create a new file &lt;code class=&quot;highlighter-rouge&quot;&gt;lib/mixins/tombstoneable.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Mixins::Tombstoneable&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;included&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;default_scope&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;deleted: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:include_deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unscope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;where: :deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;include_deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;deleted: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;destroy&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;update_attribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;destroy&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;undelete&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assign_attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;deleted: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;undelete!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;update_attribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nice! The magic is in the &lt;code class=&quot;highlighter-rouge&quot;&gt;included&lt;/code&gt; block. We augment the default scope to always look for records that have not been deleted. If we wish to include deleted records as well, we can use the &lt;code class=&quot;highlighter-rouge&quot;&gt;include_deleted&lt;/code&gt; scope:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include_deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Or if we wish to only select deleted (tombstoned) edges, we can do that too:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;now-mix-it-in&quot;&gt;Now mix it in&lt;/h2&gt;

&lt;p&gt;We never finished actually mixing this in to a model. Let’s assume we have a model called User. Only one line is needed to mix in the tombstoning behavior:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Mixins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Tombstoneable&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;and-add-the-column&quot;&gt;And add the column&lt;/h2&gt;

&lt;p&gt;We also need to make sure the database has a column to track which records have been deleted. This will be necessary for each model we wish to make tombstonable. From the command line:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rails g migration AddDeletedToUser deleted:boolean&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then modify the generated file to add a default and make the column &lt;code class=&quot;highlighter-rouge&quot;&gt;NOT NULL&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddDeletedToUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And run the migration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rake db:migrate&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;thats-it&quot;&gt;That’s it!&lt;/h2&gt;

&lt;p&gt;I have found this to be a useful practice. Let me know how your experiences go (or have gone previously) in the comments. Enjoy your not-quite-deleted records!&lt;/p&gt;</content><author><name>Mike Solomon</name></author><summary type="html">In many web apps, things need to be deleted (no way!). But actually deleting records from your database has some side effects that aren’t immediately obvious:</summary></entry><entry><title type="html">How I doubled my Internet speed with OpenWRT</title><link href="https://msol.io/blog/tech/how-i-doubled-my-internet-speed-with-openwrt/" rel="alternate" type="text/html" title="How I doubled my Internet speed with OpenWRT" /><published>2015-03-10T15:09:10-07:00</published><updated>2015-03-10T15:09:10-07:00</updated><id>https://msol.io/blog/tech/how-i-doubled-my-internet-speed-with-openwrt</id><content type="html" xml:base="https://msol.io/blog/tech/how-i-doubled-my-internet-speed-with-openwrt/">&lt;p&gt;&lt;a href=&quot;https://openwrt.org/&quot;&gt;OpenWRT&lt;/a&gt; is a powerful Linux distribution for embedded devices, such as &lt;a href=&quot;https://wiki.openwrt.org/toh/buffalo/wzr-hp-ag300h/&quot;&gt;my router&lt;/a&gt;, and this is the story of how I used it to double my bandwidth at no extra cost to myself.&lt;/p&gt;

&lt;p&gt;How? By doubling the number of Internet connections I have.&lt;/p&gt;

&lt;h2 id=&quot;my-setup&quot;&gt;My setup&lt;/h2&gt;

&lt;h3 id=&quot;my-internet&quot;&gt;My internet&lt;/h3&gt;

&lt;p&gt;My internet is through Comcast (unfortunately).&lt;/p&gt;

&lt;p&gt;Comcast has an initiative called Xfinity WiFi.
When you rent a cable modem/router combo from Comcast (as one of my nearby neighbors apparently does), in addition to broadcasting your own WiFi network, it is kind enough to also broadcast “xfinitywifi,” a second “hotspot” network metered separately from your own.&lt;/p&gt;

&lt;p&gt;This hotspot allows Comcast customers to connect with their credentials.&lt;/p&gt;

&lt;h3 id=&quot;my-router&quot;&gt;My router&lt;/h3&gt;

&lt;p&gt;My router is a &lt;a href=&quot;https://wiki.openwrt.org/toh/buffalo/wzr-hp-ag300h/&quot;&gt;Buffalo WZR-HP-AG300H&lt;/a&gt;.
Crucially, this router 1) supports OpenWRT and 2) has two independent radios.
I use one of them for my home WiFi network.&lt;/p&gt;

&lt;h3 id=&quot;my-idea&quot;&gt;My idea&lt;/h3&gt;

&lt;p&gt;By now, you’ve probably put two and two together.&lt;/p&gt;

&lt;p&gt;I use my router’s extra radio to connect to the xfinitywifi hotspot, then load balance my outbound traffic across the connection I pay for and the bonus xfinitywifi connection.&lt;/p&gt;

&lt;p&gt;Obviously this is a pretty specific scenario, but if you have:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A hotspot you have credentials for within range&lt;/li&gt;
  &lt;li&gt;A router that supports both OpenWRT&lt;/li&gt;
  &lt;li&gt;That same router has a spare radio&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;how-to-set-this-up&quot;&gt;How to set this up&lt;/h2&gt;

&lt;h3 id=&quot;1-install-openwrt&quot;&gt;1. Install OpenWRT&lt;/h3&gt;

&lt;p&gt;Find your router on OpenWRT’s &lt;a href=&quot;https://wiki.openwrt.org/toh/start&quot;&gt;table of hardware&lt;/a&gt; and follow the instructions to install it, getting your WiFi and network set up as usual.&lt;/p&gt;

&lt;h3 id=&quot;2-install-multi-wan-software-in-openwrt&quot;&gt;2. Install multi-wan software in OpenWRT&lt;/h3&gt;

&lt;p&gt;Open your router’s web interface and navigate to &lt;code class=&quot;highlighter-rouge&quot;&gt;/cgi-bin/luci/admin/system/packages&lt;/code&gt; and install &lt;code class=&quot;highlighter-rouge&quot;&gt;luci-app-mwan3&lt;/code&gt;.
This (along with its dependencies) allows you to support multiple internet connections with round-robin load balancing between them (with connection pinning for HTTPS).&lt;/p&gt;

&lt;h3 id=&quot;3-authenticate-a-mac-address-with-xfinitywifi&quot;&gt;3. Authenticate a MAC address with xfinitywifi&lt;/h3&gt;

&lt;p&gt;The xfinitywifi hotspot requires authentication, not via WPA2 or other normal network security, but with a Comcast login.
It remembers this login by way of your MAC address.
Unfortunately, it is not very easy to authenticate directly through the router, so instead we will authenticate a MAC address through a computer, then switch the apparent MAC address the router uses.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Generate a fake MAC address. Here’s one: &lt;code class=&quot;highlighter-rouge&quot;&gt;02:67:1c:16:1f:21&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.google.com/webhp?q=how%20to%20spoof%20mac%20address#q=how+to+spoof+mac+address&amp;amp;qscrl=1&quot;&gt;Spoof your MAC address&lt;/a&gt; (for your wireless adapter) on your computer.
Be sure to find out how to do it on your Linux/Mac/Windows system. Remember to record your old MAC address.&lt;/li&gt;
  &lt;li&gt;With your MAC address spoofed, connect to xfinitywifi and enter your Comcast credentials&lt;/li&gt;
  &lt;li&gt;Disconnect from xfinitywifi and restore your original MAC address&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;4-connect-the-router-to-xfinitywifi&quot;&gt;4. Connect the router to xfinitywifi&lt;/h3&gt;

&lt;p&gt;In your OpenWRT web (LuCI) interface at &lt;code class=&quot;highlighter-rouge&quot;&gt;/cgi-bin/luci/admin/network/wireless&lt;/code&gt;, press Scan on your available radio, and select Join Network for &lt;code class=&quot;highlighter-rouge&quot;&gt;xfinitywifi&lt;/code&gt;.
Name it &lt;code class=&quot;highlighter-rouge&quot;&gt;wan2&lt;/code&gt; and add it to the &lt;code class=&quot;highlighter-rouge&quot;&gt;wan&lt;/code&gt; firewall group.
Save &amp;amp; Apply your settings.&lt;/p&gt;

&lt;p&gt;Now, go to &lt;code class=&quot;highlighter-rouge&quot;&gt;/cgi-bin/luci/admin/network/network/wan2&lt;/code&gt; and go to the Advanced Settings tab.
Paste your fake and authenticated MAC address into the “Override MAC address” field.
Save &amp;amp; Apply your settings.&lt;/p&gt;

&lt;h3 id=&quot;5-prepare-mwan3-for-a-wireless-wan&quot;&gt;5. Prepare mwan3 for a wireless WAN&lt;/h3&gt;

&lt;p&gt;In your OpenWRT web (LuCI) interface at &lt;code class=&quot;highlighter-rouge&quot;&gt;cgi-bin/luci/admin/network/network/wan/&lt;/code&gt;, click the Advanced Settings tab and enter 10 under Use gateway metric and Save your settings.&lt;/p&gt;

&lt;p&gt;At &lt;code class=&quot;highlighter-rouge&quot;&gt;cgi-bin/luci/admin/network/network/wan2/&lt;/code&gt;, click the Advanced Settings tab and enter 20 under Use gateway metric and Save your settings.&lt;/p&gt;

&lt;p&gt;In your OpenWRT web (LuCI) interface at &lt;code class=&quot;highlighter-rouge&quot;&gt;/cgi-bin/luci/admin/network/mwan/advanced/networkconfig&lt;/code&gt;, you will see your network config file.
Paste this section at the bottom, adjusting as necessary with settings from your xfinitywifi connection:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;route&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'default_wan2'&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'wan2'&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'0.0.0.0'&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;netmask&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'0.0.0.0'&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'192.168.1.1'&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metric&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'20'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Normally this last step is not necessary, but for some reason mwan3 seems to need it to work with wireless networks.&lt;/p&gt;

&lt;p&gt;Submit your changes.&lt;/p&gt;

&lt;h2 id=&quot;check-it&quot;&gt;Check it!&lt;/h2&gt;

&lt;p&gt;Go to &lt;code class=&quot;highlighter-rouge&quot;&gt;cgi-bin/luci/admin/network/mwan&lt;/code&gt; and you should see both networks green!&lt;/p&gt;

&lt;p&gt;At least you will if you’re the luckiest person ever.
More likely you’ll run into problems, check out the &lt;a href=&quot;https://wiki.openwrt.org/doc/howto/mwan3&quot;&gt;mwan docs&lt;/a&gt; and Google around.&lt;/p&gt;

&lt;p&gt;Another good test is to a website that &lt;a href=&quot;https://duckduckgo.com/?q=what+is+my+ip&quot;&gt;tells you your IP&lt;/a&gt; and refresh several times and ensure you see two different IP addresses.&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;</content><author><name>Mike Solomon</name></author><summary type="html">OpenWRT is a powerful Linux distribution for embedded devices, such as my router, and this is the story of how I used it to double my bandwidth at no extra cost to myself.</summary></entry><entry><title type="html">Keybearer: Decrypt a secret using M of N keys</title><link href="https://msol.io/blog/tech/keybearer-decrypt-a-secret-using-m-of-n-keys/" rel="alternate" type="text/html" title="Keybearer: Decrypt a secret using M of N keys" /><published>2015-03-07T09:02:56-08:00</published><updated>2015-03-07T09:02:56-08:00</updated><id>https://msol.io/blog/tech/keybearer-decrypt-a-secret-using-m-of-n-keys</id><content type="html" xml:base="https://msol.io/blog/tech/keybearer-decrypt-a-secret-using-m-of-n-keys/">&lt;p&gt;Keybearer uses several independent passwords to encrypt a file and later requires a subset of those passwords to decrypt it.&lt;/p&gt;

&lt;p&gt;All operation are performed in client-side Javascript, so you can &lt;a href=&quot;/keybearer/&quot;&gt;try it live right now&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;example&quot;&gt;Example&lt;/h2&gt;

&lt;p&gt;For example, Magician Mike uses Keybearer to encrypt the password to his laptop containing his secret repertoire of tricks. He gives the 3 passcodes he used to his estranged siblings Alice, Bob, and Charlie, on the condition that at least 2 of them reunite on his death to gaze on the majesty of his secrets.&lt;/p&gt;

&lt;p&gt;After Magician Mike is tragically sawed in half by his careless assistant, Alice and Bob meet to decrypt Mike’s files using their passcodes. They are reunited through their Keybearer experience, while Charlie maintains his grudge and burns his passcode with fire.&lt;/p&gt;

&lt;h2 id=&quot;more-on-github&quot;&gt;More on GitHub&lt;/h2&gt;

&lt;p&gt;Keybearer is open source, so feel free to &lt;a href=&quot;https://github.com/msolomon/keybearer&quot;&gt;fork it on GitHub&lt;/a&gt;!&lt;/p&gt;

&lt;h2 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;/h2&gt;

&lt;p&gt;I am not responsible for anything bad that results from the use of this software, nor am I liable: use this software at your own risk. &lt;strong&gt;In no circumstances should you rely on this to protect you or your data&lt;/strong&gt;. This is a proof of concept created by someone with no security expertise.&lt;/p&gt;</content><author><name>Mike Solomon</name></author><summary type="html">Keybearer uses several independent passwords to encrypt a file and later requires a subset of those passwords to decrypt it.</summary></entry><entry><title type="html">Host your own web fonts</title><link href="https://msol.io/blog/tech/host-your-own-web-fonts/" rel="alternate" type="text/html" title="Host your own web fonts" /><published>2015-03-07T04:45:10-08:00</published><updated>2015-03-07T04:45:10-08:00</updated><id>https://msol.io/blog/tech/host-your-own-web-fonts</id><content type="html" xml:base="https://msol.io/blog/tech/host-your-own-web-fonts/">&lt;p&gt;Web fonts are very popular these days, and sites like &lt;a href=&quot;https://www.google.com/fonts/&quot;&gt;Google Fonts&lt;/a&gt; and &lt;a href=&quot;https://typekit.com/&quot;&gt;Typekit&lt;/a&gt; make it very easy to use them on your website.&lt;/p&gt;

&lt;p&gt;Hosting them yourself is also pretty easy.
I recently moved away from Google Fonts for this site using this same process.&lt;/p&gt;

&lt;h2 id=&quot;selecting-a-font&quot;&gt;Selecting a font&lt;/h2&gt;

&lt;p&gt;First, you must select a font.
Be sure that the license you have allows you to host it on the web.
We will use &lt;a href=&quot;http://www.latofonts.com/lato-free-fonts/&quot;&gt;Lato&lt;/a&gt;, which is also &lt;a href=&quot;https://www.google.com/fonts/specimen/Lato&quot;&gt;available&lt;/a&gt; on Google Fonts.&lt;/p&gt;

&lt;h2 id=&quot;making-the-font-available-for-use&quot;&gt;Making the font available for use&lt;/h2&gt;

&lt;p&gt;Let’s say we want to use Lato and allow for bold, italic, and italic bold.
This is how we would make the font accessible from CSS using Google Fonts:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'//fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'stylesheet'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'text/css'&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To self-host, the process is slightly different.
First, we must download the files and put them in a folder, such as &lt;code class=&quot;highlighter-rouge&quot;&gt;fonts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ideally we want to have each font variant (regular, bold, italic, bold + italic) in three formats: &lt;code class=&quot;highlighter-rouge&quot;&gt;.woff&lt;/code&gt; for modern browsers, &lt;code class=&quot;highlighter-rouge&quot;&gt;.eot&lt;/code&gt; for old Internet Explorer, and &lt;code class=&quot;highlighter-rouge&quot;&gt;.ttf&lt;/code&gt; for other browsers. Font Squirrel has an &lt;a href=&quot;http://www.fontsquirrel.com/tools/webfont-generator&quot;&gt;online tool&lt;/a&gt; to convert easily between them if you’re missing any.&lt;/p&gt;

&lt;p&gt;Now it’s just a matter of some simple CSS:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;&lt;span class=&quot;c&quot;&gt;/* Normal */&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@font-face&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;'Lato'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Regular.eot')&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Regular.eot?#iefix')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'embedded-opentype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Regular.woff')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'woff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Regular.ttf')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'truetype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizeLegibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;/* Italic */&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@font-face&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;'Lato'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Italic.eot')&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Italic.eot?#iefix')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'embedded-opentype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Italic.woff')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'woff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Italic.ttf')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'truetype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;italic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizeLegibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;/* Bold */&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@font-face&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;'Lato'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Bold.eot')&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Bold.eot?#iefix')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'embedded-opentype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Bold.woff')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'woff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-Bold.ttf')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'truetype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizeLegibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;/* Bold + Italic */&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@font-face&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;'Lato'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-BoldItalic.eot')&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-BoldItalic.eot?#iefix')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'embedded-opentype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-BoldItalic.woff')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'woff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;sx&quot;&gt;url('fonts/Lato-BoldItalic.ttf')&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;'truetype'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;italic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizeLegibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Your fonts should now be available.
Be sure to adjust the &lt;code class=&quot;highlighter-rouge&quot;&gt;url&lt;/code&gt; paths if you place your fonts in a different directory.&lt;/p&gt;

&lt;h2 id=&quot;applying-your-font&quot;&gt;Applying your font&lt;/h2&gt;

&lt;p&gt;Simply apply your font in CSS as usual (this will also work with Google Fonts or Typekit):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Lato&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;'Helvetica Neue'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Helvetica&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Arial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now you’re good to go!&lt;/p&gt;

&lt;h2 id=&quot;extreme-optimization&quot;&gt;Extreme optimization&lt;/h2&gt;

&lt;p&gt;If you really want to get fancy, Google Fonts has a feature called subsetting that lets you strip unused characters out from the font before transmitting it to clients, saving bandwidth.&lt;/p&gt;

&lt;p&gt;You basically have two options if you want to do it yourself.
The easy way is to use FontSquirrel’s excellent &lt;a href=&quot;http://www.fontsquirrel.com/tools/webfont-generator&quot;&gt;Webfont Generator&lt;/a&gt;, and use the subsetting options there.&lt;/p&gt;

&lt;p&gt;The hard way is complicated and you will need to work out the exact details, but a basic setup would be:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Determine what characters you use&lt;/p&gt;

    &lt;p&gt;If you have a static website, you might be able to do this fairly easily, perhaps by finding every character displayed on any page (accounting for HTML encoding).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Set up a pipeline to subset your fonts&lt;/p&gt;

    &lt;p&gt;&lt;a href=&quot;https://github.com/behdad/fonttools&quot;&gt;fontTools’&lt;/a&gt; &lt;a href=&quot;https://github.com/behdad/fonttools/blob/master/Lib/fontTools/subset.py&quot;&gt;subset.py&lt;/a&gt; is probably the best way to do this, or the related version in &lt;a href=&quot;https://code.google.com/p/googlefontdirectory/source/browse/tools/subset&quot;&gt;Google Font Directory&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use the generated files as above&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before going down this route, you probably want to measure your maximum possible speed improvement and see if it’s actually worth the trouble.&lt;/p&gt;</content><author><name>Mike Solomon</name></author><summary type="html">Web fonts are very popular these days, and sites like Google Fonts and Typekit make it very easy to use them on your website.</summary></entry><entry><title type="html">How to win when you’re small</title><link href="https://msol.io/blog/thoughts/how-to-win-when-youre-small/" rel="alternate" type="text/html" title="How to win when you're small" /><published>2015-03-04T12:23:55-08:00</published><updated>2015-03-04T12:23:55-08:00</updated><id>https://msol.io/blog/thoughts/how-to-win-when-youre-small</id><content type="html" xml:base="https://msol.io/blog/thoughts/how-to-win-when-youre-small/">&lt;p&gt;We all know it happens–the underdog wins, the smaller army emerges victorious, the startup overtakes the multinational.&lt;/p&gt;

&lt;p&gt;But why?&lt;/p&gt;

&lt;p&gt;And how can we apply this effect to our companies, sports teams, or lives?&lt;/p&gt;

&lt;h2 id=&quot;modeling-unequal-conflict&quot;&gt;Modeling unequal conflict&lt;/h2&gt;

&lt;p&gt;Enter Colonel Blotto.&lt;/p&gt;

&lt;p&gt;Colonel Blotto is a simple game, well-studied in game theory.
The Colonel must choose how to distribute troops over some number of battlefields.
The goal is to emerge victorious at more battlefields, but the Colonel doesn’t know in advance how many opponents will arrive at a given field.
Victory is determined simply by allocating more troops at a given battlefield.&lt;/p&gt;

&lt;h3 id=&quot;fighting-and-winning-at-a-disadvantage&quot;&gt;Fighting and winning at a disadvantage&lt;/h3&gt;

&lt;p&gt;Unfortunately, our Colonel is at a disadvantage and doesn’t have as many troops as the enemy.&lt;/p&gt;

&lt;p&gt;For example, if we have 3 battlefields:&lt;/p&gt;

&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Battlefield&lt;/th&gt;
    &lt;th&gt;Blotto&lt;/th&gt;
    &lt;th&gt;Enemy&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;A&lt;/td&gt;
    &lt;td&gt;4&lt;/td&gt;
    &lt;td&gt;5&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;B&lt;/td&gt;
    &lt;td&gt;4&lt;/td&gt;
    &lt;td&gt;5&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;C&lt;/td&gt;
    &lt;td&gt;4&lt;/td&gt;
    &lt;td&gt;5&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Despite this disadvantage, Blotto can still succeed:&lt;/p&gt;

&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Battlefield&lt;/th&gt;
    &lt;th&gt;Blotto&lt;/th&gt;
    &lt;th&gt;Enemy&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;A&lt;/td&gt;
    &lt;td&gt;6&lt;/td&gt;
    &lt;td&gt;5&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;B&lt;/td&gt;
    &lt;td&gt;6&lt;/td&gt;
    &lt;td&gt;5&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;C&lt;/td&gt;
    &lt;td&gt;0&lt;/td&gt;
    &lt;td&gt;5&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;This is our first bit of insight: picking your battles (and picking which to sacrifice) is unquestionably more effective given limited resources.
This is hardly earth-shaking news, but it is good to see that our model bears out conventional wisdom.&lt;/p&gt;

&lt;p&gt;In fact, there is a common application of this in politics: gerrymandering.
By &lt;a href=&quot;http://www.washingtonpost.com/blogs/wonkblog/wp/2015/03/01/this-is-the-best-explanation-of-gerrymandering-you-will-ever-see/&quot;&gt;carefully choosing districts&lt;/a&gt;, politicians have learned to win elections without the popular vote.&lt;/p&gt;

&lt;p&gt;Now, the obvious objection is that an Enemy with superior resources could easily reallocate troops to guarantee victory on a majority of battlefields—this is unfortunate for Blotto, but it reveals a second insight: the Enemy doesn’t have enough resources to win at everything! Unless the Enemy has a truly overwhelming number of resources, Blotto has a good shot at winning at least some battlefields, just as a small business can often outperform larger competitors in a few key areas by focusing on filling needs unmet by those larger competitors.&lt;/p&gt;

&lt;h3 id=&quot;fighting-at-a-major-disadvantage&quot;&gt;Fighting at a major disadvantage&lt;/h3&gt;

&lt;p&gt;But what can an outgunned Blotto do in a situation where the enemy has many more resources?&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Battlefield&lt;/th&gt;
    &lt;th&gt;Blotto&lt;/th&gt;
    &lt;th&gt;Enemy&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;A&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
    &lt;td&gt;4&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;B&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
    &lt;td&gt;4&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Even if our good Colonel allocates all troops to one battlefield, the Enemy has enough resources to prevent a Blotto victory on either battlefield.&lt;/p&gt;

&lt;p&gt;So why not add more battlefields?&lt;/p&gt;

&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Battlefield&lt;/th&gt;
    &lt;th&gt;Blotto&lt;/th&gt;
    &lt;th&gt;Enemy&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;A&lt;/td&gt;
    &lt;td&gt;3&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;B&lt;/td&gt;
    &lt;td&gt;1&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;C&lt;/td&gt;
    &lt;td&gt;0&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;D&lt;/td&gt;
    &lt;td&gt;0&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;By adding new battlefields to the game, Blotto is able to be victorious on one battlefield despite having half the troops.&lt;/p&gt;

&lt;p&gt;Of course this isn’t allowed in the game theory version (and Blotto still hasn’t won!), but it uncovers a third insight: adding new areas to compete in is advantageous for the underdog.
Doing one thing (or a few things) well is a better strategy for small companies, and it can be a great advantage in a career as well.&lt;/p&gt;

&lt;p&gt;This applies even more in situations where a victory on one battlefield may be sufficient to win–imagine a small company suddenly competing on grounds no other company knew existed.&lt;/p&gt;

&lt;h2 id=&quot;the-math-behind-the-maxim&quot;&gt;The math behind the maxim&lt;/h2&gt;

&lt;p&gt;We have seen a few good insights suggested by Colonel Blotto that can be rephrased as more conventional wisdom:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Pick your battles. If you spread your resources too thin, you are easily defeated.&lt;/li&gt;
  &lt;li&gt;Know thy enemy. If you know how your opponents will use their resources, you can pick the right battles.&lt;/li&gt;
  &lt;li&gt;The underdog can win too. It’s easy to get disheartened, but remember that you can win even with fewer resources.&lt;/li&gt;
  &lt;li&gt;If you can’t win, cheat. Add a new dimension to the game, a new product, a new battlefield. It might just be the edge you need.&lt;/li&gt;
&lt;/ol&gt;</content><author><name>Mike Solomon</name></author><summary type="html">We all know it happens–the underdog wins, the smaller army emerges victorious, the startup overtakes the multinational.</summary></entry></feed>