<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://self.brockmuellers.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://self.brockmuellers.com/" rel="alternate" type="text/html" /><updated>2026-11-11T18:04:38+00:00</updated><id>https://self.brockmuellers.com/feed.xml</id><title type="html">Sara Brockmueller</title><subtitle>I&apos;m Sara Brockmueller. Welcome to my playground for writing, learning, and collecting resources.</subtitle><entry><title type="html">Climate change resources</title><link href="https://self.brockmuellers.com/climate/2021/08/17/climate-resources.html" rel="alternate" type="text/html" title="Climate change resources" /><published>2021-08-17T00:00:00+00:00</published><updated>2021-08-17T00:00:00+00:00</updated><id>https://self.brockmuellers.com/climate/2021/08/17/climate-resources</id><content type="html" xml:base="https://self.brockmuellers.com/climate/2021/08/17/climate-resources.html"><![CDATA[<hr />

<p>I’ve spent months trying to understand climate change, the current state of mitigation strategies, and how I can make a difference. These are some resources that I’ve found helpful.</p>

<h2 id="staying-positive">Staying positive</h2>

<p><strong><a href="http://globaloptimism.com/podcast/">Outrage and Optimism podcast</a></strong></p>

<p><a href="http://brightyellow.co/hope">Positive climate-related newsfeed</a></p>

<p><a href="https://www.bbc.com/future/article/20191010-how-to-beat-anxiety-about-climate-change-and-eco-awareness">How to beat climate anxiety</a></p>

<h2 id="general-climate-change-education">General climate change education</h2>

<p><strong><a href="https://www.drawdown.org/solutions-summary-by-rank">Project Drawdown</a></strong></p>

<p><strong><a href="https://cmi.princeton.edu/wedges/intro">Intro to the concept of stabilization wedges</a></strong></p>

<p><a href="https://www.ipcc.ch/">IPCC</a></p>

<p><a href="https://www.climaterealityproject.org/climate-change-basics">Climate Crisis 101 ebook</a></p>

<p><a href="https://interactive.carbonbrief.org/impacts-climate-change-one-point-five-degrees-two-degrees/">Interactive demonstration of how different levels of climate change can affect us</a></p>

<p><a href="https://www.smartgrid.gov/the_smart_grid/smart_grid.html">Making our electricity grid “smart”</a></p>

<p><a href="https://www.tmrow.com/climatechange">A straightforward overview</a></p>

<p><a href="https://www.withouthotair.com/">Sustainable energy ebook</a></p>

<p><a href="https://www.iea.org/weo2019/">World energy outlook</a></p>

<p><a href="https://climate.mit.edu/symposia">Climate change symposia at MIT</a></p>

<p><a href="https://www.carbonbrief.org/explainer-nine-tipping-points-that-could-be-triggered-by-climate-change">Tipping points we may encounter</a></p>

<h2 id="tech-specific-resources">Tech-specific resources</h2>

<p><strong><a href="https://climateaction.tech/#join">ClimateAction.tech community</a></strong></p>

<p><strong><a href="http://worrydream.com/ClimateChange/">A fantastic overview of the ways people in tech can help</a></strong></p>

<p><strong><a href="https://www.notion.so/Near-tools-dc5421a5f4d84c40b846cf51a04bcd04">A journey through the climate crisis, told through a diverse set of articles</a></strong></p>

<p><a href="https://www.goodtechlab.io/reports">Report on ways that technology can fight climate change</a></p>

<p><a href="https://codewithoutrules.com/2019/09/10/software-developers-climage-change/">A blog post about largely non-professional approaches to fighting climate change</a></p>

<p><a href="https://bdtechtalks.com/2018/10/18/reduce-energy-consumption-with-right-code/amp/">Writing sustainable code</a></p>

<p><a href="http://carbon.ycombinator.com/&gt;">Y Combinator’s climate-specific page</a></p>

<h2 id="finding-a-job">Finding a job</h2>

<p><a href="https://medium.com/@leventov/how-to-find-a-job-in-a-tech-company-helping-to-combat-climate-change-practical-guide-e212e262a32">A post on finding relevant companies</a>, and an associated open <a href="https://climatescape.org/">database of organizations</a> fighting climate change</p>

<p><a href="https://voice.ch.ckl.st/how-i-am-fighting-climate-change-as-a-software-developer/amp/">Step-by-step guide for the job search process</a></p>

<p>Places to look for jobs:</p>
<ul>
  <li><strong><a href="https://climate.careers/">https://climate.careers/</a></strong></li>
  <li><a href="https://techjobsforgood.com/">https://techjobsforgood.com/</a></li>
  <li><a href="https://www.splashwithdolphin.com/">https://www.splashwithdolphin.com/</a></li>
  <li><a href="https://www.cleanenergyjobslist.com/">https://www.cleanenergyjobslist.com/</a></li>
  <li><a href="https://angel.co/jobs">Startup jobs on AngelList</a>; can filter by industry</li>
  <li>Look at the portfolios of VC firms that focus on social impact
    <ul>
      <li><a href="https://www.congruentvc.com/">https://www.congruentvc.com/</a></li>
      <li><a href="https://www.fifty.vc/">https://www.fifty.vc/</a></li>
      <li><a href="http://www.dblpartners.vc/">http://www.dblpartners.vc/</a></li>
      <li><a href="https://obvious.com/">https://obvious.com/</a></li>
    </ul>
  </li>
</ul>

<p>Industries with most software jobs:</p>
<ul>
  <li>Tons of options:
    <ul>
      <li>Energy use optimization (for both businesses and consumers): smart buildings, smart motors, energy tracking tools</li>
      <li>Renewable energy (mostly solar): installation design, management systems, financing/insurance</li>
      <li>Food: reducing waste, robotic farming, ML for farm operations</li>
      <li>Transportation: electric buses and fleet vehicles, charging infrastructure</li>
    </ul>
  </li>
  <li>Just a few options:
    <ul>
      <li>Grid/microgrid optimization</li>
      <li>Reforestation</li>
      <li>Technology development (these tend to be less software-focused): battery development, engineering cleaner industrial materials, direct air capture</li>
      <li>Female empowerment: microloans, improving birth control availability</li>
    </ul>
  </li>
</ul>

<h2 id="misc">Misc</h2>

<p><a href="https://aeon.co/essays/america-still-has-a-heartland-it-s-just-an-artificial-one">The backend of America</a></p>

<p><a href="https://grist.org/climate/the-war-on-climate-the-climate-fight-are-we-approaching-the-problem-all-wrong/">Should we use less violent language when talking about climate change?</a></p>

<p>While <a href="https://www.catherineingram.com/facingextinction/">this article on facing extinction</a> is immensely depressing and I don’t entirely agree with its assumptions, I like this quote about blame and anger related to climate change:</p>
<blockquote>
  <blockquote>
    <p>In a recent blog post, writer James Kunstler proposed a pithy theory of why humans chose each step of our path in history: “It just seemed a good idea at the time.”  We plunged forward with each new way of doing things, each new invention, because it made life easier at the time.  There was no intention to destroy ourselves.<br />
…<br />
As theoretical physicist Peter Russell mused in a podcast conversation with me in 2016, “What if we saw ourselves as a cosmic flame blooming in the universe and coming to its natural end?”<br />
What if we forgave everybody everything?</p>
  </blockquote>
</blockquote>

<p><a href="https://www.newyorker.com/culture/cultural-comment/what-if-we-stopped-pretending">A frequently cited example of climate nihilism that’s generally denounced by scientists</a></p>

<p>Some additional resources on why and how we must hold hope:</p>

<ul>
  <li><a href="https://medium.com/@matthewianhale/living-in-an-unstable-world-existential-climate-risk-and-the-dangers-of-climate-nihilism-bcd6653b603e">https://medium.com/@matthewianhale/living-in-an-unstable-world-existential-climate-risk-and-the-dangers-of-climate-nihilism-bcd6653b603e</a></li>
  <li><a href="https://medium.com/@maryheglar/home-is-always-worth-it-d2821634dcd9">https://medium.com/@maryheglar/home-is-always-worth-it-d2821634dcd9</a></li>
  <li><a href="https://www.vice.com/en/article/j5w374/climate-despair-is-making-people-give-up-on-life">https://www.vice.com/en/article/j5w374/climate-despair-is-making-people-give-up-on-life</a></li>
  <li><a href="https://www.theguardian.com/commentisfree/2021/mar/24/scientists-facts-feelings-climate-crisis-sadness">https://www.theguardian.com/commentisfree/2021/mar/24/scientists-facts-feelings-climate-crisis-sadness</a></li>
  <li><a href="https://behavioralscientist.org/urgency-and-agency-michael-mann-on-conquering-climate-despair/">https://behavioralscientist.org/urgency-and-agency-michael-mann-on-conquering-climate-despair/</a></li>
  <li><a href="https://www.washingtonpost.com/magazine/2021/04/12/climate-news-is-relentlessly-objectively-grim-should-we-ever-allow-ourselves-feel-optimism/">https://www.washingtonpost.com/magazine/2021/04/12/climate-news-is-relentlessly-objectively-grim-should-we-ever-allow-ourselves-feel-optimism/</a></li>
  <li><a href="https://www.forbes.com/sites/davidcarlin/2021/04/21/fighting-back-against-the-3-ds-of-climate-inaction/?sh=41eca24f18ae">https://www.forbes.com/sites/davidcarlin/2021/04/21/fighting-back-against-the-3-ds-of-climate-inaction/?sh=41eca24f18ae</a></li>
  <li><a href="https://www.vox.com/22595896/climate-change-fire-heat-wave-anxiety">https://www.vox.com/22595896/climate-change-fire-heat-wave-anxiety</a></li>
  <li><a href="https://www.washingtonpost.com/climate-solutions/2021/08/09/change-ipcc-report/">https://www.washingtonpost.com/climate-solutions/2021/08/09/change-ipcc-report/</a></li>
  <li><a href="https://www.cnn.com/2021/08/10/opinions/frightening-climate-change-report-fred-krupp/index.html">https://www.cnn.com/2021/08/10/opinions/frightening-climate-change-report-fred-krupp/index.html</a></li>
</ul>]]></content><author><name></name></author><category term="climate" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://self.brockmuellers.com/assets/images/redwood_ring.jpg" /><media:content medium="image" url="https://self.brockmuellers.com/assets/images/redwood_ring.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Morton’s neuroma treatments</title><link href="https://self.brockmuellers.com/health/2020/02/28/neuroma.html" rel="alternate" type="text/html" title="Morton’s neuroma treatments" /><published>2020-02-28T00:00:00+00:00</published><updated>2020-02-28T00:00:00+00:00</updated><id>https://self.brockmuellers.com/health/2020/02/28/neuroma</id><content type="html" xml:base="https://self.brockmuellers.com/health/2020/02/28/neuroma.html"><![CDATA[<p>I’d like to avoid surgery for my Morton’s neuroma (as well as other permanent options like alcohol injections), so I’m trying all of these first. I’ve spent a ton of time trawling the internet for all of this, so here’s the comprehensive list. Hopefully it can help someone else.</p>

<h3 id="mechanical">Mechanical</h3>

<p><strong>Footwear</strong>
These changes take pressure off metatarsals and encourage them to spread.</p>
<ul>
  <li>Correct Toes</li>
  <li>Metatarsal pads</li>
  <li>Traditional orthotics (these can be a crutch, not a solution on their own)</li>
  <li>Low heel! Even running shoes often have a high heel-to-toe drop</li>
  <li>Toe socks (Injinji)</li>
  <li>Wide/high toe boxes (a tip - take out the insole, stand on it in bare feet, see if you hang over the edge)</li>
  <li>Thinner/looser socks</li>
  <li>Skip bottom eyelets when lacing shoes</li>
  <li>Well-fitted shoes (if they are too big, you slide around and that puts more pressure on it)</li>
  <li>Adapt existing shoes: use a shoe stretcher; trim above tongue or beside toes to allow toes to spread; use thinner inserts</li>
</ul>

<p><strong>Functional correction</strong></p>
<ul>
  <li>Physical therapy (stretching, massage, toe strength, ankle strength, <a href="https://www.sportsrec.com/424817-types-of-physical-therapy-for-mortons-neuroma.html">some examples</a>)</li>
  <li>Foot self-massage</li>
  <li>Stretching calves (and maybe ankles) - this one seems very important.</li>
  <li><a href="https://www.youtube.com/watch?v=pr8KnlMDez0">Metatarsal mobilization</a></li>
  <li>Foot/glute strength</li>
  <li>Exercises: <a href="https://www.youtube.com/watch?v=W0MgZYsIqrw">set 1</a>, <a href="https://naturalfootgear.com/blogs/education/17878300-exercises-for-foot-pain">set 2</a> <a href="https://naturalfootgear.com/blogs/education/17915320-top-exercises-for-feet-toes">set 3</a></li>
</ul>

<h3 id="calming-the-nerve-and-surrounding-tissues">Calming the nerve and surrounding tissues</h3>

<p><strong>Reducing inflammation/pain</strong></p>
<ul>
  <li>Ice, ice massage</li>
  <li>Cortisone shots (they’re not great for other tissues though)</li>
  <li>Anti-inflammatories (NSAIDs, herbal)</li>
  <li>Ultrasound?</li>
  <li>Rest (but not too much)</li>
  <li>Prescription meds for nerve pain</li>
</ul>

<p><strong>Reducing pressure on the nerve</strong></p>
<ul>
  <li>Cushioned shoes</li>
  <li>Change up shoes frequently to put pressure on different areas</li>
  <li>Yoga Toes (improve mobility)</li>
  <li>Toe spacer socks while sleeping</li>
</ul>

<h3 id="promoting-bloodflowhealing">Promoting bloodflow/healing</h3>

<ul>
  <li>Contrast therapy</li>
  <li>Taping with kinesio tape (more on that below)</li>
  <li><a href="https://naturalfootgear.com/blogs/education/17882504-foot-circulation-tips">Topicals like menthol or capsaicin</a></li>
  <li>Massage of surrounding areas</li>
  <li>Laser therapy (MLS)?</li>
</ul>

<h3 id="living-with-the-pain">Living with the pain</h3>

<ul>
  <li>Find another form of exercise</li>
  <li>“Lean into” other hobbies</li>
  <li>Keep up activity as much as possible - drive, bike, use a cane, go slow</li>
  <li>Meditation, therapy</li>
</ul>

<h3 id="ideas-random-people-have-mentioned">Ideas random people have mentioned</h3>

<ul>
  <li>Muscle relaxer cream</li>
  <li>CBD cream</li>
  <li>Oral THCA for nerve pain</li>
  <li>Acupuncture</li>
  <li>Prolotherapy</li>
  <li>Chiropractors (breaking up scar tissue)</li>
  <li>Could be misdiagnosed as tarsal tunnel syndrome?</li>
  <li>Nerve pain: neuro-eze/neuro-one topicals, lidoderm patches</li>
  <li>Ball of foot <a href="https://www.youtube.com/watch?time_continue=381&amp;v=6vS8s2zLOSU&amp;feature=emb_logo">wrap</a></li>
  <li>Nerve flossing (instructions <a href="https://www.dynamicchiropractic.com/mpacms/dc/article.php?id=56083&gt;">here</a> and <a href="https://www.youtube.com/watch?v=g94F8g_jzGg">here</a>)</li>
  <li>Treat issues with lower back? L5 nerve; core strengthening</li>
  <li><a href="https://www.youtube.com/watch?v=q6ROKuM3rjs&amp;t=8s">This massage</a> which seems easy to do wrong</li>
  <li>Other injections: capsaicin, stem cells, B vitamins, platelet rich plasma</li>
  <li>Trim hole in insole under neuroma</li>
</ul>

<hr />

<h3 id="about-taping">About taping</h3>

<p>Taping strategies:</p>
<ul>
  <li><a href="https://www.youtube.com/watch?v=JdEv7ayilec">https://www.youtube.com/watch?v=JdEv7ayilec</a>
    <ul>
      <li>piece 1: cut four strips most of the way down</li>
      <li>anchor behind heel</li>
      <li>flex toes, put strips between and above toes with some stretch, anchor midfoot</li>
      <li>piece 2: short piece of tape to cover ends of strips</li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/watch?v=Db9qgYPrku4">https://www.youtube.com/watch?v=Db9qgYPrku4</a>
    <ul>
      <li>piece 1: cut piece that fits all the way around ball of foot</li>
      <li>anchor near big toe, stretch ~50-70% under foot and anchor on outer side of foot</li>
      <li>stretch over foot (50-70%) and anchor by big toe</li>
      <li>piece 2: fits over foot horizontally just behind first piece, stretch 70% and put over foot, then anchor sides</li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/watch?v=GqgQF4D792g">https://www.youtube.com/watch?v=GqgQF4D792g</a>
    <ul>
      <li>piece 1: 10 inch piece with two diamond holes in the middle, fit over problematic toes, 10% tension for the middle third of tape</li>
      <li>anchor at ends</li>
      <li>piece 2: go around under base of foot with 10% tension, anchor top midfoot</li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/watch?v=u_BTmrYSxg8">https://www.youtube.com/watch?v=u_BTmrYSxg8</a>
    <ul>
      <li>piece 1: cut three strips most of the way down</li>
      <li>anchor top midfoot, put strips between toes with some tension about an inch on either side</li>
      <li>anchor the rest on bottom of foot</li>
      <li>piece 2: strip under foot on ball, 100% tension , anchor at sides of foot</li>
    </ul>
  </li>
  <li><a href="https://www.youtube.com/watch?v=xLA11cJSp8A&amp;t=11s">https://www.youtube.com/watch?v=xLA11cJSp8A&amp;t=11s</a>
    <ul>
      <li>piece 1: cut strip to go mostly around ball of foot</li>
      <li>stretch about 100% under ball of foot</li>
      <li>anchor at sides and top</li>
    </ul>
  </li>
</ul>

<p>What’s in common between these strategies?</p>
<ul>
  <li>piece under ball of foot, between 10-100% stretch</li>
  <li>piece that goes between toes</li>
</ul>

<p>Why does taping work? (from comments on <a href="https://www.youtube.com/watch?v=Db9qgYPrku4">https://www.youtube.com/watch?v=Db9qgYPrku4</a>)</p>
<blockquote>
  <blockquote>
    <p>If you wrapped the tape completely around with consistent stretch all the way thru then yes, it would compress the tissue. That’s why I incorporated anchor points on both ends with zero stretch, so the tape behaves as a cross fiber taping for increased circulation, which will create surface area by lifting the skin towards least resistance. away from the body.</p>
  </blockquote>
</blockquote>

<hr />

<h3 id="resources">Resources</h3>

<p>There are a million articles that say roughly the same thing - metatarsal pads, orthotics, cortisone shots, surgery. Here are some more thorough and alternative views. They may or may not be reliable.</p>

<p>Some simple conservative treatments; more thorough than other sites</p>
<ul>
  <li><a href="https://bootbomb.com/mortons-neuroma-conservative-treatments/">https://bootbomb.com/mortons-neuroma-conservative-treatments/</a></li>
  <li><a href="https://www.mortonsneuroma.com/mortons-neuroma/treatments/conservative-options/">https://www.mortonsneuroma.com/mortons-neuroma/treatments/conservative-options/</a></li>
</ul>

<p>I like <a href="http://foodsforlonglife.blogspot.com/2016/04/traditional-and-alternative-methods-of.html">this article</a> - thorough review of someone’s experience</p>

<p><a href="http://www.drblakeshealingsole.com/2015/01/mortons-neuroma-general-principles.html">This guy</a> has a lot of ideas and opinions; he says you should always address all three of mechanical, inflammatory, and neurological issues.</p>

<p>A lot of alternative views <a href="http://teamdoctorsblog.com/2012/12/24/mortons-neuroma-self-help-tips-treatment-and-prevention-from-the-barefoot-running-doctor-at-team-doctors/">here</a>, with focus on massage/stretching.</p>]]></content><author><name></name></author><category term="health" /><summary type="html"><![CDATA[I’d like to avoid surgery for my Morton’s neuroma (as well as other permanent options like alcohol injections), so I’m trying all of these first. I’ve spent a ton of time trawling the internet for all of this, so here’s the comprehensive list. Hopefully it can help someone else.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://self.brockmuellers.com/assets/images/foot.jpg" /><media:content medium="image" url="https://self.brockmuellers.com/assets/images/foot.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Web scraper to find new products</title><link href="https://self.brockmuellers.com/software/2020/02/26/web-scraper-to-find-new-products.html" rel="alternate" type="text/html" title="Web scraper to find new products" /><published>2020-02-26T00:00:00+00:00</published><updated>2020-02-26T00:00:00+00:00</updated><id>https://self.brockmuellers.com/software/2020/02/26/web-scraper-to-find-new-products</id><content type="html" xml:base="https://self.brockmuellers.com/software/2020/02/26/web-scraper-to-find-new-products.html"><![CDATA[<p>I’m a huge fan of the REI used gear program. Unfortunately, they don’t always have all products available. It’s a pain to keep checking the used gear website until the product is available, and I’m often inclined to go buy it new. So, I thought I’d write a little script to scrape the site and do the work for me!</p>

<p>I’m starting off with a local script that I can run manually. The next step will be to automate it, but that’s a topic for another time.</p>

<p>This is my first foray into web scraping! I don’t know why it took me so long - it’s not particularly hard. Here’s my process.</p>

<hr />

<h2 id="getting-started">Getting started</h2>

<p>Let’s get set up!</p>

<p>For starters, I’m using Python 3.6.9 and pip3 as a package installer. (I should probably get to the modern age and update to 3.8, but this works just fine.)</p>

<p>Since I’m trying something new, I did a bit of web sleuthing to find out how people typically do this sort of thing. Python seems to be a common choice, which makes sense - it’s a great language for quick scripts and munging data. The <code class="language-plaintext highlighter-rouge">requests</code> library is a clear choice for making HTTP requests, and Beautiful Soup is popular for pulling data from HTML.</p>

<p>Let’s go ahead and install those dependencies:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pip3 install requests
$ pip3 install beautifulsoup4
</code></pre></div></div>

<p>In fact, we can be good Python developers and pin those versions in a <code class="language-plaintext highlighter-rouge">requirements.txt</code> file. (I wouldn’t typically care for a little project like this, but I’d like to be able to deploy this to something like AWS Lambda. Being explicit about your dependencies is nice when you’re running stuff externally like that.) This file can go in the project root directory and looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>beautifulsoup4==4.8.2
requests==2.23.0
</code></pre></div></div>

<p>We can install dependencies from that file by running <code class="language-plaintext highlighter-rouge">pip3 install -r requirements.txt</code>.</p>

<hr />

<p>I’ll start out by playing around in IPython (<code class="language-plaintext highlighter-rouge">pip3 install ipython</code>). It’s my favorite way to play around with Python. I’ll also create a file to hold code that I want to keep. Let’s call it <code class="language-plaintext highlighter-rouge">scrape.py</code>. (People like using Jupyter Notebooks for this sort of interactive development, but I’ve never tried it and it doesn’t seem necessary in this case.)</p>

<p>Starting IPython is easy: just run <code class="language-plaintext highlighter-rouge">ipython</code>.</p>

<hr />

<h2 id="playing-around-with-our-new-libraries">Playing around with our new libraries</h2>

<p>Great! We can start writing code now. First, I know that we’ll need to make an HTTP request to get the page we’re scraping. Let’s work that out.</p>

<p>The Python requests library is easy to use - docs are <a href="https://requests.readthedocs.io/en/master/">here</a>. Let’s try getting the HTML for a page in IPython.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="kn">import</span> <span class="nn">requests</span>
<span class="o">&gt;&gt;</span> <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"http://www.google.com"</span><span class="p">)</span>
<span class="o">&gt;&gt;</span> <span class="n">html</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span>
</code></pre></div></div>

<p>That’s some HTML! Now we need to choose the right URL to scrape. This project is automating something that I already do - making a search for a product I’m interested in. So let’s use the URL for that search. There may be a better way to scrape the site for products, but this is a decent place to start.</p>

<p>A big blob of HTML text isn’t particularly useful on it’s own. That’s where Beautiful Soup comes in. We’ll follow the quick start instructions in the <a href="https://beautiful-soup-4.readthedocs.io/en/latest/">docs</a> and get “soup” for our page:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="n">url</span> <span class="o">=</span> <span class="s">"https://www.rei.com/used/shop/search?q=magma%2030%20sleeping%20bag"</span>
<span class="o">&gt;&gt;</span> <span class="n">html</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="n">text</span>
<span class="o">&gt;&gt;</span> <span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>
<span class="o">&gt;&gt;</span> <span class="n">soup</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s">'html.parser'</span><span class="p">)</span>
</code></pre></div></div>

<p>Now we have a soup object! We can run stuff like <code class="language-plaintext highlighter-rouge">soup.a</code> to get <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> tags in the HTML.</p>

<hr />

<p>That’s a nice, self-contained piece of functionality there. Let’s make a function in the <code class="language-plaintext highlighter-rouge">scrape.py</code> file to hold that logic, so we can use it later.</p>

<p>(I had to google how to write a python function, yikes. It’s been a while.)</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>
<span class="kn">import</span> <span class="nn">requests</span>

<span class="k">def</span> <span class="nf">get_soup</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">,</span> <span class="s">'html.parser'</span><span class="p">)</span>
</code></pre></div></div>

<p>To use this in IPython, import the module: <code class="language-plaintext highlighter-rouge">import scrape</code>. When we make changes to the file, we need to reload the module. The simplest way to do that is restart IPython, but I usually prefer:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="kn">import</span> <span class="nn">importlib</span>
<span class="o">&gt;&gt;</span> <span class="n">importlib</span><span class="p">.</span><span class="nb">reload</span><span class="p">(</span><span class="n">scrape</span><span class="p">)</span>
</code></pre></div></div>
<hr />

<p>Now things get a little tricky. What do we want to pull out of the page? How do we select the right elements, and make sure it won’t break when REI makes minor changes to the website?</p>

<p>Let’s use Chrome DevTools to examine the page. All of the products that come up in the search are in a <code class="language-plaintext highlighter-rouge">&lt;ul&gt;</code> list, and each <code class="language-plaintext highlighter-rouge">&lt;li&gt;</code> item has the class <code class="language-plaintext highlighter-rouge">"TileItem</code>”. Nothing else on the page has the class <code class="language-plaintext highlighter-rouge">"TileItem"</code>, so that seems like a safe thing to filter by.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="n">items</span> <span class="o">=</span> <span class="n">search_soup</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="s">"li"</span><span class="p">,</span> <span class="n">class_</span><span class="o">=</span><span class="s">"TileItem"</span><span class="p">)</span>
</code></pre></div></div>

<p>Now we have the correct items, but each of those is still a long blob of HTML. We just want the title of the item. Titles are located in a <code class="language-plaintext highlighter-rouge">&lt;span&gt;</code> with class <code class="language-plaintext highlighter-rouge">"title"</code>. So, to get the title of the first item, we can run:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="n">title</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="s">"span"</span><span class="p">,</span> <span class="n">class_</span><span class="o">=</span><span class="s">"title"</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="n">contents</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="s">"Magma 30 Sleeping Bag"</span>
</code></pre></div></div>

<p>Beautiful Soup is a powerful tool, and you can filter HTML in almost any way imaginable. <a href="https://beautiful-soup-4.readthedocs.io/en/latest/">The docs</a> are pretty comprehensive. It’s a bit of a challenge to choose filters that are not too brittle, but otherwise it’s easy to use. I’m a fan.</p>

<hr />

<h2 id="setting-up-an-executable-script">Setting up an executable script</h2>

<p>Finally this is starting to come together. I’d like to run what we have as a script. To do this, we need a) our file to be executable, and b) an entrypoint.</p>

<p>To make the file executable, we need to put this line at the top: <code class="language-plaintext highlighter-rouge">#!/usr/bin/env python3</code>. Additionally, in the command line, we run <code class="language-plaintext highlighter-rouge">chmod +x scrape.py</code> to give us permission to execute the file.</p>

<p>There also needs to be an entrypoint that specifies the code to run. In <code class="language-plaintext highlighter-rouge">scrape.py</code>, let’s add this block at the bottom of the file:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="c1"># We'll replace this with the code we want to run.
</span>    <span class="k">print</span><span class="p">(</span><span class="s">"This is a script!"</span><span class="p">)</span>
</code></pre></div></div>

<p>Now run the script!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python3 scrape.py
This is a script!
</code></pre></div></div>

<hr />

<h2 id="putting-it-all-together">Putting it all together</h2>

<p>Now we can put it all together!</p>

<p>To do this, there are a couple of important decisions we need to make: program input and program output.</p>

<h3 id="how-do-we-get-the-search-urls">How do we get the search URLs?</h3>

<p>We could just be lazy and hardcode URLs in. But it’s nice to be able to edit code and the input URLs separately, especially if this is deployed to a service like AWS Lambda. You don’t necessarily want to redeploy your Lambda every time you want to look for a new product. So, for now, we’ll just put them in a file in the project root directory.</p>

<p>Open the file (use a <a href="https://www.geeksforgeeks.org/context-manager-in-python/">context manager</a> so resources are released appropriately).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'rei_used_gear_searches.txt'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
    <span class="n">urls</span> <span class="o">=</span> <span class="nb">file</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">splitlines</span><span class="p">()</span>
</code></pre></div></div>

<p>Apparently we need to use <code class="language-plaintext highlighter-rouge">read().splitlines()</code> instead of <code class="language-plaintext highlighter-rouge">readlines()</code>, otherwise we’ll end up with <code class="language-plaintext highlighter-rouge">\n</code> at the end of every value. Good to know.</p>

<p>Hmmm…we could make this a bit better. Search URLs are pretty obtuse and hard to read. It’s not very user-friendly to spit out a bunch of URLs in the results. So let’s label them. A CSV of <code class="language-plaintext highlighter-rouge">$LABEL, $URL</code> should work.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">urls</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">"r"</span><span class="p">)</span> <span class="k">as</span> <span class="n">csvfile</span><span class="p">:</span>
    <span class="n">reader</span> <span class="o">=</span> <span class="n">csv</span><span class="p">.</span><span class="n">reader</span><span class="p">(</span><span class="n">csvfile</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">reader</span><span class="p">:</span>
        <span class="n">urls</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="s">"label"</span><span class="p">:</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="s">"url"</span><span class="p">:</span> <span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]})</span>
</code></pre></div></div>

<p>The output:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="n">urls</span>
<span class="p">[{</span><span class="s">'label'</span><span class="p">:</span> <span class="s">'magma30regular'</span><span class="p">,</span> <span class="s">'url'</span><span class="p">:</span> <span class="s">'https://www.rei.com/used/shop/search?q=magma%2030%20sleeping%20bag&amp;size=REGULAR%20-%20RIGHT%20ZIP'</span><span class="p">},</span> <span class="p">{</span><span class="s">'label'</span><span class="p">:</span> <span class="s">'altras'</span><span class="p">,</span> <span class="s">'url'</span><span class="p">:</span> <span class="s">'https://www.rei.com/used/shop/womens-footwear?brand=Altra&amp;size=6'</span><span class="p">}]</span>
</code></pre></div></div>

<p>Our source CSV file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>magma30regular,https://www.rei.com/used/shop/search?q=magma%2030%20sleeping%20bag&amp;size=REGULAR%20-%20RIGHT%20ZIP
altras,https://www.rei.com/used/shop/womens-footwear?brand=Altra&amp;size=6
</code></pre></div></div>

<h3 id="what-results-should-we-output">What results should we output?</h3>

<p>We could just print the results of each search URL. That would be easy. But, from experience, I know that sometimes I want to know which products have been added instead of which products exist.</p>

<p>For example, perhaps I’m looking for a particular brand of shoe. I really like Altra shoes, for their zero drop and natural foot shape. There are a number of models I’d be interested in, but not all. I might do a search for them today, and not want any of the models currently available on the used gear site. In that case, I’ll want my tool to tell me if a new model has been added, so I can check it out.</p>

<p>With this goal, we need to come up with a way to tell if a product has been added since we last ran this tool. To do that, we need to save results somewhere, so we can compare results from this run with the last run.</p>

<p>Writing our results to a file is a simple way to do this. There are a couple things to consider:</p>
<ul>
  <li>We’ll need to timestamp each file, so we know which is most recent. (A naive way to do that would be to delete the old results file and write a new file. But there’s a gotcha - this isn’t robust to failure. If the program dies in between deleting the old results file and writing the new file - totally possible - we won’t be able to rerun the program and make a comparison.)</li>
  <li>We’ll also choose to write one file per product, where files are labeled with the product label (that CSV with labels in it is sure handy!). That’s an arbitrary choice.</li>
</ul>

<p>Here’s the code for writing the results of one search.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">save_products_list</span><span class="p">(</span><span class="n">results</span><span class="p">,</span> <span class="n">label</span><span class="p">,</span> <span class="n">target_dir</span><span class="p">):</span>
    <span class="n">timestamp</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nb">round</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">())))</span>
    <span class="n">filename</span> <span class="o">=</span> <span class="n">target_dir</span> <span class="o">+</span> <span class="n">label</span> <span class="o">+</span> <span class="s">'_'</span> <span class="o">+</span> <span class="n">timestamp</span> <span class="o">+</span> <span class="s">".txt"</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
        <span class="p">[</span><span class="nb">file</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">r</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">]</span>
</code></pre></div></div>

<p>Now that we have a list saved, let’s run a comparison. This is a bit messy, but it’ll do the job. It runs once per search URL.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">compare_to_previous_products</span><span class="p">(</span><span class="n">current_products_list</span><span class="p">,</span> <span class="n">product_label</span><span class="p">,</span> <span class="n">target_dir</span><span class="p">):</span>
    <span class="c1"># Get and sort all result files (so they're in order of timestamp)
</span>    <span class="n">sorted_results_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">target_dir</span><span class="p">))</span>
    <span class="c1"># Pick out the result files corresponding to this particular search label
</span>    <span class="n">previous_files</span> <span class="o">=</span> <span class="p">[</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">sorted_results_files</span> <span class="k">if</span> <span class="n">product_label</span> <span class="ow">in</span> <span class="n">f</span><span class="p">]</span>

    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">target_dir</span> <span class="o">+</span> <span class="n">previous_files</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
        <span class="n">previous_products_list</span> <span class="o">=</span> <span class="nb">file</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">splitlines</span><span class="p">()</span>

    <span class="k">if</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">previous_products_list</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">current_products_list</span><span class="p">):</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"FOUND A DIFFERENCE FOR PRODUCT {} (from {})"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span>
            <span class="n">product_label</span><span class="p">,</span> <span class="n">previous_files</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]))</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"OLD: "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">previous_products_list</span><span class="p">))</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"NEW: "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">current_products_list</span><span class="p">))</span>
</code></pre></div></div>

<p>Good enough!</p>

<hr />

<p>It’s also worth trying to make this a bit robust to changes on the REI website.</p>

<ul>
  <li>What if items no longer have the class <code class="language-plaintext highlighter-rouge">"TileItem"</code>? This could be a problem - then our list of items will just be empty and it’ll appear as if there are no products. Let’s use a handy feature of the website: when you do a search, it shows you how many results were found in a div that says “x results”. That div has the class <code class="language-plaintext highlighter-rouge">"count</code>”, and doesn’t exist if no products were found. So we’ll compare that to the number of <code class="language-plaintext highlighter-rouge">"TileItem"</code>s we found.
    <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expected_count_div</span> <span class="o">=</span> <span class="n">search_soup</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">"div"</span><span class="p">,</span> <span class="n">class_</span><span class="o">=</span><span class="s">"count"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">expected_count_div</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"No products found."</span><span class="p">)</span>

<span class="k">else</span><span class="p">:</span>
    <span class="n">expected_count</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">expected_count_div</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">"span"</span><span class="p">).</span><span class="n">contents</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">items</span><span class="p">)</span> <span class="o">!=</span> <span class="n">expected_count</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nb">AssertionError</span><span class="p">(</span>
            <span class="s">"Error on page {}: found {} items, but page said there would be {}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span>
                <span class="n">url</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">items</span><span class="p">,</span> <span class="n">expected_count</span><span class="p">)))</span>

</code></pre></div>    </div>
  </li>
  <li>
    <p>What if the “x results” <code class="language-plaintext highlighter-rouge">count</code> div ceases to exist, or its class name has changed so we can’t find it? That would be okay if the <code class="language-plaintext highlighter-rouge">TileItem</code>s are still around - the assertions we just wrote will fail. But what if the REI developers change both the <code class="language-plaintext highlighter-rouge">count</code> and <code class="language-plaintext highlighter-rouge">TileItem</code> class names? I’m going to be lazy and not solve this problem right now, but its worth noting.</p>
  </li>
  <li>What if there isn’t a <code class="language-plaintext highlighter-rouge">&lt;span&gt;</code> containing the item’s title? The code will blow up if that happens. Not elegantly, but we’ll know. This line will happily throw an error.
    <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">title</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="s">"span"</span><span class="p">,</span> <span class="n">class_</span><span class="o">=</span><span class="s">"title"</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="n">contents</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div>    </div>
  </li>
</ul>

<hr />

<p>Besides that, there’s not too much to think about. We can clean the code up, handle some edge cases, etc. We’ll also liberally sprinkle in print statements so it’s clear what’s happening when the code runs.</p>

<hr />

<h2 id="results">Results</h2>

<p>So what do we end up with? Our first run is not particularly interesting.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python3 main.py
Opening search URL file rei_used_search_urls.csv
Found 2 search URLS

----------------------------

EXAMINING PRODUCT: magma30regular
Search URL: https://www.rei.com/used/shop/search?q=magma%2030%20sleeping%20bag&amp;size=REGULAR%20-%20RIGHT%20ZIP&amp;size=REGULAR%20-%20LEFT%20ZIP&amp;size=REGULAR
No products found.

RESULT:
This is the first search for product magma30regular

----------------------------

EXAMINING PRODUCT: altras
Search URL: https://www.rei.com/used/shop/womens-footwear?brand=Altra&amp;size=6
Found product Olympus 2.0 Trail-Running Shoes
Found product Torin 2.5 Road-Running Shoes
Found product Torin 2.5 Road-Running Shoes
Found product Paradigm 2.0 Road-Running Shoes

RESULT:
This is the first search for product altras

</code></pre></div></div>

<p>But when we run it the next day:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python3 main.py
Opening search URL file rei_used_search_urls.csv
Found 2 search URLS

----------------------------

EXAMINING PRODUCT: magma30regular
Search URL: https://www.rei.com/used/shop/search?q=magma%2030%20sleeping%20bag&amp;size=REGULAR%20-%20RIGHT%20ZIP
Found product Magma 30 Sleeping Bag

RESULT:
FOUND A DIFFERENCE FOR PRODUCT magma30regular (from magma30regular_1582257372.txt)
OLD: []
NEW: ['Magma 30 Sleeping Bag']

----------------------------

EXAMINING PRODUCT: altras
Search URL: https://www.rei.com/used/shop/womens-footwear?brand=Altra&amp;size=6
Found product Olympus 2.0 Trail-Running Shoes
Found product Torin 2.5 Road-Running Shoes
Found product Torin 2.5 Road-Running Shoes
Found product Paradigm 2.0 Road-Running Shoes

RESULT:
No difference found for product altras

</code></pre></div></div>

<p>Hooray! The REI Magma 30 sleeping bag that I’ve been waiting for is finally available used. This little tool is proving useful already.</p>

<p>Coming another day: running this automatically, probably via AWS Lambda.</p>

<p>See the proof-of-concept code <a href="https://github.com/brockmuellers/find-products/tree/303bc37141469ad2776830618ed1832f056808ae">here</a>.</p>

<hr />

<p><a href="https://medium.com/@sara.brockmueller/web-scraper-to-find-new-products-1889726de573">Also published on medium.com</a></p>]]></content><author><name></name></author><category term="software" /><summary type="html"><![CDATA[I’m a huge fan of the REI used gear program. Unfortunately, they don’t always have all products available. It’s a pain to keep checking the used gear website until the product is available, and I’m often inclined to go buy it new. So, I thought I’d write a little script to scrape the site and do the work for me!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://self.brockmuellers.com/assets/images/lost_coast_hikers.jpg" /><media:content medium="image" url="https://self.brockmuellers.com/assets/images/lost_coast_hikers.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Guidelines for Java development</title><link href="https://self.brockmuellers.com/software/2020/02/05/java-guidelines.html" rel="alternate" type="text/html" title="Guidelines for Java development" /><published>2020-02-05T00:00:00+00:00</published><updated>2020-02-05T00:00:00+00:00</updated><id>https://self.brockmuellers.com/software/2020/02/05/java-guidelines</id><content type="html" xml:base="https://self.brockmuellers.com/software/2020/02/05/java-guidelines.html"><![CDATA[<p>At a previous job, I collected an incomplete set of “best practice” guidelines for Java developers on the team. I had three reasons for doing this:</p>

<ol>
  <li>
    <p>I made a lot of mistakes when first learning Java, and wanted to prevent others from falling in the same traps.</p>
  </li>
  <li>
    <p>Some junior developers were starting to work on the Java codebase, and I ended up repeating a lot of the same advice in code reviews.</p>
  </li>
  <li>
    <p>Everyone had differing opinions. That’s quite fine, but getting everyone to agree on a “correct” way of doing things leads to a much cleaner codebase. This required quite a bit of compromise, but it reduced friction afterwards.</p>
  </li>
</ol>

<p>I started out with scattered list of contentious points and common mistakes. I talked through the difficult points with other people, got reviews from interested parties, and put the final result in our developer docs. It was easy to direct developers to it when needed, and easy to append new points we wanted to specify.</p>

<h3 id="why-should-you-do-this-too">Why should you do this too?</h3>

<p>It’s important for a software team to have agreed-upon guidelines like these, whether codified or not. Clearly there’s benefit to sharing knowledge of common mistakes. Code reviews get easier - less nitpicking required. It also keeps the peace. A team can spend endless time <a href="https://en.wikipedia.org/wiki/Law_of_triviality">bikeshedding</a>, returning to the same issues repeatedly, never leaving anyone happy. Instead, you can make the controversial decisions once and leave it at that.</p>

<p>Doing this allows the whole team to own the code. Codebases become difficult to read when everyone writes code according to their own preference. It’s difficult to predict where things are defined and reason about behavior. And it’s never comfortable to fiddle around with a class that someone else wrote in their own style.</p>

<p>This obviously should be a living document, in a team wiki or something similar. Making it easy to edit keeps it relevant to your ever-changing team and product.</p>

<p>As a side note: a great way to reduce the number of guidelines you need is to implement a style guide (for example, the <a href="https://google.github.io/styleguide/javaguide.html">Google style guide</a>). It takes basically no effort to follow it if you use a formatter like <a href="https://github.com/diffplug/spotless">Spotless</a>. Similarly, you can use a static analysis tool like <a href="https://github.com/google/error-prone">Error Prone</a> to prevent people from committing certain classes of mistakes. I’ll also point out the <a href="https://docs.oracle.com/javase/tutorial/">Oracle Java Tutorial</a> as a great resource for Java beginners.</p>

<hr />

<p>With that, here are the guidelines that we used (edited a bit to be less domain-specific).</p>

<h3 id="performance">Performance</h3>

<p>Write for readability over performance. The reason for this is two-fold:</p>

<ol>
  <li>
    <p>The JVM JIT compiler is magic. In many cases, it can optimize your code better than you will, through branch predictions, loop unrolling, inlining methods, allocation elimination, lock elision, and much, much more.</p>
  </li>
  <li>
    <p>Most performance “optimizations” implemented by the programmer have a very minimal effect, especially given the aforementioned compile optimizations. Writing for readability will likely save more time than the programmer’s optimization. This is both time spent by engineers trying to understand the code, and time spent chasing down obscure bugs.</p>
  </li>
</ol>

<p>Obviously there will be some complex situations when this guideline does not hold. Use your best judgement.</p>

<h3 id="exceptions">Exceptions</h3>

<ul>
  <li>
    <p>Use checked exceptions (ex. <code class="language-plaintext highlighter-rouge">IOException</code>, <code class="language-plaintext highlighter-rouge">InterruptedException</code>) for recoverable errors, and unchecked exceptions (ex. <code class="language-plaintext highlighter-rouge">RuntimeException</code>, <code class="language-plaintext highlighter-rouge">AssertionError</code>, <code class="language-plaintext highlighter-rouge">NullPointerException</code>) for unrecoverable errors. Both types of exceptions should have clear and descriptive messages.</p>
  </li>
  <li>
    <p>Checked exceptions should be specific. In many cases it is useful to create a domain-specific checked exception.</p>
  </li>
  <li>
    <p>Do not catch unchecked exceptions. Their role is not to be caught, it is to kill the program loudly. The exception to this is when programming external-facing APIs, where there may be certain expected behavior in the case of an exception.</p>
  </li>
</ul>

<h3 id="documentation">Documentation</h3>

<p>Public methods and classes should always have a docstring that describes their role, expectations, and edge-case behavior. The format for this is often made clear in style guides. Documentation for simple getters and setters is not necessary. Document any private methods and classes if it will be useful for the reader.</p>

<h3 id="safety">Safety</h3>

<ul>
  <li>
    <p>Be careful with mutable class fields. Use immutable types when possible. Do not provide outside callers direct references to the field — return a copy, if practical. Encapsulate any mutation inside class methods, and consider what happens if multiple callers are simultaneously calling these methods.</p>
  </li>
  <li>
    <p>Do not write fun and tricky logic. Boring logic is easy to test and easy to trust. If your logic is fun, separate it into boring and individually-tested pieces.</p>
  </li>
  <li>
    <p>Handle nulls explicitly. If a field or argument may be null, use the javax <code class="language-plaintext highlighter-rouge">@Nullable</code> annotation to make the intention crystal clear.</p>
  </li>
  <li>
    <p>Consider wrapping common calls to external libraries if they have undesirable, unpredictable, or poorly defined behavior. Common examples of this in Java can be found in OpenCV and the AWS SDK. This can save you from debugging the same issue repeatedly — put the messy library call in a method, test its behavior, catch exceptions and validate input as needed, and use that method in the future. And remember — if these methods aren’t easily discoverable and useable, or if the rest of the team finds them unnecessary, putting time into building a wrapper will be a waste.</p>
  </li>
</ul>

<h3 id="collections">Collections</h3>

<p>[This seems like a silly thing to talk about at length, but we worked with a number of collections that needed almost the same interface/behavior as native Java collection types. There were weeks of heated debate over the correct approach to this, so we came to a decision and codified it. No one was quite happy, but it stopped being an issue.]</p>

<p>Do not extend native Java collection types (e.g. <code class="language-plaintext highlighter-rouge">HashMap</code>, <code class="language-plaintext highlighter-rouge">ArrayList</code>). It is very difficult to ensure that the new object will behave as expected when treated as the base type. Extending collection types is standard practice in some other languages, but Java programmers generally discourage it.</p>

<p>For simple data storage, an alternative is to store your desired collection as an attribute of another object. If you want to expose the collection methods, delegate them. If you want custom methods for your collection, you can write them on your outer class.</p>

<p>In more complex situations, consider implementing a collection interface (like <code class="language-plaintext highlighter-rouge">Map</code> instead of <code class="language-plaintext highlighter-rouge">HashMap</code>). This forces you to write all of your class’s methods, which makes behavior more predictable. (This strategy is an example of using the <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance">composition over inheritance</a> principle.)</p>

<h3 id="asynchronous-code">Asynchronous code</h3>

<p>For asynchronous code, use <code class="language-plaintext highlighter-rouge">CompletableFuture</code>s. They allow async computation to be performed without blocking a thread. One common use case is when making network calls, which involve dead time spent waiting for responses. There are admittedly drawbacks to <code class="language-plaintext highlighter-rouge">CompletableFuture</code>s, but using them everywhere allows us to chain asynchronous callbacks, and more easily reason about async processes.</p>

<h3 id="general-software-principles">General software principles</h3>

<ul>
  <li>
    <p>Choose <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance">composition over inheritance</a> whenever reasonable. In Java, this means implementing interfaces instead of extending base classes. Implementing interfaces allows your classes to guarantee multiple behavioral contracts, instead of being bound to a specific base implementation that is not robust to refactoring.</p>
  </li>
  <li>
    <p>Keep classes and methods small. Classes should represent simple entities. Methods should have one job. This is crucial for testing, refactoring, understanding, and maintaining.</p>
  </li>
  <li>
    <p>Make class fields private, and provide getters when necessary. This is necessary to encapsulate the logic in a class, so callers are not dependent on the class implementation.</p>
  </li>
</ul>

<h3 id="readability">Readability</h3>

<ul>
  <li>
    <p>Avoid large blocks of code. Use whitespace to separate tiny, logical chunks of code. (Do not be afraid of whitespace. It is your friend.) Use inline documentation to describe unclear bits of code, but better yet, restructure your code and rename methods/variables until the code is clear.</p>
  </li>
  <li>
    <p>Name your classes, fields, and methods descriptively. Auto-complete in an IDE removes the effort involved in using longer names, and clear names are what makes your code human-parseable.</p>
  </li>
  <li>
    <p>When writing a piece of code, future readers should not be able to tell what member of the team wrote it. Having an institutionally-consistent codebase allows the whole team to easily read, understand, and alter unfamiliar code.</p>
  </li>
</ul>

<hr />

<p>It’s impossible to have a complete list of guidelines — there are whole books written on this subject. However, if there is contention over an issue, or if something keeps tripping people up, it’s valuable to define what to do in that situation in the future.</p>

<hr />

<p><em><a href="https://medium.com/swlh/guidelines-for-java-development-6cfa9c28fe7e">Also published in The Startup on medium.com</a></em></p>]]></content><author><name></name></author><category term="software" /><summary type="html"><![CDATA[At a previous job, I collected an incomplete set of “best practice” guidelines for Java developers on the team. I had three reasons for doing this:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://self.brockmuellers.com/assets/images/snowy_railing.jpg" /><media:content medium="image" url="https://self.brockmuellers.com/assets/images/snowy_railing.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>