430 lines
26 KiB
HTML
430 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
|
||
|
||
<html class="no-js" lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>What does debugging a program look like?</title>
|
||
<meta name="author" content="Julia Evans">
|
||
<meta name="HandheldFriendly" content="True">
|
||
<meta name="MobileOptimized" content="320">
|
||
<meta name="description" content="What does debugging a program look like?">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
<meta property="og:title" content='What does debugging a program look like?'>
|
||
<meta property="og:type" content="website" />
|
||
<meta property="og:url" content="https://jvns.ca/blog/2019/06/23/a-few-debugging-resources/" />
|
||
<meta property="og:site_name" content="Julia Evans" />
|
||
|
||
<link rel="canonical" href="https://jvns.ca/blog/2019/06/23/a-few-debugging-resources/">
|
||
<link href="/favicon.ico" rel="icon">
|
||
|
||
<link href="/stylesheets/screen.css" rel="preload" type="text/css" as="style">
|
||
|
||
<link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
|
||
<link href="/stylesheets/print.css" media="print" rel="stylesheet" type="text/css">
|
||
|
||
|
||
<link href="/atom.xml" rel="alternate" title="Julia Evans" type="application/atom+xml">
|
||
|
||
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css" integrity="sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0" crossorigin="anonymous">
|
||
<script defer data-domain="jvns.ca" src="https://plausible.io/js/script.js"></script>
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js" integrity="sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4" crossorigin="anonymous"></script>
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||
|
||
<script defer type="text/javascript">
|
||
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
|
||
heap.load("2242143965");
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div id="skiptocontent">
|
||
<a href="#main">Skip to main content</a>
|
||
</div>
|
||
<div id="wrap">
|
||
<header role="banner">
|
||
<hgroup>
|
||
<h1><a href="/">Julia Evans</a></h1>
|
||
</hgroup>
|
||
<ul class="header-links">
|
||
<li><a href="/about">About</a></li>
|
||
<li><a href="/talks">Talks</a></li>
|
||
<li><a href="/projects/">Projects</a></li>
|
||
<li><a rel="me" href="https://social.jvns.ca/@b0rk">Mastodon</a></li>
|
||
<li><a href="https://bsky.app/profile/b0rk.jvns.ca">Bluesky</a></li>
|
||
<li><a href="https://github.com/jvns">Github</a></li>
|
||
</ul>
|
||
</header>
|
||
<nav role="navigation" class="header-nav"><ul class="main-navigation">
|
||
<li><a href="/categories/favorite/">Favorites</a></li>
|
||
<li><a href="/til/">TIL</a></li>
|
||
<li><a href="https://wizardzines.com">Zines</a></li>
|
||
<li class="subscription" data-subscription="rss"><a href="/atom.xml" rel="subscribe-rss" title="subscribe via RSS">RSS</a></li>
|
||
</ul>
|
||
</nav>
|
||
<div id="main">
|
||
<div id="content">
|
||
|
||
|
||
<div>
|
||
<article class="hentry" role="article">
|
||
<header>
|
||
<h1 class="entry-title">What does debugging a program look like?</h1>
|
||
|
||
<div class="post-tags">
|
||
|
||
</div>
|
||
<p class="meta sans">
|
||
<time class="date" datetime="2019-06-23T18:48:35" pubdate data-updated="true">
|
||
|
||
June 23, 2019
|
||
|
||
</time>
|
||
</p>
|
||
</header>
|
||
<main>
|
||
<p>I was debugging with a friend who’s a relatively new programmer yesterday, and showed them a few
|
||
debugging tips. Then I was thinking about how to teach debugging this morning, and <a href="https://twitter.com/b0rk/status/1142825259546140673">mentioned on
|
||
Twitter</a> that I’d never seen a really good
|
||
guide to debugging your code. (there are a ton of really great replies by Anne Ogborn to that tweet
|
||
if you are interested in debugging tips)</p>
|
||
<p>As usual, I got a lot of helpful answers and now I have a few ideas about how to teach debugging
|
||
skills / describe the process of debugging.</p>
|
||
<h3 id="a-couple-of-debugging-resources" class="post-heading">
|
||
<a href="#a-couple-of-debugging-resources">
|
||
a couple of debugging resources
|
||
</a>
|
||
</h3>
|
||
<p>I was hoping for more links to debugging books/guides, but here are the 2 recommendations I
|
||
got:</p>
|
||
<p><strong>“Debugging” by David Agans</strong>: Several people recommended the book
|
||
<a href="http://debuggingrules.com/">Debugging</a>, which looks like a nice and fairly short book that explains
|
||
a debugging strategy. I haven’t read it yet (though I ordered it to see if I should be recommending
|
||
it) and the rules laid out in the book (“understand the system”, “make it fail”, “quit thinking and
|
||
look”, “divide and conquer”, “change one thing at a time”, “keep an audit trail”, “check the plug”,
|
||
“get a fresh view”, and “if you didn’t fix it, it ain’t fixed”) seem extremely resaonable :). He
|
||
also has a charming <a href="http://debuggingrules.com/?page_id=40">debugging poster</a>.</p>
|
||
<p><strong>“How to debug” by John Regehr</strong>: <a href="https://blog.regehr.org/archives/199">How to Debug</a> is a very
|
||
good blog post based on Regehr’s experience teaching a university embedded systems course. Lots of
|
||
good advice. He also has a <a href="https://blog.regehr.org/archives/849">blog post reviewing 4 books about debugging</a>, including Agans’ book.</p>
|
||
<h3 id="reproduce-your-bug-but-how-do-you-do-that" class="post-heading">
|
||
<a href="#reproduce-your-bug-but-how-do-you-do-that">
|
||
reproduce your bug (but how do you do that?)
|
||
</a>
|
||
</h3>
|
||
<p>The rest of this post is going to be an attempt to aggregate different ideas about debugging
|
||
people tweeted at me.</p>
|
||
<p>Somewhat obviously, everybody agrees that being able to consistently reproduce a bug is important if
|
||
you want to figure out what’s going on. I have an intuitive sense for how to do this but I’m not
|
||
sure how to <strong>explain</strong> how to go from “I saw this bug twice” to “I can consistently reproduce this
|
||
bug on demand on my laptop”, and I wonder whether the techniques you use to do this depend on the
|
||
domain (backend web dev, frontend, mobile, games, C++ programs, embedded etc).</p>
|
||
<h3 id="reproduce-your-bug-quickly" class="post-heading">
|
||
<a href="#reproduce-your-bug-quickly">
|
||
reproduce your bug <em>quickly</em>
|
||
</a>
|
||
</h3>
|
||
<p>Everybody also agrees that it’s extremely useful be able to reproduce the bug quickly (if it takes
|
||
you 3 minutes to check if every change helped, iterating is VERY SLOW).</p>
|
||
<p>A few suggested approaches:</p>
|
||
<ul>
|
||
<li>for something that requires clicking on a bunch of things in a browser to reproduce, recording
|
||
what you clicked on with <a href="https://www.seleniumhq.org/">Selenium</a> and getting Selenium to replay
|
||
the UI interactions (suggested <a href="https://twitter.com/AnnieTheObscure/status/1142843984642899968">here</a>)</li>
|
||
<li>writing a unit test that reproduces the bug (if you can). bonus: you can add this to your test
|
||
suite later if it makes sense</li>
|
||
<li>writing a script / finding a command line incantation that does it (like <code>curl MY_APP.local/whatever</code>)</li>
|
||
</ul>
|
||
<h3 id="accept-that-it-s-probably-your-code-s-fault" class="post-heading">
|
||
<a href="#accept-that-it-s-probably-your-code-s-fault">
|
||
accept that it’s probably your code’s fault
|
||
</a>
|
||
</h3>
|
||
<p>Sometimes I see a problem and I’m like “oh, library X has a bug”, “oh, it’s DNS”, “oh, SOME OTHER
|
||
THING THAT IS NOT MY CODE is broken”. And sometimes it’s not my code! But in general between an
|
||
established library and my code that I wrote last month, usually it’s my code that I wrote last
|
||
month that’s the problem :).</p>
|
||
<h3 id="start-doing-experiments" class="post-heading">
|
||
<a href="#start-doing-experiments">
|
||
start doing experiments
|
||
</a>
|
||
</h3>
|
||
<p>@act_gardner gave a <a href="https://twitter.com/act_gardner/status/1142838587437830144">nice, short explanation of what you have to do after you reproduce your
|
||
bug</a></p>
|
||
<blockquote>
|
||
<p>I try to encourage people to first fully understand the bug - What’s happening? What do you expect
|
||
to happen? When does it happen? When does it not happen? Then apply their mental model of the
|
||
system to guess at what could be breaking and come up with experiments.</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>Experiments could be changing or removing code, making API calls from a REPL, trying new inputs,
|
||
poking at memory values with a debugger or print statements.</p>
|
||
</blockquote>
|
||
<p>I think the loop here may be:</p>
|
||
<ul>
|
||
<li>make guess about one aspect about what might be happening (“this variable is set to X where it
|
||
should be Y”, “the server is being sent the wrong request”, “this code is never running at all”)</li>
|
||
<li>do experiment to check that guess</li>
|
||
<li>repeat until you understand what’s going on</li>
|
||
</ul>
|
||
<h3 id="change-one-thing-at-a-time" class="post-heading">
|
||
<a href="#change-one-thing-at-a-time">
|
||
change one thing at a time
|
||
</a>
|
||
</h3>
|
||
<p>Everybody definitely agrees that it is important to change one thing a time when doing an
|
||
experiment to verify an assumption.</p>
|
||
<h3 id="check-your-assumptions" class="post-heading">
|
||
<a href="#check-your-assumptions">
|
||
check your assumptions
|
||
</a>
|
||
</h3>
|
||
<p>A lot of debugging is realizing that something you were <strong>sure</strong> was true (“wait this request is
|
||
going to the new server, right, not the old one???”) is actually… not true. I made an attempt to
|
||
<a href="https://tweets.jvns.ca/b0rk/status/1142812831420768257">list some common incorrect assumptions</a>. Here
|
||
are some examples:</p>
|
||
<ul>
|
||
<li>this variable is set to X (“that filename is definitely right”)</li>
|
||
<li>that variable’s value can’t possibly have changed between X and Y</li>
|
||
<li>this code was doing the right thing before</li>
|
||
<li>this function does X</li>
|
||
<li>I’m editing the right file</li>
|
||
<li>there can’t be any typos in that line I wrote it is just 1 line of code</li>
|
||
<li>the documentation is correct</li>
|
||
<li>the code I’m looking at is being executed at some point</li>
|
||
<li>these two pieces of code execute sequentially and not in parallel</li>
|
||
<li>the code does the same thing when compiled in debug / release mode (or with -O2 and without, or…)</li>
|
||
<li>the compiler is not buggy (though this is last on purpose, the compiler is only very rarely to blame :))</li>
|
||
</ul>
|
||
<h3 id="weird-methods-to-get-information" class="post-heading">
|
||
<a href="#weird-methods-to-get-information">
|
||
weird methods to get information
|
||
</a>
|
||
</h3>
|
||
<p>There are a lot of normal ways to do experiments to check your assumptions / guesses about what the
|
||
code is doing (print out variable values, use a debugger, etc). Sometimes, though, you’re in a more
|
||
difficult environment where you can’t print things out and don’t have access to a debugger (or it’s
|
||
inconvenient to do those things, maybe because there are too many events). Some ways to cope:</p>
|
||
<ul>
|
||
<li><a href="https://twitter.com/cocoaphony/status/1142847665690030080">adding sounds on mobile</a>: “In the
|
||
mobile world, I live on this advice. Xcode can play a sound when you hit a breakpoint (and
|
||
continue without stopping). I place them certain places in the code, and listen for buzzing Tink
|
||
to indicate tight loops or Morse/Pop pairs to catch unbalanced events” (also <a href="https://twitter.com/AnnieTheObscure/status/1142842421954244608">this tweet</a>)</li>
|
||
<li>there’s a very cool talk about <a href="https://qnoid.com/2013/06/08/Sound-Debugging.html">using XCode to play sound for iOS debugging here</a></li>
|
||
<li><a href="https://twitter.com/wombatnation/status/1142887843963867136">adding LEDs</a>: “When I did embedded
|
||
dev ages ago on grids of transputers, we wired up an LED to an unused pin on each chip. It was
|
||
surprisingly effective for diagnosing parallelism issues.”</li>
|
||
<li><a href="https://twitter.com/irvingreid/status/1142887472441040896">string</a>: “My networks prof told me
|
||
about a hack he saw at Xerox in the early days of Ethernet: a tap in the coax with an amp and
|
||
motor and piece of string. The busier the network was, the faster the string twirled.”</li>
|
||
<li><a href="http://peep.sourceforge.net/intro.html">peep</a> is a “network auralizer” that translates what’s
|
||
happening on your system into sounds. I spent 10 minutes trying to get it to compile and failed so
|
||
far but it looks very fun and I want to try it!!</li>
|
||
</ul>
|
||
<p>The point here is that information is the most important thing and you need to do whatever’s
|
||
necessary to get information.</p>
|
||
<h3 id="write-your-code-so-it-s-easier-to-debug" class="post-heading">
|
||
<a href="#write-your-code-so-it-s-easier-to-debug">
|
||
write your code so it’s easier to debug
|
||
</a>
|
||
</h3>
|
||
<p>Another point a few people brought up is that you can improve your program to make it
|
||
easier to debug. tef has a nice post about this: <a href="https://programmingisterrible.com/post/173883533613/code-to-debug">Write code that’s easy to delete, and easy to debug too.</a> here. I thought this
|
||
was very true:</p>
|
||
<blockquote>
|
||
<p>Debuggable code isn’t necessarily clean, and code that’s littered with checks or error handling
|
||
rarely makes for pleasant reading.</p>
|
||
</blockquote>
|
||
<p>I think one interpretation of “easy to debug” is “every single time there’s an error, the program
|
||
reports to you exactly what happened in an easy to understand way”. Whenever my program has a
|
||
problem and says something like “error: failure to connect to SOME_IP port 443: connection timeout”
|
||
I’m like THANK YOU THAT IS THE KIND OF THING I WANTED TO KNOW and I can check if I need to fix a
|
||
firewall thing or if I got the wrong IP for some reason or what.</p>
|
||
<p>One simple example of this recently: I was making a request to a server I wrote and the
|
||
reponse I got was “upstream connect error or disconnect/reset before headers”. This is an nginx
|
||
error which basically in this case boiled down to “your program crashed before it sent anything in
|
||
response to the request”. Figuring out the cause of the crash was pretty easy, but having better
|
||
error handling (returning an error instead of crashing) would have saved me a little time
|
||
because instead of having to go check the cause of the crash, I could have just read the error
|
||
message and figured out what was going on right away.</p>
|
||
<h3 id="error-messages-are-better-than-silently-failing" class="post-heading">
|
||
<a href="#error-messages-are-better-than-silently-failing">
|
||
error messages are better than silently failing
|
||
</a>
|
||
</h3>
|
||
<p>To get closer to the dream of “every single time there’s an error, the program reports
|
||
to you exactly what happened in an easy to understand way” you also need to be disciplined about
|
||
immediately returning an error message instead of silently writing incorrect data / passing a
|
||
nonsense value to another function which will do WHO KNOWS WHAT with it and cause you a gigantic
|
||
headache. This means adding code like this:</p>
|
||
<pre><code>if UNEXPECTED_THING:
|
||
raise "oh no THING happened"
|
||
</code></pre>
|
||
<p>This isn’t easy to get right (it’s not always obvious where you should be raising errors!") but it
|
||
really helps a lot.</p>
|
||
<h3 id="failure-print-out-a-stack-of-errors-not-just-one-error" class="post-heading">
|
||
<a href="#failure-print-out-a-stack-of-errors-not-just-one-error">
|
||
failure: print out a stack of errors, not just one error.
|
||
</a>
|
||
</h3>
|
||
<p>Related to returning helpful errors that make it easy to debug: Rust has a really incredible error
|
||
handling library <a href="https://github.com/rust-lang-nursery/failure">called failure</a> which basically lets
|
||
you return a chain of errors instead of just one error, so you can print out a stack of errors like:</p>
|
||
<pre><code>"error starting server process" caused by
|
||
"error initializing logging backend" caused by
|
||
"connection failure: timeout connecting to 1.2.3.4 port 1234".
|
||
</code></pre>
|
||
<p>This is SO MUCH MORE useful than just <code>connection failure: timeout connecting to 1.2.3.4 port 1234</code>
|
||
by itself because it tells you the significance of 1.2.3.4 (it’s something to do with the logging
|
||
backend!). And I think it’s also more useful than <code>connection failure: timeout connecting to 1.2.3.4 port 1234</code>
|
||
with a stack trace, because it summarizes at a high level the parts that went wrong instead of
|
||
making you read all the lines in the stack trace (some of which might not be relevant!).</p>
|
||
<p>tools like this in other languages:</p>
|
||
<ul>
|
||
<li>Go: the idiom to do this seems to be to just concatenate your stack of errors together as a
|
||
big string so you get “error: thing one: error: thing two : error: thing three” which works okay but
|
||
is definitely a lot less structured than <code>failure</code>’s system</li>
|
||
<li>Java: I hear you can give exceptions causes but haven’t used that myself</li>
|
||
<li>Python 3: you can use <code>raise ... from</code> which sets the <code>__cause__</code> attribute on the exception and then
|
||
your exceptions will be separated by <code>The above exception was the direct cause of the following exception:..</code></li>
|
||
</ul>
|
||
<p>If you know how to do this in other languages I’d be interested to hear!</p>
|
||
<h3 id="understand-what-the-error-messages-mean" class="post-heading">
|
||
<a href="#understand-what-the-error-messages-mean">
|
||
understand what the error messages mean
|
||
</a>
|
||
</h3>
|
||
<p>One sub debugging skill that I take for granted a lot of the time is understanding what error
|
||
messages mean! I came across this nice graphic explaining <a href="https://pythonforbiologists.com/29-common-beginner-errors-on-one-page/">common Python errors and what they
|
||
mean</a>, which breaks down
|
||
things like <code>NameError</code>, <code>IOError</code>, etc.</p>
|
||
<p>I think a reason interpreting error messages is hard is that understanding a new error message might
|
||
mean learning a new concept – <code>NameError</code> can mean “Your code uses a variable outside the scope
|
||
where it’s defined”, but to really understand that you need to understand what variable scope is! I
|
||
ran into this a lot when learning Rust – the Rust compiler would be like “you have a weird lifetime
|
||
error” and I’d like be “ugh ok Rust I get it I will go actually learn about how lifetimes work
|
||
now!”.</p>
|
||
<p>And a lot of the time error messages are caused by a problem very different from the text of the
|
||
message, like how “upstream connect error or disconnect/reset before headers” might mean “julia,
|
||
your server crashed!”. The skill of understanding what error messages mean is often not transferable
|
||
when you switch to a new area (if I started writing a lot of React or something tomorrow, I would
|
||
probably have no idea what any of the error messages meant!). So this definitely isn’t just an issue
|
||
for beginner programmers.</p>
|
||
<h3 id="that-s-all-for-now" class="post-heading">
|
||
<a href="#that-s-all-for-now">
|
||
that’s all for now!
|
||
</a>
|
||
</h3>
|
||
<p>I feel like the big thing I’m missing when talking about debugging skills is a stronger
|
||
understanding of where people get stuck with debugging – it’s easy to say “well, you need to
|
||
reproduce the problem, then make a more minimal reproduction, then start coming up with guesses and
|
||
verifying them, and improve your mental model of the system, and then figure it out, then fix the
|
||
problem and hopefully write a test to make it not come back”, but – where are people actually
|
||
getting stuck in practice? What are the hardest parts? I have some sense of what the hardest parts
|
||
usually are for me but I’m still not sure what the hardest parts usually are for someone newer to
|
||
debugging their code.</p>
|
||
|
||
</main>
|
||
|
||
<footer>
|
||
|
||
<style type="text/css">
|
||
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; display: inline;}
|
||
#mc_embed_signup {
|
||
display: inline;
|
||
}
|
||
#mc_embed_signup input.button {
|
||
background: #ff5e00;
|
||
display: inline;
|
||
color: white;
|
||
padding: 6px 12px;
|
||
}
|
||
</style>
|
||
<div class="sharing">
|
||
|
||
<style>
|
||
.form-inline {
|
||
display:flex; flex-flow: row wrap; justify-content: center;
|
||
}
|
||
.form-inline input, .form-inline span {
|
||
padding: 10px;
|
||
}
|
||
.form-inline input {
|
||
display:inline;
|
||
max-width:30%;
|
||
margin: 0 10px 0 0;
|
||
background-color: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
padding: 10px;
|
||
}
|
||
button {
|
||
background-color: #f50;
|
||
box-shadow: none;
|
||
border: 0;
|
||
border-radius: 5px;
|
||
color: white;
|
||
padding: 5px 10px;
|
||
}
|
||
@media (max-width: 800px) {
|
||
.form-inline input {
|
||
margin: 10px 0;
|
||
max-width:100% !important;
|
||
}
|
||
.form-inline {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<div align="center">
|
||
<form class="form-inline" action="https://app.convertkit.com/forms/1052396/subscriptions" method="post" data-uid="8884355abb" data-format="inline" data-version="5">
|
||
<span> Want a weekly digest of this blog?</span>
|
||
<input name="email_address" type="text" placeholder="Email address" />
|
||
<button type="submit" data-element="submit">Subscribe</button>
|
||
</form>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<p class="meta">
|
||
|
||
<a class="basic-alignment left" href="https://jvns.ca/blog/2019/03/26/what-are-monoidal-categories/" title="Previous Post: Why are monoidal categories interesting?">Why are monoidal categories interesting?</a>
|
||
|
||
|
||
<a class="basic-alignment right" href="https://jvns.ca/blog/brag-documents/" title="Next Post: Get your work recognized: write a brag document">Get your work recognized: write a brag document</a>
|
||
|
||
</p>
|
||
</footer>
|
||
|
||
</article>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<nav role="navigation" class="footer-nav"> <a href="/">Archives</a>
|
||
</nav>
|
||
<footer role="contentinfo"><span class="credit">© Julia Evans. </span>
|
||
<span>If you like this, you may like <a href="https://web.archive.org/web/20181228051203/http://www.uliaea.ca/">Ulia Ea</a> or, more seriously, this list of <a href="https://jvns.ca/blogroll">blogs I love</a> or some <a href="https://jvns.ca/bookshelf">books I've read</a>. <br>
|
||
<p class="rc-scout__text"><i class="rc-scout__logo"></i>
|
||
You might also like the <a class="rc-scout__link" href="https://www.recurse.com/scout/click?t=546ea46360584b522270b8c3e5d830f8">Recurse Center</a>, my very favorite programming community <a href="/categories/hackerschool/">(my posts about it)</a></p>
|
||
</span>
|
||
<style class="rc-scout__style" type="text/css">.rc-scout{display:block;padding:0;border:0;margin:0;}.rc-scout__text{display:block;padding:0;border:0;margin:0;height:100%;font-size:100%;}.rc-scout__logo{display:inline-block;padding:0;border:0;margin:0;width:0.85em;height:0.85em;background:no-repeat center url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2012%2015%22%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%2212%22%20height%3D%2210%22%20fill%3D%22%23000%22%3E%3C%2Frect%3E%3Crect%20x%3D%221%22%20y%3D%221%22%20width%3D%2210%22%20height%3D%228%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%222%22%20y%3D%222%22%20width%3D%228%22%20height%3D%226%22%20fill%3D%22%23000%22%3E%3C%2Frect%3E%3Crect%20x%3D%222%22%20y%3D%223%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%2361ae24%22%3E%3C%2Frect%3E%3Crect%20x%3D%224%22%20y%3D%223%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%2361ae24%22%3E%3C%2Frect%3E%3Crect%20x%3D%226%22%20y%3D%223%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%2361ae24%22%3E%3C%2Frect%3E%3Crect%20x%3D%223%22%20y%3D%225%22%20width%3D%222%22%20height%3D%221%22%20fill%3D%22%2361ae24%22%3E%3C%2Frect%3E%3Crect%20x%3D%226%22%20y%3D%225%22%20width%3D%222%22%20height%3D%221%22%20fill%3D%22%2361ae24%22%3E%3C%2Frect%3E%3Crect%20x%3D%224%22%20y%3D%229%22%20width%3D%224%22%20height%3D%223%22%20fill%3D%22%23000%22%3E%3C%2Frect%3E%3Crect%20x%3D%221%22%20y%3D%2211%22%20width%3D%2210%22%20height%3D%224%22%20fill%3D%22%23000%22%3E%3C%2Frect%3E%3Crect%20x%3D%220%22%20y%3D%2212%22%20width%3D%2212%22%20height%3D%223%22%20fill%3D%22%23000%22%3E%3C%2Frect%3E%3Crect%20x%3D%222%22%20y%3D%2213%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%223%22%20y%3D%2212%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%224%22%20y%3D%2213%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%225%22%20y%3D%2212%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%226%22%20y%3D%2213%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%227%22%20y%3D%2212%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%228%22%20y%3D%2213%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3Crect%20x%3D%229%22%20y%3D%2212%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22%23fff%22%3E%3C%2Frect%3E%3C%2Fsvg%3E');}.rc-scout__link:link,.rc-scout__link:visited{color:#61ae24;text-decoration:underline;}.rc-scout__link:hover,.rc-scout__link:active{color:#4e8b1d;}</style>
|
||
</footer>
|
||
<script type="text/rocketscript">
|
||
(function(){
|
||
var twitterWidgets = document.createElement('script');
|
||
twitterWidgets.type = 'text/javascript';
|
||
twitterWidgets.async = true;
|
||
twitterWidgets.src = 'http://platform.twitter.com/widgets.js';
|
||
document.getElementsByTagName('head')[0].appendChild(twitterWidgets);
|
||
})();
|
||
</script>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|