Steven HicksRamblings on development, life, and the development life.2024-01-19T15:23:04.296+00:00https://stevenhicks.me/feed.xmlSteven Hickssteven.j.hicks@gmail.com2023 In Review2024-01-19T15:23:04.296+00:00https://stevenhicks.me/blog/2024/01/2023-in-review/<p>My 2022 review ended with this paragraph:</p>
<blockquote>
<p>This is the most positive, satisfied, content year-in-review I've written in years. I'm proud of myself for finding balance and joy in 2022. I wish you, and myself, the same in 2023.</p>
</blockquote>
<p>How did this forecast pan out?</p>
<p>Wellllllllll maybe okay. I continued to find some joy and balance in 2023...but it was far from sunshine and rainbows. In fact, I hit a pretty bad patch emotionally in the fall of 2023. I've bounced back a bit. And as usual, when I look back at the totality of the year, I find things looked a little better than I thought they did.</p>
<p>For the last few years I've settled into a particular format of year-in-review article, featuring specific categories of my life and many embedded tweets. I'm going to stray from that a bit this year, and tackle it like we might tackle a sprint retro — what went well, and what didn't?</p>
<p>Scratch that, reverse it — what didn't go well, and what did — because it's always nice to end on the high notes. Consider the items that follow to be roughly in order of worst to best.</p>
<h2>What went poorly?</h2>
<h3>I plateaued. In multiple arenas.</h3>
<p>I'm not making very much art right now. I worked on a large project for a gift for my wife this year, and I've had trouble starting something up again since. I have a new pair of cycling shoes to paint, but I've struggled to figure out what to do with them. I'm feeling stuck.</p>
<p>I've been cycling regularly for nearly 30 years, but you wouldn't know it from my speed on a bike. Despite putting in 3000 miles of riding in 2023, a similar number to the previous two years, my power and speed on a bike has basically never changed. This frustrated me a lot in 2023. Rouvy, the platform I use for indoor cycling, sent emails with "something for everyone," and the rides that fit my power profile were labelled "for hobby riders." This was demoralizing and infuriating.</p>
<p>In late autumn, I re-started a membership to the local climbing gym, for the first time since before COVID-19. Within a couple weeks, I'd worked my way back up to a familiar plateau — anything labelled v3 or below was relatively easy for me; anything v4 was impossible.</p>
<p>I feel stuck in my career progression. I enjoy the team I work on and the work that I do, and I have felt like Developer Experience is the place for me since I started...but I fear my "technical" skills are atrophying, and I'm not sure I'm gaining enough other skills to justify that. I'm also getting pretty old for an individual contributor, and I fear that a loss of skills is much worse for me now than, say, ten years ago. I spent most of 2023 (and this first few weeks of 2024 😅) feeling varying levels of discomfort about this.</p>
<h3>I hit a very low point in the early autumn.</h3>
<p>Starting in May, I took a break from my therapist, because things seemed to be going well and scheduling would be difficult over the summer. This seemed fine for a while...but by autumn, with some of the aforementioned plateaus weighing on me, and some challenges at home, I felt pretty bad about myself. To make it worse, I had set a bar for myself that I would return to my therapist "when I was ready for season 2," with a clear idea of themes to work on. I never really got a clear idea of themes...and that prevented me from scheduling an appointment until much later than I needed one.</p>
<h3>I really struggled to create content.</h3>
<p>I published 3 articles in 2023! The fewest I'd published in a year since 2017. What happened??!</p>
<p>(Obviously, the articles I <em>did</em> publish were 🔥 and you should read them. Especially <a href="/blog/2023/03/matches-spoons-and-the-relationship-between-dx-and-ux/">this one about spoons, matches, and developer experience</a>.)</p>
<p>I published the last few episodes of <a href="https://podcasters.spotify.com/pod/show/a-developer-experience">A Deveoper Experience Podcast (season 1)</a> early in the year; most of that work had been done in late 2022. I intended to pick it up again in late autumn/early winter, but I never felt inspired to do it. I might be finding that inspiration now, but I'm not sure.</p>
<p>One subject dominated my content creation — a project I spent a lot of time on with Camunda docs and SEO — and I published that <a href="https://www.youtube.com/watch?v=HJGxdj_8oJ8">as a video</a>, and <a href="/blog/2023/11/how-to-deindex-your-docs-from-google/">an article</a>.</p>
<p>And it's actually that specific content that broke me, I think. I worked on that article, and avoided working on that article, for months. When I finally published it, I said out loud "I 🤬 hate writing." At this point I'm not sure I 100% believe that...but at the time I definitely did.</p>
<h3>I gave up on Mastodon.</h3>
<p>I was a huge fan of the tech community on Twitter, until it was run into the ground. In late 2022, I switched over to Mastodon as my main driver, hoping to rediscover the joy I'd found on Twitter.</p>
<p>After a year of using Mastodon, I decided I just don't like it. The same people preach or complain about the same things over and over. It's a very self-righteous place, and not much fun. Sure, Twitter was also a self-righteous place, but I also found joy in it that I never found on Mastodon.</p>
<p>I've been using <a href="https://www.threads.net/@pepopowitz">Threads</a> as my main driver for the past few months. I like it much more than Mastodon. I feel like you see more sides of people than either Twitter or Mastodon, which I think is nice. I don't find the exact joy that I found on Twitter, but there is a joy.</p>
<p>It's too quiet though. I still miss Twitter.</p>
<h2>What went well?</h2>
<h3>I read 8 books.</h3>
<p>I'm pretty sure I haven't read 8 books in one year since I was a kid! I've been a 2-to-4 book guy for basically as long as I can remember.</p>
<p>The one weird trick in 2023? I read books I wanted to read. Only one of the 8 books was non-fiction, and that one was <a href="https://www.goodreads.com/book/show/24356669-dead-wake">an Erik Larson book</a> — his books read to me more like fiction anyway. The rest of the books were science fiction, usually post-apocalyptic.</p>
<p>I found a new favorite author — <a href="https://www.goodreads.com/author/show/442240.Blake_Crouch">Blake Crouch</a>. Each of the 3 books I read had a "father/husband looking for his lost family across dimensions" theme that hit me extremely hard. The book <a href="https://www.goodreads.com/book/show/45697427-dark-matter">Dark Matter</a>, despite my friend <a href="https://www.linkedin.com/in/nialldeehan">Niall</a> ripping it to shreds after I shared it with him, is my favorite book since I read The Hitchhiker's Guide series 15 times in my teens.</p>
<h3>I picked up my guitar.</h3>
<p>In the fall, I was feeling a little shame for not making art, but I had a little more time due to not training for triathlons. I found myself picking up my guitar.</p>
<p>Don't get excited — I bought this guitar 35 years ago, when my partner at the time needed rent and I wanted a cheap guitar — and I am definitely not good at playing it. But I picked it up and just kind of strummed along to music that I liked. And then I did it again, and again.</p>
<p>I'm not saying I can really play anything worthwhile on it...but that seems like a more realistic future than it has previously.</p>
<p>And most importantly, it's been fun to mess around with.</p>
<h3>I crushed the sports.</h3>
<p>2023 was seriously incredible for me in regards to triathlon. I set PRs. I ran harder in the Fontana Triathlon, a particularly hilly race, than I've ever run before. I finished highly in multiple races. I trained more total hours than ever before. I did much better at Age Group National Championships than I expected.</p>
<p>I stayed healthy all season, largely thanks to the strength training that I've fallen in love with. I took a couple weeks off from strength in the peak of triathlon season, otherwise I lifted weights once or twice per week for the rest of the entire year.</p>
<p>I also picked up climbing again this off-season, which has been a fun change to my usual routines.</p>
<p>I continue to feel very lucky that the thing I enjoy most outside of work is something that is healthy for me.</p>
<blockquote class="text-post-media" data-text-post-permalink="https://www.threads.net/@pepopowitz/post/C1lUqVCM5mL" data-text-post-version="0" id="ig-tp-C1lUqVCM5mL" style=" background:#FFF; border-width: 1px; border-style: solid; border-color: #00000026; border-radius: 16px; max-width:540px; margin: 1px; min-width:270px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);"> <a href="https://www.threads.net/@pepopowitz/post/C1lUqVCM5mL" style=" background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%; font-family: -apple-system, BlinkMacSystemFont, sans-serif;" target="_blank"> <div style=" padding: 40px; display: flex; flex-direction: column; align-items: center;"><div style=" display:block; height:32px; width:32px; padding-bottom:20px;"> <svg aria-label="Threads" height="32px" role="img" viewBox="0 0 192 192" width="32px" xmlns="http://www.w3.org/2000/svg"> <path d="M141.537 88.9883C140.71 88.5919 139.87 88.2104 139.019 87.8451C137.537 60.5382 122.616 44.905 97.5619 44.745C97.4484 44.7443 97.3355 44.7443 97.222 44.7443C82.2364 44.7443 69.7731 51.1409 62.102 62.7807L75.881 72.2328C81.6116 63.5383 90.6052 61.6848 97.2286 61.6848C97.3051 61.6848 97.3819 61.6848 97.4576 61.6855C105.707 61.7381 111.932 64.1366 115.961 68.814C118.893 72.2193 120.854 76.925 121.825 82.8638C114.511 81.6207 106.601 81.2385 98.145 81.7233C74.3247 83.0954 59.0111 96.9879 60.0396 116.292C60.5615 126.084 65.4397 134.508 73.775 140.011C80.8224 144.663 89.899 146.938 99.3323 146.423C111.79 145.74 121.563 140.987 128.381 132.296C133.559 125.696 136.834 117.143 138.28 106.366C144.217 109.949 148.617 114.664 151.047 120.332C155.179 129.967 155.42 145.8 142.501 158.708C131.182 170.016 117.576 174.908 97.0135 175.059C74.2042 174.89 56.9538 167.575 45.7381 153.317C35.2355 139.966 29.8077 120.682 29.6052 96C29.8077 71.3178 35.2355 52.0336 45.7381 38.6827C56.9538 24.4249 74.2039 17.11 97.0132 16.9405C119.988 17.1113 137.539 24.4614 149.184 38.788C154.894 45.8136 159.199 54.6488 162.037 64.9503L178.184 60.6422C174.744 47.9622 169.331 37.0357 161.965 27.974C147.036 9.60668 125.202 0.195148 97.0695 0H96.9569C68.8816 0.19447 47.2921 9.6418 32.7883 28.0793C19.8819 44.4864 13.2244 67.3157 13.0007 95.9325L13 96L13.0007 96.0675C13.2244 124.684 19.8819 147.514 32.7883 163.921C47.2921 182.358 68.8816 191.806 96.9569 192H97.0695C122.03 191.827 139.624 185.292 154.118 170.811C173.081 151.866 172.51 128.119 166.26 113.541C161.776 103.087 153.227 94.5962 141.537 88.9883ZM98.4405 129.507C88.0005 130.095 77.1544 125.409 76.6196 115.372C76.2232 107.93 81.9158 99.626 99.0812 98.6368C101.047 98.5234 102.976 98.468 104.871 98.468C111.106 98.468 116.939 99.0737 122.242 100.233C120.264 124.935 108.662 128.946 98.4405 129.507Z" /></svg></div> <div style=" font-size: 15px; line-height: 21px; color: #999999; font-weight: 400; padding-bottom: 4px; "> Post by @pepopowitz</div> <div style=" font-size: 15px; line-height: 21px; color: #000000; font-weight: 600; "> View on Threads</div></div></a></blockquote>
<script async src="https://www.threads.net/embed.js"></script>
<h3>I returned to my therapist.</h3>
<p>Eventually I realized that I had, once again, set too high a bar for myself. I returned to my therapist in the fall, despite not having a clean "this is what we're working on in season 2" guide.</p>
<p>As it turns out, we're mostly working on the same things we were working on in season 1. 😅😬</p>
<p>We also talked about recurring mild depression, and the idea of experimenting with my brain chemistry. I've started taking ashwagandha, and it has gone pretty well. Even though I still have a lot of the "stuck" feelings mentioned above, I'm not ruminating on them very much. A couple days before the holiday break in December, I published a large project I'd been working on....only to find a handful of seemingly unrelated regressions, discovering them to be <em>definitely</em> related, and reverting the release immediately. This kind of thing would have wrecked my sleep in the past, but I was able to brush it off, and forget about it until after the holidays. I'm proud of this.</p>
<p>Probably the idea that most affects me is my fear of declining skills as I age — but I'm thankfully not losing too much sleep about it right now.</p>
<h3>I kicked ass at work.</h3>
<p>From my self-review in December:</p>
<blockquote>
<p>As I write this self-evaluation, I am convinced that I did a fantastic job this year. I produced high quality work, a high quantity of it, I stepped outside of my comfort zone, and I made the docs a better place for everyone. In the examples for the "Exceeding" Performance Indicator, there were only 2 boxes that I couldn't confidently check for myself.</p>
</blockquote>
<p>Dear reader, you know well of my self-criticism, of my self-deprecating humor, of my low self-esteem. You must also know what a surprise it was for me to write that self-review.</p>
<p>But it's true! I did a lot of work in 2023, and I am proud of it.</p>
<p>I wouldn't have recognized this if it weren't for all of our work being tracked in GitHub. When writing my self-evaluation, I looked back at all the issues and PRs that I closed in 2023. Being able to review all your work in one place is a self-evaluation superpower, and a practice I'll be taking forward.</p>
<h3>Jon and I started 2 Rad Dadz.</h3>
<p>I saved this one for last because it is my favorite thing about 2023.</p>
<p>Early in the year, <a href="https://www.jonallured.com/">my friend Jon</a> and I decided to hang out and smash some code together. We actually had talked about doing this when I left Artsy, but every time we hung out we'd end up talking and catching up.</p>
<p>I don't remember why we decided to stream our hangouts, but we did. Streaming prompted us to stick to code for most of the sessions — don't get me wrong it's nice to just hang out, but I really miss pair-programming, and there's no better pair than Jon. Also, we still <em>do</em> hang out every time to catch up, it just happens mostly off-stream.</p>
<p>Our friend <a href="https://pvin.is/">Pavlos</a> started showing up to taunt us in the comments, and <a href="https://tweets.stevenhicks.me/1458208806517841920/">there's no better friend</a>/fan than Pavlos.</p>
<p>Streaming has also kept us consistent. There is the occasional lapse due to holidays or summer, but for the most part we've been streaming every 2 or 3 weeks since we started.</p>
<p>Our first project was an idea I'd wanted to build for some time — <a href="https://github.com/pepopowitz/doing-a-good-job">a browser start page that shows a nice image and one of many nice comments I've received as feedback</a>. It feels a little silly to have a "Steve Hicks fan club" running in my browser all the time to boost me, but also maybe everyone should have that.</p>
<p>Now we're working on <a href="https://github.com/jonallured/crank_champ">an app for the Playdate</a> which will....TBH I'm not sure where we're headed, because I keep distracting Jon from the finish line with side quests.</p>
<p>Even if Pavlos wasn't our #1 fan, and it was Jon and I literally shouting to the void, it would still be an absolute highlight every other Wednesday.</p>
<h2>What's next?</h2>
<p>First and foremost, by the time I write my 2024 review post, I'm hopeful to have some resolution to my career stuck-ness. I don't really have an idea of what that means yet. But I don't want to spend another year feeling like I'm not moving forward. One of the "joys" of being in your upper 40s 💀 is that you start to count the years down instead of up. Maybe that's just me. But I'm getting pretty serious about not wasting my time.</p>
<p>Beyond that, I hope Steve in January, 2025 is still streaming with Jon, has done something with another iteration of a podcast, has a creative outlet, and still loves to stay active.</p>
<p>✌️</p>
How To De-index Your Docs From Google (And Then Fix It)2023-11-14T16:31:07.914+00:00https://stevenhicks.me/blog/2023/11/how-to-deindex-your-docs-from-google/<h2 id="the-introduction">1. The introduction</h2>
<p>Software documentation is a critical element of developer experience. I don't believe this is a bold or disagreeable statement.</p>
<p>Think about how you engage with documentation. Occasionally, you know exactly what you're looking for — you could browse the documentation navigation to find the page you need. More often, you either don't know exactly what you're looking for, or you don't know where it lives in the documentation.</p>
<p>The majority of time you interact with documentation, you probably search for the help you need. That search might happen within the documentation site itself. It might happen in your favorite search engine. Wherever it happens, you expect to find helpful results to your search terms. If you don't find helpful results, you're likely to get stuck. Get stuck too many times, and you give up entirely. This is the nightmare of every tool built for developers.</p>
<p>Imagine the docs you most interact with. You know them reasonably well, but you've forgotten a detail that you know the docs can explain. Envision yourself searching for help on this topic. Imagine browsing the results for your query — and seeing....</p>
<blockquote>
<p>0 results found.</p>
</blockquote>
<p>Zero results??!! What??? You <em>know</em> it's in there somewhere. (╯°□°)╯︵ ┻━┻</p>
<h3>The real-life story of de-indexing our docs</h3>
<p>For months I worked on reviving the search results for <a href="https://docs.camunda.org/">Camunda's version 7 documentation</a>. Our docs weren't giving 0 results for most queries, but they weren't far off. Known pertinent results were excluded consistently. Most of the results that came up were slightly related but not helpful.</p>
<p>Throughout this I learned unexpected details about SEO (Search Engine Optimization) — especially Google's flavor of SEO. In the end we were able to revive the massively deindexed content. I had my doubts that we'd get there.</p>
<h3>What to expect from this article</h3>
<p>This article describes my journey through this problem of vanishing search results in the Camunda 7 (C7) docs. There are 5 parts:</p>
<ol>
<li><a href="#the-introduction">The introduction</a>. You're almost finished reading that.</li>
<li><a href="#the-disappearance">The disappearance</a>.</li>
<li><a href="#the-investigation">The investigation</a>.</li>
<li><a href="#the-resolution">The resolution</a>.</li>
<li><a href="#the-recommendation">The recommendation</a>.</li>
</ol>
<p>That final fifth part is the payoff. If you're managing a documentation site, and unsure how to handle versioned documentation from an SEO perspective, and just looking for guidance, jump there. Most guidance for handling duplicate content in regards to SEO ignores one prime use case — versioned documentation. I hope this article fills that gap.</p>
<p>If you prefer a video format, I've posted <a href="https://www.youtube.com/watch?v=HJGxdj_8oJ8">a summary of this experience on YouTube</a>. It's told in a different way, but the message is the same.</p>
<h2 id="the-disappearance">2. The disappearance</h2>
<h3>Setting context</h3>
<p>Camunda version 8 (C8) marked a significant change from version 7 (C7). While version 7 was intended for self-managed environments, version 8 is cloud-first. Both versions are still supported, <a href="https://docs.camunda.org/enterprise/announcement/#camunda-extended-support-offering">at least until 2027</a>.</p>
<p>The large differences in approaches and product features led us to rebuild the documentation for Camunda 8. C7 docs are hosted at <a href="https://docs.camunda.org/">docs.camunda.org</a>; the version 8 documentation lives at <a href="https://docs.camunda.io/">docs.camunda.io</a>. The two different sites are built with different tooling (<a href="https://gohugo.io/">Hugo</a> vs <a href="https://docusaurus.io/">Docusaurus</a>).</p>
<p>My team, Developer Experience, manages the C8 docs; we <em>interact</em> with the C7 docs, but we mostly try to leave them alone. Basically, we try to keep the lights on, and not much more. There are issues I'd love to fix in the C7 docs, like the fact that <a href="https://github.com/camunda/camunda-docs-manual#installing-hugo">they're tied to a very-specific very-old version of Hugo</a>. But it's not worth the ROI for us...and there are <a href="https://github.com/camunda/camunda-platform-docs/issues">so many other issues in the C8 docs</a> that take priority.</p>
<p>This story is about the C7 docs. The ones that we try not to touch very much....unless something goes very very wrong with them.</p>
<h4>Camunda 7 documentation details</h4>
<p>The Camunda 7 product is currently on version 7.20. Each released version has its own documentation: <a href="https://docs.camunda.org/manual/7.20/">7.20</a>, <a href="https://docs.camunda.org/manual/7.19/">7.19</a>, <a href="https://docs.camunda.org/manual/7.18/">7.18</a>, all the way down to <a href="https://docs.camunda.org/manual/7.0/">7.0</a>. There are also two non-numeric versions of the documentation: <a href="https://docs.camunda.org/manual/latest/">latest</a> (a mirror of whichever numeric version is latest), and <a href="https://docs.camunda.org/manual/develop/">develop</a> (in-progress documentation for the next release). That's <code>n+2</code> versions of the documentation, where <code>n</code> is the number of released numeric versions. The early versions were released over 8 years ago! There are a lot of docs.</p>
<p>Much of the documentation is duplicated across versions. For example, look how similar the "Introduction" page is between <a href="https://docs.camunda.org/manual/7.4/introduction/">version 7.4</a> and <a href="https://docs.camunda.org/manual/7.19/introduction/">version 7.19</a>. There are a couple small differences, but probably 95% of the content has not changed, over 15 versions. As my 12yo would say, "foreshadowing...."</p>
<p>One other important detail — there is an in-site search for the C7 docs. Notably, it is built on a <a href="https://programmablesearchengine.google.com/about/">programmable Google search engine</a>. Basically, that means that the search functionality on the site is powered by Google. A search query entered into the C7 docs search box gives basically the same results as entering the query on google.com and filtering by <code>site:docs.camunda.org</code>. Again, "foreshadowing...."</p>
<h3>First report</h3>
<p>At some point in 2021, the search experience on docs.camunda.org began to degrade. It was not sudden, or obvious. But search results became less helpful, and obvious hits on specific search queries began to disappear. A search for BPMN — a critical and core technology in the Camunda ecosystem, and definitely documented — returned zero helpful results. Since the in-site search is built on Google, the results were degraded both within the site, and directly from google.com.</p>
<p><a href="https://forum.camunda.io/t/camunda-docs-repo-search-is-not-working-as-expected/32044">The community noticed</a>. I want to say we <em>heard</em> them....but we didn't realize how bad it was, or how much it was affecting people. We thought these were one-off complaints. We didn't prioritize the work to fix the search experience.</p>
<p>In mid-2022, we heard from Camunda's support team. They emphasized the problem. Every day, they help Camunda users find the answers to their problems. The degrading search experience was making that much harder. As a result, every member of the support team had effectively built a <a href="https://en.wikipedia.org/wiki/Method_of_loci">memory palace</a> of the C7 documentation structure. The only way for them to find content in our docs was <em>to already know where it existed</em>.</p>
<p>Upon this discovery, I wrapped up other on-going projects, and we shifted our focus to fixing the C7 search experience.</p>
<h2 id="the-investigation">3. The investigation</h2>
<h3>Finding answers in Google Search Console</h3>
<p>Early in our investigation, we noticed something strange in the Google Search Console. Many of the docs for the current version were not indexed. They were filed under the category of <a href="https://support.google.com/webmasters/answer/7440203#duplicate_page_without_canonical_tag">"Duplicate without user-selected canonical"</a> — meaning Google chose a different version of the page as canonical, and we didn't specify one with a <code><link rel="canonical"></code> hint.</p>
<p>This sounded familiar to me. When I learned to program in Ruby, I spent a lot of time searching <a href="https://ruby-doc.org">ruby-doc.org</a> for help. One thing I always found interesting was that I'd never get the latest version of the Ruby docs from my search. Searching Google for <a href="https://www.google.com/search?q=ruby+array+map&oq=ruby+array+map">"ruby array map"</a> does return a result from ruby-doc.org relatively near the top. But it's for Ruby version 2.7.0, while latest Ruby is currently somewhere in the 3s.</p>
<p>Our situation was much worse, though. We weren't getting <em>any</em> version in the results, let alone an older version.</p>
<p>Inspecting the older version pages in Google Search Console clearly revealed the reason. Our older version pages were excluded from the Google search index because they were <a href="https://support.google.com/webmasters/answer/7440203#submitted_but_noindex">"Excluded by ‘noindex’ tag"</a>.</p>
<p>Sure enough — our docs <a href="https://github.com/camunda/camunda-docs-manual/blob/master/themes/camunda/layouts/partials/header.html#L24-L26">had a check in them for the version being rendered</a>, and if it wasn't the current version, a <code><meta name="robots" content="noindex"></code> directive was applied to the page. We did this with the intention of convincing Google to index only the current version. Unfortunately, it did not have that effect.</p>
<h4>Why we had zero search results</h4>
<p>It was the combination of these two factors that caused Google to index very little of our content. We were telling Google explicitly not to index our older version pages with <code>noindex</code> directives; Google was choosing those older version pages as the canonical source, and therefore not indexing the latest version pages. 😅😬</p>
<p>This set me on a learning adventure. To me, it's obvious that in a documentation site, the latest version page is probably the one I want to find in a search. Why doesn't Google think that? And really, how does Google (and any other search engine) handle duplicate content?</p>
<h3>How search engines handle duplicate content</h3>
<p>Duplicate content happens all the time on the internet. Sometimes it's malicious (cue a generative AI ethics discussion), usually it's not. The usual example is a product page that can live in multiple categories, e.g. <code>/tops/cowboy-shirt</code> and <code>/western-wear/cowboy-shirt</code>.</p>
<p>We don't usually see the same page multiple times in search results, because a search engine chooses one as the canonical source. <a href="https://developers.google.com/search/docs/crawling-indexing/canonicalization#canonical-how">The canonical is chosen based on many factors</a> -- which page is linked most by the rest of the internet, which page is referenced in a sitemap, etc. Website owners can even suggest a recommendation for a page's canonical URL, via a <a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls#rel-canonical-link-method"><code>link rel=canonical</code> hint</a>.</p>
<p>I say "hint" because that's all <code>link rel=canonical</code> is. Google might, and in my experience often does, choose a different canonical than you specify. It's interpreting a collective story about your page from many different sources. Sometimes, it just doesn't agree with your suggestion — the rest of the internet convinces it to choose a different canonical.</p>
<p>Our docs weren't doing much to help Google interpret the collective story. We thought we were giving directives about canonicals by applying <code>noindex</code> to the old versions, but the <code>noindex</code> was applied separately from the choice of canonicals. We weren't submitting sitemaps with the true canonicals — in fact, we didn't even have sitemaps that were properly formed. It was basically 100% up to Google to figure out which version was canonical, without any of our input.</p>
<h2 id="the-resolution">4. The resolution</h2>
<p>We tried a lot of different things to convince Google to index the most recent version of our documentation. Not many of our attempts seemed to work!</p>
<p>And the feedback loop for each of them was horribly long! I'd experiment with something, and then wait weeks to see what happened. Even then, it was hard to tell if something worked. It was more obvious if it definitely <em>didn't</em> work.</p>
<h3>Submitting a correct sitemap...unsuccessfully</h3>
<p>When we realized Google wasn't working off of a sitemap, we went to submit ours, figuring it would be useful as a signal to Google about canonicals. It was rejected 😜 for being in an improper format. I wondered how long it had been in that state...</p>
<p>We corrected the sitemap's format. Our docs have a redirect rule set up so that if you visit a page <em>without</em> a version in the URL, it will redirect to the latest version of that page. We tried submitting <a href="https://github.com/camunda/camunda-docs-manual/pull/1345">a sitemap with these versionless redirecting URLs</a> — no significant change was affected.</p>
<p>We tried a sitemap with the latest version hardcoded in the URLs. This had a more positive effect than the redirecting versionless URLs, but still not very significant.</p>
<h3>Declaring canonicals...unsuccessfully</h3>
<p>We shifted our focus to declaring canonicals via <a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls#rel-canonical-link-method"><code>link rel=canonical</code></a>. Since we have so many versions of documentation, we ran these experiments on only a few pages at a time, or sometimes an entire version at a time.</p>
<p>We started with <a href="https://github.com/camunda/camunda-docs-manual/pull/1345">pointing canonicals of the current version at the redirecting versionless URLs</a>. We wanted to use the redirecting URLs so that we would never have to go back and change them. Google was unconvinced, and maintained its own opinion, usually canonicalizing an older version of the page. We also got new errors from the redirecting versionless URLs, about <a href="https://support.google.com/webmasters/answer/7440203#page_with_redirect">containing a redirect</a>.</p>
<p>We tried <a href="https://github.com/camunda/camunda-docs-manual/pull/1361">using self-referential canonicals in the current version</a>, and submitting the current sitemap. This had no effect.</p>
<p>Nothing happened when we applied the current version canonicals <a href="https://github.com/camunda/camunda-docs-manual/pull/1370">to older version pages</a>, either. Because our older version pages still said <code>noindex</code>, Google wouldn't even bother to look at the canonical link. These pages remained canonical, but also de-indexed.</p>
<p>We also experimented with the sequencing of our submissions to Google's crawler. Would it make a difference if we submitted an older page first, then re-submitted a current version page? If we submitted an entire sitemap vs an individual page?</p>
<p>Sadly, no. Occasionally something good would happen, but results were not repeatable or predictable. It seemed like there was nothing we could do to convince Google to consistently choose our latest version docs as canonical. Each experiment took weeks to play out, and it was frustrating.</p>
<h3>Building a comprehensive story...and waiting</h3>
<p>We noticed that Google was doing a lot more crawling of our docs when we released a new version, and a flood of new pages came online. Given what we'd learned about how search engines choose a canonical source — by piecing together clues from a website <em>and</em> the rest of the internet — we decided to take one final half-court shot.</p>
<p>In a few months, we'd release version 7.19, and Google would crawl it thoroughly. If we could put together a comprehensive story, with correct canonicals on most versions, and an accurate sitemap, maybe this flurry of crawling activity would convince it to canonicalize and index the correct versions.</p>
<p>We weren't sure what to do about the <code>noindex</code> situation. My opinion was that the story would be more comprehensive with the older versions <strong>not</strong> <code>noindex</code>ed. We ran <a href="https://gist.github.com/pepopowitz/c0c9bc63bd50318f54a17e19fd9788f5">an experiment</a> to see if there was a positive effect on canonicals when I removed <code>noindex</code>. <a href="https://gist.github.com/pepopowitz/f672e1d2bef12ad2adf7cb70e261fe38">There was no positive effect</a>. We decided to leave all older versions marked as <code>noindex</code>.</p>
<p>Months later, when we released our next version <a href="https://docs.camunda.org/manual/7.19/">(7.19)</a>, we submitted a sitemap containing only the 7.19 pages, and crossed our fingers. And waited again.</p>
<h3>Success!</h3>
<p>Over the next few months, we received a couple notes from our support team that suggested things had improved.</p>
<p><img src="../it-worked.png" alt="A message in Slack: "I completely forgot that C7 doc search works again. I just tried it and ... IT ACTUALLY WORKS!""></p>
<p>Eventually I logged into Google Search Console to see if things had improved. It not only had improved, but it had improved <strong>remarkably</strong>.</p>
<p><a href="https://gist.github.com/pepopowitz/cbda099efb429a904369398fb107c480">90% of our 7.19 pages were indexed</a>! I checked back later, and <a href="https://gist.github.com/pepopowitz/64ff0c6d19c56f328531ec6cfc582807">all but 4 of 500 pages were indexed</a>. Google finally agrees with us! Current version pages are canonical. And more importantly, you get accurate results again when searching our docs.</p>
<h2 id="the-recommendation">5. The recommendation</h2>
<p>Whew, the payoff! So you've got a documentation site, with duplicate content across versions. You don't want to re-live our awful experience of de-indexing your docs. What should you do?</p>
<h3>Build a cohesive picture about the canonical source.</h3>
<p>There's no one thing that resolved our situation. Search engines take a wholistic look at your website, and you need to make sure every version is telling a consistent story. <a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls">Hints and signals compound</a>. The more comprehensive story you tell, the more likely search engines are to agree with your canonical suggestions.</p>
<p>More specifically, in our experience that means:</p>
<ol>
<li>Use <code>link rel=canonical</code> tags on all older version documents, pointed at the current version.</li>
<li>Submit a sitemap that contains absolute URLs of only the current version of documents.</li>
</ol>
<p>There's not much there, but any slight deviation can cause havoc.</p>
<h3>Some other important notes</h3>
<ul>
<li>
<p><code>link rel=canonical</code> is only a suggestion!</p>
<p>It is definitely important to specify canonicals, but they aren't a guarantee. Search engines might very well choose a different page if the comprehensive story suggests your preferred canonical is incorrect.</p>
</li>
<li>
<p>Self-referential canonicals are probably not helpful.</p>
<p><a href="https://www.youtube.com/watch?v=TepFVYrBVg0&t=968s">The only time they're helpful is when the visited URL doesn't match the canonical URL</a>. If links to your docs include query-string parameters for tracking, they might be helpful. That's not how we use our docs, so declaring a self-referential canonical would give a search engine as much information as declaring <em>no</em> canonical.</p>
</li>
<li>
<p>Probably don't use <code>noindex</code>.</p>
<p>This is straying a bit from science, as my experimentation suggested that removing the <code>noindex</code> tag from our older versions was actually detrimental to our situation. But hear me out.</p>
<ol>
<li>
<p>All of my results at the time I ran that experiment were confusing, inconsistent, and reliable. I think removing <code>noindex</code> before The Big Recrawl (when we released a new version) would have also resulted in strong canonicalization success.</p>
</li>
<li>
<p>Google recommends that <a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls">you <em>don't</em> use <code>noindex</code> to prevent canonicalization</a>:</p>
<blockquote>
<p>We don't recommend using noindex to prevent selection of a canonical page within a single site, because it will completely block the page from Search. rel="canonical" link annotations are the preferred solution.</p>
</blockquote>
</li>
<li>
<p>Returning to my <a href="https://ruby-doc.org">Ruby docs</a> example from earlier — they have canonicalization problems, but their problems are way less severe than ours were. If we were indexing our old versions, our search wouldn't have broken. It would have served old versions for search results, but that's a much better situation for a stuck user than <em>zero</em> results.</p>
</li>
</ol>
</li>
</ul>
<h2>Epic conclusion</h2>
<p>This was the classic ambiguous and confusing software problem. The system was broken, and there was very little help pinpointing why. There are no examples on the internet about how to handle content duplication in versioned documentation. The horribly loose feedback loop when making changes, the unpredictability and inconsistency of canonical selection, it just all felt like I was powerless and guessing. As I got deeper into it, it became something I couldn't <em>not</em> solve. It haunted me a little. I had basically exhausted our appetite to figure it out when we took our final half-court shot. I'm glad it worked out, because that was probably my last attempt before saying "🤷🏼 sorry it just doesn't work."</p>
Matches, Spoons, And The Relationship Between DX and UX2023-03-16T12:00:51.528+00:00https://stevenhicks.me/blog/2023/03/matches-spoons-and-the-relationship-between-dx-and-ux/<p>Developer Experience (DX) is kind of having a moment right now — and not necessarily a good one. In recent months there's been a lot of discourse pushing back on the importance of DX — or at least its importance relative to the importance of User Experience (UX). My favorite article to capture the moment is <a href="https://begin.com/blog/posts/2023-02-28-redefining-developer-experience">Redefining Developer Experience</a>, by <a href="https://mastodon.online/@colepeters">Cole Peters</a>. Start there, if you aren't sure what I'm referring to.</p>
<p>I recently listened to a podcast episode that talked about the relationship between Developer Experience and User Experience. I don't recall what the episode said about this relationship....I <em>do</em> recall that I wasn't satisfied with it.</p>
<p>It's very possible that a part of me wants to write this article to make myself feel better about my life choices. I'm less than a year into my first job with the words "developer experience" in the title. Still, even if it's confirmation bias, I truly think I believe these things.</p>
<p>Since its conception, the field of Developer Experience has not defined itself clearly. As <a href="https://begin.com/blog/posts/2023-02-28-redefining-developer-experience">Cole suggests</a>, this lack of a consistent interpretation or definition is certainly a significant factor in the disagreement on the importance of DX.</p>
<p>Not long ago I was at a conference, where I met two other people with the exact title as me — "Developer Experience Engineer." All three of us did completely different things. When I meet people and tell them my title, they often ask "what does that mean"; sometimes I introduce myself as "a Developer Experience Engineer...but I'm not sure what that means!" Beyond this specific title, there seems to be an identity crisis for the entire term "developer experience".</p>
<p>I'm not sure how you think about DX. I've got two metaphors, and one questionable psychological theory, to explain how <em>I</em> think about DX, why I think it is important, and how I see its relation to UX.</p>
<h2>Burning matches</h2>
<p>If you watch a cycling race, you're likely to hear the broadcasters talk about burning matches. The metaphor is this: at the beginning of a race, each rider starts with a book of matches. Each hard effort a rider gives burns one match from the book. Riders can choose when to burn each match...but once the matches are all burned, they have no hard efforts remaining.</p>
<p>You may have noticed this in any sport. When you're fresh, you can put in a hard effort. It feels great! But put in a bunch of hard efforts over time, and your body just can't give you anything more.</p>
<p>In a cycling race, it's a balancing act of burning the matches at the best time. If you can save a match for the sprint at the end of a race, you've got a chance of winning it. Burn them all too early, and you don't.</p>
<h2>Spoon theory</h2>
<p><a href="https://en.wikipedia.org/wiki/Spoon_theory">Spoon theory</a> is a metaphor originally used in the context of chronic illness, and which has since been used to describe many other scenarios.</p>
<p>Imagine a person starts their day with a limited number of spoons. Each activity throughout the day exhausts energy, and uses one spoon — even ordinary tasks. By the end of the day, their spoons are all gone, and the person has no remaining energy for even a small task.</p>
<p>Resting replenishes their spoons, so they can start over again the next day. But spoons need to be managed carefully every day, or the person runs the risk of exhausting their supply long before bedtime.</p>
<h2>Ego depletion</h2>
<p>Both these metaphors "rhyme" with the concept of <a href="https://en.wikipedia.org/wiki/ego_depletion">ego depletion</a>.</p>
<p>Ego depletion refers to the idea that our willpower is based on our mental energy, which is of limited supply. The more mental energy we have, the more likely we are to show self-control, and make choices that are better for us in the long term. As we use our mental energy throughout the day, our willpower is weakened, and we're more likely to make short-sighted choices for immediate satisfaction. Think food choices — you might find it manageable to avoid unhealthy foods early in the day...but after a long frustrating day of work, cookies are hard to resist. Maybe that's just me.</p>
<p>The concept of ego depletion is controversial. The validity of studies that demonstrate ego depletion are questioned. This is one of the many ideas that frustratingly falls into the bucket of "seems logical....no proof in either direction....and it might just be in your head."</p>
<p>But hey, I believe in it! Therefore it is real. For me, at least.</p>
<h2>What does this have to do with building software?</h2>
<p>Ego depletion, or something like it, appears in software development too. When a developer's tooling is working for them, they're able to put more effort toward the details that make a difference for the user. When the tooling fights them, their energy is exhausted, and they're less likely to put effort toward user-facing details.</p>
<p>If I burn two matches trying to test an asynchronous event handler on a React component, those matches are gone! If I spend three spoons fighting a weird webpack error, I'm becoming exhausted. Neither of these issues directly impact the user, but I've burned my energy on them. When something comes up late in the day that <em>actually</em> impacts the user — maybe something missing in regards to accessibility, maybe an important edge case in some component that only affects a small amount of users — my energy to deal with it appropriately depends on how much I've fought my tooling all day.</p>
<p>This is why I think Developer Experience matters. DX that works with you leads to better UX. Energy I must put into <em>how</em> I'm building a product pulls directly from energy I would have put into <em>what</em> I'm building.</p>
<p>I don't care as much about how shiny and hot the tools are that I'm using. I care that the tools I'm using are guiding me to a <a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/">pit of success</a>, where I don't need to invest a ton of effort to build a proper experience for the user.</p>
<p>There's certainly a risk of over-optimizing for DX at the expense of UX, in anything we build. Developers can easily overlook that imbalance because they aren't the actual product user. But even when prioritizing the user's experience, the developer's experience matters. If creating a good UX requires a lot of effort from developers, they just aren't going to do it.</p>
<p>The answer, as always, lies somewhere in the middle.</p>
2022 In Review2023-01-31T15:51:21.474+00:00https://stevenhicks.me/blog/2023/01/2022-in-review/<p><a href="/blog/2022/01/2021-in-review/">Last year at this time</a>, I set an intention to drop the things I don't enjoy, and focus on the things I do:</p>
<blockquote>
<p>So that's my goal for this year. I want to stay focused on the things I enjoy. Maybe those things will contribute to my career...maybe they won't. But it will certainly contribute to my happiness. And the second something becomes not fun for me, I'm kicking it to the curb.</p>
</blockquote>
<p>It was rather prophetic of me to clarify that <em>"maybe those things will contribute to my career...maybe they won't."</em> In April 2022, I left my job as a product engineer at Artsy, for a position as a developer experience engineer at Camunda:</p>
<div class="tweet h-entry" id="1507007100999782417"><a href="/1507005453036752903/" class="tweet-pretext u-in-reply-to">…in reply to @pepopowitz</a><div class="tweet-text e-content" data-pagefind-body="">I've become pretty uninspired by product engineering. I've realized that I want a tighter feedback loop where I can more quickly see the appreciation of the people I'm helping. So I'm trying something new -- working as a dev experience engineer at <a href="https://twitter.com/Camunda/" class="tweet-username h-card">@<span class="p-nickname">Camunda</span></a>.<br><br>Starting next week!</div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1507007100999782417/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 15<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-03-24T14:51:06.000Z">2022 Mar 24</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">4</strong> 🙂</span></span></div>
<p>Well...more likely than prophesy is that I was already resolved to leaving my job in favor of something in developer experience when I wrote that sentence, but didn't feel comfortable saying it out loud. Regardless, it feels good to say I met my main goal.</p>
<p>If you're in it for the long haul, buckle in for a probably-too-detailed tour of my life in 2022.</p>
<h2>I got a new job</h2>
<div class="tweet h-entry" id="1507005453036752903"><div class="tweet-text e-content" data-pagefind-body="">Yeah, so news -- I've decided to leave Artsy! I'm very sad to go because it's such an amazing place to work, but I'm also really excited for what's next.<br><br>This podcast episode is full of me rambling about what I'm taking with me when I go. <a href="https://twitter.com/ArtsyOpenSource/status/1507004771416346626" class="tweet-username" data-pagefind-index-attrs="href">@ArtsyOpenSource/1507004771416346626</a></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1507005453036752903/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 22<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-03-24T14:44:33.000Z">2022 Mar 24</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">5</strong> 🙂</span></span></div>
<p>Even when 2022 started, I was feeling a bit stuck at Artsy:</p>
<div class="tweet h-entry" id="1497228072021594118"><div class="tweet-text e-content" data-pagefind-body="">Or as I like to call it, "Kaja forces Steve to confront his existential crisis head-on." <a href="https://twitter.com/AlizeNero/status/1497217162192343043" class="tweet-username" data-pagefind-index-attrs="href">@AlizeNero/1497217162192343043</a></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1497228072021594118/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-retweet">♻️ 1<span class="sr-only"> Retweets</span></span><span class="tag tag-lite tag-favorite">❤️ 9<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-02-25T15:12:44.000Z">2022 Feb 25</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">-1</strong> 🙁</span></span></div>
<p>There are surely a handful of reasons I wasn't happy at Artsy. The one I most remember, and the one I've most cited, is that <a href="https://tweets.stevenhicks.me/1507007100999782417">I wasn't able to feel the impact of my work</a>. It'd been a while since I'd been able to see my work make someone happy.</p>
<p>Developer experience seemed like a good place to feel the impact of my work. This is my first job in the space, and I think it is definitely a good place for me. I enjoy removing friction, and I think my compassion is well-directed towards other developers. It's second nature for me to approach a software interaction from the perspective of "how would I feel about this if I were new to it?" I'm learning a lot, and starting to form my opinions and perspective on developer experience.</p>
<p>I even told my boss recently that I think I'm approaching the time and place for leading a team. Since my first job as a senior developer years ago, I've floated between the idea of remaining an individual contributor and stepping into leadership. I always took a step backward when I dipped my toe into leadership. Right now I feel like I want to eventually build and lead a team in developer experience.</p>
<h3>Highlights from my new job</h3>
<ol>
<li>I opened probably my favorite PR of all time.</li>
</ol>
<div class="tweet h-entry" id="1558133775342747649"><div class="tweet-text e-content" data-pagefind-body="">Look at me, I'm contributing!!! Am I doing it right? Is this how one "does open-source"? <br><br><a href="https://github.com/camunda/camunda-platform-docs/issues/1129" class="tweet-url" data-pagefind-index-attrs="href">github.com/camunda/camunda-platform-docs/issues/1129</a><is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://github.com/camunda/camunda-platform-docs/issues/1129"><img src="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2Fcamunda%2Fcamunda-platform-docs%2Fissues%2F1129/small/onerror/" alt="OpenGraph image for github.com/camunda/camunda-platform-docs/issues/1129" loading="lazy" decoding="async" width="375" height="197" class="tweet-media tweet-media-og" onerror="this.parentNode.remove()"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1558133775342747649/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 15<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-08-12T16:50:15.000Z">2022 Aug 12</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">0</strong></span></span></div>
<ol start="2">
<li>I worked a conference booth for the first time, and found that I really enjoyed it.</li>
</ol>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Make sure to join us at the <a href="https://twitter.com/Camunda?ref_src=twsrc%5Etfw">@Camunda</a> booth at <a href="https://twitter.com/AllThingsOpen?ref_src=twsrc%5Etfw">@AllThingsOpen</a>! (Booth number 39 & 40, left to the registration) We are looking forward chatting with you 🤩 <a href="https://t.co/9uHfdceR7N">pic.twitter.com/9uHfdceR7N</a></p>— Thomas Heinrichs - @hafflgav@fosstodon.org (@hafflgav) <a href="https://twitter.com/hafflgav/status/1587452385567416320?ref_src=twsrc%5Etfw">November 1, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<ol start="3">
<li>I got excited about ChatGPT.</li>
</ol>
<iframe src="https://hachyderm.io/@pepopowitz/109446403890704587/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://hachyderm.io/embed.js" async="async"></script>
<h3>Counterpoint: I'm lonely</h3>
<p>My main concern at Camunda is that I'm lonely. At Artsy, we paired multiple times a week on work, sometimes multiple times a day. I saw people in Zoom <em>all the time</em>. My job at Camunda is much more independent. Most of the work I do is solo. We're actually really good about not having a lot of meetings, and I often go a couple days without seeing a co-worker on screen.</p>
<p>I've been good about maintaining friendships from Artsy, and hanging with my former coworkers multiple times a week. But there's something to be said for collaborating in code with someone. We've talked about this at work, and I'm hopeful that by this time next year I'm not feeling as isolated.</p>
<h2>I made a podcast</h2>
<p>Early in 2022 I recorded some conversations with friends about the tools and systems they use to get things done, and their highs and lows. I'd intended to take <strong>a lot</strong> of time to cut up the episodes and stitch them back together in thematic stories.</p>
<p>At some point in the spring I realized I'd missed my window. Triathlon season was starting, and I was spending all of my time training instead of editing. I also decided I'd set the bar too high for myself — it just felt like too much work to cut up all the episodes and stitch them back together. I guess this is why no one does this 🤷.</p>
<p>Given how happy I was with the content of these conversations, I decided to release them (almost) uncut. The first episode of <a href="https://anchor.fm/a-developer-experience">A Developer Experience Podcast</a> shipped in the fall:</p>
<div class="tweet h-entry" id="1579856174933250048"><div class="tweet-text e-content" data-pagefind-body="">if you've been thinking "I wish I could hear more of Steve's nasally voice" I have a gift for you.<br><br>I recorded some conversations last winter with people about the systems that work (& don't work) for them. I'm *finally* sharing them.<br><br>First episode ships in a couple weeks! <a href="https://twitter.com/ADevExPodcast/status/1579834957509246976" class="tweet-username" data-pagefind-index-attrs="href">@ADevExPodcast/1579834957509246976</a></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1579856174933250048/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-retweet">♻️ 2<span class="sr-only"> Retweets</span></span><span class="tag tag-lite tag-favorite">❤️ 17<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-10-11T15:27:19.000Z">2022 Oct 11</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">3</strong> 🙂</span></span></div>
<p>The podcast was my "content" focus for the year. I <a href="https://tweets.stevenhicks.me/1482343221057490946">spoke once virtually at You Got This!</a>, and I wrote a few articles (including <a href="https://tweets.stevenhicks.me/1598341903925346304">this one about breaking problems down</a>), but most of my nighttime non-triathlon-season energy went toward shipping podcast episodes.</p>
<p>I intend on making a second season of <a href="https://anchor.fm/a-developer-experience">A Developer Experience Podcast</a>. I have one more episode to release from last year, and then I'll be seeking out guests for the next season. Next season I think I'll be more broad with the starting topic — I want to talk to people who have an intense relationship with some part of their daily experience, but I don't want to limit it to productivity. If you're interested in being a guest, <a href="https://stevenhicks.me/where/">let me know</a>!</p>
<p>I also plan to tighten up the episodes a bit. When I recorded season 1, I anticipated cutting them up, and I didn't care much about how long they were. Now that I know I'll be releasing them basically uncut, I'll target around 40 minutes for recording.</p>
<h2>I made art</h2>
<p>The pandemic was great for my art habit...but I still struggle to call what I do a habit.</p>
<p>At my new job, I got to know my teammate <a href="https://www.linkedin.com/in/maria-alcantara-arceo-47713113/">Maria</a>, and learned that she is much better at being an artist than me! She set us up with a recurring weekly hangout, to catch up and practice art. It's been a little rocky lately with reschedules, but this remains my favorite weekly calendar event.</p>
<p>Some things I painted this year:</p>
<ol>
<li>My house</li>
</ol>
<div class="tweet h-entry" id="1485625020697260033"><div class="tweet-text e-content" data-pagefind-body="">We had a lot more snow last winter, when I took the photo for this painting. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/JBkMT_91Qv.jpeg"><img src="https://tweets.stevenhicks.me/img/JBkMT_91Qv.jpeg" width="900" height="1200" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1485625020697260033/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 14<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-01-24T14:46:21.000Z">2022 Jan 24</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">0</strong></span></span></div>
<ol start="2">
<li>One of my cats (Marbs)</li>
</ol>
<div class="tweet h-entry" id="1495589961076293634"><div class="tweet-text e-content" data-pagefind-body="">I painted my cat. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/I4q_LeOxcn.jpeg"><img src="https://tweets.stevenhicks.me/img/I4q_LeOxcn.jpeg" width="900" height="1200" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1495589961076293634/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 17<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-02-21T02:43:28.000Z">2022 Feb 21</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">0</strong></span></span></div>
<ol start="3">
<li>Some bike shoes!</li>
</ol>
<div class="tweet h-entry" id="1544861747865358336"><div class="tweet-text e-content" data-pagefind-body="">I've been wearing the same bike shoes for about 15 years, so I was due for new ones. Kids bought me new ones for Father's Day, so I bought paint markers and added "stripes".<br><br>Yes they're not as good as professionally custom painted shoes but I made them and I love them. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/-R3VMM80Yl.jpeg"><img src="https://tweets.stevenhicks.me/img/-R3VMM80Yl.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/ZCWlfbqcSW.jpeg"><img src="https://tweets.stevenhicks.me/img/ZCWlfbqcSW.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/lF28ha9fUk.jpeg"><img src="https://tweets.stevenhicks.me/img/lF28ha9fUk.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1544861747865358336/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 19<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-07-07T01:51:57.000Z">2022 Jul 7</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">7</strong> 🙂</span></span></div>
<p>I don't see the art part of my life changing significantly for 2023. I'd like to say I'll be <em>stronger</em> with my practice, but I think that's nothing more than wishful thinking.</p>
<h2>I stayed fit</h2>
<p>Exercise is basically my identity now. It continues to be a daily practice. I got <strong>a lot</strong> of exercise in 2022:</p>
<iframe src="https://hachyderm.io/@pepopowitz/109621669380982764/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://hachyderm.io/embed.js" async="async"></script>
<h3>I fell in love with weight training</h3>
<p>I've never been one for the gym. Whenever I'd go, I had to convince myself that it would be nearly as much fun as running or biking. That changed in early 2022:</p>
<div class="tweet h-entry" id="1486011159660662790"><a href="/1486011158188417025/" class="tweet-pretext u-in-reply-to">…in reply to @pepopowitz</a><div class="tweet-text e-content" data-pagefind-body="">Well I am in week 7 of the plan and <br><br>I REALLY LIKE STRENGTH TRAINING NOW<br><br>I look forward to every Tuesday and Friday early morning. I have no problems putting myself to bed early enough to get up at 5:15. I'm having fun learning about olympic lifts, which I'd never done before.</div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1486011159660662790/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 3<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-01-25T16:20:43.000Z">2022 Jan 25</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">5</strong> 🙂</span></span></div>
<p>I fully credit this strength work to keeping me completely injury-free in 2022. (lololol while I was writing this article I injured my neck and shoulder while lifting!!! So forget about "injury-free for 2023.")</p>
<h3>I did some triathlons</h3>
<p>My wife and I agreed that we wanted to do our first Ironman 70.3 this year — that's 70.3 kilometers total, half the distance of a full Ironman. It took <strong>a lot</strong> of training, but it was fantastic and I would do it again (when my kids don't want to spend time with me). I had it in my head that if everything went right, I could finish in 6 hours. I made some fueling mistakes on the bike and fought off muscle cramps for 12.6 miles of the 13.1 mile run, but finished about two and a half minutes under 6 hours!</p>
<div class="tweet h-entry" id="1568763353669181441"><div class="tweet-text e-content" data-pagefind-body="">Sunrise this morning was 💯. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/iaL7lDbFTR.jpeg"><img src="https://tweets.stevenhicks.me/img/iaL7lDbFTR.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1568763353669181441/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 14<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-09-11T00:48:24.000Z">2022 Sep 11</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">0</strong></span></span></div>
<p>I did a handful of other races too, including my first ever time at <a href="https://tweets.stevenhicks.me/1556455744039436288">the Age Group National Championships</a>.</p>
<h3>I rode my bike a ton</h3>
<p>Since I had to do so much training for the Ironman 70.3, I signed up for a couple long bike rides. I rode 92 miles around Lake Winnebago, and rode my first ever century ride: 105 miles to the Northern Kettle Moraine forest and back.</p>
<div class="tweet h-entry" id="1577463131420626945"><div class="tweet-text e-content" data-pagefind-body="">Aaaggghhh!<br>Tomorrow!<br>105 miles!<br>On my bike!<br>Self-supported!!<br>Almost exactly a mile of elevation gained!!<br>To fudgienuckles and back!!<br>Yes that's really the name!!!<br><br>It's the 2022 Tour de Steve and when it makes my body cry it will be a smashing success 💪💪💪🚴🚴🚴 <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/zWiFIfineU.jpeg"><img src="https://tweets.stevenhicks.me/img/zWiFIfineU.jpeg" width="777" height="1200" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1577463131420626945/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 21<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-10-05T00:58:13.000Z">2022 Oct 5</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">4</strong> 🙂</span></span></div>
<p>One other random fun experience of the summer — my friend David Berry sent me a DM asking if I wanted to meet him at Road America, a race track about an hour north of me, to ride bikes one night. Of course!</p>
<div class="tweet h-entry" id="1552630758363242505"><div class="tweet-text e-content" data-pagefind-body="">Achievement unlocked: riding bikes around a race track with one of my favorite Cream City Code speakers <a href="https://twitter.com/DavidCBerry13/" class="tweet-username h-card">@<span class="p-nickname">DavidCBerry13</span></a> <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/o_rPWQpYWn.jpeg"><img src="https://tweets.stevenhicks.me/img/o_rPWQpYWn.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/4P6MufHTCk.jpeg"><img src="https://tweets.stevenhicks.me/img/4P6MufHTCk.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/HPb-1jbWnm.jpeg"><img src="https://tweets.stevenhicks.me/img/HPb-1jbWnm.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/t_GyW8qfwy.jpeg"><img src="https://tweets.stevenhicks.me/img/t_GyW8qfwy.jpeg" width="900" height="1200" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1552630758363242505/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 13<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-07-28T12:23:13.000Z">2022 Jul 28</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">2</strong> 🙂</span></span></div>
<h2>I did fun things</h2>
<p>In 2022 I tried to be more intentional about how I spent my time, and focus on things that brought me joy.</p>
<p>I dressed up in an eagle costume and give kids high-fives all day, as they ran around in circles to raise money:</p>
<div class="tweet h-entry" id="1520108818255994880"><div class="tweet-text e-content" data-pagefind-body="">Oh you know, just spending my lunch running around the elementary school in an eagle costume and collecting high fives, as one does. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/6xRAOOIa-Q.jpeg"><img src="https://tweets.stevenhicks.me/img/6xRAOOIa-Q.jpeg" width="900" height="1200" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1520108818255994880/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 18<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-04-29T18:32:39.000Z">2022 Apr 29</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">0</strong></span></span></div>
<p>We spent some time in the Dominican Republic for spring break, and it was glorious:</p>
<div class="tweet h-entry" id="1504921440772145154"><div class="tweet-text e-content" data-pagefind-body="">My kids start spring break today, but we just got back from our spring break trip to Punta Cana. The weather there (85F/29C & sunny every day) was much better than the weather here (38F/3C & rainy).<br><br>I have not yet fully washed the salt from my hair. I haven't decided if I will. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/I35bibZq0O.jpeg"><img src="https://tweets.stevenhicks.me/img/I35bibZq0O.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/esJzsV67po.jpeg"><img src="https://tweets.stevenhicks.me/img/esJzsV67po.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/HDpY95urSi.jpeg"><img src="https://tweets.stevenhicks.me/img/HDpY95urSi.jpeg" width="1200" height="899" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1504921440772145154/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 10<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-03-18T20:43:26.000Z">2022 Mar 18</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">1</strong> 🙂</span></span></div>
<p>I went to my first concert since the pandemic started:</p>
<div class="tweet h-entry" id="1500313367218728965"><div class="tweet-text e-content" data-pagefind-body="">Omfg I went to a concert. I thought I'd be the oldest person at the <a href="https://twitter.com/illuminatihotts/" class="tweet-username h-card">@<span class="p-nickname">illuminatihotts</span></a> show but apparently there are lots of middle aged fans like me so I guess that's..good? 😅😬 <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/9yb1iV3aex.jpeg"><img src="https://tweets.stevenhicks.me/img/9yb1iV3aex.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1500313367218728965/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 15<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-03-06T03:32:35.000Z">2022 Mar 6</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">5</strong> 🙂</span></span></div>
<p>We took our annual vacation to the northwoods of Wisconsin:</p>
<div class="tweet h-entry" id="1539072921779445760"><div class="tweet-text e-content" data-pagefind-body="">Beats working. <is-land on:visible="" ready=""><div class="tweet-medias"><a href="https://tweets.stevenhicks.me/img/Z-qj0ndOZB.jpeg"><img src="https://tweets.stevenhicks.me/img/Z-qj0ndOZB.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/jSWdoDjW-s.jpeg"><img src="https://tweets.stevenhicks.me/img/jSWdoDjW-s.jpeg" width="900" height="1200" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/w6AK0K_UZH.jpeg"><img src="https://tweets.stevenhicks.me/img/w6AK0K_UZH.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a><a href="https://tweets.stevenhicks.me/img/Vc0GuhNuN0.jpeg"><img src="https://tweets.stevenhicks.me/img/Vc0GuhNuN0.jpeg" width="1200" height="900" alt="oh my god twitter doesn’t include alt text from images in their API" class="tweet-media u-featured" onerror="fallbackMedia(this)" loading="lazy" decoding="async"></a></div></is-land></div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1539072921779445760/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 21<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-06-21T02:29:13.000Z">2022 Jun 21</time><span class="tag tag-naked tag-lite sr-only">Mood <strong class="tweet-sentiment">0</strong></span></span></div>
<h2>I made some changes for my mental health</h2>
<p>That subheading is kind of dumb — isn't that something we're all constantly doing? Or at least, we make changes as soon as we notice there's a problem.</p>
<p>Anyway, some things that made a difference for me last year:</p>
<h3>I remembered I had a "SAD lamp"</h3>
<p>Living in the upper midwest part of the US means not seeing the sun for winter. And even if you do see it, you're hiding inside. And even if you go outside, you're covered in layers that block the sun from getting to you.</p>
<p>A few years ago I bought a "SAD lamp" — a lamp that is intended to trick your brain into thinking you aren't currently stuck in 5 months of winter hell. I used it a bit at first, but then largely forgot about it.</p>
<p>I got crushed with sadness at the beginning of this winter, and kept forgetting to take Vitamin D supplements. I remembered the SAD lamp and have been using it pretty much every day. At one point in December, after using the lamp for about a week straight, I remember saying "whoa, I actually feel joy right now." I'm convinced at this point that the lamp does more for me than the Vitamin D supplements (though I still take those).</p>
<p>Hopefully next winter I remember!</p>
<h3>I took a hard look at my productivity systems</h3>
<p>Late in 2023 I realized my productivity systems needed a revamp. I had a sudden realization that <a href="https://www.stevenhicks.me/blog/2021/02/maximizing-productivity/">my optimizations for a schedule that was lean on deep focus time</a> were no longer applicable. My schedule has flipped, to be honest — I now have plenty of deep focus time. The misalignment between my schedule and my productivity system was causing me to basically never get shallow tasks done (e.g. following up on comms, scheduling appointments, administrative stuff, etc.). I was waiting for a small block of time to do my shallow work....and I just wasn't getting any small blocks of time. (How's that for a humblebrag?)</p>
<p>With perfect timing, I saw that <a href="https://twitter.com/anthilemoon">Anne-Laure Le Cunff</a> was kicking off a "Mindful Productivity" course, and I jumped on it. The course was great — it opened my eyes to <a href="https://tweets.stevenhicks.me/1507391297904164873">a few problems with how I was approaching productivity</a>. It also taught me some tools I'm now using every single day.</p>
<p>I spent most of December blowing up the system I had, and fine-tuning something new. When I came back from holiday break this January, the changes were mostly in place, and I quickly noticed how much better I felt about keeping track of my work.</p>
<p>I'll do a mini-episode of <a href="https://anchor.fm/a-developer-experience">A Developer Experience Podcast</a> about this soon, with more details.</p>
<h3>I left Twitter...for now</h3>
<p>I didn't expect to write this paragraph. I didn't expect the events that transpired, and even if I had I wouldn't have predicted they'd go as foul as they did.</p>
<p>Twitter has been the social media platform that <em>just made sense</em> to me since I first joined, probably close to 15 years ago. It's been an amazing place for software developers — an incredible community of people building things.</p>
<p>But I no longer want to participate in it. I don't have any interest in supporting the CEO, and every engagement helps him. In November I opened a <a href="https://hachyderm.io/@pepopowitz/109300275553645269">Mastodon account on hachyderm.io</a>. In December, I deleted the Twitter app from my phone. For a while, I checked back daily from my laptop. Eventually I realized the only reason I was paying attention was to see what Elon burned that day. That seemed pretty unhealthy. I reflected on my goal for the year — retain the joyful, discard the joyless — and finally stopped checking Twitter regularly.</p>
<p>Overall, I'm happy that I don't hang out there anymore. Twitter fueled my rage. I think I liked that. Mastodon is less interesting, so I spend less time on social media now.</p>
<p>I'm not content with Mastodon. I'm not a pure believer in the fediverse. I definitely miss the people from Twitter. But Mastodon is now my main, until something happens to convince me to go somewhere else.</p>
<h4>I took back my tweets</h4>
<p>One other step I took in my (temporary?) exit from Twitter: <a href="https://github.com/pepopowitz/tweetback">I took my tweets back</a>, using <a href="https://github.com/tweetback/tweetback">the tweetback project</a>. The archive of all my tweets is now available at <a href="tweets.stevenhicks.me">https://tweets.stevenhicks.me</a>. You probably noticed the many "embeds" in this post that don't come from Twitter or Mastodon. Those all came from my tweet archive, and they link there instead of Twitter.</p>
<h2>What's up for 2023?</h2>
<p>To be perfectly honest, I don't have a lot of big goals for 2023. I'm not looking to change much. Everything I'd like to do more of would come at the expense of things I'm also enjoying.</p>
<p>This is the most positive, satisfied, content year-in-review I've written in years. I'm proud of myself for finding balance and joy in 2022. I wish you, and myself, the same in 2023.</p>
<div class="tweet h-entry" id="1590724402328776704"><div class="tweet-text e-content" data-pagefind-body="">Spirit guide: a guide that helps you spell spiret. Sperit. Aww fuuu come on guide, I need you right now. <br><br>Some people wake up with solutions to problems in their head. I get this garbage 👆</div><span class="tweet-metadata"><a href="https://tweets.stevenhicks.me/1590724402328776704/" class="tag tag-naked">Permalink</a><a href="https://tweets.stevenhicks.me/" class="tag tag-naked tag-lite tag-avatar"><img src="https://tweets.stevenhicks.me/img/__avatar.jpeg" width="52" height="52" alt="pepopowitz’s avatar" class="tweet-avatar"></a><span class="tag tag-lite tag-favorite">❤️ 2<span class="sr-only"> Favorites</span></span><time class="tag tag-naked tag-lite dt-published" datetime="2022-11-10T15:13:46.000Z">2022 Nov 10</time><span class="tag tag-naked tag-lite sr-only">Mood +<strong class="tweet-sentiment">2</strong> 🙂</span></span></div>
Breaking Problems Down: A Case Study2022-11-30T16:42:18.795+00:00https://stevenhicks.me/blog/2022/11/breaking-problems-down-a-case-study/<p>I've written in the past about <a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/">strategies for breaking Pull Requests (PRs) into smaller pieces</a>. In that article I give a number of recommendations for reducing the size of a PR....but it's still very abstract.</p>
<p>Recently I spent a couple months working on a large-ish project for the <a href="https://github.com/camunda/camunda-platform-docs">Camunda Platform 8 documentation</a>. The size of the project forced me to practice what I preach. It involved moving <em>a lot</em> of content, which could easily have turned into massive PRs, and left overwhelmed reviewers with no choice but to rubber stamp "LGTM" (looks good to me) on them. I also worked in relative isolation on this project, and since I was much deeper into the work than my reviewers, easily-digested PRs were an absolute requirement.</p>
<p>So consider this article almost an addendum to <a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/">my previous work</a>. Instead of describing in the abstract, now I can point you at concrete examples!</p>
<h2>Background: the project</h2>
<p><a href="https://camunda.com/platform/">Camunda Platform 8</a> includes a handful of components that work together to facilitate process orchestration. Most of them are always on the same version — but some of them aren't! Our <a href="https://camunda.com/platform/optimize/">Optimize</a> component, which you might guess empowers you to optimize a modeled process, is on a completely different version number than the rest of the components. Where most components are currently on version 8.1, the latest Optimize release is 3.9.0.</p>
<p>Unfortunately, our docs weren't reflecting this. We treated the latest version of <em>all</em> components as version 8, even if that wasn't correct. And that was this project! Get <a href="https://docs.camunda.io/optimize/components/what-is-optimize/">the Optimize docs</a> showing the correct version number.</p>
<p>As with anything, when you boil it down to a paragraph it sounds like way less work than the actual implementation. 😅</p>
<h2>The work before the work</h2>
<p><a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/#small-prs-start-long-before-the-work-starts">Small PRs start long before the PRs are opened</a>. In this case, some up-front investigation helped identify ways to break the work down.</p>
<h3>Early exploration to identify and understand the work</h3>
<p>I had two goals with the early exploration:</p>
<ol>
<li>To understand what work was needed, so that I could break it down.</li>
<li>To resolve some uncertainty about how the tooling supported solving our problem.</li>
</ol>
<p>So I built some proofs of concept:</p>
<ol>
<li><a href="https://github.com/camunda/camunda-platform-docs/pull/904">To explore the Docusaurus feature of "multi-instance versioning"</a>.</li>
<li><a href="https://github.com/camunda/camunda-platform-docs/pull/906">To rougly apply "multi-instance versioning" to our Optimize documentation</a>.</li>
<li><a href="https://github.com/camunda/camunda-platform-docs/pull/910">To make it possible to release incomplete changes</a>.</li>
</ol>
<p>Along the way, I built a couple more proofs of concept when I ran into problems I wasn't sure how to solve:</p>
<ol start="4">
<li><a href="https://github.com/camunda/camunda-platform-docs/pull/921">Reducing duplication across documentation instances</a>.</li>
<li><a href="https://github.com/camunda/camunda-platform-docs/pull/1345">Navigation issues across documentation instances</a>.</li>
</ol>
<p>This investigation and prototyping resulted in a much better understanding of the work. It even helped us identify work that, if done up front, would improve our ability to schedule and complete the remaining work in three important ways:</p>
<ol>
<li><a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/#integrating-code-a-little-at-a-time">Changes could be introduced incrementally</a>.</li>
<li><a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/#separate-infrastructural-work-from-implementations">Infrastructural changes could be introduced separate from routine content movement</a>.</li>
<li><a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/#start-with-small-scope--slice-your-stories-small">Work could be sliced into smaller deliveries</a>.</li>
</ol>
<p>The critical output of breaking down work is not only the smaller pieces. It's also the knowledge of which pieces are the scariest, riskiest, and most uncertain, so you can solve those first.</p>
<h3>Make the change easy before making the easy change</h3>
<p>Of the 3 improvements listed above, I want to call out one in particular. Before writing a single line of code for the project, I wanted to make sure we could integrate incomplete changes a little bit at a time, especially at the beginning. As the project went on, and PRs started to resemble other previous PRs, it became less important to be able to integrate incrementally. But at the beginning, this was novel work -- we weren't sure what it would/should look like, and I wanted to feel safe introducing it in incomplete parts.</p>
<p>The first PR I opened for this project was to <a href="https://github.com/camunda/camunda-platform-docs/pull/1118">introduce a "next" version of the docs</a>. With an "under construction" version, I felt free to make as many changes there as I wanted. I could deploy incomplete changes and show them to people for feedback.</p>
<p>This is not the first time I've referenced the following Kent Beck tweet, nor will it be the last:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">for each desired change, make the change easy (warning: this may be hard), then make the easy change</p>— Kent Beck 🌻 (@KentBeck) <a href="https://twitter.com/KentBeck/status/250733358307500032?ref_src=twsrc%5Etfw">September 25, 2012</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Building a seam before introducing changes is always easier than doing those two things simultaneously.</p>
<h2>Tracking the work</h2>
<p>After the initial exploration and proposal, this project sat untouched for a month or two — for no important reason, there are just other things that I worked on. But the exploration gave us enough information to start tracking the work with some level of confidence.</p>
<p>There are a handful of different tools and artifacts we use to track projects at Camunda — Trello, Jira, GitHub, Google Docs... For the Developer Experience team in particular, we've moved to using GitHub to track almost everything. So for this project, I created <a href="https://github.com/camunda/camunda-platform-docs/issues/1116">a single issue to list all the things we'd have to do to complete this project</a>. I initially filled it out from a high level, not too much detail, figuring I'd fill in more details as I learned them.</p>
<p>I have mixed feelings about this approach. I like that there is one place that tracks all of the work. I believe strongly in the importance of tracking the work publicly (or at least, visible to my team). This accomplishes that.</p>
<p>But by the end of the project the issue became pretty massive. If you're looking for something specific in that list of completed work, it's hard to find. Part of me thinks this might have been better served as a GitHub project, instead of an issue. I chose the artifact based on what I knew at the time, so I'm not holding it against myself for tracking it this way. I do think I'll be more conscious next time of how big an epic <em>might grow</em> when I decide how to track it.</p>
<h2>Explaining the work</h2>
<p>Knowing that I was working on this project mostly in isolation, it was critical to explain my work to reviewers who had less context. I learned some good habits about <a href="https://artsy.github.io/blog/2020/08/11/improve-pull-requests-by-including-valuable-context/">adding context to PRs</a> while at Artsy. On this project I got to put them to good use — especially <a href="https://github.com/camunda/camunda-platform-docs/pull/1170">adding videos to demonstrate changes</a> (<a href="https://www.loom.com/share/511730f7dbec41e9ad6fb1d748da0041">direct link to video</a>), and <a href="https://github.com/camunda/camunda-platform-docs/pull/1328">inline comments identifying the interesting changes</a>.</p>
<p>A couple other good reasons to explain work at this level:</p>
<ol>
<li>When anyone comes back to this in the future, there will be plenty of context. They shouldn't have to spend much time spelunking Slack or asking "hey I know this is a long shot, but do you remember why we wrote this line of code 6 months ago?"</li>
<li>It makes for a nice reference point whenever we want to do something similar in the future, or when someone on the internet asks a question about how to do this thing.</li>
</ol>
<h2>Separating infrastructure from routine work</h2>
<p>Pull requests that combine significant infrastructural changes with routine changes <a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/#separate-riskycontroversial-work-from-routine-work">are a recipe for losing the signal amidst the noise</a>.</p>
<p>One example of this was mentioned above — <a href="https://github.com/camunda/camunda-platform-docs/pull/1118">introducing a "next" version of the docs</a>, which was shipped separately from any content changes. After it was merged, I was free to twiddle with content all I wanted, but I wouldn't have wanted someone to have to review both types of changes in one place.</p>
<p>Another example — <a href="https://github.com/camunda/camunda-platform-docs/pull/1166">while I was moving Optimize documentation into its own section for the first time</a>, I noticed that the multiple sets of versions were going to create a cross-linking mess. We'd end up with hard-coded versions in URLs when linking across the documentation, and have to update them whenever new versions were released. Before completing <a href="https://github.com/camunda/camunda-platform-docs/pull/1166">the first Optimize docs PR</a>, I introduced <a href="https://github.com/camunda/camunda-platform-docs/pull/1170">an infrastructural enhancement for cross-linking</a>.</p>
<p>Sequence in this case probably didn't matter too much; the importance to me was that I had two related but distinct changes, and I wanted to keep the history of them separate. Aside from making it easier to review, this approach prevents <a href="https://artsy.github.io/blog/2021/03/09/strategies-for-small-focused-pull-requests/#what-is-small-and-focused">one ready feature from being held up by one disputed feature</a>.</p>
<h2>Splitting stories</h2>
<p>Even though there was a ton of content to move in this project, there presented two natural ways to split the content into smaller pieces: by version, and by content section.</p>
<p>Splitting by version was something we noticed up front. We could iterate through the different versions, starting with the most recent version and ending with the oldest, and migrate content one version at a time. This also presented itself midway through the project as an opportunity to defer work. As we worked through newer versions, we realized that the oldest versions were less important to us, and we de-prioritized them.</p>
<p>Splitting by content section was not noticed up front. In fact I only discovered this natural seam on accident — by forgetting to do two of three content sections! 😅😬 When a stakeholder pointed out that I'd only shipped one section of the first version, I decided this was fine, and actually a good way to break things down for the other versions.</p>
<h2>De-scoping mercilessly</h2>
<p>One of my constant struggles in software development is deciding when a bug or edge case should be resolved with the work I'm doing, or if it can be done later. I have a high standard for "done" — sometimes too high. Perfect is the enemy of good, as....someone says. And I fall for perfection just about every time.</p>
<p>I've gotten better at de-scoping and deferring these kinds of things, and this project was an opportunity for me to demonstrate my progress. Many issues came up as I was working, and you can see <a href="https://github.com/camunda/camunda-platform-docs/issues/1116">in the epic for this project</a> that we treated many of them as follow-up work instead of blocking issues.</p>
<hr>
<p>Do you have any real life examples of breaking problems down into smaller pieces? <a href="/where">Let me know!</a></p>
Automating Pull Requests With Sed And The GitHub CLI2022-11-15T16:33:02.834+00:00https://stevenhicks.me/blog/2022/11/automating-pull-requests/<div style="width: 50%; margin-left: auto;margin-right: auto;">
<p><img src="https://imgs.xkcd.com/comics/automation.png" alt="xkcd comic on automation"></p>
<p><em><a href="https://xkcd.com/1319/">Obligatory xkcd comic on automation</a></em></p>
</div>
<h2>TL;DR</h2>
<p><a href="https://gist.github.com/pepopowitz/125e1102d455d5cdb20c345930c4c9de">Here's a Bash script to</a>:</p>
<ol>
<li>Take a branch name</li>
<li>Create a new branch based on the original</li>
<li>Call some scripts to update files</li>
<li>Use sed to update a config setting in another file</li>
<li>Commit the changes</li>
<li>Push up a GitHub PR with a lot of information filled in</li>
</ol>
<p>On the other hand, if you've got some time...</p>
<h2>Pull up a chair and listen to me talk about my personality again 🙄</h2>
<p>My learning style looks something like this:</p>
<ol>
<li>Run into a problem</li>
<li>Discover or remember a tool that could solve that problem</li>
<li>Learn that tool just enough to solve the problem</li>
<li>Never go back to that tool until the next problem wants it</li>
</ol>
<p>It's kind of frustrating, actually — I would love to be the kind of person who dives <strong>deep</strong> into something new. I'd love to stay interested in a topic for more than one problem. Instead, I learn a little about a tool, and lose interest in it until I need it again. This is why this blog seems so scatterbrained, I think — no content strategy here, friends! Just a bunch of mismatched tangentially related solutions to very specific problems.</p>
<p>Nowhere is this more obvious than in my use of shell scripting. I know enough Bash to be ...very very casually dangerous. Like tortoise-shell-sunglasses-and-fake-leather-bomber-jacket dangerous.</p>
<p>Recently I got a chance to grow my shell scripting skills! When I recognized the need for <a href="https://www.gnu.org/software/sed/manual/sed.html">sed</a>, I was excited that I finally knew enough about it to recognize a case for it.</p>
<p>And as soon as I'd automated that, I recognized another opportunity — to get a little more knowledge of <a href="https://cli.github.com/">the GitHub CLI</a>.</p>
<h2>The use case, in detail</h2>
<p>We have <a href="https://docs.camunda.org/">some older docs at Camunda</a> that are built with Hugo. There are about 20 versions that we maintain, and <a href="https://github.com/camunda/camunda-docs-manual/branches/all?query=7.">each version is built from its own branch</a>. This is a pain when you have to update more than one version. Thankfully, that doesn't happen very often.</p>
<p>But ooohhhhh, when it does! When it does!</p>
<p>We recently learned that we had sabotaged our docs by de-indexing most of them from Google search results. Yikes! Very long story short — when providing multiple versions of documentation, make sure you explicitly declare one version as the canonical. If you don't, Google <em>will pick one for you</em>! And <em>it might pick one that you told it not to index</em>!!</p>
<p>To resolve this, I wanted to apply canonical URLs to all versions of our docs. Which meant I had to make nearly identical changes to all ~20 versions/branches. The changes themselves were relatively simple:</p>
<ul>
<li>Update the theme to include canonical URLs</li>
<li>Configure a site-level setting to use the correct base URL</li>
</ul>
<h2>Feel the pain before addressing it</h2>
<p>More personality stuff, sorry. Here's one rule of developer experience that I feel pretty strongly about:</p>
<blockquote>
<p>Before you address a problem, you should feel the pain of it as directly as possible.</p>
</blockquote>
<p>Otherwise you're just guessing at the solution.</p>
<p>As a bonus, having a way to experience the problem gives you a nice feedback loop. You'll know you've fixed the problem when you no longer experience it.</p>
<p>All this to say, I had a hunch I might want to automate these PRs, but I didn't feel comfortable automating them until I knew what the repetition looked like. So I worked through the first few manually.</p>
<p>Once I'd been through the process a few times, I recognized the repetition. For each version of the docs, I needed to:</p>
<ol>
<li>Manage my branch</li>
<li>Run a command to update a few files</li>
<li>Update a configuration file by changing one setting</li>
<li>Commit it all</li>
<li>Create a PR based on a template</li>
</ol>
<p>1, 2, and 4 were already in my toolbox. 3 and 5 were not.</p>
<h2>3. Updating a configuration file with sed</h2>
<p><a href="https://www.gnu.org/software/sed/manual/sed.html">sed is a "stream editor"</a>; for my needs, that effectively means it's a command line tool for editing the contents of a file. It's not just the editing that makes it the right tool to change a config setting — it also makes it easy to target text, by filtering the stream/file.</p>
<p>My specific use case was to modify the value of the <code>baseURL</code> configuration setting, to include a domain. So where the config file previously read:</p>
<p><code>baseURL: "/some/value"</code></p>
<p>I needed it to read:</p>
<p><code>baseURL: "https://docs.camunda.org/some/value"</code>.</p>
<p>I'd previously heard of sed, and previously used sed, but mostly in the context of copy-pasting things from the internet. This was the first time I'd looked at a problem and thought "oh, I could use sed to do that!"</p>
<p>And I'll be honest -- my understanding is still not far beyond <a href="https://en.wikipedia.org/wiki/Cargo_cult_programming">cargo-culting</a>. What I <em>can</em> say is that these are the arguments I needed:</p>
<ul>
<li><code>-i ''</code> to edit the file inline, rather than create a copy</li>
<li><code>-E</code> to use "extended" regular expressions, because my regular expression used <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Backreferences">a capture group</a></li>
<li><code>'s/...matching pattern.../...replacement pattern...'/g</code> to substitute text for a regular expression match</li>
<li>the file name to edit</li>
</ul>
<p>In the end, this is what my command looked like:</p>
<pre><code class="language-sh">sed -i '' -E 's/baseURL: \"(.*)\"/baseURL: \"https:\/\/docs.camunda.org\1\"/g' config.yaml
</code></pre>
<p>This searches the <code>config.yaml</code> file for the <code>baseURL</code> setting, and injects the domain before its current value.</p>
<p>The most frustrating part about writing this command was figuring out that I needed the <code>-E</code> flag in order to use a capture group in my regular expression.</p>
<p>Yay, computers!</p>
<h2>5. Creating a PR with the GitHub CLI</h2>
<p>I'd previously opened many GitHub PRs, and I even use the GitHub CLI to do it every single time (although I abstract it behind <a href="https://github.com/pepopowitz/dotfiles/blob/main/git.zsh#L8">an alias I call <code>open-pr</code></a>).</p>
<p>And initially, that's what I scripted. But I found myself going into the GitHub UI to update things repetitively. I figured there had to be some arguments I could use to change those values for me.</p>
<p><a href="https://cli.github.com/manual/gh_pr_create">There are</a>!</p>
<p>In particular, I was interested in these arguments:</p>
<ul>
<li><code>--web</code>: to open the web interface, so that I could make some final changes before creating the PR</li>
<li><code>-t</code>: the title of the PR</li>
<li><code>-a</code>: PR assignee, which I could set to myself with the identifier <code>@me</code></li>
<li><code>-B</code>: the base of the PR, meaning which branch it should be merged into</li>
<li><code>-b</code>: the body of the PR.</li>
</ul>
<p>I also assigned a bunch of variables ahead of time, to make my command easier to read:</p>
<pre><code class="language-sh">gh pr create --web -t "$TITLE" -a @me -B $VERSION -b "$BODY"
</code></pre>
<h2>Stitching it all together</h2>
<p>You can see my final script <a href="https://gist.github.com/pepopowitz/125e1102d455d5cdb20c345930c4c9de">in this gist</a>. I've done my best to comment it well, but feel free to leave comments in there if anything doesn't make sense or could be improved.</p>
<h2>Extra credit: automating the screenshots</h2>
<p>When using the GitHub CLI to open a PR, I chose to use the web interface because there was still one repetitive task I didn't want to automate: adding screenshots to the body of the PR.</p>
<p>I'm certain there are tools to do this, but it seemed like a can of worms I didn't want to open. Getting everything but the screenshots was 95% of the win. I didn't mind having to take two screenshots and then paste them into the GitHub UI.</p>
<p>If you've automated this step as part of opening a PR, <a href="/where">I'd love to hear about it</a>!</p>
Select The Email For Git Commits Based On The Current Directory2022-05-27T15:47:27.650+00:00https://stevenhicks.me/blog/2022/05/select-email-for-git-commits-based-on-current-directory/<p>My new job requires that commits in our repos are signed with our work emails. Setting the global email address is no problem:</p>
<pre><code class="language-sh">> git config --global user.email "my.work@email.com"
</code></pre>
<p>For no important reason, other than that I believe in the sanctity and purity of a git log (<a href="https://github.com/artsy/eigen/pull/5285/commits">sorry, Pavlos</a>), I decided that I want commits in my <em>personal</em> repos to be signed with my <em>personal</em> email.</p>
<p><em>Heads up:</em> this isn't an article about setting up <em>authentication</em> for two different users — that is pretty well described in <a href="https://code-maven.com/per-project-ssh-public-keys-git">this article about multiple SSH keys</a>. This article is about using two different email addresses to sign commits.</p>
<h2>You could use repo-level .gitconfigs</h2>
<p>The previously linked article mentions one way to use my personal email on my personal repos — but it requires setting the local git config on each repo. This is similar to how we set it above globally:</p>
<pre><code class="language-sh">> git config user.email "my.personal@email.com"
</code></pre>
<p>We aren't specifying <code>--global</code> here, so it's applying the configuration change to the current repo only.</p>
<p>This is probably a fine way to solve the problem, but I don't like the idea of having to remember to do this for every personal repo I clone. I see a very likely future where I clone a repo, make a handful of commits and push them up, then realize they were signed with my work email. 😝</p>
<h2>Folder-level .gitconfigs don't exist</h2>
<p>Structurally, I keep my projects organized in a single folder that clarifies the owner: <code>~/dev/personal/</code>, vs <code>~/dev/camunda/</code>.</p>
<p>I'd love to be able to create one .gitconfig in the <code>~/dev/personal/</code> folder, and have it apply to all its child repos. That's not supported, unfortunately. I guess this makes sense, as it might be asking a lot for git to crawl up the tree to find all configuration files.</p>
<h2>Conditional git includes FTW</h2>
<p>We can use <a href="https://git-scm.com/docs/git-config#_conditional_includes">conditional git includes</a> to solve the problem! (h/t <a href="https://stackoverflow.com/a/43884702">this StackOverflow answer</a>)</p>
<p>To use a conditional git include, you specify a condition and a path to a file. If the condition is met, the file is expanded and its contents are included in your configuration.</p>
<p>The condition could be something like <code>onbranch</code>, to assess which branch you're currently on, or <code>hasconfig:remote.*.url</code> to look for a specific remote URL. In our case, we'll use the <code>gitdir</code> condition to assess the repo's working directory.</p>
<p>We'll set this up so that the global <code>.gitconfig</code> contains my work email. If a <code>gitdir</code> condition is met based on the repo's directory, a partial <code>.gitconfig</code> will override the email with my personal one.</p>
<h2>The final solution</h2>
<p>My global <code>.gitconfig</code> contains my default email address, and the conditional include:</p>
<pre><code>[user]
name = Steven Hicks
email = my.work@email.com
[includeIf "gitdir:~/dev/personal/"]
path = .gitconfig-personal
</code></pre>
<p>For any repos that are inside that <code>~/dev/personal/</code> folder, the <code>.gitconfig-personal</code> file will be applied. The <code>.gitconfig-personal</code> file contains only the properties I want to override:</p>
<pre><code>[user]
email = my.personal@email.com
</code></pre>
<h2>Proof</h2>
<p>You can pass a flag to <code>git config</code> to tell it to list out the current configuration: <code>git config -l</code>.</p>
<p>When I'm in a work directory, this emits my default email address:</p>
<pre><code class="language-sh">❯ git config -l
user.name=Steven Hicks
user.email=my.work@email.com
includeif.gitdir:~/dev/personal/.path=.gitconfig-personal
</code></pre>
<p>Note that the <code>includeif</code> shows up here, but the condition was not met, so it doesn't bring over the contents of the conditional file.</p>
<p>Most importantly, when I'm working in a personal repo, I see my personal email address:</p>
<pre><code class="language-sh">❯ git config -l
user.name=Steven Hicks
user.email=my.work@email.com
includeif.gitdir:~/dev/personal/.path=.gitconfig-personal
user.email=my.personal@email.com
</code></pre>
<p>It actually includes <em>both</em> email addresses...but the last one wins, and git uses my personal email address to sign the commits.</p>
<hr>
<p>Is this the only way to solve this problem? Likely not. If you know of another way, <a href="https://twitter.com/pepopowitz">let me know</a>!</p>
Setting Up A New Dev Environment With Existing Dotfiles (via rcm)2022-04-15T15:56:03.882+00:00https://stevenhicks.me/blog/2022/04/setting-up-a-new-dev-environment-with-existing-dotfiles-via-rcm/<p>I started a new job recently (<a href="https://twitter.com/pepopowitz/status/1507005453036752903">as a Developer Experience Engineer at Camunda</a>!). One of my favorite things about starting a new job is setting up my dev environment. It gives me a chance to look through my previous dev environment and do some housekeeping — what do I really need to bring along with me, what can I drop, and what do I want to replace with something similar or better? It's like spring cleaning...but just like with my house, I only do it every few years.</p>
<p>During my previous job at Artsy, <a href="https://github.com/jonallured">my BFF Jon</a> taught me all about the importance of archiving your <a href="https://www.freecodecamp.org/news/dotfiles-what-is-a-dot-file-and-how-to-create-it-in-mac-and-linux/">dotfiles</a> with <a href="https://github.com/thoughtbot/rcm">rcm</a>, as a means to (a) share cool aliases and tools with each other, and (b) make it easier to set up a new environment. "Ok cool," I said, "I'll do whatever you tell me to, Jon," and I pushed all my dotfiles to a <a href="https://github.com/pepopowitz/dotfiles">public repo</a>.</p>
<p>But a dotfiles repo as a means to set up a new environment is a lot like backing up a database — if you don't <em>actually practice</em> restoring it, how do you know what you're backing up? And how do you know if the restore will <em>actually work</em>? I'll admit that I did not really know how well transferring my dotfiles would work, as I had not practiced it. I just did what Jon told me to do...in my defense, that works out 99% of the time.</p>
<p>One fresh laptop later, and I've got a good idea of what this side of rcm & dotfiles management looks like. It took a journey to get there, and I'll share that in a bit — but here's a TL;DR if you don't care for drama.</p>
<h2>TL;DR</h2>
<p>I settled on this process for setting up my environment with an existing dotfiles repo:</p>
<h3>1. Clone my <a href="https://github.com/pepopowitz/dotfiles">dotfiles repo</a>.</h3>
<p>From my root:</p>
<pre><code class="language-sh">~> git clone git@github.com:pepopowitz/dotfiles.git ./.dotfiles
</code></pre>
<p>That last argument (<code>./.dotfiles</code>) specifies a folder to clone into. My repo is named <code>dotfiles</code>, with no <code>.</code> prefix. <a href="https://github.com/thoughtbot/rcm">rcm</a>, out of the box, wants a folder named <code>~/.dotfiles</code>, with the <code>.</code> prefix. There's a flag to tell rcm to use something different, but I don't like to argue with the robots.</p>
<h3>2. Install <a href="https://github.com/thoughtbot/rcm">rcm</a>.</h3>
<p>I chose to install via <a href="https://brew.sh/">homebrew</a>. You do you.</p>
<pre><code class="language-sh">~> brew install rcm
</code></pre>
<h3>3. For files that should use the dotfiles repo as the source:</h3>
<p>a. Use the <a href="http://thoughtbot.github.io/rcm/rcup.1.html"><code>rcup</code></a> command to pull files from <code>~/.dotfiles</code> to the root:</p>
<pre><code class="language-sh">~> rcup filename
</code></pre>
<p>Note that there's no <code>.</code> at the beginning of the filename when sync'ing in this direction. rcm is looking for a file in your <code>~/.dotfiles</code> repo with this name, and nothing in your repo will have the <code>.</code> prefix.</p>
<p>b. Make necessary adjustments to the <code>~/.filename</code> file.</p>
<p>c. <code>git commit</code> and <code>git push</code> from your <code>~/.dotfiles</code> repo.</p>
<h3>4. For files that should use a new local file as the source</h3>
<p>a. Use the <a href="http://thoughtbot.github.io/rcm/mkrc.1.html"><code>mkrc</code></a> command to push the file from <code>~/</code> into your <code>~/.dotfiles</code> repo:</p>
<pre><code class="language-sh">~> mkrc .filename
</code></pre>
<p>Note that there <em>is</em> a <code>.</code> at the beginning of the filename when sync'ing in this direction. This time, rcm is looking for a file in your <code>~</code> root with this name, and your config files in <code>~</code> will all have the <code>.</code> prefix.</p>
<p>b. Browse the <code>git diff</code> of <code>~/.dotfiles</code> to make sure you didn't miss anything important</p>
<p>For example, when I did my <code>.zshrc</code> file, I noticed that git was planning to remove a few setup lines that I hadn't accounted for — a setup line for the version manager <a href="http://asdf-vm.com/"><code>asdf</code></a>, a configuration setting for <a href="https://ohmyz.sh/">oh-my-zsh</a> that I wanted to port to plain old zsh, and an imported file that I knew I'd eventually want.</p>
<p>c. <code>git commit</code> and <code>git push</code> from the <code>~/.dotfiles</code> repo.</p>
<p>🎉! Repeat steps 3 and 4 until everything's sync'ed up with your dotfiles repo.</p>
<hr>
<p>Phew, now that half my readers got what they needed and moved on, here's a recap of my journey for those with more time.</p>
<h2>I don't get it, what do you mean dotfiles and what is rcm?</h2>
<p>Okay, some definitions.</p>
<h3>Dotfiles</h3>
<p>Dotfiles is a cute little name to encapsulate all those configuration files in the root of your environment, many of whose names begin with a dot (<code>.</code>). Things like <code>.gitconfig</code>, <code>.zshrc</code> or <code>.bashrc</code>, etc. These files define all the settings that make your terminal your terminal.</p>
<p>A nice thing to do with your dotfiles, or more specifically <strong>the ones that don't contain sensitive data</strong>, is to lump them into a single repo and share them on GitHub. (For the sensitive data, you should extract that stuff to a separate file that <em>isn't included</em> in your dotfiles repo, and include it in your shell setup script. You'll have it on your machine, but no one else will ever see it.)</p>
<p>I say this is a nice thing to do because sharing is caring. Are you as disappointed in macos's bluetooth support as I am? <a href="https://github.com/pepopowitz/dotfiles/blob/ccf9df7045d4943ac41f15102fd309ac3a272477/aliases.zsh#L18">Here's an alias you can borrow to reset the coreaudio and bluetooth processes</a>. <a href="https://github.com/anandaroop/dotfiles/blob/master/aliases.sh#L38">Here's an alias for displaying a pretty git log tree</a>, demonstrating that <a href="https://github.com/anandaroop">my friend Roop</a> is cool and has some deep knowledge of formatting git logs.</p>
<h3>rcm</h3>
<p><a href="https://github.com/thoughtbot/rcm">rcm</a> is a tool you can use to maintain your dotfiles. It <a href="https://en.wikipedia.org/wiki/Symbolic_link">sym-links</a> the dotfiles in your root directory to files in another folder, which you can then initialize as a git repository and push up to GitHub.</p>
<p>But I'm not gonna lie....those rcm docs are hard for me to read and understand. They're basically <a href="https://en.wikipedia.org/wiki/Man_page">manpages</a>, and I don't do well with those. I can never find the information I'm looking for. And that's why I was a bit scared going through this process — yes, I can see the documentation for the tools provided by rcm....no, I can't see how they fit together in a real-world scenario.</p>
<h2>My journey to set up my new environment</h2>
<p>I started with my <a href="https://github.com/pepopowitz/dotfiles/tree/7312a92d9330966e5aeb0441160674e0175fad33">existing dotfiles repo</a>. It was very Artsy, and I wasn't sure how much I wanted to carry over. I used <a href="https://ohmyz.sh/">oh-my-zsh</a> for basically only the <code>git</code> plugin, and I had a bunch of aliases that were specific to Artsy. If I'd wanted to do this quickly, I likely could have cloned the repo to <code>~/.dotfiles</code>, run one rcm command (I'm not sure which one), and called it a day. But I took my time instead. If you know me well, you're thinking "<em>of course</em> you took your time, Steve."</p>
<p>As mentioned above, the first couple things I did were:</p>
<h3>1. I cloned my dotfiles repo into <code>~./.dotfiles</code></h3>
<pre><code class="language-sh">~> git clone git@github.com:pepopowitz/dotfiles.git ./.dotfiles
</code></pre>
<h3>2. I installed <a href="https://github.com/thoughtbot/rcm">rcm</a></h3>
<pre><code class="language-sh">~> brew install rcm
</code></pre>
<p>Before I could get comfortable with the process of setting up my environment based on existing dotfiles, I needed to mess around. So I experimented with some files.</p>
<h3>3. I started with a file I knew I'd be overwriting: <code>Brewfile</code>.</h3>
<p>In my dotfiles I had a file named <code>Brewfile</code>, which was a dumped homebrew bundle file containing all the tools I'd installed on my previous laptop. I used rcm's <a href="http://thoughtbot.github.io/rcm/rcup.1.html"><code>rcup</code></a> command to bring it from my <code>~/.dotfiles</code> repo into <code>~</code>:</p>
<pre><code class="language-sh">~> rcup Brewfile
</code></pre>
<p>This resulted in a <code>.Brewfile</code>, not <code>Brewfile</code>, in my root (the difference being the preceding <code>.</code>). I had hoped this rename would happen; it was nice to see it in action. rcm must assume that I want (a) the file in my root to be named with a preceding <code>.</code>, and (b) the file in my <code>~/.dotfiles</code> repo to be named without a <code>.</code>, and handle that translation for me. This step proved to me that this was the case.</p>
<p>Further confirmation — if I look at all the files managed by rcm, I can see how they link a file in the root with a <code>.</code> in the name to a file in <code>/.dotfiles</code> <em>without</em> a <code>.</code>. I can see which files rcm is managing with the <a href="http://thoughtbot.github.io/rcm/lsrc.1.html">lsrc command</a>:</p>
<pre><code class="language-sh">~> lsrc
/Users/stevenhicks/.Brewfile:/Users/stevenhicks/.dotfiles/Brewfile
/Users/stevenhicks/.README.md:/Users/stevenhicks/.dotfiles/README.md
/Users/stevenhicks/.aliases-force.zsh:/Users/stevenhicks/.dotfiles/aliases-force.zsh
/Users/stevenhicks/.aliases.zsh:/Users/stevenhicks/.dotfiles/aliases.zsh
/Users/stevenhicks/.functions.zsh:/Users/stevenhicks/.dotfiles/functions.zsh
/Users/stevenhicks/.gitconfig:/Users/stevenhicks/.dotfiles/gitconfig
/Users/stevenhicks/.hyper.js:/Users/stevenhicks/.dotfiles/hyper.js
/Users/stevenhicks/.pear-data:/Users/stevenhicks/.dotfiles/pear-data
</code></pre>
<p>In this case, I really just wanted to see what would happen; I knew that I didn't want the <code>~/.Brewfile</code> verbatim. So...</p>
<h3>4. I got a fresh export from homebrew and replaced it:</h3>
<pre><code class="language-sh">~> rm .Brewfile
~> brew bundle dump --file=~/.Brewfile
</code></pre>
<p><code>~/.Brewfile</code> now contained my new bundle, but the homebrew dump had replaced the sym-link from <code>~/.Brewfile</code> to <code>~/.dotfiles/Brewfile</code>. So I needed to hand control back over to rcm. To do this, I used a different command from rcm: <a href="http://thoughtbot.github.io/rcm/mkrc.1.html"><code>mkrc</code></a>.</p>
<pre><code class="language-sh">~> mkrc .Brewfile
</code></pre>
<p><code>mkrc</code> takes a file in your root (<code>~</code>), moves it to <code>~/.dotfiles</code>, and sym-links the original root file to point at the one in your <code>~/.dotfiles</code>. In this case, it points <code>~/.Brewfile</code> at <code>~/.dotfiles/Brewfile</code>.</p>
<p>Confirming that my <code>~/.dotfiles</code> repo included my new changes:</p>
<pre><code class="language-sh">~> cd .dotfiles
~/.dotfiles> git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: Brewfile
no changes added to commit (use "git add" and/or "git commit -a")
</code></pre>
<p>💪🏻💪🏻💪🏻💪🏻💪🏻</p>
<h3>5. I pushed my Brewfile changes up to GitHub.</h3>
<pre><code class="language-sh">~/.dotfiles> git commit -am "remove a bunch of stuff from brewfile"
~/.dotfiles> git push origin
</code></pre>
<p>I removed the output from these commands because they're pretty noisy. But while doing this, it gave me an error that I hadn't yet set up my committer name and email in a global <code>.gitconfig</code>! That makes sense — I'd not yet synced my <code>.gitconfig</code> from my dotfiles. So I did that next.</p>
<h3>6. Sync <code>~/.dotfiles/gitconfig</code> into a new <code>~/.gitconfig</code>.</h3>
<p>I kind of got lucky, since this second file was the opposite direction — whereas with <code>~/.Brewfile</code> I wanted to use a new file as the source, in this case I wanted to use the <code>~/.dotfiles</code> version as the source.</p>
<p>There's another rcm command for sync'ing in this direction: <a href="http://thoughtbot.github.io/rcm/rcup.1.html">rcup</a>. This one takes a file from the <code>~/.dotfiles</code> directory and creates a sym-link in <code>~</code> to point at it. In this case, I told it to point a sym-link from <code>~/.gitconfig</code> to <code>~/.dotfiles/gitconfig</code>:</p>
<pre><code class="language-sh">~> rcup gitconfig
</code></pre>
<p>I then made some changes to the .gitconfig, like updating my email address and removing a couple unused aliases. These changes needed to be committed to my GitHub repo, so I did a <code>git commit</code> and a <code>git push</code>.</p>
<p>Like I said, I was lucky. The first two files I tried were intended to sync in opposite directions, with one being based on the dotfiles version, and one being based on a new file. At this point I was feeling pretty comfortable with the flow for the rest of my dotfiles...which I described above in the TL;DR.</p>
<hr>
<p>Have you used rcm to manage your dotfiles? Am I doing something different than you are? I'd love to hear about how.</p>
2021 In Review2022-01-28T14:12:37.490+00:00https://stevenhicks.me/blog/2022/01/2021-in-review/<p>Whew! 2021! What a year. Stuff happened!!! And some things, too!!!</p>
<p><a href="/blog/2022/01/why-i-write-a-year-in-review/">As I wrote recently</a>, I have a pretty strong recency bias when I try to write these articles. When I started writing this year's review, I thought it was going to be pretty negative. I'm not feeling super great right now — dealing with a bit of burnout, feeling tired of The Hustle ™️ that permeates software development, and quite honestly not sure what I'm doing with my career. But we'll get to that.</p>
<p>As I look back at the rest of 2021, I realize...it wasn't too bad. It was actually pretty good!</p>
<h2>I accomplished my goals</h2>
<p>At the beginning of 2021, <a href="/blog/2021/01/2020-in-review/">I set 4 goals for myself</a>. I managed to hit all 4 of them, I think!</p>
<h3>(1) Writing more frequently and (2) lowering my personal bar for creating content.</h3>
<p>I destroyed these goals! Crushed them. I wrote 22 articles on this site in 2021, and <a href="https://artsy.github.io/author/steve-hicks/">3 others on the Artsy Engineering blog</a>. This is <em>by far</em> the most I've ever written in a year. I don't know if I've gotten over 12 before.</p>
<p>I was able to write so much because I got comfortable writing smaller, less complete articles. In the past I've held back articles that didn't feel like they had a solid point or complete story; this year, I started a practice of keeping track of "things I'm thinking about this week" in my journal. I fell off that practice multiple times, but I also picked it up again multiple times. Those journal entries turned into many of the articles here.</p>
<p>But I also wrote a lot of complete articles! I wrote <a href="https://www.stevenhicks.me/blog/2021/02/maximizing-productivity/">an entire series on maximizing small blocks of time</a>! It wasn't just lowering the bar; I generally felt fired up about writing this year.</p>
<h3>(3) Running and cycling regularly.</h3>
<p>I made a comment last year that I doubted I'd match my mileage from 2020 in 2021, but <em>I was wrong</em>! Mostly.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In 2020 I buried myself in being active. I figured I'd take a small step back in 2021 when the world opened up and I was able to do other things. Instead I doubled down on being active.<br><br>My Strava self-promotion/I mean my Strava unwrapped, in charts:</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1476929945855213568?ref_src=twsrc%5Etfw">December 31, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I increased my total mileage by 600 miles! I focused more on the bike in 2021 than the run, and that mostly explains the jump in mileage. I hit nearly 3000 miles on my bike — a distance I honestly never expected I'd ever hit. I'm very proud of these numbers, and more important than the numbers is the fact that a daily workout is just part of my life right now. Barring unforeseen circumstances, I take one rest day per week, and that one day without a workout I'm thinking the entire time "oh I could just sneak in a bike ride, it'd be no big deal."</p>
<p>But hey I'm getting lots of experience with burnout and I'm pretty sure taking a rest day a week is a good practice, as much as it hurts.</p>
<h3>(4) Eating better.</h3>
<p>This one is a little more on the fence, but I'm counting it as a win. I <em>think</em> I'm eating better. I eat fewer junky snacks. I've never been a salad person and during the holidays I got really excited about a salad my wife made, and I've now made that salad for myself almost every day for the last month or two. I tried a few gimmicks to help me make better choices, and they didn't really work, but overall I feel like I'm taking in better food than I have in the past.</p>
<p>I still eat fast food a couple times a week, though. I love it too much. Food is such an emotional experience for me, and I know that's not a great thing, but I'm not willing to give that up right now.</p>
<h2>I made some podcasts</h2>
<h3>Artsy Engineering Radio</h3>
<p>Early in 2021, <a href="https://twitter.com/ArtsyOpenSource/status/1347281477453684736">we announced that we'd be making an Artsy engineering podcast</a>. I was personally feeling extremely inspired by my friend <a href="https://twitter.com/ashfurrow">Ash</a> leaving Artsy, and wanting to fill all the gaps he left and then some. Throughout the year, we've gotten more people involved in making episodes, with the goal of <a href="https://artsyengineeringradio.buzzsprout.com/">Artsy Engineering Radio</a> being a way for people at Artsy to contribute to the developer community with little effort. We've definitely done that — check out our episodes, there is so much good stuff in there, and so many wonderful voices.</p>
<p>My personal favorite episodes to make:</p>
<ul>
<li><a href="https://artsyengineeringradio.buzzsprout.com/1781859/8549214-4-scaling-your-impact">A discussion with Ash about scaling your impact</a> — and <a href="https://artsyengineeringradio.buzzsprout.com/1781859/8549218-8-scaling-your-impact-pt-2-this-time-with-orta">another with Orta Therox</a></li>
<li><a href="https://artsyengineeringradio.buzzsprout.com/1781859/8600736-19-humanizing-the-workplace">A conversation with Justin Bennett about humanizing the workplace</a></li>
<li><a href="https://artsyengineeringradio.buzzsprout.com/1781859/9055665-31-making-big-changes-with-laura">An interview with Laura Bhayani about making large changes</a></li>
<li><a href="https://artsyengineeringradio.buzzsprout.com/1781859/9825390-season-1-recap">A recap of our first "season"/year of the show</a></li>
</ul>
<h3>How Devs Work/The Developer Experience</h3>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I wrote a little more about my intentions with How Devs Work<a href="https://t.co/wtMZws4OAj">https://t.co/wtMZws4OAj</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1433839087304253451?ref_src=twsrc%5Etfw">September 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>One of the things I really loved about Artsy Engineering Radio was the editing & production. A few months into that project, we chose to outsource that work, since I was the only one interested in it, and that wasn't sustainable.</p>
<p>But I wanted to scratch that itch again, and I couldn't stop thinking about how much I wanted to make a podcast about the different ways in which developers get things done. I decided this winter's project would be a podcast — initially I've called it "How Devs Work" but "The Developer Experience" is a much better name and I just need to figure out how to use that without conflicting with an existing podcast.</p>
<p>I've recorded 5 guests so far — with 3 more to go — and then I get to cut them all up and stitch them all back into a narrative about how all of us, even the people we think are really good at it, are constantly trying to figure out how to keep our sh🤬t together and manage our work.</p>
<h2>I wrote a ton</h2>
<p>Like...<em>a ton</em>. I mentioned above how my 25 articles this year were more than double my previous record. Here are some of my personal favorites from the year:</p>
<ul>
<li>I had a weird holiday season to begin 2021 where I was extremely motivated and productive around the house. I <a href="/blog/2021/01/tips-for-working-with-legacy-code/">wrote about how a kitchen faucet replacement was similar to working with legacy code</a>.</li>
<li>I wrote what I think is <a href="https://www.stevenhicks.me/blog/2021/09/thoughts-on-code-distance/">a very good article on something I've been calling "code distance"</a> — when you have two files that are very related to each other, but they live very far away from each other in the file system.</li>
<li>I wrote an entire <a href="https://www.stevenhicks.me/blog/2021/02/maximizing-productivity/">series on maximizing productivity in small blocks of time</a>! I'd wanted to write a series for a while; this is the first one I was able to complete without losing steam.</li>
<li>Over on the Artsy Engineering blog, I wrote about <a href="https://twitter.com/ArtsyOpenSource/status/1369378723057831938">how to actually make small pull requests</a>. It's a thing we talk about doing, but it's not often we're taught <em>how</em> to do it.</li>
<li>Most articles I write don't get a lot of readers. I have basically one popular article — it's about <a href="https://www.stevenhicks.me/blog/2020/02/working-with-variables-in-cypress-tests/">variables in Cypress tests</a> and it gets hundreds of times more views than anything else. This year I wrote another article with a surprising number of page-views — this time on <a href="https://www.stevenhicks.me/blog/2021/05/syntax-highlighting-diffs/">syntax highlighting in diffs</a>. These weird one-off technical articles are successful, I guess. If I were chasing page-views, I would do something with this information.</li>
</ul>
<p>I also wrote some threads on Twitter that I think were neat.</p>
<p>Here's one about how I automated a summer chores list for my kids with Trello:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I just spent some time building a summer chores board for the family and WOW the automation you can set up in trello now is pretty great.</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1403779981814153221?ref_src=twsrc%5Etfw">June 12, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>And another about how helpful it is to keep track of when you fall into recurring emotional patterns:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">For the last few days I've been feeling distracted, uneasy, and any time I'd think about doing something "productive" I would say "nah I just want to lay down" and I'd lay and doomscroll. <br><br>Tonight I recognized it - my body was overwhelmed by large, nebulous tasks again.</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1445214476098314241?ref_src=twsrc%5Etfw">October 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>One other last bit of content I'm proud of this year — it's not writing, but I'm going to file it here anyway. I had to demo a very boring feature at work, so I <a href="https://www.youtube.com/watch?v=tIxCeI1Vz4E">made a ridiculous video about my kids as robots creating NFTs</a>. If you're looking for someone to make dumb videos all day every day, please hire me.</p>
<h2>I spoke in person again!</h2>
<p>And I'm not going to lie, it was weird.</p>
<p>I got a chance to give my "Getting Unstuck" talk at <a href="https://that.us/">That Conference</a>, which was back in hybrid form after skipping 2020. It was much smaller than the typical That Conference — obviously, since there's still a pandemic. COVID numbers were looking promising for the event, and I was <em>so excited</em> to start speaking in person again, since presenting virtually is basically awful.</p>
<p>It was strange to be around a lot of people indoors. It was strange to see so many friends in-person! It was also strange to not feel as excited about speaking as I have in the past.</p>
<p>I'm not sure about this part of my future. I think I'm going to continue letting it sit until I feel an urgency to get back at it.</p>
<p>I think I also presented once or twice virtually in 2021 — I'm actually not sure. The disinterest I have in tracking my speaking events is further evidence to myself that this isn't something I should do right now.</p>
<h2>I got passionate about programming computers</h2>
<p>This is kind of a weird one for me. The past few years I've been feeling like I want to spend less time going deep on building things, so that I could focus on more knowledge-sharing types of tasks. But in 2021, I reversed course. I'm still trying to figure this out.</p>
<p>In April of 2021, I switched product teams at Artsy (to the Partner Experience team, focused on the experience of our gallery and fair partners). This isn't really an unusual thing — I usually change teams every 6 to 9 months, as my learning stagnates.</p>
<p>But I switched to what is objectively the best product team at Artsy (sorry friends), and I have <em>loved</em> it. As <a href="https://twitter.com/anna_carey">Anna Carey</a> put it, I love everyone at Artsy, but this team is made up of people who would be my closest friends if we lived near each other. I'm over 9 months into this team, and still feeling inspired my teammates every day, and not having any feelings about wanting to change teams. <em>This</em> is unusual for me — the stability and comfort.</p>
<p>For much of the past 9 months, I got to know and pair closely with Anna — an incredible young engineer with a ridiculously bright future. This was a rare situation where I was extremely interested in mentoring and she was also very interested in being mentored — and quite honestly, mentoring <em>me</em> in many ways. She's since moved to another team, but this relationship was one of my absolute highlights of 2021.</p>
<p>The two of us got really into some infrastructural and architectural refactoring on Artsy's CMS project. I continue to be interested in this work, and have advocated for our team to elevate this kind of work to our OKRs, so that I can work on it without feeling like I'm off in the weeds not helping the team.</p>
<p>At some point in the fall (October?), as a team we agreed to set that kind of work aside to focus on a product release. This kind of crushed me for a while, and I think I'm only now recovering. I was really excited to find interest in that kind of work; and terribly disappointed when asked to not do it anymore. For now I'm back to doing this work, which is great — but I definitely had some feelings that I discussed many times with my therapist.</p>
<p>Outside of Artsy, I <em>also</em> found some passion in building. I <a href="https://twitter.com/pepopowitz/status/1378011234453360646">got excited about RedwoodJS</a> in the early part of the year, and started building <a href="https://github.com/pepopowitz/pendulina">an app for tracking my workouts</a>. Unfortunately I've lost some steam on this project, and I'm not sure when I'll pick it back up again. 🤷 That's what it's like to be one of my side projects, I guess.</p>
<h2>I made some art!</h2>
<p>The isolation of 2020 gave me time to explore watercolor painting. In 2021, as things started to open up, I got too busy with other things and lost touch with painting. For the first half of 2021, I didn't make much art.</p>
<p>But in the summer, when an Artsy remote retreat encouraged me to "engage with art" for an afternoon, I got the chance to explore some techniques I'd been thinking about:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">My assignment for the afternoon was to "engage with art," so I became a street artist? <a href="https://t.co/wYch33Tzns">pic.twitter.com/wYch33Tzns</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1427754769561554952?ref_src=twsrc%5Etfw">August 17, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I love spray-painted stencil art on sidewalks...to get to do it for an afternoon <em>as a work assignment</em> was incredible.</p>
<p>This got me feeling inspired about art again. I painted a little bit more in the second half of the year, including this painting inspired by a photo I took while riding bikes with my wife in the summer:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Finished! A very slightly late Christmas gift for tomorrow. <a href="https://t.co/dUPFTIE4g0">pic.twitter.com/dUPFTIE4g0</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1474984885790167041?ref_src=twsrc%5Etfw">December 26, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I haven't been practicing art every day, but I think I'm pretty consistently practicing at least once a week right now. That's certainly not artist levels, but it's exciting to find the interest, time, and dedication to this gratifying practice.</p>
<h2>I exercised a ton</h2>
<p>As mentioned in the "goals" section, <a href="https://twitter.com/pepopowitz/status/1383458933608652802">I didn't run as much</a> in 2021, but I <a href="https://twitter.com/pepopowitz/status/1449365522806710284">rode my bike a ton more</a>. I did well at some races — <a href="https://twitter.com/pepopowitz/status/1401709494724333569">I almost podiumed</a> in June, and in August <a href="https://twitter.com/pepopowitz/status/1439347314532003840">I podiumed twice in one week</a>. I continue to <a href="https://twitter.com/pepopowitz/status/1416585549721309193">enjoy running and biking significantly more than swimming</a>.</p>
<p>On one bike ride, I caught a picture of a deer leaping across the road!</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I skipped That 5k this morning in favor of a bike ride, and oooooo it was nice. <a href="https://twitter.com/hashtag/ThatConference2021?src=hash&ref_src=twsrc%5Etfw">#ThatConference2021</a> <a href="https://t.co/v1h5fZNzmQ">pic.twitter.com/v1h5fZNzmQ</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1420367916416000000?ref_src=twsrc%5Etfw">July 28, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Exercise <em>is</em> a daily practice for me. I'll keep this up in 2022. I don't need to make it a goal; it's just going to keep happening.</p>
<h2>I saw almost all of my coworkers</h2>
<p>In July, after 19 months of not having seen a coworker in person, my friend <a href="https://twitter.com/jonallured">Jon Allured</a> and I arranged to hang out in the city of La Crosse, Wisconsin for a day, as the only two members of the Artsy Flyover office. We ate food, drank beers, and saw art together.</p>
<p>In September, my wife found out she'd be doing a race in Spain! She'd qualified for it in 2019, but it finally happened in 2021. We had a very short time to plan a trip to Europe — my first visit there!</p>
<p>It was amazing. We spent about a week in Spain:</p>
<blockquote class="twitter-tweet"><p lang="es" dir="ltr">Buenas noches desde España 👋 <a href="https://t.co/es16UstICv">pic.twitter.com/es16UstICv</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1453775419329679379?ref_src=twsrc%5Etfw">October 28, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>And after Spain, I bounced over to Berlin, to work from the Artsy office for about a week!</p>
<blockquote class="instagram-media" data-instgrm-captioned data-instgrm-permalink="https://www.instagram.com/p/CV91b-jjyid/?utm_source=ig_embed&utm_campaign=loading" data-instgrm-version="14" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);"><div style="padding:16px;"> <a href="https://www.instagram.com/p/CV91b-jjyid/?utm_source=ig_embed&utm_campaign=loading" style=" background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%;" target="_blank"> <div style=" display: flex; flex-direction: row; align-items: center;"> <div style="background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 40px; margin-right: 14px; width: 40px;"></div> <div style="display: flex; flex-direction: column; flex-grow: 1; justify-content: center;"> <div style=" background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 100px;"></div> <div style=" background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 60px;"></div></div></div><div style="padding: 19% 0;"></div> <div style="display:block; height:50px; margin:0 auto 12px; width:50px;"><svg width="50px" height="50px" viewBox="0 0 60 60" version="1.1" xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-511.000000, -20.000000)" fill="#000000"><g><path d="M556.869,30.41 C554.814,30.41 553.148,32.076 553.148,34.131 C553.148,36.186 554.814,37.852 556.869,37.852 C558.924,37.852 560.59,36.186 560.59,34.131 C560.59,32.076 558.924,30.41 556.869,30.41 M541,60.657 C535.114,60.657 530.342,55.887 530.342,50 C530.342,44.114 535.114,39.342 541,39.342 C546.887,39.342 551.658,44.114 551.658,50 C551.658,55.887 546.887,60.657 541,60.657 M541,33.886 C532.1,33.886 524.886,41.1 524.886,50 C524.886,58.899 532.1,66.113 541,66.113 C549.9,66.113 557.115,58.899 557.115,50 C557.115,41.1 549.9,33.886 541,33.886 M565.378,62.101 C565.244,65.022 564.756,66.606 564.346,67.663 C563.803,69.06 563.154,70.057 562.106,71.106 C561.058,72.155 560.06,72.803 558.662,73.347 C557.607,73.757 556.021,74.244 553.102,74.378 C549.944,74.521 548.997,74.552 541,74.552 C533.003,74.552 532.056,74.521 528.898,74.378 C525.979,74.244 524.393,73.757 523.338,73.347 C521.94,72.803 520.942,72.155 519.894,71.106 C518.846,70.057 518.197,69.06 517.654,67.663 C517.244,66.606 516.755,65.022 516.623,62.101 C516.479,58.943 516.448,57.996 516.448,50 C516.448,42.003 516.479,41.056 516.623,37.899 C516.755,34.978 517.244,33.391 517.654,32.338 C518.197,30.938 518.846,29.942 519.894,28.894 C520.942,27.846 521.94,27.196 523.338,26.654 C524.393,26.244 525.979,25.756 528.898,25.623 C532.057,25.479 533.004,25.448 541,25.448 C548.997,25.448 549.943,25.479 553.102,25.623 C556.021,25.756 557.607,26.244 558.662,26.654 C560.06,27.196 561.058,27.846 562.106,28.894 C563.154,29.942 563.803,30.938 564.346,32.338 C564.756,33.391 565.244,34.978 565.378,37.899 C565.522,41.056 565.552,42.003 565.552,50 C565.552,57.996 565.522,58.943 565.378,62.101 M570.82,37.631 C570.674,34.438 570.167,32.258 569.425,30.349 C568.659,28.377 567.633,26.702 565.965,25.035 C564.297,23.368 562.623,22.342 560.652,21.575 C558.743,20.834 556.562,20.326 553.369,20.18 C550.169,20.033 549.148,20 541,20 C532.853,20 531.831,20.033 528.631,20.18 C525.438,20.326 523.257,20.834 521.349,21.575 C519.376,22.342 517.703,23.368 516.035,25.035 C514.368,26.702 513.342,28.377 512.574,30.349 C511.834,32.258 511.326,34.438 511.181,37.631 C511.035,40.831 511,41.851 511,50 C511,58.147 511.035,59.17 511.181,62.369 C511.326,65.562 511.834,67.743 512.574,69.651 C513.342,71.625 514.368,73.296 516.035,74.965 C517.703,76.634 519.376,77.658 521.349,78.425 C523.257,79.167 525.438,79.673 528.631,79.82 C531.831,79.965 532.853,80.001 541,80.001 C549.148,80.001 550.169,79.965 553.369,79.82 C556.562,79.673 558.743,79.167 560.652,78.425 C562.623,77.658 564.297,76.634 565.965,74.965 C567.633,73.296 568.659,71.625 569.425,69.651 C570.167,67.743 570.674,65.562 570.82,62.369 C570.966,59.17 571,58.147 571,50 C571,41.851 570.966,40.831 570.82,37.631"></path></g></g></g></svg></div><div style="padding-top: 8px;"> <div style=" color:#3897f0; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:550; line-height:18px;">View this post on Instagram</div></div><div style="padding: 12.5% 0;"></div> <div style="display: flex; flex-direction: row; margin-bottom: 14px; align-items: center;"><div> <div style="background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(0px) translateY(7px);"></div> <div style="background-color: #F4F4F4; height: 12.5px; transform: rotate(-45deg) translateX(3px) translateY(1px); width: 12.5px; flex-grow: 0; margin-right: 14px; margin-left: 2px;"></div> <div style="background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(9px) translateY(-18px);"></div></div><div style="margin-left: 8px;"> <div style=" background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 20px; width: 20px;"></div> <div style=" width: 0; height: 0; border-top: 2px solid transparent; border-left: 6px solid #f4f4f4; border-bottom: 2px solid transparent; transform: translateX(16px) translateY(-4px) rotate(30deg)"></div></div><div style="margin-left: auto;"> <div style=" width: 0px; border-top: 8px solid #F4F4F4; border-right: 8px solid transparent; transform: translateY(16px);"></div> <div style=" background-color: #F4F4F4; flex-grow: 0; height: 12px; width: 16px; transform: translateY(-4px);"></div> <div style=" width: 0; height: 0; border-top: 8px solid #F4F4F4; border-left: 8px solid transparent; transform: translateY(-4px) translateX(8px);"></div></div></div> <div style="display: flex; flex-direction: column; flex-grow: 1; justify-content: center; margin-bottom: 24px;"> <div style=" background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 224px;"></div> <div style=" background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 144px;"></div></div></a><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/CV91b-jjyid/?utm_source=ig_embed&utm_campaign=loading" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank">A post shared by Steven Hicks (@pepopowitz)</a></p></div></blockquote> <script async src="//www.instagram.com/embed.js"></script>
<p>The trip was amazing. I put my hand on every old building, having never touched anything like it before. And getting to see coworkers was surreal. My friends <a href="https://twitter.com/SarahHaq5">Sarah</a>, <a href="https://twitter.com/pinadoleada">Matt</a>, Christian, and <a href="https://twitter.com/AlizeNero">Kaja</a> were amazing hosts.</p>
<p>Things got more surreal when I got stuck in London on the way home, and <a href="https://twitter.com/pvinis">my friend and coworker Pavlos</a> commuted across the city of London to hang out with me for two hours. Who does that??? One of the best human beings on the planet, that's who.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Get yourself a friend like <a href="https://twitter.com/pvinis?ref_src=twsrc%5Etfw">@pvinis</a>, who will commute two hours across the city of London to hang out for a few hours and listen to you complain about air travel problems. <a href="https://t.co/YTvsc8oE0c">pic.twitter.com/YTvsc8oE0c</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1458208806517841920?ref_src=twsrc%5Etfw">November 9, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>While in Europe, I found out I would be heading to New York in December for an Artsy holiday party, too! I'd seen literally one coworker in the span of 23 months; suddenly I'd see almost all of them, from the US and Europe, in one month. I met so many people in person finally, and almost all of them were as tall as I expected them to be.</p>
<h2>I burned out again. And again.</h2>
<p>Which brings me back to the beginning. I thought this was going to be a really rough recap of the year. It's not! As far as 2021s go, mine was pretty solid, and I don't have a lot to complain about.</p>
<p>So why do I feel like this? Why am I so down on the last year? I mean yeah, it was a global pandemic still, but there's more to it.</p>
<p>2020 was an epic crash-and-burn-and-rise-like-the-phoenix type of year — for a lot of us. 2021 for me was more like watching a kid play a racing game in the arcade — bumping up against burnout on the sides, steering themselves off the edge just long enough to get a bit of momentum, steering back into the burnout again. Momentum...burnout. Momentum...burnout. Momentum...burnout. There was no moment in 2021 that felt as drastically bad or severe as in 2020...the lows, while more muted, felt more frequent.</p>
<p>And I'm catching myself at one of those lows right now. I had figured out in my head where I was going with my career...now I'm suddenly less certain. It's winter in Wisconsin...I'm low on Vitamin D. I had to put away the work I was enjoying...I'm just now getting back to it. I had planned to speak at multiple events this January...but I found I (a) didn't feel safe, and (b) didn't really want to. Many things have contributed to my feeling down — but I have a bright hope that it's just the bad part of the cycle right now.</p>
<p>And to be perfectly honest, it's been getting better now for a week or two. I advocated to do more of the deep technical work I'd been finding interesting; I've been feeling really connected to my family and my closest teammates; and most importantly...</p>
<h2>I'm learning how important it is to protect my down time</h2>
<p>I kind of buried the lede, I think, but this is really it for me. There's this culture of Hustle ™️ in tech. If you want to advance your career, you have to Hustle. Blog, speak, build courses, contribute to free-as-in-your-soul Open Source, build a side project. You can advance your career <em>without</em> doing these things, but at a snail's pace. I've been pursuing The Hustle ™️ too, and now...I'm tired of it. More than tired, actually — I'd say it gives me anxiety now to think about how I could/should be Hustling. That's not a very fun place to be.</p>
<p>You know what is fun for me? Riding my bike. Running. Ugh, even swimming. And good grief, even <a href="https://twitter.com/pepopowitz/status/1486011156607250441">lifting weights</a>, somehow!!</p>
<p>And painting. And drawing. And spending time with my wife and kids.</p>
<p>And sometimes still writing. And recording my <a href="https://github.com/pepopowitz/how-devs-work">How Devs Work</a> podcast project.</p>
<p>That's where I'm happiest right now, and that's where I'm going to focus. My therapist has helped me recognize this year that my body basically runs on shame and guilt — always doing the things I think I <em>should</em> be doing, that I think someone else is expecting me to do. That's exhausting, and honestly, it's not very much fun.</p>
<p>So that's my goal for this year. I want to stay focused on the things I enjoy. Maybe those things will contribute to my career...maybe they won't. But it will certainly contribute to my happiness. And the second something becomes not fun for me, I'm kicking it to the curb.</p>
Why I Write A "Year In Review"2022-01-14T14:12:37.490+00:00https://stevenhicks.me/blog/2022/01/why-i-write-a-year-in-review/<p>The irony of this post: I'm writing about <em>why I like to write a year in review post</em> instead of writing a year in review post. Don't worry fans, it'll come before February 1.</p>
<p>I've started putting together my 2021-in-review article, and while doing so I've been thinking about why I think it's so important. Year in review posts force me to look back through time and see all the things I did that year. Every time I write a year in review, I remember pretty clearly how I felt the last couple months of the year. But I kind of forget what I did or how I felt the 10 months before that. This recency bias is especially problematic because winter in Wisconsin <em>suuuuuckkkksss</em>, and I suffer annually from Seasonal Affective Disorder, so the last month+ of the year almost always feels depressing.</p>
<p>My process for writing a year-in-review looks like this:</p>
<ol>
<li>Read my previous year-in-review.</li>
<li>Look back through Twitter, my calendar, and my notes to refresh my memory on what I did this year.</li>
<li>Keep track of the most interesting and significant things I did this year.</li>
<li>Try to find threads and stories in the most significant things I did this year.</li>
<li>Process this all and think about things I'd like to differently in the next year.</li>
</ol>
<p>I don't really believe in "new year resolutions" — I don't really need a way to codify making myself feel bad about not meeting goals, I do that plenty well on my own. I also think big goals are meaningless without introducing systems to foster those goals, and resolutions seem pie-in-the-sky without systems. I guess that's probably subjective, and comes down to how you implement resolutions.</p>
<p>I <em>do</em> believe very much in self-reflection. I'm maybe pretty old for not fully understanding my emotions or being able to describe them or identify triggers for them. But writing a year-in-review post helps me with all of that — and understanding those things helps me better make decisions and design systems for myself.</p>
On Conviction: Knowing When To Give Up On A Problem2021-10-07T14:12:37.490+00:00https://stevenhicks.me/blog/2021/10/on-conviction-and-knowing-when-to-give-up-on-a-problem/<p>Recently I had an experience where a conviction and my determination helped me dig deep to solve a problem. When reflecting on it, I started to wonder when conviction is helpful as a developer, and when it is harmful. One can get stuck down a rabbit hole when they can't let go of something; but deep problems often benefit from a strong belief that they can be solved.</p>
<p>The recent experience: <a href="https://twitter.com/anna_carey">Anna</a> and I were looking at tests that didn't work. We have a form with some radio buttons, and when the buttons are clicked there are some side-effects on the page. We wanted to test this interaction — find the radio button, click it, and make sure certain things were/weren't showing on the page. We couldn't get the test to pass because the side-effects never showed up. We gave up and moved to a testing model we were less happy with — calling the "onSelect" handler of the radio button directly, instead of simulating how the user would make that thing happen.</p>
<p>Later that day I couldn't let it go. I needed to know why we couldn't simulate click on that radio option. I eventually <em>did</em> figure out why. There were many tools & tricks I used along the way -- console.logging, console.logging from within the node_modules folder, code removal for isolation, github's UI to show me the previous version of the code, .... but when it came down to it, I wouldn't have used any of those tools without <strong>conviction</strong>.</p>
<p>I <em>knew</em> that we should have been able to simulate click and test for the side-effect. It was something I've done before (or at least I was convinced it was something I've done before.). Even though I was fighting challenges left and right, conviction kept me digging.</p>
<p>But at some point conviction becomes a detriment. You end up deep down a rabbit hole with no clear escape. In this example <em>I was right</em>, but in many other examples <em>I am wrong</em>. In this example I found the answer, but in many other examples I've given up and moved on.</p>
<p>How does one know when to cut bait? How do you decide when your conviction is misguided or not worth fighting for?</p>
<p>We talked about this in <a href="https://artsyengineeringradio.buzzsprout.com/1781859/9328525-37-request-for-comment-7">today's episode of Artsy Engineering Radio</a>. <a href="https://twitter.com/jonallured">Jon</a> suggested the "15 minute rule" (or whatever number of minutes you decide). After 15 minutes of not making progress, it's time to give up. But this is always squishy for me. Sometimes you make progress within 15 minutes but it's not really progress — you take a few steps down a path but it still ends up leading you far away from the exit. These are the times when you end up down that rabbit hole, far from escape. Other times it takes you a little longer than your limit to make progress, but how would you know that if you hadn't violated the 15 minute rule?</p>
<p>I wasn't able to answer the question when we recorded, but having thought about it more I think I see it as <a href="https://ashfurrow.com/blog/specializing-in-being-a-generalist/">arrows in a quiver</a>. Each tool & trick I used to figure out why this wasn't working was a different arrow I could try, to help me isolate the problem. If an arrow isn't fruitful or it proves to be a lot of effort, I move on to the next arrow. When an arrow is fruitful, it points me at something new in the code, and I get a new bag of tricks to try <em>there</em>. If at any point I run out of arrows, that's when it's time for me to reassess. Is my conviction misguided? Am I thinking about this wrong? And is it worth moving forward, or is it time to cut bait? Is there someone I can talk to to bounce ideas off of, to get a check on whether my mental model is making sense?</p>
<p>This is why I find pairing to be so valuable. It's less about the specific code you're writing as a pair, and more about acquiring new arrows for your quiver. Watching someone else solve a problem teaches you new tools and tricks. It took me a long time as a JavaScript developer to learn that I could edit things in <code>/node_modules</code> directly. I don't remember the moment that I learned that strategy, but I definitely didn't learn it on my own. I learned it by watching another developer while pairing.</p>
<h3>Because you're dying to know</h3>
<p>Why couldn't we simulate a click on that radio option and verify that a side-effect occurred? <a href="https://github.com/artsy/palette/blob/a006273ad60b7c8af36ed037ee03cc273e5e21f1/packages/palette/src/elements/Radio/Radio.tsx#L62">Because the handler for clicking on the radio button was debounced</a>. <a href="https://css-tricks.com/debouncing-throttling-explained-examples/">Debouncing</a> is a way to prevent an event from firing multiple times in a row (think rapid-fire clicking). This call was debounced for a reason — but it was also preventing our handler from being called unless we used <a href="https://jestjs.io/docs/timer-mocks">Jest's fake timers</a> to advance time. As I said in <a href="https://artsyengineeringradio.buzzsprout.com/1781859/9328525-37-request-for-comment-7">the podcast</a> — the one part of my life I feel like I'll never have a full grasp of is testing asynchronous events in JavaScript 😅</p>
Thoughts On Code Distance2021-09-17T09:27:10.311+00:00https://stevenhicks.me/blog/2021/09/thoughts-on-code-distance/<p>I've been thinking this week about code distance — the distance between bits of code that are related to the same feature. Traversing a few lines in a single small function is a small distance; traversing a hundred or so lines from one end of a file to another is a medium distance; traversing entire folder structures is a large distance.</p>
<p>Sometimes code distance bothers me, sometimes it doesn't.</p>
<p>An example where it doesn't bother me: the distance between tests & their setup code. This came up recently in our Artsy Slack re: <a href="https://www.stevenhicks.me/blog/2021/09/what-javascript-tests-could-learn-from-rspec/"><code>let</code> in RSpec tests</a>. Sometimes the variable you're looking at in a test was set up 100 lines away from the line you're reading. You have to either navigate this distance to fully understand the code, or make an assumption about the setup. Test setup is one area where I feel comfortable making assumptions; I'm not sure why, honestly. When I write tests, I put everything unique to the test in or near it. I guess that's made me feel comfortable assuming that anything that isn't in or near a test can be assumed to contain a reasonable default.</p>
<p>Code distance of features, on the other hand, bothers me tremendously. When I need to update multiple files in distant parts of a codebase in order to add a new feature, this feels like a huge opportunity to miss something. This happens in our Artsy CMS artwork form when I want to add a new field to the form — there are <em>a lot</em> of different files I need to touch, in a lot of different places. We miss at least one of these places just about every time we add a new feature.</p>
<p>I wonder if there's a way to represent the "code distance" in a PR? A PR with a high "code distance" value could represent:</p>
<ul>
<li>An unfocused PR that has potential to be broken into multiple smaller PRs</li>
<li>A cross-cutting PR that makes infrastructural changes (and is therefore expected to have a large code distance)</li>
<li>An indication that something in the architecture of your code could change to reduce the distance. Are large code-distance PRs happening frequently? That probably means your code doesn't align with the features you're building.</li>
</ul>
<p>And then based on a history of PRs, I wonder if you can see trends of code distance? Are there <a href="https://en.wikipedia.org/wiki/Radar_chart">radar charts</a> or some better visualization to show the distance of changes in a PR? If I see PRs with similar distances or visualizations, is that a clue to re-architect a bit and shorten that distance?</p>
<p>🤷 If you've got answers or thoughts about this, <a href="https://twitter.com/pepopowitz/status/1438879983691239425">let me know</a>!</p>
What JavaScript Tests Could Learn From RSpec2021-09-10T11:36:40.590+00:00https://stevenhicks.me/blog/2021/09/what-javascript-tests-could-learn-from-rspec/<p>When I started at Artsy a few years ago, I'd never written a line of Ruby. I feel at home with JavaScript — it's
been my buddy since I started my career over 20 years ago. I've written enough tests in JavaScript that I sometimes
feel like I can write them in my sleep (as long as they don't involve async React events 😅).</p>
<p>Most of the code I write at Artsy is still JavaScript, but now I write some Ruby code too, and I've written enough
RSpec tests that I'm starting to form opinions about what I think they should look like.</p>
<p>My most recent work has been JavaScript again. I've been writing Jest tests against one of our React apps. But
rather than reaching for the testing patterns I'd become accustomed to over my years of JavaScripting, I'm finding
that something's missing in my Jest tests! My experiences with RSpec have me longing for two features in Jest:</p>
<!-- more -->
<ol>
<li><code>context</code> blocks</li>
<li><code>let</code> blocks</li>
</ol>
<h2>1. <code>context</code> blocks</h2>
<p>A <a href="https://relishapp.com/rspec/rspec-core/v/2-11/docs/example-groups/basic-structure-describe-it"><code>context</code> block</a>
in an RSpec test is, as I understand it, literally the same thing as a <code>describe</code> block. Like it's just an alias of
<code>describe</code>. What's the point, you ask?</p>
<p>The difference is in, well, context. Well-organized RSpec tests use <code>describe</code> to describe what's being
tested...and <code>context</code> to describe scenarios of the thing being tested.</p>
<p>For example, if I wanted to test the <code>multiply</code> method of a <code>Calculator</code> class, I might write some test scenarios
that look like this:</p>
<pre><code class="language-rb">describe "Calculator" do
describe ".multiply" do
context "when the first value is negative" do
context "when the second value is negative" do
it "returns a positive number" do
end
end
context "when the second value is positive" do
it "returns a negative number" do
end
end
end
end
end
</code></pre>
<p>See the difference in those test cases between <code>describe</code> and <code>context</code>? The way I think about it is: if the
statement coming after my <code>describe</code>/<code>context</code> describes a pre-condition for the test, it's a <code>context</code>; otherwise
it's a <code>describe</code>.</p>
<p><code>context</code> wouldn't be hard to implement in JavaScript — I'd bet there are test frameworks that have it. It'd just be an alias of <code>describe</code>.</p>
<h2>2. <code>let</code> blocks</h2>
<p><a href="https://relishapp.com/rspec/rspec-core/v/2-11/docs/helper-methods/let-and-let"><code>let</code> blocks</a> are used in an RSpec
test to set things up for your test scenario.</p>
<p>Here's a test for a <code>Counter</code> class, verifying that when I call the <code>increment</code> method on an instance, its stored
value becomes <code>1</code>.</p>
<pre><code class="language-rb">describe "counter" do
let(:counter) { Counter.new }
describe "increment" do
it "increments by 1" do
counter.increment
counter.value.should eq(1)
end
end
end
</code></pre>
<p>If you're new to Ruby, the only line that doesn't translate almost directly to a similar JavaScript expression is
the <code>let</code> statement.</p>
<p>The <code>let</code> statement in RSpec <a href="https://medium.com/@tomkadwill/all-about-rspec-let-a3b642e08d39">creates a method with a specified name, which lazily evaluates to the result of a
block</a>. In this case, we get a method named <code>counter</code>, which is evaluated to a new instance of the
<code>Counter</code> class. There are a few important things to note about <code>let</code> blocks:</p>
<ol>
<li>They're evaluated lazily (by default). That <code>counter</code> doesn't actually get created until I reference it.</li>
<li>They're memoized. Wherever I reference <code>counter</code> within that <code>describe "counter"</code> block, I'm getting the same
instance. It's initialized to whatever I return inside the <code>let</code> block.</li>
<li>I can override a <code>let</code> block deeper inside the tree of tests, by declaring another <code>let(:counter)</code> later. When I
do this, the closest <code>let</code> block in the tree for that thing is the one that gets used.</li>
</ol>
<p>I don't think it's possible to implement <code>let</code> in JavaScript — at least not in the way it exists in RSpec. It relies on <a href="https://www.leighhalliday.com/ruby-metaprogramming-method-missing">Ruby meta-programming to intercept calls to missing methods</a>, which just doesn't exist in JavaScript. The <a href="https://github.com/enova/givens">givens</a> library does something pretty close, but it relies on string keys to define things, and there's a bit of extra work when working with TypeScript.</p>
<h2>What's the big deal?</h2>
<p>On the surface these two features don't seem like much, but they provide a really powerful framework for organizing
test cases and the associated test setup.</p>
<p>With the <code>let</code> blocks being lazily evaluated, and override-able, I can set up data at the exact level of tests that
I need it. When I need to override it for a certain set of tests, I can put another <code>let</code> block in that set. In
JavaScript I can define functions to set up my data for me with just the changes I need at each test level, or I
can use <code>beforeEach</code> blocks, but all that can get pretty noisy.</p>
<p>And with <code>context</code> blocks, I can more clearly lay out the scenarios of my tests. Yes, I <em>could</em> just do this with
more <code>describe</code> blocks in JavaScript, but how often have you found JavaScript tests that actually do this? I've
personally seen/written too many tests to count named something like
<code>it("returns false when the flag is enabled, they're located in the US, but they have brown hair.")</code>. That's three
scenarios rolled into one test name. It works, but being able to nest different <code>context</code> blocks to define my
complex scenarios is much easier to read.</p>
<h2>Examples</h2>
<p>Here's an example of some tests I could write in RSpec with <code>let</code> and <code>context</code>:</p>
<pre><code class="language-rb">describe "Calculator" do
let(:calculator) { Calculator.new }
describe ".multiply" do
let(:result) { calculator.multiply(first, second) }
context "when the first value is negative" do
let(:first) { -1 }
context "when the second value is negative" do
let(:second) { -3 }
it "returns a positive number" do
result.should eq(3)
end
end
context "when the second value is positive" do
let(:second) { 3 }
it "returns a negative number" do
result.should eq(-3)
end
end
end
end
end
</code></pre>
<p>HOW COOL IS THAT! Every <code>describe</code>/<code>context</code> block has <em>exactly</em> the setup data it needs defined clearly inside it.
And each block has very little noise to distract you.</p>
<p>Here's what I'd do in JavaScript/Jest to accomplish something similar:</p>
<pre><code class="language-JavaScript">describe("Calculator", () => {
let calculator
beforeEach(() => { calculator = new Calculator() })
describe(".multiply", () => {
let first, second
function getResult() {
return calculator.multiply(first, second)
}
describe("when the first value is negative", () => {
beforeEach(() => { first = -1 })
describe("when the second value is negative", () => {
beforeEach(() => { second = -3 })
it("returns a positive number", () => {
expect(getResult()).toEqual(3)
})
})
describe("when the second value is positive", () => {
beforeEach(() => { second = 3 })
it("returns a negative number", () => {
expect(getResult()).toEqual(-3)
})
})
})
})
})
</code></pre>
<p>There's definitely a bit more noise here, especially with the <code>beforeEach</code> and <code>let</code> statements. It's not a <em>lot</em>
more noise, but it is definitely more noise.</p>
<p>In real life I wouldn't expect to find tests like the above JavaScript example. I'd expect to find the tests in
JavaScript looking more like this:</p>
<pre><code class="language-JavaScript">describe("Calculator", () => {
let calculator
beforeEach(() => { calculator = new Calculator() })
describe(".multiply", () => {
it("returns a positive number when the first number is negative and the second number is negative", () => {
const result = calculator.multiply(-1, -3)
expect(result).toEqual(3)
})
it("returns a negative number when the first number is negative and the second number is positive", () => {
const result = calculator.multiply(-1, 3)
expect(result).toEqual(-3)
})
})
})
</code></pre>
<p>You could certainly make the case for this contrived example that <em>this</em> is actually the most readable set of
tests, because there's less code. I would have a hard time arguing. But most real-life tests are more complex than
these examples, with state and side-effects to mock out, and more scenarios and edge cases worth testing. Each test case here includes multiple conditions, but there are only two permutations represented. Once
things get a little more complicated than these contrived examples, the RSpec tests become the clear winner for me —
they're easier to read and manage, with their <code>let</code> and <code>context</code> blocks more discretely describing your test
scenarios.</p>
<p>You could also argue that the bigger win here would be breaking scenarios into individual <code>describe</code> blocks in
JavaScript tests, instead of cramming the entire scenario into one long <code>it("...")</code> statement. I wouldn't argue
that either.</p>
<h2>Caveats</h2>
<p>The day after I wrote this article, a conversation started in the Artsy slack about how confusing <code>let</code> was because
it moved variable initializations far away from where the tests used them.</p>
<p>This makes sense! I think it points to two truths in software development:</p>
<h3>Code readability is subjective</h3>
<p>For years I was convinced that practices like small functions or long and descriptive function names were
<em>objectively</em> more readable. I leaned into this, and my code reviews almost always included comments on what I
thought would make the code more readable.</p>
<p>As more people pushed back on my feedback over time, I realized that the feedback I was giving was <em>subjective</em>. I
still like code that uses many short functions wired together, but not everyone finds that more readable! I've
stopped giving readability feedback on PRs, unless I can provide nearly-objective facts or scenarios that point to
a readability improvement.</p>
<p>In this article, I find the RSpec <code>let</code> examples to be much more readable than the JavaScript examples. But you and
your team might not! Maybe the distance between a <code>let</code> block's definition and its method's usage makes it hard for
you to follow the test. That's cool!</p>
<h3>Any cool thing can be abused</h3>
<p>Earlier in this article I linked to <a href="https://medium.com/@tomkadwill/all-about-rspec-let-a3b642e08d39">an article that describes <code>let</code> blocks in more detail</a>. It includes
<a href="https://www.rubydoc.info/github/rspec/rspec-core/RSpec%2FCore%2FMemoizedHelpers%2FClassMethods%3Alet">a warning from the actual <code>let</code> docs</a>:</p>
<blockquote>
<p>Note: <code>let</code> can enhance readability when used sparingly (1,2, or maybe 3 declarations) in any given example
group, but that can quickly degrade with overuse. YMMV.</p>
</blockquote>
<p>I've definitely seen code where I had a hard time following a stream of <code>let</code> blocks. The RSpec example I gave
above reads nicely to me — but it's probably teetering on the edge of where <code>let</code> usage becomes confusing. I'm
guessing I have a slightly higher tolerance for this particular abstraction than my friends who don't like
it...again pointing to readability being subjective.</p>
<p>Having said all that — lately every time I try to write JavaScript tests, I find myself trying (unsuccessfully) to
recreate that RSpec example above. It represents exactly how I want to think about complex test scenarios. Each
level of the tests has exactly the setup that is unique to that level. There's very little distraction or noise at
each <code>context</code> and <code>it</code>. It totally aligns with
<a href="https://www.stevenhicks.me/blog/2018/01/chekhovs-gun-and-better-unit-tests/">my desire to minimize irrelevant test setup</a>.
I'm in ❤️ ❤️ ❤️ ❤️ ❤️.</p>
Introducing "How Devs Work"2021-09-03T09:59:43.267+00:00https://stevenhicks.me/blog/2021/09/introducing-how-devs-work/<p>I'm planning to work on a podcast this upcoming winter that I'm going to call "How Devs Work".</p>
<p>I started thinking about it last winter or spring, but didn't start working on it. Summers for me are all about being outside — trips with the family, camping, and especially training for triathlons & trail races. During the summer my alarm goes off before 5:30am most days, so I can get a long workout in before (a) it gets too hot and (b) I have to work. I've come to terms with the fact that I just don't have time for side projects when it's nice outside.</p>
<p>Now is the time of year when I start to get the itch to work on a "project" over the winter, though, and that's happening again.</p>
<p>Podcasts are probably my favorite medium for learning about new things. I think there's an opportunity to make them even better for learning. The tried-and-true formula for podcasts in tech right now is "interview someone new each episode for an hour," and that's not my plan.</p>
<p>My intentions with "How Devs Work":</p>
<ol>
<li>Thirty minutes an episode. Anything longer is toooo lonnggggg.</li>
<li>Treat a "season" of episodes like content. Tell cohesive stories. Slice up interviews with different people based on themes. You might hear the same person in episodes 2, 5, and 7, if that's where it makes sense.</li>
<li>This first season will talk about the <em>systems</em> that people use to get their work done.</li>
</ol>
<p>I recognize that this is more work than the standard formula. If you know me, though, you recognize that this kind of thing is right in my wheelhouse — over-polishing something and making it look like I spent entirely too much time on it (which I probably did) 😬</p>
<p>On that note, I'm looking for people interested in talking to me about the systems they use to get their work done. If you're interested, please <a href="mailto:steven.j.hicks+how-devs-work@gmail.com">reach out</a>!</p>
Avoiding Test-Case Permutation Blowout2021-08-26T10:38:50.829+00:00https://stevenhicks.me/blog/2021/08/avoiding-test-case-permutation-blowout/<p>Sometimes you want to write a test for a business rule that's based on multiple variables. In your goal to cover the rule thoroughly, you start writing tests for each permutation of all variables. Quickly it blows up into something unsustainable. With n variables for the business rule, you get 2<sup>n</sup> permutations/test cases. This is manageable with 2 variables (4 test cases), but at 3 variables (8 test cases) it becomes ridiculous, and anything beyond that feels immediately uncomfortable.</p>
<p>I've noticed myself using an alternate pattern for this, which results in n+1 test cases instead of 2<sup>n</sup>. It gives good coverage despite not testing all permutations, and I actually think the test cases themselves become easier to follow. The approach looks like this:</p>
<ol>
<li>Write one test that covers the positive case, where all the variables are in the correct state to make the thing happen.</li>
<li>Write one test per variable, to cover the negative case where the variable's value would cause the thing to <em>not</em> happen.</li>
</ol>
<p>An example: imagine we have a message that we only want to show when (a) the user is logged in, (b) they have green hair, and (c) they like bananas.</p>
<p>The "all permutations" approach would result in 8 tests:</p>
<pre><code>when the user is logged in
when the user has green hair
when the user likes bananas
the message appears
when the user does not like bananas
the message does not appear
when the user has brown hair
when the user likes bananas
the message does not appear
when the user does not like bananas
the message does not appear
when the user is not logged in
when the user has green hair
when the user likes bananas
the message does not appear
when the user does not like bananas
the message does not appear
when the user has brown hair
when the user likes bananas
the message does not appear
when the user does not like bananas
the message does not appear
</code></pre>
<p>You can see in this list of test cases the inspiration for the alternate approach — there is only 1 of 8 tests that test for the positive case. The rest are all combinations of values that turn the message off.</p>
<p>The alternate "turn it off one variable at a time" approach results in 4 test cases:</p>
<pre><code>when the user is logged in, has green hair, and likes bananas
the message appears
when the user is not logged in
the message does not appear
when the user has brown hair
the message does not appear
when the user does not like bananas because they are some kind of monster
the message does not appear
</code></pre>
<p>We have zero test cases for multiple fields being turned off, but that might be a <em>good</em> thing — if multiple variables are turning the message off, you can't really say with 100% confidence that the test is passing because both fields are being checked.</p>
<p>I think I tend to evolve toward this latter approach even without thinking about it, but now that I've become aware of this tendency, I've been using this as a starting point instead of the "eventual evolution."</p>
Syntax Highlighting Diffs In Code2021-05-26T12:24:46.699+00:00https://stevenhicks.me/blog/2021/05/syntax-highlighting-diffs/<p><a href="https://www.markdownguide.org/">Markdown</a> is a markup language used, usually by developers, to add light-weight formatting to text. It uses symbols to represent formatted elements, like <code>#</code> to represent the <code>h1</code> on a page.</p>
<p>I, like many other developers with blogs, <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/blog/2021/05/syntax-highlighting-diffs.md">write all my articles in Markdown</a>. I use <a href="https://11ty.dev">eleventy</a> to turn the Markdown source files into HTML & CSS for your browser.</p>
<p>It took a lot for me to like Markdown. The symbols are cryptic and hard to remember at first. I grew to love it as a nice way to add light-weight formatting to my blog. It's also useful for contributing to projects on GitHub, as most communication there happens via Markdown.</p>
<h2>Fenced Code Blocks</h2>
<p>A commonly used element in Markdown, especially for things like developer blogs, is the <a href="https://www.markdownguide.org/extended-syntax/#fenced-code-blocks">fenced code block</a>. If I want to show you a block of code, in my article source I create a fenced code block with a "fence" of backticks before and after the code:</p>
<pre><code>```
var value = 6;
```
</code></pre>
<p>When <a href="https://11ty.dev">eleventy</a> builds my website, it converts that fenced code block into a <code><pre><code>....</code></pre></code> element. The example above looks like this:</p>
<pre><code>var value = 6;
</code></pre>
<h2>Syntax Highlighting A Fenced Code Block</h2>
<p>You might have noticed some nice colors in the example directly above. My website uses <a href="https://highlightjs.org/">highlight.js</a> to highlight syntax of all code blocks.</p>
<p>In the above example, highlight.js is doing its best to interpret which language my code block is using. It looks to me like it does a nice job!</p>
<p>If I find that it mis-interprets a code block, I can specify a language by appending it to the fence, like this:</p>
<pre><code>```javascript
var value = 6;
```
</code></pre>
<p>I try to always specify the language. Why? While highlight.js interprets which language syntax to use, VS Code doesn't. By specifying the language on all code blocks, I get nice syntax highlighting in my editor, too:</p>
<p><img src="../vs-code-highlighted.png" alt="Highlighted syntax in VS Code"></p>
<h2>Syntax Highlighting A Code Diff</h2>
<p>Here's the cool thing I learned today:</p>
<p>If you're using Markdown with syntax highlighting, there's a good chance you already have support to highlight code diffs!</p>
<p>Let's say I want to show you what changed in a code sample. Instead of specifying the programming language in the fenced code block, I can specify <code>diff</code>. Like the changes would be represented when running a <code>diff</code> between two files, lines removed get a <code>-</code> prefix and lines added get a <code>+</code> prefix.</p>
<p>For example, what if I had the value wrong in the example above? In my source I'd specify a code block like this:</p>
<pre><code>```diff
- var value = 6;
+ var value = 7;
```
</code></pre>
<p>Because highlight.js supports <code>diff</code> as a language, it knows what to do with those lines:</p>
<pre><code class="language-diff">- var value = 6;
+ var value = 7;
</code></pre>
<p>(Apologies to the deuteranopians out there. If it's hard to tell, that top line is rendered as red, and the bottom as green.)</p>
<h2>Why Am I So Excited?</h2>
<p>I like to build <a href="https://github.com/artsy/relay-workshop/tree/main/src/exercises/02-Fragment-Container">hands-on tutorials</a>, and something I hadn't until now figured out is how to clearly represent removing or replacing lines. With the <code>diff</code> syntax language, I can totally do this — and I don't have to install or configure anything new to support it!</p>
Automating social posts with GitHub actions and Zapier2021-05-06T13:50:41.238+00:00https://stevenhicks.me/blog/2021/05/automating-social-posts-with-github-actions-and-zapier/<p>Automation is weird. There must be a cognitive bias that explains the urge to <a href="https://xkcd.com/974/">automate</a> <a href="https://xkcd.com/1205/">things</a> as a <a href="https://xkcd.com/1319/">developer</a>. I think it's a combination of two things:</p>
<ol>
<li>There's an inflection point where the payoff of automation doesn't matter anymore, because the perceived investment is so low. (This is almost certainly an inaccurate assessment.)</li>
<li>Sunk cost fallacy quickly sets in and you continue to think the automation is <em>just around the corner</em>, and you can't possibly give up now.</li>
</ol>
<p>A real-life example:</p>
<p>I spend a maximum of five minutes putting together social media posts when I write a new article on my blog. Five minutes! But I will be goddamned if I'm going to do that manually. <strong>Am I a cave person?!?!?!?!</strong> I am not. There are certainly existing services I can piece together to do this for me, right? With like, zero effort?</p>
<p>It turns out I'm kind of right!</p>
<h2>Why would I even do this?</h2>
<p>I dream of a day where writing blog posts is a thing I do during the day. For now, it's a thing I do late at night. Mornings are for workouts, days are for working, early evenings are for kids' activities, mid-evenings are for dinner and TV with my partner....and finally late-evenings are for vomiting my brain-thoughts here. Most of the nights I write articles, I get sucked into a groove and finally finish them around 11pm or 12am.</p>
<p>Not very many people read articles at 12am. You might if you're having trouble sleeping. But I don't want to share my new articles at 12am — I want to share them early the next morning, so that more people see them.</p>
<p>You're right — I could totally schedule the sharing of my articles the next day with something like TweetDeck or Buffer. Or I could just wait until the right time and post things manually. But like I said above, <strong>I am not a cave person</strong>. I'm a developer, and I can build this solution myself!</p>
<h2>What does this look like?</h2>
<p>My dad is a retired plumber. One of the things I'm most impressed with is his ability to look at a three-dimensional space, identify something valuable at a source, identify the location that resource is needed, and propose a physical combination of materials to get from source to target. It's honestly magical — he sees stuff in the ceiling of the basement that I don't see, routes that I would never know existed, combinations and solutions I'd never consider.</p>
<p>I <a href="https://www.stevenhicks.me/blog/2021/01/tips-for-working-with-legacy-code/">replaced a kitchen faucet this winter</a>, but I can't do squat with copper pipes! I think I inherited his system-level vision, though — only applied to software development.</p>
<p>From my dad's plumber eye, here's what our end solution looks like:</p>
<h3>A. I create a pull request for my new article, with a scheduled merge date.</h3>
<h3>B. A GitHub action merges the article at that scheduled date.</h3>
<h3>C. Once merged, the article is added to a tweets.xml RSS feed.</h3>
<h3>D. The <a href="https://zapier.com/">Zapier workflow automation platform</a> watches the tweets.xml RSS feed for new posts. When it sees one, it posts the content from that feed to Twitter/LinkedIn/MySpace/etc.</h3>
<h2>Step 1: Output a new RSS feed: tweets.xml</h2>
<p>This is a pretty straight-forward step for me, given I'm using <a href="https://www.11ty.dev/">eleventy</a> to statically generate my blog, and I already have it generating a <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/_feed/feed.xml.njk">feed.xml</a> so that you can subscribe to my new posts.</p>
<p>I create <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/_feed/tweets.xml.njk">a new template for a feed named <code>tweet.xml</code></a>, and I <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/_feed/tweets.xml.njk#L29">iterate all tweetable articles on my blog</a>. I define "tweetable articles" in my <code>eleventy.js</code> as <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/.eleventy.js#L28-L34">articles that contain a <code>tweet</code> property</a>. This means the only articles that will show up in this feed are the ones <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/blog/2021/03/prs-for-personal-projects.md">I specify a <code>tweet</code> property for</a>.</p>
<p>I <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/_feed/tweets.xml.njk#L36">hydrate the contents of each item with the value of the <code>tweet</code> property</a>.</p>
<p>When this template gets processed by eleventy, it generates <a href="https://www.stevenhicks.me/tweets.xml">an RSS feed that contains the exact contents I want to share to social media</a>.</p>
<p>One thing that's important here is to make sure the feed you're generating <a href="https://validator.w3.org/feed/">is valid</a>. Every time I've had an article not publish when I think it should publish, it's because the feed I've generated is no longer valid. Computers only do what you tell them to do!</p>
<h2>Step 2: A GitHub action to merge new posts on a schedule</h2>
<p>If I were to commit an article with a <code>tweet</code> property to <code>main</code> directly, I'd get a new article in my <code>tweets.xml</code> feed right away. Because I'm typically writing late at night, I don't want that! I want to wait to add my article to <code>tweets.xml</code> until a time of day when people aren't sleeping.</p>
<p>I've written previously about <a href="https://www.stevenhicks.me/blog/2021/03/prs-for-personal-projects/">the benefits of creating PRs for personal projects</a>, but here's another benefit of that practice — I can use <a href="https://github.com/gr2m/merge-schedule-action">a GitHub action to merge a PR at a scheduled time</a>, and delay publishing a new article until a better time of day.</p>
<p>With <a href="https://github.com/pepopowitz/stevenhicks.me/blob/main/.github/workflows/merge-schedule.yml">this action configured on my website</a>, I can add <a href="https://github.com/pepopowitz/stevenhicks.me/pull/30#issue-601297076">a timestamp to a PR for my new article</a> using a specific format, and <a href="https://github.com/pepopowitz/stevenhicks.me/runs/2199184916?check_suite_focus=true">the next time my action runs</a>, it will merge the scheduled PR.</p>
<p>I've got my action scheduled to check for mergeable PRs at 15:15 UTC every day. That's 9:15 or 10:15 every morning for me. This is a time I've decided it's worth sharing my posts.</p>
<h2>Step 3: A Zapier zap to publish from an RSS feed to a social platform</h2>
<p>At this point I've got the ability to merge new articles at a scheduled time, and an RSS feed containing bodies of social media posts that's only updated on that schedule.</p>
<p>The final step – watch the <code>tweets.xml</code> RSS feed for new items, and push them to Twitter/LinkedIn/etc.</p>
<p><a href="https://zapier.com">Zapier</a> is an automation workflow platform. We can tell it to watch for a specific event (additional items in an RSS feed), and perform a specific action (publish the contents of that RSS item to social media platforms).</p>
<p>In this case, we'll set up a zap that listens to a "New item in Feed" and with its contents, "Creates Tweet in Twitter". There are lots of settings in the Zapier UI here but I'll highlight the most pertinent: generating the tweet itself.</p>
<p><img src="../zapier-to-twitter.png" alt="Zapier connecting RSS to Twitter"></p>
<p>The most important thing worth noting here is that you're going to set up the action to consume specific fields from the RSS feed: <code>Raw Content</code> and <code>Link</code>. When this zap runs, it's going to effectively capture the <code>tweet</code> property from my new post and share that content to Twitter, because that's what's in <code>Raw Content</code>.</p>
<p>Zaps run every 15 minutes or so, so there's a very slight delay between when an article is merged to <code>main</code> and when it gets shared to social platforms. Eventual consistency, amiright?</p>
<p>But cool — Every time I schedule an article to be merged, as long as I include a <code>tweet</code> property, <em>the robots post it to social media for me</em>. This is the dream! Less nonsense like opening Twitter and LinkedIn on my own. More robots just doing it all for me. <strong>Robots!!!!</strong> 🤖🤖🤖 ❤️❤️❤️ 💪🏼💪🏼💪🏼</p>
<p>Now I can get back to more human tasks, like drinking micro-brews and watching professional cycling races. Thanks, robots!</p>
One thing I wish I'd done as a new tech lead2021-04-22T11:01:22.070+00:00https://stevenhicks.me/blog/2021/04/one-thing-i-wish-id-done-as-a-new-tech-lead/<p>In the recent <a href="https://podcasts.apple.com/us/podcast/13-effective-tech-leadership-question-mark/id1545870104?i=1000517292477&uo=4">Artsy Engineering Radio episode with Anson</a>, <a href="https://twitter.com/ansonewanger">Anson</a> mentions the different types of leader you can be as a tech lead - sponsor, mentor, and/or coach.</p>
<p><a href="https://www.diversityincbestpractices.com/the-difference-between-mentoring-coaching-and-sponsorship/">Here's a good breakdown of the difference between the three</a>:</p>
<ol>
<li>Coaches talk <em>to</em> you.</li>
<li>Mentors talk <em>with</em> you.</li>
<li>Sponsors talk <em>about</em> you.</li>
</ol>
<p>In the episode Anson shared that he didn't know which style he'd be as a new tech lead. I recalled that when I was a new tech lead, I had pre-conceptions about which style I should be based on what <em>I</em> looked for in a tech lead.</p>
<p>That was a mistake on my part, and it definitely contributed to me stepping down 6 months after taking on the role. Throughout my (short) time as a tech lead I learned a couple things:</p>
<ol>
<li>What <em>I</em> looked for in a tech lead didn't significantly impact the kind of tech lead my team wanted.</li>
<li>Different engineers on the team wanted a different balance of leadership styles from their tech lead.</li>
</ol>
<p>I didn't fully understand #2 until after I'd stepped down. And I <em>still</em> haven't figured out what that balance should have looked like for everyone on the team.</p>
<p>But — in the spirit of <a href="https://www.youtube.com/watch?v=FxTXf6qyaGk">"asking vs guessing"</a> — there's no reason I should have tried to figure that out on my own <em>anyway</em>.</p>
<p>If I were the new tech lead on a team today, I would:</p>
<ol>
<li>Have conversations with my team about sponsorship vs coaching vs mentorship, to align us on what those styles mean.</li>
<li>Have conversations with each engineer on the team about which style(s) they wanted from their tech lead.</li>
</ol>
<p>There's no reason to think it would be easy for anyone to answer that question in one conversation. But having those conversations more explicitly would have informed me on how to be a better leader for everyone on the team...instead of trying to figure it out on my own.</p>
Cross-Section Of A Redwood App2021-04-02T13:57:02.534+00:00https://stevenhicks.me/blog/2021/04/cross-section-of-a-redwood-app/<p>I'm in love with <a href="https://redwoodjs.com/">RedwoodJS</a>. It puts opinions around all the decisions you need to make to build a React app, and abstractions around the most common features you need to make the app usable. While I might not agree with <em>all</em> of Redwood's opinions, I'm perfectly happy to accept them for the sake of shipping things. It allows me to focus on building features instead of worrying about infrastructure and setup.</p>
<p>I recently started building <a href="https://github.com/pepopowitz/pendulina">a training journal app</a> with Redwood. I've been embracing <a href="https://www.stevenhicks.me/blog/2021/03/prs-for-personal-projects/">pull requests as a means to tell stories</a> on this personal project, and I noticed that some of my PRs show a nice cross-section of all the Redwood abstractions from the UI to the database.</p>
<p>In this article I'll give you a little background on the app I'm building and some fundamental Redwood concepts, and then we'll take a look at one of my recent PRs to see the cross-section of a Redwood app.</p>
<h2>Some background about my app</h2>
<p>For the last few years I've tracked my running/triathlon training plans with a spreadsheet. It's worked fine, but are we even web developers if we're not turning spreadsheets into web apps?</p>
<p>I'm starting to ramp up my training for this year, and I decided I want <a href="https://github.com/pepopowitz/pendulina">an app for tracking it</a> this time. Nothing too complicated — I want to track my scheduled workouts, my actual workouts, and get a sense for whether I'm on-target with my training.</p>
<p>Here's what it looks like so far:</p>
<p><img src="../pendulina-before.png" alt="My training journal app, showing two weeks worth of workouts"></p>
<h2>The change I want to introduce</h2>
<p>My database model has an entity that represents each workout I want to get in. I call it a <code>PlanWorkout</code>.</p>
<p>So far a <code>PlanWorkout</code> tracks things like activity (running, biking, etc.), target miles and/or duration, and intention for the workout (recovery, race, etc.).</p>
<p>One thing that's missing — is the workout a <em>key</em> workout? Is it the race I'm working up to? Is it maybe not a race, but a workout I'm really interested in crushing, like an <a href="https://www.triathlete.com/training/find-functional-threshold-power/">FTP test</a>? I like to see these workouts on my grid, so I know when they're coming up.</p>
<p>The PR we'll look at adds a single field — <code>isKeyWorkout</code> — to the <code>PlanWorkout</code> model, and propagates it up the entire stack, from database to UI.</p>
<h2>Some Redwood fundamentals</h2>
<p>Here are some things to know about Redwood:</p>
<ol>
<li>A Redwood app consists of a React front-end and a GraphQL API back-end. You don't need to set any of this up...Redwood's CLI takes care of it for you.</li>
<li>The Redwood <a href="https://learn.redwoodjs.com/docs/tutorial/our-first-page">Page</a> construct represents a page that a user can hit in your React app.</li>
<li>When you want to interact with your GraphQL back-end from your React front-end, you render a <a href="https://learn.redwoodjs.com/docs/tutorial/cells">Cell</a> component.</li>
<li>Within each Cell, you specify a few things:
<ul>
<li>The queries/mutations it will send to the GraphQL endpoint.</li>
<li>The <code>Empty</code>, <code>Loading</code>, <code>Error</code>, and <code>Success</code> states of the component, based on data loaded from the GraphQL endpoint.</li>
</ul>
</li>
<li><a href="https://learn.redwoodjs.com/docs/tutorial/redwood-file-structure#the-api-directory">The back-end</a> is supported by <a href="https://github.com/pepopowitz/pendulina/blob/main/api/db/schema.prisma">a database schema definition file</a>, <a href="https://github.com/pepopowitz/pendulina/blob/main/api/src/graphql/planWorkouts.sdl.js">GraphQL schema definition files</a>, and <a href="https://github.com/pepopowitz/pendulina/blob/main/api/src/services/planWorkouts/planWorkouts.js">services for handling data interaction and business logic</a>. Redwood ties these all together to give you a GraphQL endpoint with access to your database.</li>
</ol>
<p>I've been thinking of these abstractions like a diagram of Earth's core, with the database at the center and the UI on the outside:</p>
<p><img src="../cross-section.png" alt="Cross-section of a Redwood app, in the style of a 3d globe with a cutout to show the layers inside."></p>
<h2>A PR that introduces the changes</h2>
<p>I've done my best to tell a story in <a href="https://github.com/pepopowitz/pendulina/pull/28">the PR associated with this work</a>. To summarize what's happening:</p>
<ol>
<li>We <a href="https://github.com/pepopowitz/pendulina/commit/5b524a05804f3e0c51f3b7ee103c072a7b1158df">add a new field to the table representing a workout in the schema file</a>, and run <a href="https://www.prisma.io/">prisma</a>'s migration tool to update the local database with the new field. (Prisma is the ORM that Redwood uses under the covers.)</li>
<li>We <a href="https://github.com/pepopowitz/pendulina/pull/28/files#diff-09aba008345d26431717e3d7f5739bc7b66ff73456c93505ddd55a9fc06e489fR80">expose the new field in all GraphQL endpoints that need it</a> (queries <em>and</em> mutations).</li>
<li>We <a href="https://github.com/pepopowitz/pendulina/pull/28/files#diff-426f93dc3554728620e8e25c32bd5d4a4fbb7da8e702e0508718586e4fb00dc2R20">add the new field to all queries/mutations that need it</a>, via the corresponding Cells.</li>
<li>We <a href="https://github.com/pepopowitz/pendulina/pull/28/files#diff-61e6d2ce866a1c0722e323254f9fd7a792e86b6bde8597f53c05b72f254f2cf2R136">add the new field in any UI components that will interact with it</a>.</li>
</ol>
<p>With these changes we can identify key workouts! Notice how the race at the end of week 2 is now (subtly) highlighted:</p>
<p><img src="../pendulina-after.png" alt="My training journal app, showing two weeks worth of workouts, with a race highlighted"></p>
<p>There's not a lot to this PR, but it demonstrates changes at most of the layers in our cross-section.</p>
<p>A couple layers are missing. There are no changes to a <code>Page</code> or <code>service</code> — the PR is adding a new field to an <em>existing</em> page, and no changes are needed there. If you're curious, <a href="https://github.com/pepopowitz/pendulina/blob/c52774d775e7a58b19a203684dff1e7f8bf86491/web/src/pages/EditPlanWorkoutPage/EditPlanWorkoutPage.js">here is the <code>EditPlanWorkoutPage</code> that wraps the <code>EditPlanWorkoutCell</code> modified in the PR</a>, and <a href="https://github.com/pepopowitz/pendulina/blob/c52774d775e7a58b19a203684dff1e7f8bf86491/api/src/services/planWorkouts/planWorkouts.js">here is the <code>planWorkouts</code> service that facilitates database interactions for the <code>PlanWorkoutForm</code> modified in the PR</a>.</p>
<p>In creating this PR, I used a similar one as a guide; when I need to add a field in the future, I'll use this PR as a guide.</p>
<h2>Consistent patterns mean less thinking</h2>
<p>Consider apps you've worked with that don't have consistent patterns for common problems. You've probably spent a good amount of time digging through existing code to find a pattern that solves a problem, then more time deciding which of the multiple patterns you find is most representative of the preferred approach.</p>
<p>With Redwood imposing opinions and structure, changes that travel the entire stack from UI to database are consistent and repeatable. No need to research the patterns and choose the correct one — there's only one pattern, and it's consistently applied throughout the app.</p>
<p>(And if you've worked with me in the past you just did a spit take, having watched me use semicolons on one line and no semicolons on the next, completely incapable of consistency in code.)</p>
Setting A Default For A Redwood CheckboxField2021-04-01T23:05:03.941+00:00https://stevenhicks.me/blog/2021/04/redwood-checkboxfield-default/<p><a href="https://redwoodjs.com">RedwoodJS</a> supplies <a href="https://redwoodjs.com/docs/form.html#form">many types of fields for forms</a>, including a <code>CheckboxField</code> for emitting a..well...checkbox field.</p>
<p>I recently <a href="https://github.com/pepopowitz/pendulina/pull/28/files#diff-61dbca71a04a93cffe147a9d4f3646a7bb3893efd96e98df31442e81c250aaf8R137-R142">added a <code>CheckboxField</code> to a form for my training journal</a>, and had some trouble setting a default value on it.</p>
<p>Turns out I had the prop name incorrect. I expected it to be <code>defaultValue</code> like other form components, but the correct prop is <code>defaultChecked</code>:</p>
<pre><code> <CheckboxField
name="isKeyWorkout"
className="rw-input"
errorClassName="rw-input rw-input-error"
defaultChecked={props.planWorkout?.isKeyWorkout}
/>
</code></pre>
<p>I tracked this down by looking for the original implementation of that component, and <a href="https://github.com/redwoodjs/redwood/pull/719/files#diff-ad72b10dba26b0f02b51f33ff7d8ed95212279033a78f54fe5b3d0f8c037f881R115">finding a test that demonstrated the proper way to set a default value</a>.</p>
<p>I couldn't find this in the docs anywhere, so maybe this will help you one day when searching for "redwoodjs checkboxfield default".</p>
PRs For Personal Projects2021-03-25T22:30:01.334+00:00https://stevenhicks.me/blog/2021/03/prs-for-personal-projects/<p>Based on the amount of questions I've heard from newer devs about their GitHub profiles in the past year, it's clear they're being used to screen candidates. This is most certainly unfair, as not every good developer has the time to build a public GitHub persona. I don't personally think a GitHub profile is necessary for new devs — though I do think that there is value in having one, as a means to distinguish themselves from the other many candidates.</p>
<p>Assuming you've got the time to build up a public profile on GitHub...what kinds of projects should you put on it? I've heard this question from several newer developers in the past few months, and I think it's fair but slightly misguided.</p>
<p>I actually don't think it matters much <em>what you decide to build</em> — because I think there are better ways to highlight your skills from your GitHub profile.</p>
<h2>Share PRs, not projects</h2>
<p>PRs are much more digestible than a project. If I sent a link to <a href="https://github.com/pepopowitz/pendulina">my training journal project</a> to a job lead....where would they start? The README.md isn't helpful, I hope they don't start there. There is most certainly garbage code in the project. The artifacts of late nights when I wrote code I wasn't proud of just to ship something. That might be the first thing they look at! I don't want that. There's entirely too much surface area in any entire codebase to accurately describe your work.</p>
<p>So...I've been taking a cue from <a href="https://twitter.com/jonallured">my friend Jon</a> and creating PRs for even my personal projects. <a href="https://github.com/pepopowitz/pendulina/pull/18">Here's a recent example</a>: a PR where I introduced a new feature to an app I'm using to plan and track my workouts. No one's going to review this PR! I easily could have committed these changes directly to <code>main</code> and moved on.</p>
<p>If someone asked to see some of my work, I wouldn't share that project — I'd share that PR. It more accurately captures how I work, how I think, how I solve problems, and what it'd be like to work with me.</p>
<p>There's more value I'll get out of creating a PR like this:</p>
<ol>
<li>I can link to it if I decide to write an article about how I implemented it</li>
<li>I can share it if someone is interested in knowing what it looks like to work with <a href="https://redwoodjs.com/">RedwoodJS</a></li>
<li>If I need to do similar work in the future, I have an isolated example to look at</li>
<li>It enforces good commit hygiene, which is really easy to let slide on a personal project</li>
</ol>
<p>It's a little bit of extra work to create the PR, but that's a lot of good reasons to do it.</p>
<p>PRs are a nice little snapshot of a cohesive set of work. They're great for more than just code review. You should use them for your personal projects.</p>
<p>And if you're not sure what the PR should look like, <a href="https://artsy.github.io/blog/2020/08/11/improve-pull-requests-by-including-valuable-context/">I've got some suggestions</a>.</p>
Rule 4: Warm Up To Get Into Flow State Sooner2021-02-25T09:58:54.901+00:00https://stevenhicks.me/blog/2021/02/warm-up-for-deep-work/<p><em>This is an article from the series <a href="../maximizing-productivity">Maximizing Productivity During Small Blocks Of Time</a>.</em></p>
<p>Deep coding work is like building a LEGO spaceship in my head. It takes a while to construct the model, and it takes focus to keep the model from collapsing. Once it collapses I have to rebuild the model from scratch. I've explained to my lovely interrupting children that this is why I get mad when I'm interrupted — what seems like a 30 second request to them is a much longer distraction for me.</p>
<p>I've estimated in my head that I need about 20-30 minutes to get into a state where I'm in the flow, the spaceship is fully constructed, and I can manipulate it like a master builder. I call this my "ease-in time." I think of it like <a href="https://en.wikipedia.org/wiki/Critical_mass_(sociodynamics)">critical mass</a>, or a moat that I have to cross to get to an adventure.</p>
<p>I find writing to be similar — I need that ease-in time to get into a writing mindset before I feel effective. When I've got 45 minutes until bedtime, which is frequently when I write, it's easy to convince myself that it's not even worth starting. I'm only going to get 15 minutes of quality writing time after I ease in. <strong>Why bother?</strong></p>
<p>During my ease-in time, I have very little self-control or direction. I'm mostly doing my best to keep myself from all the distractions I want to chase. It's sloppy. Sometimes I end up on Twitter. Sometimes I don't. The sessions when I end up on Twitter take longer to feel productive than the sessions when I don't.</p>
<p>Until recently I treated this as something I couldn't influence. I thought I just had to ride out my ease-in time, it takes what it takes, and I'd get to deep flow eventually.</p>
<p>But then I thought about athletes.</p>
<h2>Athletes don't warm up on Twitter</h2>
<p>Athletes don't just do whatever they want before a game — they <em>warm up</em>. Giannis Antetokounmpo doesn't go into a game cold — he warms up by dribbling and shooting jump shots. When I get on a bike, I'm not ready to crush my ride right away — it takes ten minutes of gradually increasing my cadence and power before my body feels ready. I don't just put on my shoes and run five miles — I do stretches and strengthening exercises. (Maybe <em>you</em> just put your shoes on and run but I am old and I want to avoid injury.)</p>
<p>Athletes don't warm up by scrolling through Twitter or watching <a href="https://www.youtube.com/watch?v=e1BYAfrUwLk">incredible marble machine videos on Youtube</a>. They do drills that warm their body up for the specific movements they'll be making.</p>
<p>With easing-in I've been allowing myself the <em>time</em> to warm up, but I wasn't guiding my time properly. I was taking jump shots to warm up for a bike ride. My warm-up time wasn't effective. It didn't actually warm me up for deep coding or writing.</p>
<h2>What makes a good warm-up activity?</h2>
<ol>
<li>It's easy to start the activity. You're using it to warm up for the deep work — it shouldn't itself require a warm-up.</li>
<li>It's easy to stop the activity. It's important to set it aside when it's time to focus on your intention. You don't want to get sucked into your warm-up.</li>
<li>Specificity. This is the piece I was missing. Warm-ups should exercise the muscles you're going to use during your time block.</li>
</ol>
<h2>Writing warm-ups</h2>
<p>Intentional writing warm-ups have reframed the amount of time I need to work on my writing practice. Whereas I used to see that 45 minutes before bed as not worth starting, I now see it as an opportunity to make at least a <em>little</em> bit of meaningful progress. I'm more likely to start a short writing session instead of conceding to Twitter or <a href="https://www.youtube.com/user/globalcyclingnetwork">GCN</a> for the night, because I can usually get myself into the writing mindset in ten minutes or less.</p>
<p>Some things I've done for writing warm-ups:</p>
<ul>
<li>Time-boxed free-writing</li>
<li>Re-reading/editing an in-progress article</li>
<li>Picked a page out of the book <a href="https://www.amazon.com/642-Things-Write-About-Writers/dp/1452127840/">"642 Things To Write About: Young Writers Edition"</a>. ("Young Writers Edition" because my partner teaches middle-school English online, and that's the book that's on our shelf 😅)</li>
</ul>
<p>I'm sure this is probably a technique that real writers talk about, and there are probably lots of other good resources for this.</p>
<h2>Coding warm-ups</h2>
<p>Warming up to write code feels more natural to me because I've done this unintentionally for years. It feels different now that I'm applying intention, though. It feels like I have self-awareness and I've got agency in how long my warm-up will take.</p>
<p>Things I've tried for coding warm-ups:</p>
<ul>
<li>Time-boxed work on a personal project. Emphasis on time-boxed, because I <em>don't</em> want to get sucked in. If you try this option, make sure this work doesn't require its own warm-up: right now I have a personal project that's always kind of floating around in my brain when I'm not working, so I can usually jump right into it.</li>
<li>Time-boxed PR Review. Yeah, I know I told you in <a href="../categorize-by-depth">the previous article</a> that you should defer that to your smallest time slots, but I have found it a good way to ease into deep coding too.</li>
<li>Small learning challenges. My friend <a href="https://twitter.com/bhoggard">Barry</a> recently shared <a href="https://www.executeprogram.com/courses/typescript">an interactive TypeScript course with very small exercises</a>. The exercises are <em>tiny</em>, so I can get a taste of coding without getting sucked in.</li>
</ul>
<p>Things I want to try:</p>
<ul>
<li>Writing a couple unit tests. They wouldn't even have to be tests for my focus project — the important thing is that I'd be warming up the "muscles" I'll be using. I haven't done this yet only because I keep forgetting to try it.</li>
<li>A code kata. The challenge here is to find a kata that's short enough to qualify as a warm-up instead of a full-fledged problem. It'd be easy to get sucked into this. I haven't put the effort into finding a short enough kata yet.</li>
</ul>
<h2>I'm not a robot</h2>
<p>I know it seems like it but I'm not always on. Sometimes I self-indulge. Sometimes I still let my hair down and "warm up" by scrolling through Twitter.</p>
<p>I have more awareness of when I'm doing it though. And intention — I'm <em>allowing</em> myself to warm up in a way that I know is less effective as a treat. Like allowing myself to drink a Coke twice a week even though it's bad for me. (This is what I tell myself...and you...to convince us that I have agency and self-control. Is it working? 😬)</p>
<p>I also don't always <em>need</em> a warm-up. Sometimes my mind is in the right space from the start, and I'm able to jump right into my deep work. I try to do a check within the first few minutes of my time-block to see if I need an intentional warm-up. I think I need it about 50% of the time for writing, and maybe a little less for coding.</p>
<hr>
<p>Warming up for deep work in small blocks of time is pretty new to me but so far it's been really effective. I spend a lot less time getting into the mindset I need to be effective. If you've got suggestions on how to warm up for deep work, <a href="https://twitter.com/pepopowitz/status/1364988164386000901">I'd love to hear about them</a>!</p>
Rule 3: Categorize Tasks By Depth, And Work Them At The Right Time2021-02-22T12:19:45.621+00:00https://stevenhicks.me/blog/2021/02/categorize-by-depth/<p><em>This is an article from the series <a href="../maximizing-productivity">Maximizing Productivity During Small Blocks Of Time</a>.</em></p>
<p>I find an hour to be enough time to get into deep work. I haven't always felt this way, but I do now.</p>
<p>But what about even shorter blocks of time? What about those half-hour gaps between meetings? Is it worth starting anything meaningful during those, or should I just burn that time on Twitter?</p>
<p>As with everything, the answer lies somewhere in between. I don't think the ROI is worth getting into deep thought for a half-hour, but I <em>do</em> find those pesky half-hour blocks to be extremely useful for <em>shallow</em> work.</p>
<p>What qualifies as shallow work?</p>
<ul>
<li>Making a personal phone call</li>
<li>Raising a question in Slack</li>
<li>Scheduling meetings</li>
<li>Creating tickets for new issues</li>
<li>PR review (if they're on the smaller side)</li>
<li>Small updates to docs</li>
<li>Investigating a tool I learned about recently</li>
<li>Answering a question I'm curious about</li>
</ul>
<p>Many of these things require very little time, but "shallow work" isn't really about the amount of time involved for the task itself. It's about how much time it would take me to get into the level of focus required for the task. Everything in that list requires very little priming. I could drop any one of them immediately and spend very little time getting back into it an hour later.</p>
<h2>Avoid shallow work during deep focus blocks</h2>
<p>This is probably the most controversial thing I'll say in this series: <strong>procrastinate shallow work when you come across it during times of deep focus</strong>. Instead, track the task however you track your work, tag it as "shallow," and save it for a later tiny block of time.</p>
<p>When that next tiny block of time comes up, knock out as many shallow tasks as you can.</p>
<h2>Ten seconds of distraction is better than five minutes of distraction</h2>
<p>I've heard people say something to the effect of "if it takes less than five minutes, do it now." But I don't need <em>any</em> help getting distracted, especially when I'm trying to do deep work. Every unrelated task that you do during deep work is an opportunity to pull yourself out of your flow.</p>
<p>Stomp these distractions out. Recognize them as quickly as you can. Say "Not today, Satan," and save them for a time when you don't need your attention, focus, or deep thought.</p>
<h2>My system for categorizing tasks by depth</h2>
<p>What works for you will probably look different than what works for me, but I use <a href="https://todoist.com">Todoist</a> to manage my day. I've shared <a href="https://twitter.com/pepopowitz/status/1261323105479331841">more detail about how I use it</a>, but I categorize my tasks using <a href="https://todoist.com/help/articles/how-to-best-use-labels">the labels feature</a>.</p>
<p>When I'm in the middle of deep work and I come across a distracting task, I'll use Todoist's <a href="https://todoist.com/help/articles/keyboard-shortcuts">global Quick-Add shortcut</a> (ctrl-cmd-A on a Mac) and add a task with the appropriate label: <code>@shallow</code>, <code>@medium</code>, or <code>@deep</code>.</p>
<p>And that's it! I'm distracted for less than a minute and I stay focused.</p>
<p>The rest of the day I'll handle my <code>@shallow</code> tasks during my extra-short time blocks, my <code>@deep</code> tasks during my longer time blocks, and...I don't really have a good description of when I handle the <code>@medium</code> tasks. I use it for tasks between <code>@shallow</code> and <code>@deep</code> but I don't have a simple heuristic to describe when I use it.</p>
<hr>
<p>Next time we'll answer the question that actually prompted me writing this series: <a href="../warm-up-for-deep-work">"How can I get into flow state more quickly so that I can accomplish something meaningful in smaller blocks of time?"</a></p>
Rule 2: Slice Work Smaller2021-02-18T12:35:33.248+00:00https://stevenhicks.me/blog/2021/02/slice-work-smaller/<p><em>This is an article from the series <a href="../maximizing-productivity">Maximizing Productivity During Small Blocks Of Time</a>.</em></p>
<p>I'm a +7 perfectionist. I don't like to walk away from work until t's are crossed, i's are dotted, tests are written, and the work is <em>done</em>-done.</p>
<p>Perfectionism is a double-edged sword. While it produces beautiful work, it also prevents me from shipping things when they're "good enough," or even from collecting helpful feedback before I consider the work "complete." Sometimes it takes a non-trivial effort to convince myself to open a PR when the edges aren't yet polished.</p>
<p>My perfectionism has another negative effect — it prevents me from feeling accomplished when I have only a short amount of time to put toward the work. "One hour to put toward this feature? Pfffttt that'll only get me 10% done," my brain says. I dread even starting the work because I know I'm not going to "finish" within an hour.</p>
<p>The trick for me is to break work into smaller slices.</p>
<h3>Smaller slices shift the meaning of "meaningful progress"</h3>
<p>Instead of setting a goal of finishing an entire feature, I treat it like a good agile team treats their work — I break the feature into sub-features, or steps, or layers, or milestones. Anything that breaks the problem down into smaller problems.</p>
<p>These smaller tasks shift my perspective — a small block of time was not enough to finish an entire feature, but it's usually enough to make progress on at least one or two of the smaller tasks. The reframing lowers the bar for what "meaningful progress" means. An hour of work suddenly feels much more impactful. I don't have to fight the voice in my head that tells me it's not worth starting. My milestones for progress seem more attainable.</p>
<h3>Smaller slices make me feel good</h3>
<p>I've also now got dopamine on my side.</p>
<p>Large chunks of work feel nebulous. They make me feel bad. Like I'm never going to finish. Like I'm slow. Leaving a non-tasked story in "In Progress" for a week is frustrating. It's embarrassing to give the same update in standup several days in a row — "ummm I'm still working on the edit screen."</p>
<p>Smaller slices allow me to check things off a list more frequently, or to drag a sub-task from "In Progress" to "Done." This simple act feels like an accomplishment, <a href="https://www.psychologytoday.com/us/blog/the-truisms-wellness/201610/the-science-accomplishing-your-goals">complete with dopamine rush</a>. No matter how many tasks remain or how small the task I checked off, it <strong>literally feels good</strong> when I complete one.</p>
<p>I actually left you a good heuristic above for knowing when you need to break your work down. If you're repeating your daily update...it might be time to break that work smaller. It will help you feel like you're making progress, and it'll give better visibility to the rest of your team.</p>
<h3>A couple other good reasons to slice work smaller</h3>
<ol>
<li>It's easier for people to review a smaller scope of work. Large PR reviews often end in "LGTM" (looks good to me) because they're just too difficult to digest.</li>
<li>It tightens the feedback loop of delivery & iteration. Smaller chunks can be shipped faster. We can learn from them more quickly and iterate.</li>
</ol>
<hr>
<p>Slicing my work smaller helped me answer a very specific question I was trying to answer: "How can I accomplish something meaningful in a shorter amount of time?"</p>
<p>In the next article I'll answer another question: <a href="/blog/2021/02/categorize-by-depth/">"How can I take advantage of those <em>very</em> short blocks of time?"</a></p>
Rule 1: Dedicate Time. And Schedule It.2021-02-15T20:26:13.172+00:00https://stevenhicks.me/blog/2021/02/dedicate-time-and-schedule-it/<p><em>This is an article from the series <a href="../maximizing-productivity">Maximizing Productivity During Small Blocks Of Time</a>.</em></p>
<p>My first rule for maximizing productivity in small blocks of time is to dedicate time to a specific goal, and schedule it on my calendar.</p>
<p>An hour of time that has no declared focus often turns out to be exactly that — unfocused, scatterbrained, not dedicated to any one thing. In an unfocused hour I might feel like I make a bit of progress on several things, but rarely will I feel like I made significant progress on any <em>one</em> thing. Spending 10 minutes each on five things feels much less productive than spending 50 minutes on one thing. If I dedicate an hour to a specific task or project, I feel more focused from the start. There's nothing <em>forcing</em> me to stay dedicated...but having an intention gives me something to return to when distractions beckon.</p>
<p>The benefits compound when I <strong>schedule this dedicated time on my calendar</strong>. I feel more in control of my day. Control is a big thing for me — my worst days are the ones where I feel like I have no agency in what I'm doing. Dedicating time on my calendar for work that <strong>I</strong> want to accomplish satisfies my need for autonomy.</p>
<p>Recurring dedicated time on your calendar is a great way to build a practice, too. I've been using this strategy to strengthen my writing practice.</p>
<p>The hardest part about scheduling dedicated time is the discipline required to use it for its intended focus. For my first six months at Artsy, I had a weekly block of time on my calendar for "sharpening my tools" (h/t <a href="https://twitter.com/jonallured">Jon Allured</a>). I <strong>never once</strong> used it for its intended purpose.</p>
<p>What's the difference between the hour of sharpening I never did and the hour of writing I successfully dedicate every Monday? I think it's about specificity. I never really had an idea of what I'd do when it was time to sharpen. I wanted to keep my dev tools up to date, but that was such a nebulous goal that I never knew where to start.</p>
<p>With my writing hour, my goal is smaller — work on a blog article. Even if I don't know what the article is about yet, I know where to start...</p>
<p>And I'll tell you all about that later, in Rule 4.</p>
Maximizing Productivity During Small Blocks Of Time2021-02-10T16:14:26.556+00:00https://stevenhicks.me/blog/2021/02/maximizing-productivity/<p>One of my primary self-improvement projects is to use my time more effectively, especially the small blocks in between meetings. I'm not a manager, but I have more of a <a href="http://www.paulgraham.com/makersschedule.html">manager's schedule than a maker's schedule</a>. I willingly accept more meetings than most developers. I like the interaction, I like to get others unstuck, and I like to participate in cross-team conversations. The result: most of the empty time on my calendar is in blocks of an hour or less.</p>
<p>It's hard to do deep work in an hour or less. If I make full use of the hour, sure, I'll probably feel good about my progress...but that assumes I can get into the deep work promptly and that I don't get distracted. In reality, what looked on my calendar like a good hour often ends up feeling like 15 minutes of meaningful progress. If I'm lucky.</p>
<p>Thankfully, I have a lot of good role models to emulate at <a href="https://twitter.com/artsyopensource">Artsy</a>. Some of my most admirable coworkers accomplish meaningful work in the same size blocks of time that I would have previously dismissed as too small for meaningful work. They might not know it, but I've been watching and taking notes.</p>
<p>Over the next couple weeks, I'll share the rules I've developed for myself to work more effectively with my small blocks of time. I don't always follow them, but the days I do are the days I feel most successful, productive, and effective.</p>
<h3>My Rules For Maximizing Productivity In Small Time Blocks</h3>
<ul>
<li><strong>Rule 1:</strong> <a href="../dedicate-time-and-schedule-it">Dedicate time. And schedule it.</a></li>
<li><strong>Rule 2:</strong> <a href="../slice-work-smaller">Slice work smaller.</a></li>
<li><strong>Rule 3:</strong> <a href="../categorize-by-depth/">Categorize tasks by depth, and work them at the right time.</a></li>
<li><strong>Rule 4:</strong> <a href="../warm-up-for-deep-work/">Warm up to get into flow state sooner</a></li>
</ul>
Is discipline a muscle?2021-02-01T16:11:00.616+00:00https://stevenhicks.me/blog/2021/02/is-discipline-a-muscle/<p>I've never considered discipline to be a personal strength. I'm envious of people who present as well-disciplined. It's always felt like a thing I just can't do.</p>
<p>Last year I ran my first trail marathon, and I knew when I started training that if I wanted to finish the race without injury, I would need more discipline than I'd ever shown before. I told myself stories about how hard it was going to be for me to follow a training plan consistently.</p>
<p>At the end of my training I was proud of myself for being consistent in the face of doubt. I was also surprised at how <em>easy</em> it felt to stay consistent.</p>
<p>It was certainly made easier by COVID-19, as most of our other obligations and activities were cancelled. But I also never fell off the wagon of training. I never had a week where I made up excuses to avoid training.</p>
<p>There's also no doubt that the rewards for my discipline kept me going. There was an instant reward — feeling really good after a workout. There was also a potential long-term reward that I was really interested in — being one step closer to calling myself an ultra-marathoner.</p>
<p>This past month I've avoided alcohol and sweets — again I told myself stories about how hard it was going to be. But at the end...I honestly wanted to keep going. I've tried to improve my diet many times but this felt like the first time it wasn't unbearable. It wasn't nearly as hard as I expected it to be.</p>
<p>Anyway, this has me wondering 3 things:</p>
<ol>
<li>was I never as bad at discipline as I thought I was?</li>
<li>am I only as disciplined as I am interested in the rewards?</li>
<li>is discipline a muscle and I've finally figured out that I can train it?</li>
</ol>
<p>I suspect it's at least a bit of all 3. #3 is the most exciting though because it gives me confidence that I can keep improving.</p>
2020 In Review2021-01-31T12:00:00+00:00https://stevenhicks.me/blog/2021/01/2020-in-review/<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Looking through photos from this year and I found this, from my 10yo in January.<br><br>This would have been a *much* better version of 2020. <a href="https://t.co/BLX7Ljxf58">pic.twitter.com/BLX7Ljxf58</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1335637959983370242?ref_src=twsrc%5Etfw">December 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h2>TL;DR</h2>
<p>My goals coming into 2020 were:</p>
<ul>
<li>Continue to find balance and do more of the things most important to me.</li>
<li>Get better at dealing with intense but important conversations.</li>
<li>Run a marathon or longer on trail.</li>
</ul>
<p>I accomplished all three!</p>
<p>But 2020 was way more complex than these three goals. It was kind of a four-act play for me, generally in this order but not without blurry edges:</p>
<p><strong>Act 1:</strong> I felt like I was crushing it early in the year.</p>
<p><strong>Act 2:</strong> Ummmmm I don't know if you remember this but we had a global pandemic and racial injustice crisis last year and neither will end soon.</p>
<p><strong>Act 3:</strong> I curled into a ball and focused on self-care.</p>
<p><strong>Act 4:</strong> My health-meter filled back up and I got back to crushing it somehow.</p>
<p>There were some rough spots in 2020. There were also some resounding personal successes. I hesitate to celebrate the successes without first acknowledging the privilege I have in writing a recap of 2020 that doesn't once involve my life, livelihood, or humanity being threatened. But successes are meant to be celebrated so here we go.</p>
<p>You made it this far — we must be best friends and you must be here to read the whole thing!</p>
<h2>Act 1: Feeling pretty good</h2>
<h3>No more tech lead</h3>
<p>I started 2020 by stepping down from a tech lead position at <a href="https://artsy.net">Artsy</a>. For a lot of people this would feel bad, but for me it felt <strong>amazing</strong>. I thought a lot about how I enjoyed spending my time as an engineer and how I didn't, and I found that being a tech lead <em>prevented</em> me from doing the things I wanted to do. I envisioned the role including a lot of pairing, unblocking, and mentoring, but really it was more about strategy, planning, and to be blunt, meetings. It was scary to step down only months after getting the opportunity but doing so enabled me to do more of the pairing/unblocking/mentoring that I'd looked forward to.</p>
<h3>CodeMash 2020</h3>
<p>One of my favorite recent traditions has been speaking at CodeMash in early January. I got to teach a full room about Test Driven Development with JavaScript:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Packed room for <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a>’ workshop on TDD with Jest.<a href="https://twitter.com/hashtag/DeveloperCommunity?src=hash&ref_src=twsrc%5Etfw">#DeveloperCommunity</a> <a href="https://twitter.com/hashtag/CodeMash?src=hash&ref_src=twsrc%5Etfw">#CodeMash</a> #@codemash <a href="https://t.co/7p6ZcEc2LM">pic.twitter.com/7p6ZcEc2LM</a></p>— Chris | Dev Advocate (@saltnburnem) <a href="https://twitter.com/saltnburnem/status/1214925956902785024?ref_src=twsrc%5Etfw">January 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I also got to repeat my fave CodeMash activity — drinking beers in a hot tub with <a href="https://twitter.com/housecor">Cory House</a> and <a href="https://twitter.com/vforvidane">Victor</a>...geez Victor, you're one of my favorite reasons to go to CodeMash and I don't know your last name. I missed this a lot this year.</p>
<h3>Some local speaking</h3>
<p>I knew with my intentional focus on myself & my family that I wouldn't be speaking at many conferences, so I focused on speaking at local meetups. I talked about how we're using <a href="https://cypress.io">Cypress</a> at <a href="https://artsy.net">Artsy</a> a few times — including once in Chicago, right before things shut down around here. My friend <a href="https://twitter.com/ben_felda">Ben</a> skipped work to take the train down with me. I didn't know what I was doing and made us take the sloooooooowwwww train. I think this is the last time I went to a bar.</p>
<h3>Writing</h3>
<p>Maybe you haven't noticed but technical articles are pretty few and far between on this site. Somehow I put out two in early 2020.</p>
<ul>
<li>I wrote an article about <a href="https://www.stevenhicks.me/blog/2020/02/working-with-variables-in-cypress-tests/">working with variables in Cypress tests</a>. It surpassed an old ranty article about <a href="https://www.stevenhicks.me/blog/2016/12/ssis-is-annoying/">SSIS</a> to become my most popular article ever according to Google Analytics.</li>
<li>I wrote about a strange project to <a href="https://www.stevenhicks.me/blog/2020/03/saving-myself-one-minute-per-day-netlify-functions-custom-search-engine/">build a CLI in my browser search bar using Netlify Functions</a>.</li>
</ul>
<h3>Meditation streak</h3>
<p>I hit a 30-day streak for meditation! I'd wanted to do this for years but I'm the kind of person who finds routines to be <strong><em>sooooooo booooring</em></strong> so this was really hard.</p>
<p>Not surprisingly, once the pandemic hit I basically stopped meditating. More surprisingly, I haven't really picked it up since. It's okay, though — I've discovered that running and cycling <em>are</em> meditation for me, so I just make sure to do them once a day instead.</p>
<h2>Act 2: FFFFFFFffffffuuuuuuuuuuuuuuu what just happened?</h2>
<p>And then it became apparent that COVID-19 was a real thing. Everything shut down, including my big scary thing of the year, a trail marathon I'd signed up for.</p>
<p>And then it became apparent that our country was built on a rigged, unfair foundation. Amidst a backdrop of continuous race-based injustices, we begged our local "law enforcement" to stop lashing out at our community. Some people worried about property destruction; we worried about the fact that our 9 year old wouldn't go to bed because she was afraid of tear gas blowing in the windows, and that our local "law enforcement" didn't know how to communicate in ways that didn't involve beating people.</p>
<p>And health problems, woof. I got wrecked by a large kidney stone — my wife had to drop me off at the ER at 2am. I stumbled in on my own, doubled over in pain, and she drove off because that's all she could do. My aunt (unsuccessfully) battled cancer. My mom (successfully) battled cancer and an aneurysm.</p>
<p>What. A. Mess.</p>
<h3>Speaking</h3>
<p>I tried speaking at online events, thinking it would help me feel normal again. It was really hard to connect with people. I'll speak virtually again for events that are especially meaningful to me, but I don't intend on spending a lot of time speaking until it's possible in person again. To be honest, I'm not even sure I'll put a lot of time into it <em>then</em>, either. This part of me might have broken.</p>
<h3>Writing</h3>
<p>It was hard to focus on writing, amongst other things. I found a bit of outlet sharing productivity tips on Twitter.</p>
<p>I wrote up some Really Good™️ remote work tips:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Remote working tips that no one seems to want to talk about:</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1237574431855087616?ref_src=twsrc%5Etfw">March 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>And "this one neat trick" for falling asleep when your mind doesn't want you to:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">An article came up for me in my news aggregator about "this amazing one trick that will help you fall asleep in minutes." The trick was basically (1) lay down, (2) calm down, and (3) fall asleep. Like "draw two circles, then draw the rest of the f'ing owl" but for falling asleep.</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1243566894180962305?ref_src=twsrc%5Etfw">March 27, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>And a summary of how I use <a href="https://todoist.com/">Todoist</a> to manage my day (when I'm successful at managing my day):</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">This tweet by <a href="https://twitter.com/ryanlanciaux?ref_src=twsrc%5Etfw">@ryanlanciaux</a> convinced me to finally write up my thoughts about how I use <a href="https://twitter.com/todoist?ref_src=twsrc%5Etfw">@todoist</a> to manage my day. <a href="https://t.co/GDhmhpOMir">https://t.co/GDhmhpOMir</a><br><br>So....a (very verbose) thread 👇</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1261323105479331841?ref_src=twsrc%5Etfw">May 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h2>Act 3: Let's talk about me some more</h2>
<p>Recently my therapist helped me recognize this thing that I do to cope when the world around me is screaming — I (figuratively) curl into a ball and hide under the covers and pretend the world doesn't exist outside my bed. <em>Figuratively</em> speaking. Though it at least partially originates from the night my cousin died when I was 9, and I <em>literally</em> curled into a ball and hid under the covers to escape the screaming in my house.</p>
<p>Anyway....I curled into a ball and hid under the covers for a lot of 2020. I focused on me, and the things I could directly control.</p>
<h3>I made some art</h3>
<p>I'd wanted to explore watercolors for a long time, and now I had time to do it:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I played with watercolors some more, and it made me feel like I was making a physical <a href="https://twitter.com/css_doodle?ref_src=twsrc%5Etfw">@css_doodle</a>??? <a href="https://t.co/weu3IiglBl">pic.twitter.com/weu3IiglBl</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1252673332585148418?ref_src=twsrc%5Etfw">April 21, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">More watercolors! <br><br>This is my view for much of our hiking of the <a href="https://twitter.com/IceAgeTrailOrg?ref_src=twsrc%5Etfw">@IceAgeTrailOrg</a> since covid started. <a href="https://t.co/FerAEUdGIK">pic.twitter.com/FerAEUdGIK</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1283162159946571778?ref_src=twsrc%5Etfw">July 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Kinda kitschy but happy fall I guess? <br><br>I'm still in love with watercolors (but fell off the wagon for the summer). <a href="https://t.co/3wW1UzfRRX">pic.twitter.com/3wW1UzfRRX</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1311878229335199745?ref_src=twsrc%5Etfw">October 2, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I wish I could say this remained a regular part of my life, but it hasn't. I pick up the brushes and pens every now and then, but not more than once a month.</p>
<h3>We got another cat</h3>
<p>We named him Todd because we thought it'd be funny if he shared a name with that guy everyone knew in high school. The name turned out to be appropriate. Also he has white armpits.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Things yelled at our cat Todd today:<br><br>"Stop stealing my beef jerky Todd!"<br>"Get away from my muffin, Todd!"<br>"Eat your own food, Todd!"<br>"Todd, why are you sleeping on that bag of trash?"<br>"Give me back my BLT Todd!"<br><br>It's like we have our very own college roommate. (But cuter) <a href="https://t.co/CvR04wt9te">pic.twitter.com/CvR04wt9te</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1286831697175154692?ref_src=twsrc%5Etfw">July 25, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h3>I wrote a bit</h3>
<p>I wasn't terribly prolific with my writing, but two of my all-time favorite articles came out of this period.</p>
<ul>
<li>I got to brag about all my amazing coworkers in <a href="https://artsy.github.io/blog/2020/08/11/improve-pull-requests-by-including-valuable-context/">an article about including context in PRs</a>. Communication in PRs is a topic I've been really interested in for the last year or so. I'm currently working on a followup to this article.</li>
<li>I did some reflecting on <a href="https://www.stevenhicks.me/blog/2020/10/i-am-slow-and-thats-okay/">why I'm a slow developer</a>. This article did really well on <a href="https://dev.to/pepopowitz/i-m-slow-and-that-s-okay-239m">dev.to</a>, and I made their "top 7" articles that week. They gave me a sticker for it at the end of the year.</li>
</ul>
<h3>I beat the hell out of my body</h3>
<p>The July trail marathon I'd signed up for in January was cancelled in April. I wasn't sure if I should keep training, or stick a fork in it and try again in 2021. I decided to keep training, figuring I had the time anyway.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Earlier this week, my big race of the year was cancelled. I'm going to keep training anyway, because runs like this are everything to me right now. <a href="https://t.co/HJtVXO8r6T">pic.twitter.com/HJtVXO8r6T</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1254425863577391104?ref_src=twsrc%5Etfw">April 26, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>This training became my everything. It was the most consistent I'd ever been, at <strong>anything</strong>. It was a lot of time to myself. I felt like there was so little I could control, but for 20-30 miles a week I knew I would go to the woods, run until my body screamed, and drink a protein shake and/or beer afterward.</p>
<p>I eventually ran a marathon on my own, in scorching heat. I was disappointed by my performance in the moment, but the experience of training and then "racing" was the absolute highlight of 2020 for me.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Woof. This was really hard. I planned poorly. Not enough water stops, way more elevation than estimated, I completely forgot that there was a three mile section through a sunny meadow (it scorched me. Twice.), and I fought cramps for the last 7 miles.<br><br>B4 & after: <a href="https://t.co/PhghgybnUw">https://t.co/PhghgybnUw</a> <a href="https://t.co/vbBMazDlnj">pic.twitter.com/vbBMazDlnj</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1286843586017931264?ref_src=twsrc%5Etfw">July 25, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I exercised a lot in 2020, including a 62 mile ride through hilly southwest Wisconsin in October:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Epic skip day today. 100km on the bike in southwest Wisconsin. 4000ft of climbing, too many cows, ladybugs, and woolly bear caterpillars to count. <br><br>(And full on leg cramps at mile 59 but we don't have to talk about that) <a href="https://t.co/d1TKuG5bS4">pic.twitter.com/d1TKuG5bS4</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1313597916922216455?ref_src=twsrc%5Etfw">October 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>By the end of the year I'd racked up almost 3000 miles of running and biking.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Many things about 2020 sucked, but one way it didn't: I ran almost 900 miles and biked more than 2000 miles this year. More than 200 miles further than I'd ever run in a year, and almost 800 further then I'd ever biked in a year. <a href="https://t.co/pnISuxaRDU">pic.twitter.com/pnISuxaRDU</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1344707633291403265?ref_src=twsrc%5Etfw">December 31, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>As my wife says, and as has become a mantra for me during the most intense moments of training:</p>
<blockquote>
<p>Choosing your own suffering is the ultimate privilege.</p>
</blockquote>
<h3>I tried to become a better listener</h3>
<p>There was a point where I wanted to quit seeing my therapist this year. She convinced me to start treating it more like a practice and I stuck it out.</p>
<p>My biggest breakthrough this year was finally grasping what it means to <em>listen</em>. I left one session feeling extremely grateful that I had this person that I could just <em>talk</em> to, and they wouldn't judge anything I said, and there were no old patterns we'd fall into that left at least one of us not feeling heard. Almost instantly I felt sad because I realized that I did not provide that for my wife or kids. I read two books about listening before my next session.</p>
<p>My most significant takeaway came from the book <a href="https://www.amazon.com/Hear-You-Surprisingly-Extraordinary-Relationships/dp/0999104004/ref=sr_1_1">I Hear You</a>, which among other ideas stressed the importance of validating someone's feelings. The idea that <strong>you don't have to agree with someone to validate their feelings</strong> seems obvious now, but it was a significant conflict for me until I read that. I still struggle a bit with this, but I've gotten much better at it.</p>
<h2>Act 4: I can't control the wind or the waves but I can steer the ship</h2>
<p>Late in 2020 I started to emerge from my bubble. My first order of business:</p>
<h3>I tried to find a community</h3>
<p>Without in-person community events, I wanted to find a community where I could meaningfully contribute. I joined <a href="https://bloggingfordevs.com/">Blogging for Devs</a>, a community built by <a href="https://twitter.com/monicalent">Monica Lent</a> for developers looking to expand the reach of their blog. It's a great community but it didn't work for me. I felt like I was not consistent or experienced enough to contribute to the group. It also helped me realize that I was looking for a group of people that I felt closer to from the start.</p>
<p>My good friend <a href="https://twitter.com/amandadaering">Amanda</a> kicked off a weekly call with engineering managers and leaders from the Milwaukee area. <strong>This</strong> was what I was looking for. For a couple months I hung out once a week over lunch with 4 to 8 peers and we talked about challenges we were facing. 10/10 would do again. This is the most connected I've felt to people since in-person events were a thing.</p>
<p>I also hung out a number of times with my friend <a href="https://twitter.com/AnAccidentalDev">Michael</a> and the community he's built in the Cincinnati area, and I felt pretty connected to all of them too. By the end of the year, Michael and I decided we each wanted an accountabili-buddy for our community-based goals, so we started meeting every other week. This has been going really well.</p>
<h3>I remembered I was an active participant in Artsy's culture</h3>
<p>Late in 2020 I got one of those "do you have a few minutes?" messages that prompt you to hold your breath. They make you think you did something awful, you're being let go, or someone is about to tell you they're leaving.</p>
<p>In this case it was the latter. The person that most embodies the Artsy culture — my dear friend <a href="https://twitter.com/ashfurrow">Ash</a> — was leaving Artsy.</p>
<p>There's another pattern I've repeated throughout my career in which I start a new job, enjoy and take advantage of the culture, but kind of forget that I'm an active participant in it. It's not until a few significant departures that I suddenly remember that I'm not just there to enjoy the culture, I'm there to help feed it.</p>
<p>Ash's departure kicked me in the pants this time. I lost sleep the first night after he told me, wondering what was going to happen when he was gone. He <em>was</em> the culture.</p>
<p>The next night I lost more sleep...but this was different. I started thinking about all the gaps that Ash was leaving, and how many of them were gaps I wanted to fill.</p>
<p>Since I joined Artsy, I figured that when I left it would be for a job in developer relations. That seems like the logical progression given my favorite parts of my job are mentoring, writing, teaching, getting people unstuck, etc. That second night of restlessness it struck me that I don't have to <em>leave Artsy</em> to do this stuff. <strong>I can just do this stuff</strong>.</p>
<p>The next week I and a few others started <a href="https://artsy.github.io/blog/2021/01/06/introducing-artsy-engineering-radio/">Artsy Engineering Radio</a>. I decided to pick up Ash's writing office hours after he left. I started putting more time into the <a href="https://github.com/artsy/relay-workshop">Relay learning curriculum</a> that a few of us are putting together. I found the courage to tell my manager that I wanted to spend as much time doing this kind of work as possible.</p>
<p>In fact I probably asked permission 15 more times. I volleyed between feeling fearless to do whatever I want until someone complains, and feeling needy for approval. At this point I've got all the approval I need and I'm doing more of what I want to do instead of what I thought was expected of me.</p>
<p>It feels pretty good.</p>
<h2>2021</h2>
<p>In 2021 I'm rethinking how I approach goals. I'm not planning for specific outcomes — I'm planning for habits and practices. Like....</p>
<ol>
<li>Writing more frequently. How frequently? More than before.</li>
<li><a href="https://www.stevenhicks.me/blog/2021/01/lowering-the-bar/">Lowering my personal bar for creating content</a>. I'm starting by allowing myself to publish incomplete thoughts instead of fully composed articles.</li>
<li>Running and cycling regularly. I doubt I'll match my mileage from 2020 given I won't be doing a marathon, but that's fine because I just want to do it regularly.</li>
<li>Eating better. Yeah...for real. I avoided alcohol and sweets for January 2021 and it was easier than I thought it would be. I'm going to limit my intake of garbage by giving myself a garbage-food-budget each week.</li>
</ol>
<hr>
<p>Phew. We did it! It only took me 3000 words and somehow you read every last self-indulgent one of them!</p>
<p>Wishing you all the best in 2021, friends.</p>
My nicest self-evaluation ever2021-01-26T12:15:34.903+00:00https://stevenhicks.me/blog/2021/01/my-nicest-self-evaluation-ever/<p>It's self-evaluation time at Artsy. I've never done this before, and it's kind of scary, but after realizing how remarkably nice mine was I thought it'd be worth sharing. I added a few notes for context <em>(👋 they look like this)</em>.</p>
<blockquote>
<p><strong>How did you perform on your business and personal development goals last quarter?</strong></p>
<ul>
<li>I've made significant improvement to my skills working in public with other Artsy engineers. I've pushed up a lot of in-progress work to get eyes on it sooner, and I've worked to add a lot of context to my PRs. This openness has helped me identify how to slice down work collaboratively instead of putting it all on myself to figure out.</li>
<li>I've dedicated significantly more time to upkeep and cross-team work. I'm the primary maintainer of diffusion and convection dependencies <em>(👋 two projects our team works with)</em>. I've taken over as point person for integrity <em>(👋 an end-to-end testing project)</em>. I facilitated an engineering-wide retro and a team retro for Purchase <em>(👋 a team I'm not on)</em>.</li>
<li>I've become more active and more consistent with making external impact. I was a main contributor to the launch of Artsy Engineering Radio. I took over writing office hours from [redacted], and [redacted] and I have been working regularly on blog posts together. I took primary ownership of the Relay Peer Learning group <em>(👋 we're building materials for learning Relay)</em> and have been regularly making progress building a curriculum.</li>
</ul>
</blockquote>
<blockquote>
<p><strong>What business and personal development goals would you like to work on next quarter?</strong></p>
<ul>
<li>Grow Artsy Engineering Radio. Make it better from a listener's perspective by improving audio & refining the content. Develop an audience. Improve the website experience by giving it & its episodes a dedicated home, embedding audio on episode pages, and maybe including transcripts.</li>
<li>Continue growth on cross-team work. Devote time to integrity <em>(👋 an end-to-end testing project)</em> to eliminate recurring flaky tests. Get at least one module of the Relay Peer Learning curriculum <em>(👋 we're building materials for learning Relay)</em> complete. Solicit more help with developing the curriculum.</li>
<li>Foster more frequent contributions to the Artsy blog.</li>
</ul>
</blockquote>
<p>Maybe your self-evaluations always look like this...mine do not. One thing I sincerely appreciate about Artsy is that I can be a very harsh self-critic in my reviews — I have never felt like I need to focus on the good things to avoid being punished in my career advancement.</p>
<p>And ooooooooo boy am I good at being a self-critic. Contrast my glowing self-eval above with some of these highlights from past reviews:</p>
<blockquote>
<ul>
<li>Contributing to Artsy's ecosystem beyond my team was a mixed bag. I did contribute to the large typography update. My interest in integrity <em>(👋 an end-to-end testing project)</em> and the figma palette plugin <em>(👋 a stalled project intended to make it easier to use our design system)</em> dwindled, though, and I did not partake in the first Future Friday <em>(👋 a fortnightly thing we do for upkeep)</em> due to feeling like I needed to focus on direct product work.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Find an approach to cross-organization impact that works for me.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Relax my need to make a point when I'm feeling my autonomy encroached, assuming it's not threatening my happiness.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Communicate opinions effectively & convincingly. I find myself often feeling unheard when sharing my opinions. I don't believe that minds can be changed very easily, but I don't try very hard to change them.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Communicate past experiences in a way that's useful to the team. I find myself with pertinent experience but unable to communicate the value of it to my team.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Whether it's code that could be rewritten or build processes that could be improved, I have a high tolerance for inconvenience. I could make more impact to the team if I were to smooth out the rough edges when I run into them, instead of accepting them as they are.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>I tend to get tunnel-vision when I'm working on a project, and defer work that isn't directly related to my project with the hope that I'll pick it up in between projects. I don't often pick this work up in between projects either, because I get eager to get back to project work. This same effect prevents me from blogging more, which I think is important, I want to do, but I still don't find time for.</li>
</ul>
</blockquote>
<p>All this to say...I'm not really sure what's up with this quarter's self-evaluation. It feels different.</p>
<p>I hope you also get to say nice things about yourself. It feels good.</p>
Notes are just short articles2021-01-25T09:37:32.601+00:00https://stevenhicks.me/blog/2021/01/notes-are-just-short-articles/<p>After writing about <a href="/blog/2021/01/lowering-the-bar/">lowering my personal bar for new content</a>, I spent some time setting up a second "channel" of content on my site for notes.</p>
<p>The next day I read <a href="https://twitter.com/zachleat/status/1353075180415799300">this article from Zach Leatherman about retiring a Notes section</a> and it gave me pause:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Retired the Notes section of my web site and rolled all of the content into the main blog.<br><br>If I’m being honest with myself, I think I implemented this because I felt like everything that went into my blog needed to be super polished. I’m outgrowing this feeling 😅 <a href="https://t.co/qSfBmJJpJZ">https://t.co/qSfBmJJpJZ</a></p>— Zach Leatherman (@zachleat) <a href="https://twitter.com/zachleat/status/1353075180415799300?ref_src=twsrc%5Etfw">January 23, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>This morning, my coworker <a href="https://twitter.com/pvinis">Pavlos</a> shared <a href="https://pvin.is/post/my-device-naming-scheme">an article on naming schemes</a>, and while I was on his site I noticed he'd shared <a href="https://pvin.is/post/ok-ok-ill-start">what inspired him to start writing more regularly</a>. What inspired me the most was the brevity of this post, and others — some of his articles are longer, but many are also very short. Almost like what I was going to call the "Notes" section of my site.</p>
<p>Sooooo I've ripped out the incomplete Notes section. Everything I write is just going to be an article, and there's no reason they have to be long. I wanted to put classification around how long something would take to read. I still think there is a distinction between short and long content — I just don't think it warrants separate channels.</p>
Lowering the bar for content creation2021-01-21T12:00:00+00:00https://stevenhicks.me/blog/2021/01/lowering-the-bar/<p>Today I'm thinking about lowering the bar for content creation by embracing smallness. Often when I want to create something it becomes <em>huge</em> in my mind, and I don't deliver it because I lose interest before achieving the grandiosity.</p>
<p>Maybe I can break ideas up into smaller pieces. Whereas my instinct is to write an article with all 7 tips for ____, maybe I can release 7 articles 1 at a time. This would tighten the feedback loop of writing & getting public feedback. It might also remind people that I'm actively writing, rather than thinking I've dropped off the face of the earth again.</p>
<p>This parallels a thing in software product management — slice stories small so that you can release & iterate faster.</p>
<p>Maybe this starts with me actually releasing this writing warmup as a "note" on my website. "Notes" will be lighter-weight, shorter reads.</p>
Tips For Working With Legacy Code, Courtesy Of My New Kitchen Faucet2021-01-07T12:00:00+00:00https://stevenhicks.me/blog/2021/01/tips-for-working-with-legacy-code/<p>This holiday break I replaced our kitchen faucet. The neck was loose and it wiggled, and there was a little bit of water starting to pool at the base. It wasn't leaking yet but it seemed like it might start leaking soon. There's probably a gasket or something I could have replaced but the faucet wasn't great and I had the time off of work. I figured now would be a good time to replace it.</p>
<p>We own an old<sup><a href="#footnote-1">(1)</a></sup> home. The main drawback of owning an old home is that every project takes five times longer than you expect it to. Anything that's hidden behind walls is made of old materials and it was introduced when building codes were far less safe. As you start opening things up during a project you find solutions from long ago, many of which are terrifying to work with. Like this electrical box containing 10 old wires with crumbling insulation that killed another of my holiday break projects:</p>
<p><img src="../nightmare-electrical-box.jpg" alt="An absolute mess of an electrical box containing 10 wires with crumbling insulation"></p>
<p>My kitchen faucet project had all the makings of a classic old-home project. The instructions for installing the new faucet were very well-written and easy to follow! But my kitchen is old. Who would win — the new faucet or the old home?</p>
<p>The old home won. As things went wrong, I noticed parallels between my faucet installation project and working with legacy code. I've distilled the experience into a handful of tips.</p>
<h2>Tip 1: Reframe "legacy code"</h2>
<p>There doesn't seem to be a canonical definition of "legacy code", but it seems software engineers use the term to mean "code that I inherited that I must support." This definition isn't itself negative, but it has very negative connotations for most engineers. Since the code is inherited, it probably doesn't follow the patterns or style the new owner would like to see. It probably doesn't have enough test coverage to thoroughly describe its intentions or prevent regressions. There's likely technical debt that has been accrued over its lifetime. All of these factors make it difficult to change. But we <em>need</em> to change it, because it's still in production, and we still need to support it.</p>
<p>Much of what I've described above is purely factual, but our cognitive biases and past experiences with legacy code apply a layer of emotional distortion. <a href="https://en.wikipedia.org/wiki/Fundamental_attribution_error">The fundamental attribution error</a> convinces us that the existing code wasn't well-factored or well-conceived by the original author. The <a href="https://en.wikipedia.org/wiki/Hindsight_bias">hindsight bias</a> convinces us that it could have been written much more simply and clearly. We remember the last time we had to support legacy code and how we broke production while trying to make a single small change. We start thinking it would be much easier to rewrite this code from scratch than to support it.</p>
<p>As I began my faucet project, I noticed similar feelings. The last time I'd tried to do a simple project in this house I found that mess of wiring, closed the box up, and called an electrician. How could the previous owners have been so careless with electricity? They must have had a death wish, I thought.</p>
<p>I started thinking about how if this were a software project, I'd want to rewrite the entire kitchen. Correction — I'd rewrite the entire <em>house</em>. The wiring is confusing and nasty all over, not just the kitchen. New construction would be <strong>so much easier</strong> to work with.</p>
<p>But I know "the big rewrite" is a trap in software. The hindsight bias and overuse of the phrase "technical debt" discredit the learning and the journey that went into creating legacy code. They overlook all of the features that are working well. If "the big software rewrite" were like building a new house from scratch, you'd move into a house with a beautiful kitchen and overall layout...but cardboard boxes for furniture, bathrooms with buckets for toilets, and nowhere to park your car.</p>
<p>This thought gave me a lot of appreciation for my old home. I don't want to do a big rewrite on it. The neighborhood is amazing. The house is the perfect size for us. We just finished our basement. Sure, the electrical is terrifying in most of the house and the main bathroom is peach, but just like existing software, it's <strong>a legacy</strong>. There's too much good stuff here to throw it all away and start over. Incremental improvement is the way to go — just like with a legacy software project.</p>
<h3>Tip 1A: Dealing with integration problems <strong>is the job</strong></h3>
<p>In <a href="https://www.youtube.com/watch?v=3XscuivvUzI">a talk that I love to give about getting unstuck</a>, I encourage software developers to reframe our understanding of getting stuck on difficult problems. Getting stuck isn't something that prevents you from doing your job as a software engineer; it <em><strong>is</strong></em> the job of a software engineer. We get stuck and have the persistence to get unstuck. That's problem solving. We're good at it, and that's why we get paid to do this job.</p>
<p>This perspective ran through my mind as I thought about the things that typically go wrong with a home improvement project. I've always considered problems with old plumbing or electrical to be a nuisance. Steps that I shouldn't need to take. A distraction from the <em>actual</em> home improvement project. But I was wrong — integration with the existing plumbing/electrical/construction <em><strong>is</strong></em> the home improvement project.</p>
<p>It's similar to working with legacy code. When you introduce new code, you'll always face the challenge of integrating it with the existing system. Somehow you need to make a seam for your changes. Once your changes are introduced you need to verify they don't negatively affect the existing system. These aren't merely tasks that <em>impede you</em> from supporting legacy code...these tasks <em>are</em> the legacy code support.</p>
<h2>Tip 2: Identify incremental improvement opportunities by de-risking</h2>
<p>So now we've all gained appreciation for the existing system, and we're in agreement that the best way to make changes is through incremental improvement. Where do we start? How do we know <em>which parts</em> of the system to improve first?</p>
<p>Often the system will present the answer to you. Is there a subsystem that's flaky and needs to be reset often? A section of code that breaks every time you try to make a small change? Is there a service that was written by a departed teammate in a language that no current engineers feel confident supporting? Start there.</p>
<p>This is de-risking. You can't predict exactly when a line of code will fail, but you <em>can</em> predict which services or subsystems are likely to fail sooner rather than later. Identify them and improve them before they get a chance to fail.</p>
<p>With my kitchen sink, I'd been noticing water pooling at the base of the neck for a couple weeks. It was nothing catastrophic, but it was an indication of a weakness. It may not be failing now, but it would likely fail soon.</p>
<h2>Tip 3: <em>You</em> own the code</h2>
<p>The first integration problem I ran into with my kitchen faucet was that the window sill behind the faucet extends as a shelf, and it was in the way:</p>
<p><img src="../shelf-in-the-way.jpg" alt="A kitchen faucet unable to be installed, due to a shelf obstructing it."></p>
<p>When we picked out a new faucet, I never considered that the faucet currently installed was probably deliberately chosen because of its short height. This allowed them to install it below the shelf. We chose a taller replacement faucet though, and the shelf prevented us from installing it.</p>
<p>My initial instinct was to return the faucet and find a shorter one. My partner suggested I cut a notch into the shelf. I resisted and thought "but this shelf is part of the house." She convinced me pretty quickly. It took us a while to pick out this faucet and we both really liked it, and this is <em>our</em> house, so we can do whatever we want with the shelf. We're planning on renovating the kitchen in the next few years, and it's very unlikely the shelf will survive that remodel. We could survive a few years with a notch in a shelf, especially if it means we enjoy the feature we use the most in our house.</p>
<p>So I cut a notch in the shelf with a reciprocating saw:</p>
<p><img src="../notch.jpg" alt="The kitchen faucet in place, with a notch cut into the shelf to make space."></p>
<p>As I painted the new edges, I thought about how this was classic Steve — amplifying the importance of an <em>existing</em> feature at the expense of a new feature. I do this when I'm working with code. If code is already there and it's working well, I have an intense aversion to modifying it. I avoid refactoring anything that I don't <em>need</em> to change. This is extremely noticeable when I'm working with teammates who refactor freely. I get feelings of anxiety and discomfort as they make more changes than I would, and I get visions of a late night fixing an outage caused by a change that didn't need to be made.</p>
<p>My aversion to refactoring sometimes leads to a feeling of <em>borrowing</em> code — like the codebase isn't mine, it's the previous owner's, and I'm temporarily passing through. But like the flimsy shelf above my faucet, it's helpful to remind myself that <strong>I own this code</strong>. If there is code that doesn't look right, <strong>I can change it</strong>, because it's mine, and I'm the one supporting it.</p>
<h2>Tip 4: Perfect is the enemy of done</h2>
<p>My aversion to unnecessary refactoring has one major benefit: it prevents me from blowing a project into something much larger. When modifying a legacy codebase you regularly face decisions about how far to pull a loose thread.</p>
<p>There were several points in my faucet project where I caught myself in this kind of decision point. From the start, I wasn't sure if I'd be content with replacing only the faucet. It'd be nice to have a new sink too...but it wasn't as critical as the faucet. The entire kitchen needs a remodel...but that would be <strong>way</strong> too much work for a holiday project. The connectors on the faucet supply lines didn't fit the valves coming out of the wall, and I could have replaced the valves with something more modern...but that would have meant doing real actual plumbing. Even the window sill/shelf could have become a bigger project if I'd decided to remove it entirely.</p>
<p>Somewhere in between refactoring nothing and refactoring everything is a sweet spot for navigating these types of decision points. Practice helps you find that sweet spot. <a href="https://en.wikipedia.org/wiki/Timeboxing">Time-boxing</a> or <a href="https://en.wikipedia.org/wiki/Pomodoro_Technique">the Pomodoro technique</a> can be helpful for preventing yourself from pulling a refactoring thread too far. Being transparent and detailed in standup about the changes you're making makes it hard for you to go days with a vague update of "I'm still refactoring." Working with your team lead or product owner to prioritize the exact bits that <em>need</em> to change will help you avoid nebulous tasks with no end in sight.</p>
<p>And sometimes it comes down to showing self-restraint and acceptance that something is "good enough". It's okay to ship code that you wouldn't show in an interview. It's okay to not refactor the next code down the call stack. If you need it, I grant you permission to not solve <em>all</em> the problems today.</p>
<p>Oh, and here's my new kitchen faucet:</p>
<p><img src="../finished-installation.jpg" alt="A shiny new kitchen faucet installed in an old kitchen."></p>
<hr>
<p><a name="footnote-1"></a>
<sup>1</sup> as a resident of the Milwaukee area working for a company based in New York, I've discovered that "old home" is a very relative term, depending on where you live. My home was built in the 1930s — around here, that's pretty old. It's certainly not pre-revolutionary...but it's old enough to make home improvement difficult.</p>
Generating Social Sharing Images In Eleventy2020-12-10T12:00:00+00:00https://stevenhicks.me/blog/2020/12/generating-social-sharing-images-in-eleventy/<p>Inspired by <a href="https://twitter.com/jlengstorf">Jason Lengstorf</a>, I added social sharing images to all my blog posts on this site. This means that when you share an article here to a place like Twitter, you'll get a nice big card like this:</p>
<p><img src="../twitter-example.png" alt="Example of sharing an article from this site on Twitter"></p>
<p>Sweet! I can't get enough of those 70s shag carpet vibes.</p>
<h2>Prior art</h2>
<p>Before I show you how <em>I</em> hooked this up to my eleventy site, consider <a href="https://dev.to/5t3ph/automated-social-sharing-images-with-puppeteer-11ty-and-netlify-22ln">this article by Stephanie Eckles about using puppeteer to generate social share images</a>. If you want to use HTML & CSS to create your social sharing images, that is probably what you're looking for!</p>
<p>The method I chose<sup><a href="#footnote-1">(1)</a></sup> uses <a href="https://cloudinary.com/">Cloudinary</a> to overlay the article title onto a common social sharing image.</p>
<p>Most of what I needed was covered by Jason in his articles on <a href="https://www.learnwithjason.dev/blog/add-text-overlay-cloudinary/">adding text overlays in Cloudinary</a>, <a href="https://www.learnwithjason.dev/blog/design-social-sharing-card/">designing a social sharing card</a>, and <a href="https://www.learnwithjason.dev/blog/auto-generate-social-image/">auto-generating social share images with <code>get-share-image</code></a>. Thanks, Jason!</p>
<p>Heads up that the most time-consuming part was tweaking the Cloudinary text overlays. Lots of fiddling with cryptic arguments. It's literally pushing pixels to get text in the right place.</p>
<h2>Emitting the image URLs in eleventy</h2>
<p>Here's my addition to this problem space: <a href="https://github.com/pepopowitz/stevenhicks.me/pull/14">a PR that shows everything I needed to do to hook up the images in eleventy</a>!</p>
<p>There's not a lot there, but let's walk through it.</p>
<h3>1. Add the <code>get-share-image</code> dependency</h3>
<p>You'll do this with <code>npm install @jlengstorf/get-share-image</code> or <code>yarn add @jlengstorf/get-share-image</code>. <a href="https://github.com/pepopowitz/stevenhicks.me/pull/14/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R20">I added it to my <code>devDependencies</code></a> because I care about separating dev dependencies from runtime dependencies. Maybe you don't care — I'm not going to arm-wrestle you over it.</p>
<h3>2. Add an eleventy computed data file</h3>
<p><a href="https://www.11ty.dev/docs/data-computed/">Eleventy's computed data files</a> inject computed properties into a page template for each page they apply to. Like maybe you want to compute a social sharing image URL that's based on the article title!</p>
<p>I <a href="https://github.com/pepopowitz/stevenhicks.me/pull/14/files#diff-e45e8998b62ae0dac0f40e46f3db483cf93f3b027a20b4649e8988a78785b371">added a file named <code>blog.11tydata.js</code> to the <code>blog/</code> folder</a>. I chose to put it in the <code>blog/</code> folder because I only wanted to generate social images for my blog articles; it seemed silly to me to generate a social image for my about page that said "About". I'm not sure if the file name needs to start with <code>blog</code>, but that's what the docs did in their example (<code>posts/posts.11tydata.js</code>), so I just went with it.</p>
<p>The contents of <code>blog/blog.11tydata.js</code>:</p>
<pre><code class="language-javascript">const getShareImage = require('@jlengstorf/get-share-image').default;
module.exports = {
eleventyComputed: {
shareImage: (data) => {
return getShareImage({
// settings for cloudinary overlays
});
},
},
};
</code></pre>
<ol>
<li>We pull in the <code>get-share-image</code> dependency.</li>
<li>We export an object with a property named <code>eleventyComputed</code>.</li>
<li>Each property of <code>eleventyComputed</code> is a computed property that becomes available in your page templates. In our case, we compute a property named <code>shareImage</code>. The value of it is the result of a call to <code>getShareImage</code> with a bunch of configuration for the Cloudinary overlay.</li>
</ol>
<p>This <code>shareImage</code> property gets computed for each page within <code>blog/</code>, based on its metadata (that's what the <code>data</code> argument passed into the function represents).</p>
<p>The only dynamic data here for my site <a href="https://github.com/pepopowitz/stevenhicks.me/pull/14/files#diff-e45e8998b62ae0dac0f40e46f3db483cf93f3b027a20b4649e8988a78785b371R20">was the <code>title</code> property that gets passed to <code>getShareImage</code></a>:</p>
<pre><code class="language-javascript">module.exports = {
eleventyComputed: {
shareImage: (data) => {
return getShareImage({
// ...
title: data.title,
// ...
});
},
},
};
</code></pre>
<h3>3. Emit the <code>shareImage</code> property in your template</h3>
<p>I have <a href="https://github.com/pepopowitz/stevenhicks.me/blob/5d95aaa4145975ba6abd36f5696442347d7ed7b0/_includes/layout.pug">one base page template for my site</a>. It's based on the <a href="https://pugjs.org/">pug language</a>.</p>
<p>I <a href="https://github.com/pepopowitz/stevenhicks.me/pull/14/files#diff-6bc31447ed9d02b94fbb1d63dd5659e7d2a6d6e7f8ab4c757650c16e189a7316">updated it to emit a <code>shareImage</code> in the appropriate meta tags if it exists</a>:</p>
<pre><code class="language-pug">
meta(property='og:image' content=`${shareImage || 'https://www.stevenhicks.me/static/img/avatar.jpg'}`)
meta(property='twitter:image' content=`${shareImage || 'https://www.stevenhicks.me/static/img/avatar.jpg'}`)
</code></pre>
<p>All blog articles will have that <code>shareImage</code> computed property, so they'll emit their generated images. Pages like Home and About won't have a <code>shareImage</code> computed because I put the <code>blog.11tydata.js</code> file in the <code>blog/</code> folder — so they'll get stuck with an image of my face. MY FACE!</p>
<h2>Wrap it up, Steve</h2>
<p><a href="https://github.com/pepopowitz/stevenhicks.me/pull/14/files">The PR</a> ends up being 39 lines added — and over half of that is configuration settings for the text overlay. JavaScript is neat!</p>
<p>You likely found this article because you've already got an <a href="https://www.11ty.dev/">eleventy</a> site, but if you don't, you should absolutely give it a look. It's a great option for building a blog or any other site where the data doesn't change frequently. I find it more intuitive than other popular options. This example especially demonstrates how well it's designed for generating dynamic content. Every time I come across a new problem I'm delighted to find there's a simple mechanism built into eleventy to solve it.</p>
<hr>
<p><a name="footnote-1"></a>
<sup>1</sup> Hahahahaha I act like I had agency in this decision but really I didn't see Stephanie's article until I had invested a lot of time in generating an image template based on Jason's articles. I'm as much a sucker for the sunk-cost fallacy as anyone, and here we are.</p>
Sandwiches, Froyo, and Delivering A Cohesive Message2020-11-14T12:00:00+00:00https://stevenhicks.me/blog/2020/11/sandwiches-froyo-and-delivering-a-cohesive-message/<p><img src="../froyo-sandwiches.jpg" alt="Sandwiches, Froyo, and Delivering A Cohesive Message"></p>
<p>I love sandwiches. I <strong>love</strong> sandwiches. Yes — I would marry them, if I could.</p>
<p>This week my friend <a href="https://twitter.com/jonallured">Jon</a> asked me what I ate for lunch one day. It was the right day to ask. I spent probably too much time describing to him my current favorite sandwich — the amazing <a href="https://thechocolatefactorywi.com/menu/">Phoenix sub from local chain The Chocolate Factory</a>. It's not a complicated sandwich but it's got a specific vibe and it does it well. Jon was really impressed 🙄 and asked me if I was a professional sandwich-eater. I did not dispute.</p>
<p>It got me thinking about other sandwiches because, well, yummmm. The "Velvet Elvis" - an absolute chef's kiss that a Milwaukee bar stopped selling years ago, but still makes an appearance in my kitchen — peanut butter, banana, and bacon, "grilled" in a buttery frying pan. If you're a fan of salty & sweet it will make you cry. And a sandwich that I've only heard about — <a href="https://www.parisibakery.com/parisi-bakery-deli-sandwiches/">The Dennis</a> — mentioned many times in our work Slack by my New York-based coworkers. It looks like a real monster.</p>
<p>Amazing sandwiches are amazing because they are cohesive. They push you to the edge of your comfort level but they show the restraint and respect to not push you over. They are adventurous...but <em>responsibly</em> adventurous.</p>
<p><strong>Unlike my kids at the frozen yogurt place.</strong></p>
<h2>Andes Candies, Boba Balls, Raspberries, and sour Gummi Worms</h2>
<p>My kids are currently 9 and 12. It's been a while since we've been to the local frozen yogurt joint (ugh, COVID), but when we've gone their combinations give me shivers. Oooo, they've got strawberry yogurt! And Reese's Pieces! And Andes Candies! Oooo, sour Gummi worms, brownie bites, raspberries, and marshmallows!</p>
<p>Don't get me wrong - each of things, on its own, is delicious:</p>
<ul>
<li>Reese's Pieces</li>
<li>Andes Candies</li>
<li>sour Gummi Worms</li>
<li>brownie bites</li>
<li>raspberries</li>
<li>marshmallows</li>
</ul>
<p>But together? An absolute nightmare. Barf city. I won't even finish the leftovers when my kids put together these nightmare concoctions.</p>
<h2>I'm so hungry right now but what does this have to do with delivering a clear message?</h2>
<p>When you're communicating a complex idea to someone, it's tempting to tell them everything you know. Every single detail you've learned is valuable to you. There are <em>so many cool things</em> you've learned about your topic, and you've put a lot of time into it. You just want to share <strong>all of it</strong> with your audience.</p>
<p>But is all of it necessary? Is some of it noise? How much of it will distract someone from the main idea you're teaching them?</p>
<p>Think of it like that trip to the frozen yogurt place. You're not my kids — you know better than to mix <em>all</em> the toppings you like in one dish. You'd rather craft something cohesive. Even though you <em>love</em> toasted coconut <em>and</em> Andes Candies <em>and</em> raspberries, you wouldn't dare put them all in the same dish.</p>
<p>Treat your communication and content the same way. Not every concept needs to be shared. Find the ones that fit together and create a cohesive narrative.</p>
<p>If you're drafting an email to your leader, they don't need to know <em>everything</em> that happened in the latest incident. Tell them the most important facts.</p>
<p>If you're writing a blog post, it doesn't have to be 5000 words long. Less is better — if you feel like you've got several stories to tell, tell them in separate blog posts.</p>
<p>If you're building a presentation, don't add slides just to fill time. Your listeners will learn a lot more from one cohesive story than three stories and 13 unrelated points.</p>
<p>If you're building a workshop or course, you don't have to teach every feature. Focus on the features that most align with your learning objectives. Give them resources to learn more when there could be side quests, but your workshop doesn't need to include the entire universe.</p>
<p>Cut out the Gummi Bears if you're going for a mint and white chocolate vibe. Skip the performance analysis if you're going for an intro to building an app. Be the Velvet Elvis. Be the Dennis. Be the Phoenix. Be cohesive. Cut out the noise that doesn't fit your narrative.</p>
I Am Slow And That's Okay2020-10-13T12:00:00+00:00https://stevenhicks.me/blog/2020/10/i-am-slow-and-thats-okay/<p><img src="../camper-in-pieces.jpg" alt="A super sweet Lego camper in pieces"></p>
<p>Yesterday I had a 1:1 with my coworker/friend <a href="https://twitter.com/deliciousnachos">Nicole</a>. I told her about my continuous fight with being a slow developer. Especially when working with developers who move quickly, I often feel shame about not "producing" quickly enough. Having dealt with this for a long time, I thought I'd understood it. But Nicole helped me understand it far more deeply.</p>
<p>I've long explained that I was slow because I explore problems thoroughly and I set a high bar for myself before considering my work ready to review. Nicole helped me realize that, sure, I carry those attributes...but they probably don't <em>cause</em> my perceived slowness. They might even be <em>side effects</em> of what causes me to move slowly.</p>
<p>She described feedback she'd received about her approach to design. She'd been asked to show her work more often, in an incomplete state, to show her progress. When she put that into practice she didn't receive the praise she was expecting — she received different negative feedback. Her team was expecting her to show a low-res version of the entire design each time she shared her work, but she was showing a high-res version of very small parts of the design. To her team, it looked like she was obsessing over a few details and losing sight of the overall picture.</p>
<p>Nicole explained that she was focused on those few details because they were the hard parts. They could make or break the entire experience. She hadn't lost sight of the overall picture — she had identified the <strong>biggest obstacles</strong> to the overall picture and she was <strong>de-risking</strong> them. Figuring out the hardest 20% of a design was critical to how the rest would fit together. Once that was done, the other 80% would fall into place. But because she started by working out the critical details, it appeared externally as if she wasn't making much progress.</p>
<p>At this point I interrupted. "YES NICOLE!". <strong>This</strong> is why I move slowly. I'm doing the slow/hard parts <em>first</em> instead of <em>last</em>. For the first 80% of time in a project or task it will look and feel like I am accomplishing nothing. <strong>But I am doing the hardest work, preparing to make the other 80% of the work take 20% of the time</strong>.</p>
<p>The approach you use to solve a problem is a learned adaptation. Something has taught us that this is the best approach, or at least the best approach <em>for us</em>. For Nicole and I both it's an adaptation to failed projects. She's been burned by designing too far ahead, without giving adequate consideration to technical details that could break the design. Similarly, I've been burned by putting off the hardest 20% of a project until the end. Finding out near the end of a project that all the work I've done <em>won't actually work</em> is frustrating and I don't enjoy it.</p>
<p>If I eat the frog<sup><a href="#footnote-1">(1)</a></sup> — if I tackle the most difficult challenges first — I'll find any blockers early on. I'll also set the work up to move quickly at the end. Proving the concepts up front enables me to easily assemble the components once I know they all work.</p>
<p>It's like I'm building a super sweet Lego camper. I could follow the steps in order from 1 to 527, but if I know steps 126, 390, and 409 are going to be extra tricky, it would behoove me to solve them first. Figuring out those three critical steps will take me a while, and there won't be much visible progress. But once they're solved the other 524 steps will snap easily and quickly into place<sup><a href="#footnote-2">(2)</a></sup>.</p>
<hr>
<p>How do you approach problems? Do you save the hard stuff for the end, or do you eat the frog and tackle the difficult parts early in a project? <a href="https://twitter.com/pepopowitz/status/1316374951868260353">Share your strategy with me on Twitter</a>.</p>
<p>And if you're interested in learning more strategies for solving complex problems, check out <a href="https://www.youtube.com/watch?v=3XscuivvUzI">this talk I've given called Getting Unstuck</a>.</p>
<hr>
<p><a name="footnote-1"></a></p>
<p><sup>1</sup> Abraham Lincoln once tweeted that Darkwing Duck said that Mark Twain once wrote "eat a live frog first thing in the morning and nothing worse will happen to you the rest of the day." I would link you to a reliable source for this quotation, but there doesn't seem to be one. In fact, the most trustworthy information I've found on this quote <a href="https://quoteinvestigator.com/2013/04/03/eat-frog/">finds no proof Mark Twain said it</a>. But whatever — today thanks to the book <a href="https://www.goodreads.com/book/show/95887.Eat_That_Frog_"><em>Eat That Frog!</em></a> we interpret this (possibly made-up) Mark Twain quote to mean "do the thing you most want to procrastinate."</p>
<p><a name="footnote-2"></a></p>
<p><sup>2</sup> I know this isn't a watertight analogy. Even <em>I</em> just follow the Lego instructions from step 1 to 527. But real life projects don't come with 527 sequential steps and beautiful illustrations so please just go with it.</p>
Saving Myself One Minute Per Day With Netlify Functions And A Custom Search Engine2020-03-04T12:00:00+00:00https://stevenhicks.me/blog/2020/03/saving-myself-one-minute-per-day-netlify-functions-custom-search-engine/<p>I visit the <a href="https://github.com/artsy/reaction">artsy/reaction</a> repository in GitHub about 15 times a day. It's the repo I most contribute to at Artsy, and I spend a lot of time working with PRs there.</p>
<p>You might think I've bookmarked it in my browser, but I haven't. I don't really use bookmarks very much - I've become one of those people who wants to type commands instead of looking for items in a UI list.</p>
<p>You also might think that I can easily find the URLs I frequently visit in my Chrome omnibar results. You're not totally wrong...the result I'm looking for when I type <code>reaction</code> is usually in the dropdown. But there's often a lot of noise in those results, and it takes me a few seconds to find the one I'm looking for.</p>
<p>Here, take a look:</p>
<img class="square-corners" src="../search-for-reaction-before.gif" alt="Me entering `reaction` into Chrome and having to scour half a dozen results to find the URL I want" />
<p>There's only about half a dozen results in the omnibox, but that's enough to confuse me. Some are general Google search terms - those are especially noisy. The others are all very similar and it's hard for me to pick out which one I'm looking for. I <em>know</em> this is not that big of a deal but it just bothered me.</p>
<p>It takes me a few seconds each time! 15 times a day! Do I look like I live in a cave? Each one of those few seconds is a tiny paper-cut. What I'd <em>love</em> to be able to do is open my browser, type some variation of <code>reaction</code>, and magically end up at <a href="https://github.com/artsy/reaction">github.com/artsy/reaction</a>.</p>
<p>You could solve this problem many ways, but I chose JavaScript. I <strong>always</strong> choose JavaScript. JavaScript and I are like super best friends. In this case, I added a custom search engine to my browser...to send a query to a function I have running on Netlify...to interpret the text and redirect me somewhere. I already have several alternatives installed that could have solved the problem (including <a href="https://www.alfredapp.com/">Alfred</a>) - but my approach fit best with my habits. If you're interested in just seeing the code, it lives at <a href="https://github.com/pepopowitz/goto">github.com/pepopowitz/goto</a>.</p>
<h2>Adding A Custom Search Engine</h2>
<p>You might not know this feature is there, but you can add custom search engines to your browser. Custom search engines enable you to search a specific URL by typing a specific word or phrase into your search bar, followed by the query.</p>
<p>In fact, your browser has probably added a bunch for you already. I use Chrome and this is what my custom search engines look like in my settings:</p>
<img class="square-corners" src="../custom-search-engines.jpg" alt="Custom Search Engines in Chrome" />
<p>I didn't add any of those. I suspect Chrome added them for me whenever I visited a page that took a search argument in the URL.</p>
<p>At any rate, we can add one of these ourselves. It's not a complicated entry form. You specify a name, a shortcut (which you'll enter into your search bar when you want to use the custom search engine), and a URL pattern that will include your query. Here's the search engine I entered for my problem:</p>
<img class="square-corners" src="../goto-search-engine.jpg" alt="GOTO search engine in Chrome" />
<p>I chose the shortcut <code>goto</code>. Now when I type <code>goto</code> into my search bar, Chrome will forward the text that follows to my custom URL.</p>
<h2>Building A URL Expander To Respond To The Custom Search Engine Requests</h2>
<p>Now that I've got an entry point into my custom function, I needed to process a query. Effectively, I want a URL expander that that takes a code and redirects you to a URL.</p>
<p>My first attempt at building a URL expander was to write an Express app that I could run locally. The code was not difficult to write, and it worked! Keeping it running locally was a challenge, though. I figured there was a way to automate startup, but it seemed complicated, and I wasn't really excited to figure it out. In the meantime, every time I rebooted I opened a terminal and started the server. This wasn't a burden....at first. But then I had a week where my MacBook couldn't maintain a wifi connection, and I had to reboot at least once a day. Restarting my Express app became a burden and I started thinking about ways to keep my link expander always running.</p>
<p>From my personal website, I know that I can create a <a href="https://github.com/pepopowitz/stevenhicks.me/blob/master/_redirects"><code>_redirects.md</code> file</a> in any Netlify-hosted project and <a href="https://docs.netlify.com/routing/redirects/">Netlify will handle redirects for me</a>. This is how I handle "link shortening" for slides when I give talks. But it's a static map of URLs, and I wanted something a little smarter.</p>
<p>I remembered reading <a href="https://medium.com/netlify/creating-your-own-url-shortener-with-netlify-forms-and-functions-a4286f55ea6c">Phil Hawksworth's article on building a link shortener</a> so I looked at that. Unfortunately, this wasn't quite a match. In Phil's example, you shorten a link by submitting it through a form, and a corresponding redirect gets committed to a file. The redirects themselves are still statically defined.</p>
<p>I wanted my redirects to be evaluated <em>dynamically</em>. There are a lot of URLs I visit within github.com, and I didn't want to have to add a new rule every time I started working on a different project. If I suddenly start working on the <a href="https://github.com/artsy/eigen">Artsy iOS app</a>, I don't want to have to update my redirects. I want my link expander to understand that I'm requesting a different Artsy repository and just redirect me there.</p>
<p>It struck me that this would be a good opportunity to play with Netlify functions. I'm a massive Netlify fan, but haven't had a reason to take advantage of functions yet. I wanted a function that would take a query and redirect recognized queries to a mapped URL and unrecognized queries to Google. The logic of the function seemed straightforward.</p>
<h2>Netlify Functions</h2>
<p><a href="https://docs.netlify.com/functions/overview">Netlify functions</a> are an abstraction around AWS Lambda functions. They're self-contained serverless functions that you include in the source of your Netlify-hosted site. Netlify takes care of a whole bunch of AWS infrastructural nonsense that you don't want to deal with. When Netlify deploys your site, it also deploys your Netlify functions to AWS, and makes them accessible at a path on your site. When you want to run a function, you request the endpoint that Netlify deployed for you.</p>
<p><a href="https://docs.netlify.com/functions/build-with-javascript/">The Netlify docs</a> do a great job of describing how to write a Netlify function in JavaScript. Kent C. Dodds also wrote <a href="https://kentcdodds.com/blog/super-simple-start-to-serverless">a really great walkthrough of setting up your first Netlify function</a>.</p>
<h2>Implementing A URL Expander In A Netlify Function</h2>
<p>A crude implementation of a URL expander might look like this:</p>
<pre><code class="language-javascript">exports.handler = function(event, _context, callback) {
const query = event.queryStringParameters.query;
let redirectUrl = 'https://google.com?q=' + query;
if (query.toUpperCase() === 'REACTION') {
redirectUrl = 'https://github.com/artsy/reaction';
}
callback(null, {
statusCode: 302,
headers: {
Location: redirectUrl,
'Cache-Control': 'no-cache',
},
body: JSON.stringify({}),
});
};
</code></pre>
<p>The interface to this function is <a href="https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html">described in the AWS docs</a>. Here's a line-by-line recap of this implementation.</p>
<pre><code class="language-javascript">exports.handler = function(event, _context, callback) {
</code></pre>
<p>In the line above, we're exporting a function named <code>handler</code>. It must be named <code>handler</code>, or AWS won't know how to run it.</p>
<p>There are three arguments into our handler: <code>event</code>, <code>context</code>, and <code>callback</code>. <code>event</code> gives us access to information about the request. <code>context</code> gives us access to the execution environment in AWS. <code>callback</code> is how we send a response from our function.</p>
<pre><code class="language-javascript">const query = event.queryStringParameters.query;
</code></pre>
<p>In this line, we access the search term from the query string parameters on the <code>event</code> argument.</p>
<pre><code class="language-javascript">let redirectUrl = 'https://google.com?q=' + query;
if (query.toUpperCase() === 'REACTION') {
redirectUrl = 'https://github.com/artsy/reaction';
}
</code></pre>
<p>This is the first bit of code that is unique to our implementation. We'll default our <code>redirectUrl</code> to a google search for the term. If it's something we recognize, we'll override the <code>redirectUrl</code>.</p>
<pre><code class="language-javascript">callback(null, {
statusCode: 302,
headers: {
Location: redirectUrl,
'Cache-Control': 'no-cache',
},
body: JSON.stringify({}),
});
</code></pre>
<p>We're back to some Lambda paperwork. We call the <code>callback</code> function that is passed into our function. The first argument is for an error, which we don't have, so we're passing <code>null</code>.</p>
<p>The second argument is an object describing our response. In our case that includes a <code>statusCode</code> of <code>302</code>, which means the page has moved temporarily. I'm scared of using the <code>301</code> status, for permanent redirects, in a tool that I'm actively developing. Maybe I'm being silly.</p>
<p>We're specifying a couple headers, including the <code>Location</code> to redirect to.</p>
<p>Finally, there is an empty <code>body</code>. Even though the contents are empty, this property is required - otherwise you'll get an <code>Incorrect function response body</code> error.</p>
<h2>A Better URL Expander In A Netlify Function</h2>
<p>The previous example shows the gist of how we'll translate search terms into URLs, but it isn't very future-proof. Every time we need to translate a new term, we'd need to update the function. This sounds super not fun.</p>
<p>The implementation I ended up with lives at <a href="https://github.com/pepopowitz/goto/blob/master/functions/goto/goto.js">https://github.com/pepopowitz/goto</a>. The code is interesting (and recursive so I'm expecting a stack overflow any minute 😅), but the most exciting thing to me is the way I'm able to manage rules with a single JSON object:</p>
<pre><code class="language-javascript">exports.urlMap = {
staging: 'https://staging.artsy.net',
prod: 'https://artsy.net',
// ...
gh: {
'': 'https://github.com',
'*': 'https://github.com/artsy/{0}',
'**': 'https://github.com/artsy/{0}/{1}',
prs: 'https://github.com/notifications/beta',
me: {
'': 'https://github.com/pepopowitz',
'*': `https://github.com/pepopowitz/{0}`,
'**': 'https://github.com/pepopowitz/{0}/{1}',
},
// ...
</code></pre>
<p>Explaining all of these rules could be its own blog post, but here's a high level overview:</p>
<ul>
<li>The keys of any object indicate a search term. In other words, <code>production</code> will take me to the Artsy production site.</li>
<li>Nested objects indicate sequential search terms. Example: <code>gh prs</code> goes to the new GitHub notifications view (which is 🔥).</li>
<li>Rules with <code>*</code> in them are wildcards. Words that follow the search term are used as arguments to format the URL. For example, <code>gh reaction pulls</code> will go to <code>https://github.com/artsy/reaction/pulls</code>.</li>
</ul>
<p>The best part about these rules is that I don't need to change them when I need to start working on a new Artsy project. The second best part is that it's a single file that contains all the rules. The third best part is that since Netlify deploys this function, I can change my rules by pulling up the file on GitHub, editing it in the browser, and committing the changes. It's magical.</p>
<h2>The Result</h2>
<p>This is what it looks like when I want to visit the Artsy <code>reaction</code> repository today:</p>
<img class="square-corners" src="../search-for-reaction-after.gif" alt="Me entering `gh reaction` into Chrome and going directly to the repo I'm looking for" />
<p>That's 15 fewer paper-cuts per day! Now I can spend them somewhere else.</p>
<h2>What's Next?</h2>
<p><code>goto</code> is pretty stable for my needs right now. I'm mostly just adding static rules at this point. I might add a secret search term that lists out all known search terms, for when I forget about older rules.</p>
<p>But what's next could be something more. It might be completely unrelated to expanding URLs. This project illustrated to me that I've found a powerful combination. Point a custom search engine at a Netlify function that can interpret commands, and you have all the power of a CLI right in your browser.</p>
Working With Variables In Cypress Tests2020-02-18T12:00:00+00:00https://stevenhicks.me/blog/2020/02/working-with-variables-in-cypress-tests/<p><a href="https://cypress.io">Cypress</a> is a great tool for writing automated tests against your web app. It can make end-to-end tests a lot less scary for a JavaScript developer like me. One of the most impressive things to me is how excited developers are to write Cypress tests. It says a lot about a tool when people are practically falling over each other to introduce it into their codebase.</p>
<p>Cypress has <a href="https://docs.cypress.io/guides/references/trade-offs.html">several self-acknowledged limitations</a>. When I first learned of Cypress, I read that <a href="https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html">working with variables was significantly more difficult than most of the JavaScript I've written</a>. I initially dismissed this as an edge case that I didn't need to worry about. And I was correct for my first handful of tests!</p>
<p>And then I wasn't correct anymore. I wanted to write a test that did three things:</p>
<ol>
<li>View a list of articles</li>
<li>Click on the first article</li>
<li>Request a separate API endpoint for that article to get more information</li>
</ol>
<p>I'll refer to this test as <code>myExtractedURLParamTest</code> in the rest of this article.</p>
<p>I couldn't hard-code the API URL, because the ID might be different every time the test ran. I knew I had access to the URL in my Cypress test, so I thought I'd grab that when I viewed the article page, extract the ID, and then make a request to the API based on the extracted ID. This is when I learned that working with variables in a Cypress test is not intuitive.</p>
<h2>Why is it hard to work with variables in Cypress?</h2>
<p>The Cypress docs include <a href="https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html">a lengthy writeup on how to work with variables</a>, including this short paragraph on why traditional <code>const</code>/<code>let</code>/<code>var</code> assignments don't work as expected:</p>
<blockquote>
<p>You cannot assign or work with the return values of any Cypress command. <strong>Commands are enqueued and run asynchronously.</strong></p>
</blockquote>
<p>The emphasis is my own. When you write a Cypress test, it feels like each command is a statement that's executing immediately, but that's not the case. <a href="https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Asynchronous">Each command you write in a Cypress test is added to a queue of commands</a>, each of which will be executed in order <em>asynchronously</em> when the test runs. Your test is effectively a script for Cypress to play back at a later date. This asynchronous nature of commands enables one of Cypress's greatest features: automatic waiting for each command.</p>
<p>But it also means you can't return values from one command and use that value in the next command. <a href="https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html">That lengthy Cypress help doc</a> comes in handy to understand how to work with variables, but there are several concepts on that page and others that we'll tie together to write <code>myExtractedURLParamTest</code>.</p>
<h2>Extracting the URL parameter</h2>
<p>Cypress gives us access to the current URL through the <a href="https://docs.cypress.io/api/commands/location.html#Syntax"><code>.location()</code> command</a>. In our <code>myExtractedURLParamTest</code> test, when we're visiting the first article page, <code>.location()</code> might return something like <code>http://localhost:1234/articles/5678</code> where <code>5678</code> is the article ID. We don't really care about the origin (<code>http://localhost:1234</code>), and we can specify only the portion of the location that we're interested in, in this case the <code>pathname</code>:</p>
<pre><code class="language-javascript">cy.location('pathname'); // /articles/5678
</code></pre>
<p>Note that a series of commands starts by accessing the <code>cy</code> global variable.</p>
<h2>Extracting the article ID</h2>
<p>The pathname includes information we don't need: the prefix <code>/articles/</code> is not part of the article ID. What I'd <em>like</em> to do is take the result of the pathname, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split"><code>.split() it</code></a> based on slashes, and take the last fragment.</p>
<p>My initial instinct for this test was to write something like this:</p>
<pre><code class="language-javascript">// this is tempting but it will not work.
const articleID = cy.location('pathname').split('/')[2];
</code></pre>
<p>But this doesn't work. Remember how all Cypress commands are asynchronous? In JavaScript, asynchrony is handled with <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a>. Cypress commands <a href="https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Promises">are a specific implementation of promises</a>. We can't <code>.split("/")</code> the result of <code>cy.location("pathname")</code>, because the return value isn't a string - it's a Cypress version of a promise!</p>
<p>If you're familiar with promises, you might predict the proper way to split the pathname - by chaining the <code>.location()</code> call to <a href="https://docs.cypress.io/api/commands/then.html#Syntax"><code>.then()</code></a>, and working with the result of the <code>.location()</code> promise. Like this:</p>
<pre><code class="language-javascript">cy.location('pathname').then(path => {
// path is the value from the previous command, `location("pathname").
// In our example, the value of `path` is "/articles/5678".
const articleID = path.split('/')[2];
});
</code></pre>
<p><em><strong>Update:</strong> <a href="https://dev.to/bahmutov">Gleb from Cypress</a> pointed out in a comment on dev.to that I could use the <code>.invoke()</code> and <code>.its()</code> commands to simplify this code. <a href="https://dev.to/bahmutov/comment/ll1o">Take a look at his suggestions!</a></em></p>
<h2>Storing the article ID for future use</h2>
<p>Cool, now we've got the ID of the article. We aren't going to use it right away, so we want to store it somewhere that our test can access it later. Cypress provides a feature named <a href="https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Aliases">"aliases"</a> for storing variables for future use. Specifically, we'll use the <a href="https://docs.cypress.io/api/commands/as.html#Syntax"><code>.as()</code> command</a> to alias a value.</p>
<p>The challenge in our test is that <code>.as()</code>, like all commands, can only be called from a Cypress chain. Remember that Cypress chains start with accessing the <code>cy</code> global object, then chaining any commands onto it. We need to get our <code>articleID</code> into this sequence of commands somehow:</p>
<pre><code class="language-javascript">cy.????.as("articleID")
</code></pre>
<p>Cypress provides a command named <a href="https://docs.cypress.io/api/commands/wrap.html#Syntax"><code>.wrap()</code></a> to accomplish this. <code>.wrap()</code> takes a value and yields it as the result of a command, which can then be chained to any other Cypress commands. Our example test will look like this:</p>
<pre><code class="language-javascript">cy.location('pathname').then(path => {
const articleID = path.split('/')[2];
cy.wrap(articleID).as('articleID');
});
</code></pre>
<p>Once the <code>.as('articleID')</code> command runs, we'll have access to our article ID from any command later in the test, aliased as <code>articleID</code>.</p>
<h2>Accessing the aliased article ID</h2>
<p>Aliased values can be accessed using another command: <a href="https://docs.cypress.io/api/commands/get.html#Syntax"><code>.get()</code></a>. When retrieving values with named aliases, as in our situation, we specify the name with an <code>@</code> prefix, like this:</p>
<pre><code class="language-javascript">cy.get('@articleID');
</code></pre>
<p>We'll chain another <code>.then()</code> command to work with the result of the call to <code>.get()</code>:</p>
<pre><code class="language-javascript">cy.get('@articleID').then(articleID => {
// do stuff with the articleID
});
</code></pre>
<p>For <code>myExtractedURLParamTest</code>, we'd build up a new URL in that function body, and call <code>cy.request()</code> to hit our API, like this:</p>
<pre><code class="language-javascript">cy.get('@articleID').then(articleID => {
cy.request(`/api/articles/${articleID}`).then(response => {
expect(response.status).to.eq(200);
// And any other assertions we want to make with our API response
});
});
</code></pre>
<h2>Tying it all together</h2>
<p>The final test looks like this:</p>
<pre><code class="language-javascript">it('myExtractedURLParamTest', () => {
// Visit the articles list and click on the first link
cy.visit('/articles');
cy.get('[data-cy=article]').click();
// Wait until we're on an article page
cy.location('pathname').should('match', /^\/articles\/.*$/);
// Extract the article ID from the URL and alias it
cy.location('pathname').then(path => {
// path = "/articles/234234234"
const articleID = path.split('/')[2];
cy.wrap(articleID).as('articleID');
});
// Access the article ID from the alias
cy.get('@articleID').then(articleID => {
// do stuff with the articleID
cy.request(`/api/articles/${articleID}`).then(response => {
expect(response.status).to.eq(200);
expect(response.body.title).to.eq(
'A stolen $15,000 wooden monkey was returned to a Danish art museum?'
);
});
});
});
</code></pre>
<p>In the end, we used the following Cypress commands to string this all together:</p>
<ul>
<li>The <a href="https://docs.cypress.io/api/commands/location.html#Syntax"><code>.location()</code> command</a> to access the current URL</li>
<li>The <a href="https://docs.cypress.io/api/commands/then.html#Syntax"><code>.then()</code> command</a> to work with the result of the previous command</li>
<li>The <a href="https://docs.cypress.io/api/commands/wrap.html#Syntax"><code>.wrap()</code> command</a> to yield a known value from a new command</li>
<li>The <a href="https://docs.cypress.io/api/commands/as.html#Syntax"><code>.as()</code> command</a> to alias a value and store it for other commands to use</li>
<li>The <a href="https://docs.cypress.io/api/commands/get.html#Syntax"><code>.get()</code> command</a> to access an aliased value</li>
</ul>
<p>It's a little more roundabout than most of the JavaScript I've written in my life. The asynchronous nature of Cypress commands changes the way we pass information between them, but the features are all there for us to write robust tests.</p>
<h2>Update</h2>
<p><a href="https://dev.to/bahmutov">Gleb from Cypress</a> <a href="https://dev.to/bahmutov/comment/ll1o">pointed out in a comment on dev.to</a> that I could simplify the step where we extract the ID from the URL and alias it. Instead of this:</p>
<pre><code class="language-javascript">cy.location('pathname').then(path => {
const articleID = path.split('/')[2];
cy.wrap(articleID).as('articleID');
});
</code></pre>
<p>...we can take advantage of two more commands built into Cypress. The <a href="https://docs.cypress.io/api/commands/invoke.html#Syntax"><code>.invoke()</code> command</a> will invoke a function on the result of the previous command, and the <a href="https://docs.cypress.io/api/commands/its.html#Syntax"><code>.its()</code> command</a> will access a property on the result of the previous command. The simplified code looks like this:</p>
<pre><code class="language-javascript">cy.location('pathname')
.invoke('split', '/')
.its(2)
.as('articleID');
</code></pre>
<p>Much more readable. Thanks, Gleb!</p>
2019 In Review2020-01-01T12:00:00+00:00https://stevenhicks.me/blog/2020/01/2019-in-review/<h2>TL;DR</h2>
<p>If we're going to measure 2019 by accomplishments, I crushed it! I became a national, international, and keynote speaker. I won my age group at a local triathlon and stood on a podium for the first time since the second grade district spelling bee. I taught several workshops, including one to almost 150 people, and my first standalone course outside of a conference.</p>
<p>Alas...I am not going to measure my 2019 solely by accomplishments.</p>
<p>I started 2019 feeling burned out from over-commitment. I'd crammed too many speaking engagements into the end of 2018. I wanted to find balance. I felt like I was not able to give my best self to any of the many things I was interested in doing. I needed to step back.</p>
<p>And I did. I made a lot of progress in 2019. I learned to say "no" more often than I was comfortable. I limited my involvement to only things I was <strong>extremely</strong> interested in. I learned that I need to trust my instincts - and stand up for them.</p>
<p>But I still over-committed myself to the many things I love doing. I faced a handful of challenges throughout the year that made me question my priorities and identity. I struggled to reconcile the meditative stance that "you are enough" with my seemingly unshakeable habits and responses that endanger relationships with my wife and children.</p>
<p>I'm not sure if I've found balance this year. I've made a lot of progress...but I'm not sure I'm all the way there.</p>
<p>I'll enter 2020 with many questions. Am I successful? Yes. Was 2019 my best year? A hard "no". Am I focusing on the things I want to be focusing? Am I giving my best self to the people I care about most? Myself? My kids? My wife? ¯\_(ツ)_/¯</p>
<p>So I'm not sure what I think of my 2019. I did some great things. I struggled <strong>a lot</strong>. I <em>think</em> I'm in a better place now than I was in the middle of 2019. I'm still working to find that balance of giving myself to strangers, friends, my family, and myself.</p>
<p>Having said that - here are the things I accomplished and learned in 2019! Hope yours was great, and cheers to a better 2020.</p>
<h2>Speaking</h2>
<p>I hit my sweet spot for speaking engagements in 2019 - 12, one per month.</p>
<p>In January, I delivered a hugely successful workshop at <a href="https://www.codemash.org/">CodeMash</a>. I had nearly 150 people in the room to learn React, and got incredible praise from attendees. I also delivered my Getting Unstuck talk for the first time.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Move your feet, lose your seat! <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a>'s excellent "Building Your First React App" workshop has every seat filled and most of the floor space taken too!<a href="https://twitter.com/hashtag/codemash?src=hash&ref_src=twsrc%5Etfw">#codemash</a> <a href="https://t.co/A1tQ9FrzK5">pic.twitter.com/A1tQ9FrzK5</a></p>— Michael Richardson (@AnAccidentalDev) <a href="https://twitter.com/AnAccidentalDev/status/1082665637229531141?ref_src=twsrc%5Etfw">January 8, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>In April, I attained "international" speaker status, delivering a workshop and talk at <a href="https://forwardjs.com/">ForwardJS</a> in Ottawa. I got miserably sick while I was there, and haven't boarded a plane without sanitizing wipes since. Ottawa is a pretty city. I'd go back.</p>
<p>In May, I attained "national" speaker status, <a href="https://www.youtube.com/watch?v=3XscuivvUzI&list=PLE7tQUdRKcyaOq3HlRm9h_Q_WhWKqm5xc&index=43&t=0s">giving my Getting Unstuck talk at RailsConf</a> in the Twin Cities. I <strong>love</strong> this talk. It is useful for <strong>every</strong> developer, regardless of experience level, and regardless of tech stack. You should watch it.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">"Getting stuck is not a reflection of your skill level."<br><br>- <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> <a href="https://twitter.com/hashtag/RailsConf?src=hash&ref_src=twsrc%5Etfw">#RailsConf</a> <a href="https://twitter.com/hashtag/tech?src=hash&ref_src=twsrc%5Etfw">#tech</a> <a href="https://twitter.com/hashtag/programming?src=hash&ref_src=twsrc%5Etfw">#programming</a> <a href="https://twitter.com/hashtag/preach?src=hash&ref_src=twsrc%5Etfw">#preach</a><br><br>👏🏻👏🏻👏🏻👏🏻👏🏻👏🏻👏🏻👏🏻👏🏻👏🏻👏🏻</p>— Hilary Stohs-Krause (@hilarysk) <a href="https://twitter.com/hilarysk/status/1123979771967430661?ref_src=twsrc%5Etfw">May 2, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>In July, I got to deliver a workshop at <a href="https://www.kcdc.info/">KCDC</a> in Kansas City - a conference I'd wanted to attend since I started speaking. 2019 was the first time I didn't have a schedule conflict, so I was thrilled to submit and more thrilled to be accepted.</p>
<p>In October came my biggest accomplishment of 2019 - my first keynote! When I originally heard from the <a href="https://cwitc.org/">Central Wisconsin IT Conference</a> asking if I was interested in keynoting, I thought it was a joke. Why me instead of someone else, but more to the point, <strong>of course I was interested</strong>. I had so much fun doing this, and I think I crushed it. <a href="https://twitter.com/joel__lord">My friend Joel</a> says so:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Thank you <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> for one of the greatest keynotes I've had the chance to see!<a href="https://twitter.com/hashtag/CWITC?src=hash&ref_src=twsrc%5Etfw">#CWITC</a></p>— Joel Lord (@joel__lord) <a href="https://twitter.com/joel__lord/status/1183131405884252161?ref_src=twsrc%5Etfw">October 12, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>There's one event I missed in 2019 that made me really sad - <a href="https://www.thatconference.com/wi">That Conference</a>. A week after I was accepted to speak, I found out I had to be at <a href="https://www.artsy.net/">Artsy</a> HQ in New York for an engineering on-site. I had to withdraw from <a href="https://www.thatconference.com/wi">That Conference</a>, and live vicariously through everyone who got to go. I was extra sad to miss a handful of friends speaking. I'm hoping to make it back in 2020.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Looks like the <a href="https://twitter.com/hashtag/THATConference?src=hash&ref_src=twsrc%5Etfw">#THATConference</a> posts are starting to roll in. I'm **SO SAD** that I won't be there this year. (I'm "stuck" in NYC for a work thing.)<br><br>Here's a list of things I'd do if I were there. If you're going, you should do them for me!</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1157676321297838080?ref_src=twsrc%5Etfw">August 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I don't think I'll hit 12 engagements in 2020. I have a couple local things lined up, but nothing outside of Milwaukee. I don't even have any open CFP submissions (note: I lied. I submitted once while writing this article 🙄). Traveling is hard - I'm mostly focused on local events. I'll submit to some bigger events too, I just haven't figured out yet which ones align with our busy schedule. I'm still looking to attain "overseas" speaker status.</p>
<p>I'd love to give my Getting Unstuck talk more. I'll likely submit it alone unless the event requires more. I'm also feeling a little burned out with preparing talks, which is another reason to submit just that one.</p>
<h2>Workshops</h2>
<p>Another of my big goals for 2019 was to teach more workshops. I taught three times at conferences, including the aforementioned smashing success at CodeMash.</p>
<p>More significantly, I also taught a workshop outside of a conference. <a href="https://twitter.com/amandadaering">My friend Amanda</a> helped me out by sponsoring and organizing. It wasn't huge...but it was a huge step toward teaching on my own. I'll definitely do this a few more times in 2020.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Have heard so much interest from employers and developers in React lately. So, <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> and I decided to bring his all day hands on workshop here! Tix & scholarship tix info: <a href="https://t.co/7HLak4Gk1P">https://t.co/7HLak4Gk1P</a></p>— Amanda Daering (@amandadaering) <a href="https://twitter.com/amandadaering/status/1176214795139387393?ref_src=twsrc%5Etfw">September 23, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>One other really neat thing came out of a workshop. In my React workshop at CodeMash, I earned my second biggest accomplishment of 2019 - my new friend <a href="https://twitter.com/jaycincotta">Jay Cincotta</a> took the class, loved it, and reached out to me a few weeks later to ask for mentorship. Again, when I got his email, I thought he was joking - <strong>of course</strong> I was interested in hanging out with him once every couple weeks, giving him advice, and helping him solve problems. Oh, and he'd pay me for it.</p>
<p>It doesn't scale very well, but this is my absolute favorite style of consulting. One-to-one, focused on solving specific problems. We talk about theory and principles along the way, but first and foremost, we're working to get a product delivered for Jay. Throughout the year I've watched him become a strong React developer and do an incredible amount of work. Our meetings have become sparse, but I've acquired a really great friend. 10/10 would do this again.</p>
<h2>Writing</h2>
<p>Writing is one of those things that I <strong>love</strong> to do, but it falls far enough down my priorities that I never do it as much as I want. 2019 demonstrates this well.</p>
<p>I published four articles on my blog in 2019. I wrote about <a href="/blog/2019/01/2018-in-review/">2018</a>, about <a href="/blog/2019/02/building-a-workshop/">building a workshop</a>, about <a href="/blog/2019/06/guidance-for-the-big-refactor/">big refactors</a>, and about <a href="/blog/2019/09/on-becoming-a-tech-lead/">becoming a tech lead</a>. Not a lot of content, but I'm proud of all those articles. I <em>started</em> two other articles that never got finished.</p>
<p>I also wrote 2.5 articles for the Artsy blog in 2019. In all cases, they were collaborative articles - this is something I'd never done before, but have found that I really enjoy. It's an interesting challenge to make sure everyone feels heard. I co-wrote about <a href="https://artsy.github.io/blog/2019/01/23/artsy-engineering-hiring/">the Artsy engineering hiring process</a> and <a href="https://artsy.github.io/blog/2019/05/24/server-rendering-responsively/">the tooling we use for server-rendering a responsive web app</a>. (The other .5 is <em>so close</em> to shipping, but still has some work to be wrapped up. Look for it in the next week or so!)</p>
<p>I'd love to say 2020 will feature more writing, but I think we all know what we're dealing with at this point 😬. I do want to be better about letting articles sit in an "in-progress" queue, and working on them as I find inspiration. I'd also like to write shorter articles, since I often lost interest before finishing longer articles.</p>
<h2>Work</h2>
<p>In 2019 I became a remote worker! Sure, I started working remotely in 2018, but it wasn't until 2019 that I felt confident about it. I've become more structured with my time than ever before. I do lots of 1:1s to stay connected. I co-work with friends like <a href="https://twitter.com/VassiLC">Leo</a> to keep myself from spiraling into dark loneliness. I over-communicate. I've weirdly found myself over-emoting on video calls when I'm muted, without thinking about it. Big smiles and belly laughs.</p>
<p>I've also developed some good habits as a result of remote work. I've finally developed a consistent meditation habit. I meditated 23 work-days in a row at one point. In 2020, I'll hit 30 at least once. I've finally embraced early morning workouts, preferably daily. Meditation and exercise are really important for me to set the trajectory of my day, which is hugely important as a remote worker.</p>
<p>I took on the role of tech lead for my team at Artsy. I've learned that there is no one way to lead a team. I went into it expecting that the things I wanted from a tech lead would be the things my team wanted from a tech lead....but it turns out everyone is different, and expects different things from their leaders. I've also done a lot of reflecting on what <strong>I</strong> want to be doing with my time, versus doing what I think others expect of me.</p>
<h2>Cream City Code</h2>
<p>Cream City Code 2019 was not great. The event itself was great...the organization leading up to it, not so much. I’ve helped organize this event for its entire life, and it feels like a third child to me. I love it <strong>so much it hurts</strong>.</p>
<p>But the organization went so disastrously bad this year that I had to step away. The core CCC team made the decision early on to join forces with another event. We were excited at the prospect of having less work to do, fewer costs to cover, and a unique spin on a tech conference. Many of our problems from previous years seemed like they’d be solved.</p>
<p>As the year went on, it became clear that our expectations were not going to be met. As we got closer to the date of the event, we felt increasingly helpless to do anything about it.</p>
<p>I lost sleep. I felt awful physically. I ruminated. After talking to my therapist a few sessions in a row, I decided to dump the work on the rest of the core team and remove myself from organization, because my well-being couldn’t handle being around it. As they’ve always done, my friends - especially <a href="https://twitter.com/rachelkrau">Rachel Krause</a>, <a href="https://twitter.com/davidpine7">David Pine</a>, <a href="https://twitter.com/ben_felda">Ben Felda</a>, <a href="https://twitter.com/zuul86">Adam Pritzl</a> - worked ridiculously hard and put together an amazing event.</p>
<p>The thing I learned most from CCC was to trust my instincts, and more importantly, to <em>fight</em> to defend my instincts. I am a passive person when it comes to conflict, and I give up quickly when I meet resistance. I need to stand stronger when I have strong beliefs.</p>
<h2>Sporty Spice</h2>
<p>I scaled back my participation in triathlons in 2019, so that I could better focus on trail running. I love trail running - it is very meditative for me.</p>
<p>I ran two trail half marathons. At the Dances With Dirt race in Devils Lake I fought off cramps for the last four miles in terrible heat and humidity. It wasn't a great race from a results perspective, but I loved it.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">They're not all winners but they are all exactly where I want to be for a couple hours.</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1150396957925548034?ref_src=twsrc%5Etfw">July 14, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>At the North Face Endurance Challenge in the Southern Kettle Moraine Forest, I got the results I wanted - a trail half marathon in under 2 hours.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">"Crushed" my goal of under 2 hours in a half marathon trail race this weekend, by 24 seconds. <br><br>As clearly indicated by this goofy, exhausted, sweaty, calf-cramped, satisfied grin. <a href="https://t.co/tVXSEK8fuq">pic.twitter.com/tVXSEK8fuq</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1173420254296453120?ref_src=twsrc%5Etfw">September 16, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>One other cool thing happened this year - I won my age group at a small race in my wife's home town! It was supposed to be a triathlon, but heavy rain cancelled the bike portion. I haven't stood on a podium since the second grade, when I finished second in the Oconomowoc school district spelling bee. I lost on the word "embarrassed," if you must know.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">A short, small race was made shorter & smaller by buckets of rain. That won't prevent me from being proud of my first time on a podium since I took 2nd place in the second grade Oconomowoc school district spelling bee.<br><br>(My wife won her age group too, but that's not new for her.) <a href="https://t.co/phBF8AQjwt">pic.twitter.com/phBF8AQjwt</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1175954995331645440?ref_src=twsrc%5Etfw">September 23, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>In 2020, I'm going to run more trails. I'm in the process of choosing a marathon or 50k race. The North Face Endurance Challenge is cancelled or changed or...something..., and I don't know if it'll be back in Wisconsin. I've found a few other potential races, I just haven't decided which race I want to focus on yet.</p>
<h2>Well-being</h2>
<p>Well-being was my biggest challenge of 2019. I started the year off with some health problems. Our 13 year old cat Mr. Turtle died. We hired someone to do a basement remodel, which was stressful and brought out the worst in me. I started seeing a therapist. Cream City Code wrecked me. I had some more physical health problems. I entered a midlife crisis, questioning why I was spending time doing things I didn't enjoy.</p>
<p>This is the reason I can't say 2019 was a smashing success. I had so many instances of success this year, and I'm grateful for and proud of all of them. But I also felt emotionally crushed for much of the year.</p>
<p>I'm doing better. As I've pulled myself further away from commitments, I've found time for myself. I've started playing piano and guitar on a regular basis. I'm bad at both, but I'm learning! I found some old drawings from art school, and they inspired me to draw again. We got a new cat - Captain Marble - and he is a punk, but also nice and cute.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Ugh, officemates with no respect for boundaries, amiright? <a href="https://t.co/TfjvQpkb5m">pic.twitter.com/TfjvQpkb5m</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1181941731031502848?ref_src=twsrc%5Etfw">October 9, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h2>2020</h2>
<p>The things I'm most focused on for the next year:</p>
<ul>
<li>
<p>Continuing to find balance. I want my kids and my wife to get the best me, instead of the leftovers of a person stretched too thin. My scary thing for 2020 is to devote more time to <strong>myself</strong> and my family.</p>
</li>
<li>
<p>Undoing some very old habits and patterns. I quickly fall back into bad habits during intense but important conversations. I'm working hard with my therapist to break free from these loops in 2020.</p>
</li>
<li>
<p>Running a marathon (or 50k) on trail. Five years ago I said "I can't imagine ever wanting to run more than 6 miles," but here I am ¯\_(ツ)_/¯. I really want "ultrarunner" to become part of my identity. It will take way more structure - I'll need to follow a training plan more closely than I ever have to run that long.</p>
</li>
</ul>
<p>Aside from that, we'll see. I make no promises, other than that I shall do my best.</p>
On Becoming A Tech Lead2019-09-13T12:00:00+00:00https://stevenhicks.me/blog/2019/09/on-becoming-a-tech-lead/<p>My coworker Chris recently asked me for advice on being a new parent. He and his partner are expecting a baby in the next couple months, and he was curious how I, father of two above-average children, recommended he prepare.</p>
<p>I believe my exact words were:</p>
<blockquote>
<p>lolololol OMFG there is nothing you can do to prepare for children.</p>
<p>~ me</p>
</blockquote>
<p>He asked "but are there any books or resources you recommend?"</p>
<p>I said "ha ha ha ha ha NO. You can't possibly prepare."</p>
<p>And then we went on our way.</p>
<p>I felt awful afterward. I realized that I did a <em>terrible</em> job of getting my point across. If I even knew what my point was! I thought about it for the next day, trying to figure out what I was really trying to say.</p>
<h2>My actual advice for new parents</h2>
<p>I went back to Chris to apologize for my awful advice. He assured me he wasn't terrified by my advice, but it's pretty obvious that he <em>could</em> have been terrified by it.</p>
<p>I explained what I meant:</p>
<blockquote>
<p>There is nothing you can do to prepare for children, in the sense that it doesn't really matter. Every day is going to be something you've never dealt with before. Parenthood is messy - there is no guidebook, because every baby is different, and so is every family. You're basically making shit up the entire time, and you're on very little sleep so you're not even making shit up <strong>well</strong>.</p>
<p>But that is what's beautiful about it. You get through it, every single day. Some of the days are miserable - you start the day already ego-depleted, and you have to somehow get to the end of the day <strong>and</strong> take care of your baby who won't stop screaming <strong>and</strong> then not be a jerk to the ones you love <strong>and</strong> you still haven't eaten anything because you don't have time. But you get through it....together....with your baby...and your partner.</p>
<p>And it's beautiful but terrifying, but you forget all the bad stuff, and two years later you decide you want another baby.</p>
<p>~ me</p>
</blockquote>
<h2>But like....tech leads and stuff?</h2>
<p>I might have buried the lede a bit - last week I was chosen to be the tech lead on my team! :partycat:</p>
<p>In the transition, my team's previous tech lead did a great job of sharing all the things he's been doing, to help me prepare. He wrote up a ton of docs, which were all really great. He said "I'm still here for a week - let me know if you have any questions." I said "I will definitely hit you up before you leave."</p>
<p>But I never hit him up.</p>
<p>When I realized this afternoon that I never reached out to him for more advice, and that he'd officially left Artsy by now, I worried that I'd missed an opportunity. But I realized that the things I'm going to need the most help with are all contextual. He couldn't possibly transfer all his system and organization knowledge to me, and if he could, I couldn't possibly absorb it. And it probably wouldn't matter anyway, because every situation will likely require context and nuance, which isn't a thing that can be knowledge-transferred.</p>
<p>I realized that I've found a parallel between becoming a tech lead and becoming a parent. I've prepared myself to head into this tech lead position the same way I recommended Chris prepare for parenthood: <strong>not at all</strong>. Knowing that every situation is going to be unique, and probably difficult, but I'm going to figure it out. Or <em>not</em> - and that's okay, too.</p>
<p>And then in a couple years I'll probably want another baby, or team, or something. Or maybe kittens.</p>
Guidance For THE BIG REFACTOR2019-06-19T12:00:00+00:00https://stevenhicks.me/blog/2019/06/guidance-for-the-big-refactor/<p>Most refactoring resources are about specific actions you can take in your code. Extract method, Remove middle-man, ...the kinds of actions that are well-documented at <a href="https://refactoring.com/catalog/">refactoring.com/catalog/</a>. Missing is more general guidance for undergoing <strong>THE BIG REFACTOR</strong>. You know the one. The one you're dreading, but you know you'll need to eventually do. The one you have ideas for but can't figure out how to start. The one that will in the end pay off large amounts of technical debt, but will cost you large amounts of brain cycles along the way.</p>
<p>I'm currently in the middle of one of these. I feel a bit like I'm in the weeds right now, struggling to find open air. So, yeah - this article is as much for me as it is for you. But my situation prompted me to take a step back, and start thinking meta. As I struggle through my own <strong>BIG REFACTOR</strong>, and think about the things that have gone well or poorly, I offer this handful of suggestions to you - brave soul who is about to enter <strong>THE BIG REFACTOR</strong>.</p>
<h2>1. Move things out of the way that don't belong</h2>
<p>First things first, make yourself a clean workspace. There are functions that exist in the code you'll be tackling that shouldn't be there. Utilities that don't have anything to do with the current object. Objects that are <em>related</em> to the code you're changing, but can live on their own. Move these things away from the problem - extract them to a method or object somewhere else.</p>
<h2>2. Choose your replacement strategy</h2>
<p>Will you...</p>
<ul>
<li>Modify the existing code-path? This requires you to complete before you can commit. Depending on how <strong>BIG</strong> this <strong>BIG REFACTOR</strong> is, this can be problematic and stressful.</li>
<li>Create a second code-path side-by-side? Things can get out of sync this way. You'll need to look for changes to the original before merging, or enforce changes be made in both code-paths. This <em>does</em> allow you to test side-by-side, which is nice.</li>
<li>Incrementally introduce your new code, using <a href="https://martinfowler.com/bliki/BranchByAbstraction.html">branch-by-abstraction</a>? I am adding this option to sound smart - I only learned about it while looking for some guidance today, and haven't figured out how to apply it in my current scenario. It sounds great, though!</li>
</ul>
<p>I don't know what the right answer is, for me or for you. They all make sense, sometimes, and I don't have a good heuristic for identifying the right fit. I often regret the path I choose - I regret the path I initially chose for this <strong>BIG REFACTOR</strong>. I doubt you'd want to use my heuristic even if I had one.</p>
<p>Spend some time thinking about which of these paths best fits before you begin.</p>
<h2>3. Cover the system with tests</h2>
<p>Not just any tests, the <em>right</em> tests. Not tests that test implementation of existing code - those will get deleted during your refactor, and won't tell you if you broke something.</p>
<p>The right tests are at a higher level than what you're replacing/rewriting, and which don't care about the implementation you're writing.</p>
<p>They might be integration tests. They might be UI tests (I'm totally digging <a href="https://cypress.io">Cypress</a> for those right now). Possibly a controversial opinion on my part - they don't necessarily have to be <em>permanent</em> tests - they can just be your guide for now, and you can delete them when you're done.</p>
<p>Cover <em>everything</em> you'll be replacing with these tests. You'll use them along the way to test for regressions.</p>
<h2>4. Focus on your health.</h2>
<p>You will get swallowed up by the problem. Don't let it take away the things you know you need to be healthy and happy. Don't lose sleep, don't skip meals, don't skip exercise. Treat it like a marathon, not a sprint. Pace yourself. You're not going to power through <strong>THE BIG REFACTOR</strong>.</p>
<p>Take breaks. Let your mind wander and think about the problem on its own. Your brain will work on the problem without you forcing it to. It's called <a href="https://en.wikipedia.org/wiki/Incubation_(psychology)">incubation</a>.</p>
<h2>5. Document everything.</h2>
<p>Keep a NOTES.md file in your codebase, right next to your changes, so everyone can see it. Keep track of...</p>
<ul>
<li>Things you discover that you know you'll have to account for</li>
<li>Things you think might help you solve problems along the way</li>
<li>Issues you run into that you know you'll have to fix</li>
<li>Ideas of things you want to change - make a backlog, don't work on them all at once</li>
</ul>
<h2>6. Commit & get feedback frequently.</h2>
<p>Committing frequently is not so hard. Finish each phase of your <strong>BIG REFACTOR</strong> by committing the work and pushing it. This allows you to start each next phase with a clean slate; which makes it easier to revert when you get stuck in a corner or decide on a direction change.</p>
<p>Getting feedback frequently is harder, at least for me. I like for my work to be reasonably polished before I submit it for feedback. I'm trying to get better at this. As my coworker Chris told me today, he could have reassured me that I was on the right track with <em>half</em> the work I'd done so far. My emotional state <em>definitely</em> could have used that encouragement a day or two ago.</p>
<p>Feedback is especially hard with big problems, because you have the large problem space modeled in your head, and it's hard to share that with others. It can feel counterproductive to bring someone into your model - there's lots of explaining to do. Resist the temptation to drive forward on your own, though. Getting feedback will help you make sure you're on the right path, and give you ideas along the way.</p>
<h2>7. Take baby steps.</h2>
<p>Know generally where you're headed, but think about how you can get there in as small of steps as possible. Apply small refactoring actions, and commit them frequently.</p>
<p>Address the biggest issues early if you can, but don't force them if you aren't sure of the right abstraction yet. The right time will present itself.</p>
<h2>8. Acknowledge that <strong>THE BIG REFACTOR</strong> is hard work.</h2>
<p>Allow yourself to take time. Writing code that solves a new problem is easier than writing code that solves an old problem. When you're solving a new problem, you don't know very many edge cases, and you can approach with simpler abstractions. When you're solving an old problem, the edge cases are all known, and the right abstractions to cover them all take more thought and consideration.</p>
<h2>Good luck!</h2>
<p>I hope your <strong>BIG REFACTOR</strong> treats you well! Or at least, you pay off some debt with minimal scars. Let me know how it goes, and <a href="mailto:steven.j.hicks@gmail.com">send me an email</a> or <a href="https://twitter.com/pepopowitz">hit me up on Twitter</a> if you have advice that I didn't cover!</p>
Building A Workshop2019-02-19T12:00:00+00:00https://stevenhicks.me/blog/2019/02/building-a-workshop/<p>Presenting a 45-minute session at a meetup or conference is really fun. I've never thought that was enough time to do more than inspire people to research the topic. In 2018, I decided I wanted to get more out of speaking. I wanted to feel like I was <em>teaching</em> people, in addition to inspiring them.</p>
<p>So I started leading workshops. I <a href="https://twitter.com/MusicCityTech/status/1002264602422456331">led a half-day workshop at Music City Tech</a> on writing Test-Driven JavaScript, and then <a href="https://twitter.com/pepopowitz/status/1049406551994306560">a full-day workshop at Dev Up</a> called "Building Your First React App". In early 2019 I <a href="https://twitter.com/anaccidentaldev/status/1082665637229531141">led my React workshop again at CodeMash</a>.</p>
<p>I've definitely walked away from these first few feeling like I'm teaching and making a difference. If workshops are something you've considered, I encourage you to give them a shot. I <em>think</em> most conferences get significantly less submissions for workshops than talks, so it is easier to get started.</p>
<p>If that's you, then you might be interested in some things I've learned about building workshops in the past year!</p>
<h2>It takes a lot of time</h2>
<p>A half-day workshop probably took me about 50 hours to put together; a full-day workshop took about 100 hours.</p>
<p>That's a lot of time! Before it scares you off, you should know these things for context:</p>
<ul>
<li>
<p>It takes me about 40 hours of work to put together a one-hour talk. I obsess over the flow of a talk, I add drawings, and I script (effectively) every talk I write. This is more time than most people put into a talk. So 50/100 hours is likely on the high side of what it would take you to build a workshop.</p>
</li>
<li>
<p>I have a very specific balance of talking-to-hands-on that I strive for. I don't like to talk for more than 20 minutes before people get their hands on their keyboard for 20 minutes. I think this forces me to put more time into thinking about how exercises can build from each other, and how I can break exercises down into smaller pieces.</p>
</li>
</ul>
<h2>Think a lot about what kind of workshop you want</h2>
<p>The answer to this question probably has a big effect on how much time it takes to build your workshop. I've seen a few different styles of workshop, each with advantages and disadvantages.</p>
<h3>The self-driven workshop</h3>
<p>Some workshop leaders say a few words at the beginning, then let people dig in on their own for the rest of the 4 hours. I call this a "self-driven workshop."</p>
<p>The self-driven workshop is a favorite of attendees who want to work on their own time. They don't really need the leader there, so they can take the exercises home with them.</p>
<p>On the other hand, this strategy gives learners more opportunity to distribute skill-wise. Fast learners will finish everything within an hour; slow learners will take more. Many learners will even leave when they realize it's completely self-driven, knowing they can work on it at their convenience.</p>
<p>My preference is to engage with learners early and often - so I stay away from the self-driven workshop. If you batch your exercises small, you see less of a gap between the fast and slow learners. You might end up with a range of 10 to 20 minutes with small exercises, compared to 1 hour to 3.5 hours for a self-driven workshop. This is more likely to keep everyone engaged for the entire session, and it gives me more opportunities to interact with people.</p>
<h3>The lecture</h3>
<p>Some workshop leaders can fill all 4 hours with instruction. They are deep experts in their field, and have tons of knowledge to share.</p>
<p>I am not this level of expert. I also don't think 4 hours of lecture is engaging for an audience. Human attention span is short. You get maybe 20 minutes before people start to check out.</p>
<p>At a workshop, people have their laptops in front of them. If you give them an opportunity to check out, they will take it, and you will struggle to get them back. This doesn't mean they'll sit there and play minesweeper for the next 3 hours. It means they'll spend 20 minutes on Twitter, then decide "this sucks, I'm not getting anything out of it", then walk out to another session.</p>
<p>It isn't only 100% talking that makes for a boring workshop. Even if you split your workshop into 1 hour talking then 3 hours of hands-on, that first hour is an opportunity for people to check out before you even get to the exercises. At the very least, I'd consider breaking this balance into [30 minutes talking, 1:30 exercises, :30 talking, 1:30 exercises]. Even better would be 15 minutes talking/45 minutes exercise repeated 4 times.</p>
<h3>The interactive workshop</h3>
<p>No surprise I'm sure, but this is definitely my preferred style of workshop. I strive for about 15 minutes of talking, then about 20-30 minutes of exercises of some sort.</p>
<p>Variety of exercise is also important in this style of workshop. Repetitive exercises can get boring. My favorite workshops that I've attended have used a variety of styles of exercise. A mix of hands-on coding, looking at code, pairing, just talking about things,… In my React workshop, I have learners draw components on handouts with a pen.</p>
<p>Running an interactive workshop requires strict time-boxing, to make sure you're covering all the things you want to cover. It is really easy to run over time on exercises. You're an expert in your field, and your audience is full of beginners - every exercise you write will take them longer than you think.</p>
<p>This is okay, they're learning! You just need to be okay with cutting an exercise short when you know time is up.</p>
<h2>Get people talking to each other within the first 20 minutes</h2>
<p>If you're interested in leading a lecture or self-driven workshop, this is probably not necessary.</p>
<p>Agile coaches/speakers are especially great at making this happen. Even if it's a simple "meet your neighbor", getting people talking early sets the tone of keeping people interactive and engaged.</p>
<p>In my workshops, I've mostly only tried the "meet your neighbor". I required pairing for one exercise in my Test-Driven workshop, but it didn't go great. It seems like a lot of people have resistance to pairing. I have a deep love for pairing. I think if done well it can be career-changing, so I still want to share that experience with people.</p>
<p>I'll back off on the pairing <em>requirement</em> in the future, and make it <em>optional but encouraged</em>. I also could better support pairing by taking time up front to say “if you want to pair, raise your hand right now…and find each other.” It would require people moving seats, but it might work.</p>
<h2>Written instructions are really helpful</h2>
<p>It is <strong>incredibly</strong> helpful to give your audience detailed instructions for exercises. Anything less will leave them lost and confused. Don't assume anything is understood that you haven't already explained. Provide links to resources for things that you didn't explain. The more details you give them, the fewer questions you'll have to answer.</p>
<p>It's even helpful to include basic information that you <em>did</em> cover in your lecture/instruction. Some people might not have understood it when you were speaking. Others might do better with reading than listening.</p>
<p>Even the exercises where laptops aren't used should have written instructions. Once your learners are conditioned to look at your written instructions for each exercise, they will look there even when there aren't any.</p>
<h3>Post the instructions in a public location</h3>
<p>This isn't a requirement, but attendees are really appreciative when they can (a) finish the exercises on their own, and (b) share them with their coworkers. I put all of mine <a href="https://github.com/pepopowitz/your-first-react-app-exercises">in a GitHub repository</a>.</p>
<h2>Enable people to jump into later exercises easily</h2>
<p>Very few people will complete every exercise in a workshop, from start to finish. Some might join your session halfway through. Some might get stuck on an exercise, and since <a href="#the-interactive-workshop">you're strictly time-boxing</a>, they might not have time to finish it.</p>
<p>To account for this, build your exercises in a way that someone can jump into a later one without having finished everything before it. This will help them focus on the current exercise, and it will free you up from solving issues from 3 exercises ago.</p>
<p>I accomplished this <a href="https://github.com/pepopowitz/your-first-react-app-exercises">in my React workshop</a> by having each exercise start with the "completed" state of the previous. I have a separate folder for each exercise, and all the code for all exercises is readily available. I give them the starting point for each exercise, and a folder containing the completed state in case they get stuck.</p>
<p>I've also seen people accomplish this with git branches, commits, or tags.</p>
<h2>Finding the right difficulty level is...difficult</h2>
<p>It's important to me to have a difficulty level that (a) isn't too hard for brand new beginners, but (b) doesn't bore the advanced learners. This is a really hard balance to find.</p>
<p>The way I first approached it, with my TDD workshop, was to try to make the exercises <em>so long</em> that no one would ever finish. I thought this would keep the advanced learners interested, without the beginners having to worry about how far they got.</p>
<p>This approach definitely appeased the advanced learners, but it had a damaging effect on the beginners. They became stressed out that they were never able to finish. Several people commented on how far behind they felt after my workshop. This felt awful - I <strong>never</strong> want to make beginners feel that way.</p>
<p>With that knowledge in hand, I did a better job of planning exercises in my React workshop. I aimed for activities that would take <em>me</em> about 5 minutes to complete. Because I have significantly more experience in the topic, this ends up being <strong>plenty</strong> of work for the advanced learners without overwhelming the beginners. Just in case an advanced student finished work quickly, I threw in a link to an interesting related article and/or a vague "bonus" exercise that could keep them busy.</p>
<p>You will get a wide range of experience levels in your workshop. It’s up to you to decide what you want to do with those people. Do you want to teach to the least experienced person in the room, at the risk of someone more advanced walking out? Are you more interested in keeping the advanced students happy at the risk of frustrating the beginners? I aim for the middle, and expect to lose a few people on either end.</p>
<h2>Be flexible with time</h2>
<h3>Give people breaks</h3>
<p>I knew going into my first workshop that I needed to account for breaks. <em>No one</em> can sit for 4 hours, no matter how funny my dumb jokes are. I scheduled one break into my Test-Driven workshop, about halfway through.</p>
<p>We didn't get anywhere near halfway before we needed it. I could see the lack of focus in their eyes. We took our break earlier than expected, and then a second a little over an hour later. My schedule was a little messed up, but we recovered.</p>
<p>Now I factor in two breaks per 4 hour session, about ten minutes for each break. I don't schedule them, though. I find it more effective to gauge the temperature of the room when we've been sitting for an hour. I start looking ahead at my schedule (which is conveniently broken down into 15-20 minute chunks), to figure out when our next break will fit in.</p>
<h3>Let them choose the last topics</h3>
<p>It's hard to stick to an exact schedule for a workshop. Embrace this by over-planning topics. Plan out a couple more than needed to fill the session, but let your group choose which topics they're most interested in at the end. They'll appreciate having control over the curriculum, they'll get more out of your session, and you'll feel like you've tailored your course for them.</p>
<h2>Watch out for logical leaps</h2>
<p>I'm still learning this lesson.</p>
<p>It's easy, as an expert in your topic, to make logical leaps. In my React workshop, we talk about <code>setState</code> for managing component state. The next logical concept to me is application-level state. But there is a big difference between managing component state and managing application state. It took me months of experience to understand the nuances and differences between the two.</p>
<p>It's hard to boil that experience down into 15 minutes of lecture. It's a big leap for someone who just learned about component state to understand <em>what</em> application state is, <em>how</em> it's different from component state, <em>why</em> it matters, and <em>OMG this doesn't look anything like component state</em>.</p>
<p>It is definitely your responsibility to figure out how to guide them through these leaps. But it's also easy to underestimate the size of those leaps. Err on the side of caution. Always assume the leap is bigger than you think it is, and never assume they'll be able to make the leap without a lot of assistance from you.</p>
<p>I look back at this tweet often, as a reminder of how slowly I should be transitioning through concepts while teaching:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sometimes I read things on edutwitter from someone who thinks they have discovered something amazing, which often sounds profoundy, thinky, or researchy, and I sigh and reflect on how music educators have known said thing for years. In evidence I give you Mrs Curwen, 1886: <a href="https://t.co/fvPgXVJUL9">pic.twitter.com/fvPgXVJUL9</a></p>— Martin Fautley (@DrFautley) <a href="https://twitter.com/DrFautley/status/1024227769004306432?ref_src=twsrc%5Etfw">July 31, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h2>Focus on the learners</h2>
<p>It feels nice to have a room of people looking to you for guidance. Just remember that the workshop is not about you, or your ego, or all the things that you know - it's about the awesome people who came to learn from you. Do everything in your power to make learning easier for them.</p>
2018 In Review2019-01-29T12:00:00+00:00https://stevenhicks.me/blog/2019/01/2018-in-review/<h2>2018</h2>
<p>A year is a long time. What you feel about yourself today is most certainly different than what you felt about yourself one year ago.</p>
<p>They say that the most important parts of a talk/lecture/presentation are the very beginning, where you hook them...and the very end, which is what they remember. The same could be said for a year. You could do 100 amazing things in the first nine months, but if the last three months stunk? You'd think you had an awful year.</p>
<p>That explains why I had a hard time remembering what I was most proud of when I looked back at my 2018 in December.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">I'm feeling in a bit of a slump right now, and it took me way longer to think of these than I would have liked. I'm hoping to get better at celebrating (& remembering) my victories this year. <br><br>And also, to get better at focusing on the practice, not the outcome.</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1078291468102586368?ref_src=twsrc%5Etfw">December 27, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I took a look back at my Twitter feed from 2018, though, and things looked pretty dang good.</p>
<h3>I Did More Speaking Than Expected!</h3>
<p>Heading into 2018, I thought that I was going to have to cut back on speaking. My job wasn't happy about me taking time "off" to speak.</p>
<p>But I still managed to present 12 times! This is as many times as I presented in 2017.</p>
<p>I took a few vacation days to do it, which I wasn't thrilled about. I also had some help from <a href="https://twitter.com/oobert">my friend Tony</a> advocating for me at work, convincing people that I was helping the company. I also did a good amount of local speaking - 5 times out of 12, to be precise. This breakdown also mirrored my 2017 speaking engagements.</p>
<h4>At CodeMash</h4>
<p>My speaking year started off with a trip to Ohio for CodeMash, a conference I'd heard a lot of things about. My talk went great, but my highlight was giving a lightning talk.</p>
<p>In one of the pre-compilers, we learned how to use a Recurrent Neural Network (RNN) to take a bunch of samples of text, and generate similar samples. It turns out there is a <a href="https://www.kaggle.com/rounakbanik/ted-talks">huge collection of TED Talk transcripts available</a> - I used them to generate a new TED Talk, which I then presented at the CodeMash lightning talks....as a TED Talk. It was super fun!</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/CodeMash?src=hash&ref_src=twsrc%5Etfw">#CodeMash</a> Lightning Talks happening now! <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> giving a machine learning-generated TED Talk <a href="https://t.co/IBtvjnl7lr">pic.twitter.com/IBtvjnl7lr</a></p>— John M. Wright (@Wright2Tweet) <a href="https://twitter.com/Wright2Tweet/status/951251467675029504?ref_src=twsrc%5Etfw">January 11, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">.<a href="https://twitter.com/jennifermarsman?ref_src=twsrc%5Etfw">@jennifermarsman</a> & <a href="https://twitter.com/Saelia?ref_src=twsrc%5Etfw">@Saelia</a> did a great workshop yesterday, on using neural networks to turn existing bodies of text into new ones.<br><br>And thus, I find myself preparing a lightning talk in which I present a fake TED talk, generated from existing TED talks, as a TED talk. <a href="https://twitter.com/hashtag/codemash?src=hash&ref_src=twsrc%5Etfw">#codemash</a> <a href="https://t.co/1Fw3bzNgUZ">pic.twitter.com/1Fw3bzNgUZ</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/951122404088131584?ref_src=twsrc%5Etfw">January 10, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>You can <a href="https://www.youtube.com/watch?v=qpeq-4dFVvg">listen to the talk on YouTube</a>.</p>
<h4>At Open Source North</h4>
<p>Open Source North is one of my favorite conferences to speak at. <a href="https://twitter.com/jeffurban">Jeff Urban</a> puts together a great conference.</p>
<p>The venue is also great:</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">I repeat my request from last year - if every conference could just install a Japanese garden for pre-game relaxation, that'd be great.<a href="https://twitter.com/hashtag/osn2018?src=hash&ref_src=twsrc%5Etfw">#osn2018</a> <a href="https://t.co/H0q69BTB8Y">pic.twitter.com/H0q69BTB8Y</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1007266869013831681?ref_src=twsrc%5Etfw">June 14, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I had a great time here, and a lot of people joined me to talk about React testing.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Packed room for <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> dropping knowledge bombs with <a href="https://twitter.com/fbjest?ref_src=twsrc%5Etfw">@fbjest</a> and <a href="https://twitter.com/Airbnb?ref_src=twsrc%5Etfw">@Airbnb</a>’s <a href="https://twitter.com/hashtag/Enzyme?src=hash&ref_src=twsrc%5Etfw">#Enzyme</a>.<br>.<br>.<a href="https://twitter.com/OpenSourceNorth?ref_src=twsrc%5Etfw">@OpenSourceNorth</a> <a href="https://twitter.com/hashtag/OSN2018?src=hash&ref_src=twsrc%5Etfw">#OSN2018</a> <a href="https://twitter.com/hashtag/DeveloperCommunity?src=hash&ref_src=twsrc%5Etfw">#DeveloperCommunity</a> <a href="https://t.co/zBjr7BpAi1">pic.twitter.com/zBjr7BpAi1</a></p>— Chris DeMars (@saltnburnem) <a href="https://twitter.com/saltnburnem/status/1007295805445890049?ref_src=twsrc%5Etfw">June 14, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>10/10 would do again....though I can't submit this year, due to it being held on my partner's birthday.</p>
<h4>At That Conference</h4>
<p><a href="http://www.thatconference.com">That Conference</a> is a special event to me - the first conference I attended, and the place I caught the speaking bug. I was thrilled to speak for the second time here. Even more thrilled when one of my speaker heroes, <a href="https://twitter.com/housecor">Cory House</a>, walked into the room halfway into my talk. If you watch <a href="https://www.youtube.com/watch?v=-JeyIdKPNsE&t=39m55s">the video</a>, you can hear me say "Hi, Cory!" because I am a total spaz and got excited.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Solid React testing advice from <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> at <a href="https://twitter.com/hashtag/ThatConference?src=hash&ref_src=twsrc%5Etfw">#ThatConference</a> <br><br>1. Consider extracting complex logic to plain JS utility functions. <br>2. Minimize common setup so it’s easy to see what makes each test unique.<br>3. Use enzyme-to-json for shorter snapshots<br>4. Optimize for fixing tests <a href="https://t.co/jRJd0np2vI">pic.twitter.com/jRJd0np2vI</a></p>— Cory House 🏠 (@housecor) <a href="https://twitter.com/housecor/status/1026867353185861632?ref_src=twsrc%5Etfw">August 7, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h4>In A Hot Dog Costume</h4>
<p>In the fall, I spoke for the first time at the Milwaukee JS meetup. Because it was halloween, and because I have a children's hot dog costume in my closet that I haven't gotten enough value out of, I talked about <a href="https://www.11ty.io">EleventyJS</a> and <a href="https://netlify.com">Netlify</a> while in a children's hot dog costume.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">We are the only place in <a href="https://twitter.com/hashtag/mketech?src=hash&ref_src=twsrc%5Etfw">#mketech</a> where a hotdog gives the presentation. At least, we are pretty sure. <a href="https://t.co/zzb3xeHVfh">pic.twitter.com/zzb3xeHVfh</a></p>— Milwaukee JavaScript (@mkejs) <a href="https://twitter.com/mkejs/status/1057413941536714752?ref_src=twsrc%5Etfw">October 30, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>You can <a href="https://t.co/dfAmGqgINb">watch a recording of this talk</a>!</p>
<p>I tried something in this talk that I don't usually do, and that I usually caution people to avoid - live demos. It felt like the right conditions for demos. I knew the subject very well, and I thought the meetup audience would want more than slides.</p>
<p>I'm not sure if it was the right decision. At the very least, I would not do demos while wearing a hot dog costume again. I spent a lot of time moving the costume out of my face while typing.</p>
<p>I also literally put someone to sleep in this talk. It was night-time and he was older, but I also just went way too long. But I'm one of the few people who can say I've done live demos while in a hot dog costume!</p>
<h3>I Started Recording My Talks</h3>
<p>One of my "goals" (what even are goals?) is to speak at an international conference. I'd heard from several people that many larger/international conferences won't even consider you without a video. I've not spoken at a conference that recorded my talk, so I took it upon myself and <a href="https://www.youtube.com/watch?v=-JeyIdKPNsE">started</a> <a href="https://www.youtube.com/watch?v=KxqKZHg7RWU">recording</a> <a href="https://www.youtube.com/watch?v=bmNuOsD0zF0">my talks</a>.</p>
<p>Inspired by advice from <a href="https://jeremybytes.blogspot.com/2016/09/recording-live-presentations.html">Jeremy Clark</a> and <a href="https://digitaldrummerj.me/recording-my-conference-talks/">Justin James</a>, I decided the video mattered less than the audio. I bought a cheap-ish action camera (GoPro Hero Session); it's fine. It records video! But not great video. It lasts long enough on one charge to do slightly more than one hour-long session.</p>
<p>I traded a bike trailer with <a href="https://twitter.com/ivaneisenberg">Ivan Eisenberg</a> for a Zoom recorder. It is much better than the camera. I wear two microphones when I'm presenting, to get good recorded sound, but no one makes fun of me. (At least not to my face.)</p>
<p>I'm not great at editing. Correction - I'm not even mediocre. It's hard to get good at something when you do it about 4 times a year. But hey, people can see that I don't throw up on myself when I speak, and that's all that matters. I think?</p>
<h3>I Did Less Writing Than I'd Hoped</h3>
<p>I was secretly hoping to write about one article a month in 2018. I ended up with less than half that. I spent <strong>a lot</strong> of time working on talks/workshops, and it prevented me from writing more.</p>
<p>I shared a few of my articles on Medium. <a href="https://medium.com/@pepopowitz/chekhovs-gun-and-better-unit-tests-96fb439e6d0e">I wrote about what we can learn about unit testing from a Russian playwright</a>, <a href="https://medium.com/@pepopowitz/10-tips-for-writing-conference-talks-20d958bf3f51">I shared some tips for writing a conference talk</a>, and <a href="https://medium.com/@pepopowitz/5-tips-for-writing-a-conference-submission-ebd41ae9a961">I offered advice on submitting an idea to a conference</a>.</p>
<h3>But I Got Paid To Write!</h3>
<p>I've kicked around the idea of getting into developer relations for a while. One thing I've never been sure about is being paid to write/speak about a specific product, instead of "whatever I want." This feels like a situation where I'd have trouble motivating myself.</p>
<p>I got an opportunity to find out, when <a href="https://twitter.com/flydotio">Fly</a> accepted my proposal to <a href="https://fly.io/articles/adding-another-layer-to-the-stack/">write an article for them</a>. They have a great team. They were really helpful getting me up to speed with Fly Edge Apps, and they helped me craft an idea and a solid article.</p>
<p>I came away from the experience feeling like I could definitely write/speak for a living, especially if I believed in the product. I'm still worried about the amount of travel that a dev advocate does. That just isn't realistic for me right now. But maybe this type of role is in my future.</p>
<h3>I Was On A Podcast! Twice!</h3>
<p>Early in 2018, <a href="https://twitter.com/davidpine7">David Pine</a> connected me with <a href="https://twitter.com/raelyard">Dave Rael</a>, and I got to be a guest on the Developer On Fire podcast! I had a great time <a href="https://developeronfire.com/podcast/episode-317-steven-hicks-scary-things">talking to Dave about "doing scary things", becoming a speaker, and tightening your feedback loops</a>. And the movie "Medicine Man".</p>
<p>In early summer, while at <a href="https://twitter.com/MusicCityTech">Music City Tech</a> in Nashville, I got a chance to record another podcast - this time with <a href="https://twitter.com/mgroves">Matt Groves</a>, on the Cross-Cutting Concerns podcast. <a href="https://crosscuttingconcerns.com/Podcast-089-Steve-Hicks-Speaking">We talked about the differences between writing an hour long talk and building a half-day workshop</a>. Spoiler: there are differences.</p>
<h3>My Scary Thing Was Workshops!</h3>
<p>This time last year, I was trying to decide what my "scary thing" was going to be in 2018. I had some candidates, but I wasn't really sure where I wanted to invest my time.</p>
<p>While I was at CodeMash 2018, I bounced in between the workshops, taking notes on what seemed to work well and what didn't. By spring, I decided that leading workshops was where I wanted to put my energy for 2018. I started submitting workshop ideas to conferences. I was lucky enough to have ideas accepted twice.</p>
<h4>Music City Tech</h4>
<p>In late May, at Music City Tech in Nashville, I led a workshop called "Building Quality JavaScript With Test-Driven Development". It was 4 hours long, and took <em>a lot</em> of time to build. It went pretty well, but I definitely learned a few things about keeping people engaged & energized. I needed to take more breaks than I thought. My exercises were too long & too difficult. In one case, I made too large of a leap from one exercise to the next, and didn't give enough background or context.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">"Building Quality JavaScript with Test-Driven Development" with <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a>. <a href="https://t.co/WIuXiW9wz9">pic.twitter.com/WIuXiW9wz9</a></p>— Music City Tech (@MusicCityTech) <a href="https://twitter.com/MusicCityTech/status/1002264602422456331?ref_src=twsrc%5Etfw">May 31, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h4>Dev Up</h4>
<p>In October, I led a workshop called "Building Your First React App" at the dev Up Conference in St. Louis. This went really well! I applied a lot of my learnings from my first workshop. Smaller exercises; more variety of exercises; lots of context & background for every exercise.</p>
<p>This was a full-day workshop - to be honest, I don't think a half-day workshop on React would be enough to really get someone started.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Oodles and oodles of thanks to this group, who've hung out with me all day long to talk about <a href="https://twitter.com/reactjs?ref_src=twsrc%5Etfw">@reactjs</a> at <a href="https://twitter.com/hashtag/devup2018?src=hash&ref_src=twsrc%5Etfw">#devup2018</a>. <a href="https://t.co/WKhkmUnhEm">pic.twitter.com/WKhkmUnhEm</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1049406551994306560?ref_src=twsrc%5Etfw">October 8, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h3>And A Trail Half Marathon!</h3>
<p>Staying fit is as important to me as sharing with the tech community. I've raced about 3 triathlons a summer with my partner the last few years, and I've considered myself a mountain biker for the last 20 years. In 2018 I learned that I really really loved trail running. So much so that I've shifted my identity from "mountain biker" to "trail runner".</p>
<p>I have long said that I never wanted to run more than a 10k, but something about a half marathon on trail got me excited. I raced the North Face Endurance Challenge 10k in September of 2017, and loved it - this was enough to convince me I could do a half marathon in 2018.</p>
<p>And I did it! And I loved it. And it went great. Except the part where I forgot my water bottle at home. This was a bigger deal than you'd think - there was a 5 mile stretch in between water stations, and I drink a lot while I run.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Step 1: Find something you love to do.<br>Step 2: Find a good support system. <br>Step 3: Do more of the thing you love to do, with help from your support system.<br>Step 4: Happiness.<br><br>/fin <a href="https://t.co/kEp7QXl2y9">pic.twitter.com/kEp7QXl2y9</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1041502063824457728?ref_src=twsrc%5Etfw">September 17, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h4>And A Road Half Marathon!</h4>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Finished my second half marathon 45 seconds ahead of my A-goal. The weather was a perfect 45°F. The beer tent was cozy. My wife missed her A-goal by a couple minutes, but this was her first half marathon, she did great, and I'm super proud of her. <a href="https://t.co/3KVTb5tXYr">pic.twitter.com/3KVTb5tXYr</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/1058814295474143232?ref_src=twsrc%5Etfw">November 3, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h3>And A New Job! (Again)</h3>
<p>I started a new job in December, 2017. I didn't really enjoy it, for a variety of reasons. I knew pretty quickly that it was a temporary stop.</p>
<p>In June 2018, I met <a href="https://twitter.com/jonallured">Jon Allured</a> at Open Source North. We completely hit it off, and hung out a bunch that day. Within a couple weeks, he introduced me to <a href="https://www.artsy.net">Artsy</a>, and I started a long interview process.</p>
<p>In October, 2018, I started as an engineer at Artsy. I'm working out of my basement, as the main office is in New York City. I love everything about it. I'm learning a ton. I'm becoming a Rails developer, which is a strange thing for me to say. One thing I know for sure - <a href="https://github.blog/2018-12-07-octoverse-emoji-on-github/">ruby developers use ❤️ emojis A LOT</a>.</p>
<h3>Cream City Code</h3>
<p>In October, we held the first ever Cream City Code - a re-branded MKE DOT NET. I've helped organize this conference the last few years, and the day of the event is an absolute blast for me. Unfortunately, I missed most of it this year, due to a conflict. CCC was bigger than ever this year, and the team continues to get better at putting on this event. I am really proud of the lineup we put together for speakers this year - yet bummed that I barely got to see any of them 😢.</p>
<h3>Burnout</h3>
<p>October was an absolute mess for me. I wrote a full-day workshop for dev Up Conference, spoke twice & led that workshop at dev Up, helped put on Cream City Code, started my new job and spent the first week in NYC, and spoke at MKE JS. Most of that happened in the first couple weeks of October. Things were rough at home, as I was spending a lot of time with people that weren't my family.</p>
<p>I had another event scheduled for October - LibertyJS. I had to cancel it. I felt really awful, but I did a really bad job of planning my month. In the end, I decided I would rather have the organizers of LibertyJS hate me than have my partner and kids hate me. I backed out.</p>
<p>I burned myself out. By the end of October, I didn't want to spend a minute of my free-time away from my family. I skipped Milwaukee Code Camp - an event that means a lot to me, as a local free event for the community. I just didn't have it in me.</p>
<p>I'm mostly over the burnout now. I did it to myself, though, and I need to be more mindful of my schedule in the future.</p>
<h2>2019</h2>
<p>That's my 2018. What's ahead for 2019?</p>
<h3>Less Speaking</h3>
<p>Looking at open CFPs, I have a lot of conflicts for events that I'd like to submit to. I'm also doing a better job of communicating with my partner about the events that are on my radar. There just aren't nearly as many events I'll be submitting to this year.</p>
<p>I'm okay with this. The nice thing about less speaking is that I'll have more time for writing. I think I'll be able to meet my goal pace of one article per month.</p>
<p>The hardest part is that I'm not able to submit to two of my absolute favorite conferences: Open Source North and Self.conference. Hopefully things align better in 2020. (Holy crap. Next year is 2020.)</p>
<h3>More Workshops!</h3>
<p>On the other hand, I now have a really solid React workshop that I'm planning on giving more often. I'm submitting it to conferences, but I also plan on running it in Milwaukee a couple times, for the community here. I'll charge enough to get a space paid for and some food, and maybe enough money to buy myself a beer.</p>
<h3>The Year of Trail Running</h3>
<p>I ❤️ trail running. I love it. I've got several races on my calendar already for this year - including 2 half marathons, and a 9 mile race. I dream of a future where I am a crazy ultra-marathon runner. I don't have time for that yet, but this year I want to get my calves pretty dirty.</p>
<p>Combining trail races with triathlons and road races, I have 11 races that I'm looking at this year. I might have a problem.</p>
<h3>Finding Balance</h3>
<p>More than anything, I just want to find balance. I don't want to burn myself out again. I want to feel like I'm giving back to the Milwaukee & midwest dev community, and still have energy left for my family and running.</p>
<p>2019 is the year of balance for me. And I wish you the same.</p>
5 Tips For Writing A Conference Submission2018-08-13T12:00:00+00:00https://stevenhicks.me/blog/2018/08/tips-for-writing-conference-submissions/<p>Writing a winning conference talk submission seems like a dark art.</p>
<p>In the past couple months, I've read every word of nearly 250 conference talk submissions. Multiple times. I'm on the speaker committee for <a href="https://www.creamcitycode.com/">Cream City Code</a> (nee MKE DOT NET), and we recently finalized our selections for our October 2018 event.</p>
<p>I <strong>cannot</strong> refute the idea that small animal sacrifice or eye of newt will get you into your next tech conference. I <strong>can</strong> share some tips for writing a submission that I'd love to read, though!</p>
<p><strong>Note:</strong> I'll pick on myself a little in this article, so as not to pick on anyone else. All examples are based on my own talks.</p>
<h2>Tip 1: Clear titles are better than witty titles</h2>
<p>Some people recommend that you craft a witty title in your submission, to stand out from the others.</p>
<p>A better way to stand out is to have a title that tells me (and the attendees) exactly what the talk is about. If you can craft a witty or punny title and still give me full clarity on the talk subject, that's great. But I will not dock you for a simple, straightforward title like "Building A Blog With Static Website Generation." It tells me what I'm going to learn about!</p>
<p>On the other hand, a title like "Back-ends? Where we're going, we don't need...back-ends..." is a super awesome reference to <a href="https://www.youtube.com/watch?v=flge_rw6RG0">a great movie</a>, but it doesn't tell the reviewers what the talk is about. Worse, it doesn't tell <strong>the attendees</strong> what the talk is about. So when they check the schedule right before the next session, deciding where to go next, they will skip over your talk and find a title they understand.</p>
<p>In my experience, talks don't get accepted/rejected <strong>by conference organizers</strong> based on title alone. They do get chosen/skipped <strong>by attendees</strong> based on title alone, though. Help them find the right session by using a clear title.</p>
<h2>Tip 2: Don't stress out about the "elevator pitch"</h2>
<p>We use <a href="https://www.papercall.io/">Papercall</a> to manage our CFP. Papercall provides two fields for people to describe their talk: Abstract and Description.</p>
<p>Description is not character-limited. Abstract is limited, though, to 300 characters. Characters - not words.</p>
<p>It is <strong>really hard</strong> to write something worthwhile in 300 characters. There are a few strategies I've seen (or used) to maximize the 300 characters:</p>
<ul>
<li>Cut sentences/phrases out of the Description field to get it down to 300</li>
<li>Write a single sentence that is a lengthy version of the title</li>
<li>Apply SEO skills, and pack keywords</li>
</ul>
<p>All these result in something that is pretty much meaningless to me. I've stopped reading the Abstract field.</p>
<p>I'm sure I'm an outlier on this, and you should definitely still fill this in for submissions to <a href="https://www.creamcitycode.com/">Cream City Code</a> and other events. But your efforts are better spent on the Description than the 300-character Abstract.</p>
<h2>Tip 3: Tell me why I should attend, and what I'll walk away with</h2>
<p>Sometimes we get submissions where the Description field is about the same length as the 300-character Abstract field.</p>
<p>I almost always down-vote these submissions, for lack of information. The Description field is not character-limited. It ends up on the website, and in the printed program. This is what attendees read to decide where to go - give them a good idea of what they're going to learn about!</p>
<p>There are several components I love to see in a Description:</p>
<ul>
<li>a common pain-point, or an explanation why the talk is important</li>
<li>a brief description of the main topic</li>
<li>a few things I'll take away from the talk</li>
</ul>
<p>These components lead again to clarity: not only for me, but for the people looking for a session to attend.</p>
<p>The most important thing your Description does is get the right audience in the room. You don't want <strong>everyone</strong> to attend your talk, you want the people who will most benefit from it.</p>
<p>If you describe a problem, the solution, and some takeaways, you'll get the right attendees in your session. You'll also help the organizers feel confident about the lineup they're putting together.</p>
<p>It's also important to note that you can write a Description that far exceeds 300 characters, but still doesn't give me enough information. Don't add fluff to make your Description longer. Add words that are meaningful, and that add value.</p>
<h2>Tip 4: A few small errors are fine...but not too many</h2>
<p>In the second grade, I took second place in the Oconomowoc School District spelling bee. I lost on the word "embarrassed." The jokes wrote themselves: "It's okay, Steve! Don't be...(wait for it)...<strong>EMBARRASSED</strong>!!!" To this day, I am convinced I spelled it correctly, and the judges did not.</p>
<p>That is to say, I am an above average speller. I am also married to an English teacher - and I think there is a super-secret English teacher test for potential partners, to weed out the grammatically deficient. So I think I'm above average at grammar, too.</p>
<p>A couple small errors will get noticed by me, but I'll look past them. We all make mistakes - I once submitted a cover letter with the wrong company name! I'm willing to overlook a couple small issues.</p>
<p>But I won't overlook a submission with lots of spelling or grammar errors. It makes me think you haven't thought much beyond the abstract. If you don't care about the quality of your submission, I'm not sure you'll care about the quality of your talk. Most of all, I'm worried you might not do a great job of communicating to the audience.</p>
<p>Use tools to check your spelling and grammar. Use <a href="http://hemingwayapp.com/">Hemingway</a> or a similar tool to reduce the reading level of your submission. If you struggle with grammar and spelling, have someone review your submission.</p>
<h2>Tip 5: Please don't make people feel bad</h2>
<p>Sometimes things are worded in a way that I feel like I'm an idiot, or not smart enough to attend your session. If I'm a WordPress developer, the sentence "If you're still using WordPress, you're doing it wrong." makes me feel bad about myself. I'll admit that I'm pretty sensitive - but if your abstract makes me feel bad about myself, I'm not going to attend.</p>
<p>You probably intend to be funny. You and your officemates maybe say things like "you're doing it wrong" as a joke, and you laugh with each other. I get that. But I still don't like it.</p>
<p>Submissions with exclusive/gate-keeper language like this are a red flag to me. They worry me that someone will attend your session who doesn't get your jokes. That you won't have a good sense of your audience, and you'll push the jokes too far. And that we'll have to have a conversation that none of us are happy about.</p>
<h2>Keep submitting your talk!</h2>
<p>All of these recommendations are based on my preferences. Different conferences are organized by different people with different preferences. Even within <a href="https://www.creamcitycode.com/">Cream City Code</a>, there are differences in the preferences of all 6 reviewers. (This is intentional, and a benefit. We want different preferences, to give the lineup a good balance.)</p>
<p>This is one of the main reasons conference talk submission seems like a dark art. What gets you into one conference won't get you into another. Different reviewers are looking for different things.</p>
<p>The large majority of submissions to conferences are well-written. Conferences just don't have room for them all. At <a href="https://www.creamcitycode.com/">Cream City Code</a>, we start with our highest-rated sessions, then fit other highly-rated sessions around them to give broad topic coverage. There are a lot of really good submissions that we just can't pick, because we only have 30 slots!</p>
<p>If you submit to a conference and don't get in, there's a good chance you did nothing wrong. Please don't take it personally. Keep submitting!</p>
<p>It's all about finding a group of reviewers with which your talk resonates.</p>
10 Tips For Writing Conference Talks2018-04-22T12:00:00+00:00https://stevenhicks.me/blog/2018/04/tips-for-writing-talks/<div>
<img class="half-scale" src="../anxiety-over-time.jpg" alt="My anxiety while preparing for a talk looks like a saw." />
</div>
<h2>Speaking is fun, you should try it.</h2>
<p>For the last couple years, my priorities in life have been three things:</p>
<ol>
<li>My family</li>
<li>Being active</li>
<li>Speaking at tech meetups & conferences</li>
</ol>
<p>Speaking is pretty new for me. My first talk in public was in February, 2016. In the last couple years, I've given over 20 talks at local meetups and code camps, and conferences throughout the Midwest. It's been a ton of fun, and I highly recommend trying it.</p>
<p>I've learned <strong>a ton</strong> about speaking in the last couple years. Here are 10 things I've learned about preparing a conference or meetup talk.</p>
<h2>Tip 1: Keep your thoughts organized.</h2>
<p>It takes me months to write a talk. Often I have multiple ideas formulating at the same time. If I tried to keep all of the different ideas in my brain, I would lose about 95% of them. I need tools to keep track of it all.</p>
<p>To stay organized, I use two main tools.</p>
<h3>Trello</h3>
<p>I keep a Trello board to keep track of all my speaking ideas, talks, submissions, and upcoming engagements. This is high-level stuff, just so I know what I need to be working on every night. I have lists for conferences I want to submit to, open CFPs, abstracts I need to write, talks I'm writing, and talks that are ready to submit.</p>
<h3>Git Repository</h3>
<p>I have a Git repository that contains all my talks. This is more detailed than what's in my Trello board. Abstracts, outlines, slides - it's all in the repo. I wrestle back and forth about whether this should be private or public. It is currently private.</p>
<h2>Tip 2: Get outside. Creativity comes from many places.</h2>
<p>Most of my ideas don't come while I'm working. I do a lot of thinking and workshopping ideas in my head in a few key places - in the shower, in bed when I fall asleep/wake up, listening to podcasts in my car, and above all during workouts.</p>
<p>I generally prefer working out by myself, because it's my best thinking time. I feel bad when people ask me if I am looking for a workout buddy, because unless it's my wife, the answer is almost entirely no. I really like being social with people, but exercise is such a personal thing to me. It's actually the only time I can think of that I'll say "no" to company. It's just such a valuable time for my mind.</p>
<p>My ideas usually take months before I even turn them into a conference submission. They simmer in my brain. I work through outlines and brainstorms, making sure that I have enough of a cohesive message to deliver it to an audience.</p>
<h2>Tip 3: Keep a notebook on you at all times.</h2>
<p>The ideas come at all times. I've lost enough ideas in the past that I want to be able to write them down as soon as I get them.</p>
<p>I take notes as I think of things. My daughters make me these awesome pocket-sized notebooks out of printer paper, and I keep them in a slim wallet case in my pocket, along with a pen.</p>
<p>I have become my father. :)</p>
<p>Sometimes the things I write down are points I want to make, sometimes they are jokes, sometimes they are drawings I want to include. Sometimes they are just the way I want to deliver a specific point.</p>
<h2>Tip 4: Don't apply force to an idea. Let it present itself.</h2>
<p>Sometimes I feel like an idea has legs, but I just can't find the overall message. I've found that forcing it to become something is less effective than waiting for it to present itself.</p>
<p>When I hit a brick wall with an idea, I still add it to my Trello board. If it's a good idea, it will eventually work its way back to the front of my thoughts.</p>
<p>My favorite talk - <a href="https://steven-j-hicks-speaking.netlify.com/do-scary-things">Maximize Professional Growth By Doing Scary Things</a> - took 9 months to graduate from an idea to an outline.</p>
<p>If I feel like I have enough notes & ideas to deliver it to an audience, it becomes an outline. I give it a folder in my Git repo. I build an outline out of all the notes I've written down. They are often scatterbrained and messy.</p>
<p>Then I let it simmer some more.</p>
<h2>Tip 5: Start with content and a message. Save the slides for last.</h2>
<p>I'll work for a month or so at building an outline. At this stage, I'm mostly worried about two things - all of the content I think is worth delivering, and a single message I want my audience to walk away with.</p>
<p>I prefer a simple nested outline, in a text editor. It's quick and easy for me to move things around. I've heard others say they use mind-mapping software at this stage.</p>
<p>I don't start writing abstracts or slides until I've got a rough outline and a message established.</p>
<h2>Tip 6: Don't write an abstract if you aren't certain you can write the talk.</h2>
<p>When I first started trying to get into speaking, I didn't have much restraint on the abstracts I submitted. I wrote and submitted as many as I could think of, with little thought to how I would fill 45 minutes. This got me in trouble.</p>
<p>Once, I found that I had to write a talk that I wasn't really interested in. This was very hard to do, and I just kept procrastinating. I eventually reached out to the "organizers" (who happened to be my coworkers) and asked if I could change topics. Thankfully, I could.</p>
<p>I also found that I had too many talks to write this way. Different conferences picked up different ideas, and I had to write them all. This is probably normal when you're starting out, but I think my tendency to abstract without a solid idea of the content exacerbated the problem.</p>
<p>Now, I don't feel like I can write a solid abstract without a good outline. The outline isn't finalized at this stage - but it gives me a good idea of what's in the talk, so that I can summarize it with confidence.</p>
<p>Writing my abstract takes me many tries. I'll throw a few attempts in a markdown file in my repo, and none of them are usually what I end up with. I pick and choose from all the attempts, and assemble them into something I feel good about.</p>
<h2>Tip 7: Get feedback from people. Sort the feedback before you address it.</h2>
<p>Armed with a rough draft of an abstract, I'll reach out to a few friends, and ask them for feedback. I generally ask a few key people who I know will give me the kind of feedback I'm looking for. It takes time to determine who you can trust to give you good feedback, but it's worth the effort to find out.</p>
<p>When you get helpful, constructive feedback, it helps you improve your ideas & abstracts tremendously. But even with feedback from people you trust, not all feedback is worth addressing.</p>
<p>Sometimes, you get feedback that is more about the person giving the feedback, and less about your abstract. It could be a suggestion that you rewrite your talk to be about something they're more interested in. It might be that their past experiences influence their opinions of what you've written. Feedback from friends almost always comes from good intentions - but I've found it important to sort the feedback I get. I will generally only address feedback with the following characteristics:</p>
<ol>
<li>It is definitely about my idea or abstract.</li>
<li>It is specific. We have an idea on how I can improve it, or we can at least identify what the problem is.</li>
<li>It is more than just an opinion.</li>
</ol>
<p>This is not to say that I don't like feedback - I love it. I'll continue to ask for feedback, and you should too. It's just important to recognize when it is actually something you can/should address.</p>
<h2>Tip 8: Remember that your talk is for the audience.</h2>
<p>As I'm turning my outline into a full-length talk, I often have to remind myself that the talk isn't for me - it's for the people listening. Even if it's a story I'm telling about my experiences, people aren't listening to <strong>me</strong> - they are listening to my message. They aren't interested in how <strong>I</strong> got through a problem, they are interested in how my experiences could help <strong>them</strong> get through their problems.</p>
<p>I focus on optimizing the flow of the information. I want my listeners to digest the topic with as little effort as possible. I once resorted to a card-sorting exercise, because I just couldn't work out the flow.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Sometimes, putting together a talk looks more like this than "making slides." Trying to find a way to fit pieces together to make it flow. <a href="https://t.co/LGbh3dkn63">pic.twitter.com/LGbh3dkn63</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/909875532120170498?ref_src=twsrc%5Etfw">September 18, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I want my talks to be (1) clear, (2) inspiring, and (3) entertaining. Making this happen is usually harder than identifying the content & message. I spend a lot of time editing my talk to meet these goals. The attendees paid to be at the conference, though, and I didn't - so any effort I can put in to give them more value is worth it.</p>
<h2>Tip 9: The perfect image is hard to find. Make your own!</h2>
<p>When my outline approaches "good enough," I finally start to build my slides. I use a tool called <a href="http://remarkjs.com/">RemarkJS</a> for my slides. It's an HTML/CSS/JS-based presentation tool, and it allows me to write all my slides in Markdown. Since I already have an outline in a text file, it's pretty easy to turn that into Markdown slides.</p>
<p>As I'm doing this, I start thinking about imagery. Images add a lot to your slides, but they have to be the right images. Showing an unrelated image can distract listeners - they spend time trying to decipher how the image relates, instead of listening to you.</p>
<p>I've spent a lot of time looking for the right images.</p>
<p>Early on, I would do an image search on Google. This takes more time than you think - especially if you're looking for something specific.</p>
<p>I've used sites like <a href="//unsplash.com">Unsplash</a>, <a href="//pexels.com">Pexels</a>, and <a href="//flickr.com">Flickr</a>, and these all have great images. They still take a lot of time when you're looking for something specific, though.</p>
<p>Inspired by <a href="//twitter.com/reverentgeek">David Neal</a>, I started drawing my own images on an iPad. Initially, they were bad. Even today, many of them are not great. But they add two major benefits to my talks:</p>
<ol>
<li>They add a personal touch to the slides. People overwhelmingly appreciate this, and comment on it often.</li>
<li>I spend significantly less time drawing all my images than I spent searching for them.</li>
</ol>
<p>Sometimes I get to a point in my slide preparation where all I want to do is draw images. It's therapeutic, and it breaks up the monotony of building out slides and practicing.</p>
<div>
<img class="half-scale" src="../pizza.jpg" alt="My oven sets off a smoke alarm every time I cook a pizza." />
<cite>Seriously, though. Try to find a good image of an oven that sets off a smoke alarm every time you cook a pizza in it.</cite>
</div>
<h2>Tip 10: Find The Way That Works For You</h2>
<p>If you're interested in presenting at tech meetups and conferences, or getting better, you should <a href="https://channel9.msdn.com/Shows/On-NET/Public-speaking-with-Scott-Hanselman-Kendra-Havens-Maria-Naggaga-Nakanwagi-Kasey-Uhlenhuth-and-Donov">watch this video from MSDN</a>. There are a ton of good tips in it, but my favorite that comes out of it is from <a href="https://twitter.com/shanselman">Scott Hanselman</a> -</p>
<blockquote>
<p>Find the way that works for you, and do it unapologetically.</p>
</blockquote>
<p>This applies to so much more than just speaking, but it is definitely applicable here. In the past couple years, I've watched more videos and read more tweets and articles about speaking than 99% of the world. The one thing I can say for certain is that the process is different for everyone.</p>
<p>I effectively script every talk that I do. I rely on my notes as I speak. I don't read them word for word, but I have thought through almost every word I'm going to say ahead of time. I do this because I'm afraid that if I ramble, I'm going to say things I regret. Scripting your talk goes against almost every speaker's advice. But it works for me, because it makes me feel more comfortable when I'm up there, and I can deliver those lines without sounding like a robot. (I think.)</p>
<p>I also don't practice very much. This also goes against most speakers' advice. Many even say "you should practice so much you're bored with your talk." But I spend so much time with the ideas bouncing around in my brain, and editing my outline, and editing my outline some more, that I don't feel like I need a ton of practice. By the time I've actually built my slide deck, I know most of the content pretty well.</p>
<p>And really, that's all that matters when you're writing a talk - do what works for you. If you don't know what works for you, try some things that you think might. You'll find out pretty quickly if they don't.</p>
<p>Different strokes for different folks, as they say.</p>
On Overcoming Rejection2018-03-01T12:00:00+00:00https://stevenhicks.me/blog/2018/01/on-overcoming-rejection/<p>2017 was huge for me. After discovering that I wanted to get into speaking at conferences, in 2017 I made those dreams a reality. I attacked the conference submission season with reckless abandon. I was going to submit anywhere, and let <strong>them</strong> tell me "no" instead of telling <strong>myself</strong> "no." There was some rejection here and there, but also a lot of success. At one point, I had received so many acceptances that I started retracting submissions. There are only so many weekends in a Wisconsin summer, and I didn't want to fill them all with conferences.</p>
<p>By the end of 2017, I'd spoken at a handful of events, and I was feeling really good about what 2018 would bring. Things got off to a good start. In the fall of 2017, I got accepted to two 2018 events - <a href="http://www.codemash.org/">CodeMash</a> in Ohio and <a href="http://webcon.illinois.edu/">WebCon</a> in Illinois.</p>
<p>Unfortunately, I'd done a bad job of reading my calendar. When I shared the news with my wife, she reminded me that we had planned on taking the kids somewhere for spring break the week of WebCon. I avoided sending the email to WebCon for a week or two - but I had to decline the invite.</p>
<h2>Karma</h2>
<p>The second I sent the email, I said out loud "I've got some bad karma coming my way." One of the things I hate most in life is saying you'll do something, then backing out. Whether it's going to a concert, meeting for lunch, speaking at a conference, whatever - I am a reliable person, and doing things that chip away at my reliability make me mad.</p>
<p>Today, I received a notice from <a href="https://ndcoslo.com/">NDC Oslo 2018</a> that my sessions were not accepted. Nevermind the fact that NDC is a notoriously difficult conference to get into, and that I probably don't have the experience yet to get into an international conference. All I could think was "this is my 11th straight conference rejection since I sent that email to WebCon! It's obviously karma! @#$#@$!#$@#!!!"</p>
<p>After venting to some friends about how frustrated I am, I am done with this nonsense. This whole WebCon/karma/0-11 thing is just a story I keep telling myself. It's got a nice narrative quality to it. It would make good TV.</p>
<p>But it's stupid. It's just a set of events that happened. It doesn't define me. I let it define me in my self-talk, too much. But I know that it doesn't.</p>
<p>In talking to people recently about how frustrated I am with my "curse," one suggestion consistently arose. "Stop it. Channel this into something productive." So with that, here are some things we all need to remember to do when we are feeling rejected. Yes, "we." This is as much for me as it is for you.</p>
<h2>Talk To Someone</h2>
<p>This is probably the simplest and most obvious thing we can do. But I'm saying it anyway - because sometimes I forget to do it, so maybe you do too. I've let this karma storyline fester in my mind for a long time. Saying it out loud to people helps me remember that <strong><em>I'M JOKING</em></strong> and this is not a thing to actually believe.</p>
<p>Hopefully you have someone who is better at <strong>listening</strong> than I am (developers just want to fix things!). Saying things to someone else feels so much better than saying them to yourself. This is probably because we treat ourselves like total crap with our self-talk.</p>
<h2>Change Your Self-Talk</h2>
<p>Think about the things you say to yourself when things aren't going your way. It's not good, right? Today I said to myself "Why the f*** am I doing this anymore?" I actually said that to <a href="https://twitter.com/davidpine7">David Pine</a>, too. He correctly told me to stop being a dummy.</p>
<p>You'd never say those awful words about a friend who was struggling. "Why the f*** are you still doing that? You're terrible at it." Even writing it as a hypothetical situation feels ridiculous.</p>
<p>Treat yourself like you'd treat your friends. Build yourself up. Don't tear yourself down. Again, maybe this is obvious to you. It's obvious to me too, but it doesn't mean that I don't forget to do it sometimes.</p>
<h2>Find A Mantra</h2>
<p>Mantras are a big help for me getting through challenges.</p>
<p>For sports-y things, the one I always use is "Come on." This is a thing that rock-climbers say a lot to each other, especially when bouldering, as they are about to make a tough move. I'm actually not totally sure if it's a thing ALL rock climbers say, or if it's just a thing that they say at the gym I climb at. It's a phrase that I say to myself when I need a confidence boost. It gives me instant strength and power.</p>
<p>I didn't really have a mantra for rejection, until today. I found this one <a href="http://alwayswellwithin.com/2015/09/27/mantras-to-boost-confidence/">on the internet</a>, and I like it.</p>
<blockquote>
<p>My "failures" are stepping stones to success.</p>
</blockquote>
<p>I think it is something I'm going to keep around, for moments like today. Maybe repeating a mantra can help you, too.</p>
<h2>Meditate</h2>
<p>I'm trying to get more regular at meditation. One of the things that really helped me get into speaking was weekly guided meditation from my yoga instructor. I've struggled to find time to do it since then, but I think I just recently found a time that I can get 5 minutes in every day.</p>
<p>Meditation has been really helpful for me. If you haven't tried it, or it's been a while, I recommend it. I use the <a href="http://www.whil.com/">whil</a> app because it is free through work, but I wouldn't hesitate to try another one. If you're pretty good with focus, you probably don't even need an app. Sometimes I just need it to keep me from chasing shiny things.</p>
<p>After I finished venting about my NDC rejection, the first thing I did was a "confidence" meditation. It helped a ton. It is a guaranteed and immediate way to change your self-talk. Try it some time.</p>
<h2>Promote The Things That Are Working</h2>
<p>Here are some things that have gone my way during the time that I've been declined by 11 conferences.</p>
<ul>
<li>I got a new job, and I really like it!</li>
<li>I spoke at CodeMash! That's also a pretty hard conference to get into!</li>
<li>While I was there, I got to hang out with the guy who inspired me to start speaking at conferences! We talked about our families!</li>
<li>Also while I was there, I put together a lightning talk in one day, and it was the most fun talk I've ever given!</li>
<li>Just today, <a href="https://twitter.com/raelyard">Dave Rael</a> released a new <a href="http://developeronfire.com/podcast/episode-317-steven-hicks-scary-things">Developer On Fire</a> podcast episode, featuring me! This is awesome. The guests he has on are some of my heroes. To even be <strong>CONSIDERED</strong> to be on his podcast is an honor.</li>
<li>I scheduled a time to talk to Clark Sell on the <a href="https://www.youtube.com/playlist?list=PLbvKfz3RGx_zTquL6x028DYufot2v5Ddr">AskTHAT</a> series! This is exactly the kind of stuff I want to do to propel myself into speaking greatness!</li>
<li>Other things I'm not remembering or can't really talk about!</li>
</ul>
<p>Instead of worrying about how long the streak is going to go before I get accepted, I'm going to tell people about that stuff. Maybe this approach of promoting the positive can help us focus on the things that make us feel good about ourselves.</p>
<h2>Create Something</h2>
<p>I had a lot of negative energy when I got the NDC email. That kind of energy doesn't really help anyone. Again, we all know this....this is just a friendly reminder.</p>
<p>Channeling that energy into this post has been really helpful. It's removed the thoughts of "Why am I still even trying???" from my head, and replaced them with ideas. Ideas that will become blog posts, and talks, and abstracts.</p>
<p>For me, channeling the energy means "producing something helpful." For you, channeling it might mean "expressing your feelings". The medium might be art, it might be writing, it might even be code. Whatever it is, creative energy is so much more valuable than destructive.</p>
<p>I know you know these things. I know them too. Let's keep reminding each other, okay?</p>
<h2>Keep Laying Seeds</h2>
<p>When I woke up today, I was frustrated about rejection #10, which I'd received last night. Lying in bed, I reminded myself something I've been thinking a lot lately -</p>
<blockquote>
<p>Just keep laying seeds. Eventually some of them will grow.</p>
</blockquote>
<p>This afternoon, I overheard a coworker say almost the exact same thing about buying stocks. I consider this to be official confirmation that this is a good approach.</p>
<p>I'm going to keep throwing submissions at conference CFPs. I hope you lay more seeds, too. If we keep it up, eventually one is going to grow like Jack's beanstalk.</p>
2017 In Review2018-01-31T12:00:00+00:00https://stevenhicks.me/blog/2018/01/2017-in-review/<h2>2017</h2>
<p>People say scary things about turning 40. Your body hurts more. You wonder if you're doing the right thing with your life. You just feel sleepy all the time.</p>
<p>2017 was my 40th year on this planet. While I won't totally argue with some of the things people say about being 40, I will also say this - I hope every year can be as good as my 40th year was.</p>
<h2>Speaking</h2>
<p>One of my goals in 2017 was to get out and speak a ton. I had a goal of speaking 12 times - averaging once per month. I nailed that exactly! In 2017, I gave....</p>
<ul>
<li>6 talks at conferences</li>
<li>2 talks at code camps</li>
<li>4 talks at meetups</li>
</ul>
<p>The talk I gave most was <a href="https://steven-j-hicks-speaking.netlify.com/getting-started-with-react">Getting Started With React</a>. This was a surprise for me - I hadn't even considered doing an "intro" style talk at the beginning of the year. I figured no one would want to hear "another intro." I was wrong.</p>
<h3>Self.conference</h3>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">It's going to take me a couple days to digest all the amazing ideas for being a better person and dev <a href="https://twitter.com/selfconference?ref_src=twsrc%5Etfw">@selfconference</a>. 10/10 will do again.</p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/866071098718392329?ref_src=twsrc%5Etfw">May 20, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>In May, I got to speak at Self.conference, in Detroit. As you can see, I was not prepared for this experience. I've been to conferences before, but I've never been to one that affected me so much emotionally. There were so many amazing talks that were focused on making you a better person, not just a better developer. I would love to go back here.</p>
<p>I hope <a href="https://twitter.com/crebma">@crebma</a> keeps Self.conference going for many more years, so people can experience what I did.</p>
<h3>Open Source North</h3>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">.<a href="https://twitter.com/OpenSourceNorth?ref_src=twsrc%5Etfw">@OpenSourceNorth</a> has a Japanese garden right outside the doors and it. is. glorious. Best conference amenity ever. <a href="https://twitter.com/hashtag/osn2017?src=hash&ref_src=twsrc%5Etfw">#osn2017</a> <a href="https://t.co/MzzIiMuZiY">pic.twitter.com/MzzIiMuZiY</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/872875607843119104?ref_src=twsrc%5Etfw">June 8, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I wasn't sure what to expect when I made the trip up to the Twin Cities in June, for Open Source North. I came away really impressed with the conference, and especially the organizers. I got some really great tips from <a href="https://twitter.com/jeffurban">Jeff Urban</a> about how we could improve the organization of <a href="http://mkedotnet.com">MKE DOT NET</a>. I would go back to this conference in a heartbeat, if they'd have me. I think I did pretty well!</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">The most amazing talk of the day nod goes to <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> -- engaging presentation, exciting content. <a href="https://twitter.com/hashtag/staticweb?src=hash&ref_src=twsrc%5Etfw">#staticweb</a></p>— Rachel Walwood (@walwoodr) <a href="https://twitter.com/walwoodr/status/872916663108329472?ref_src=twsrc%5Etfw">June 8, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h3>That Conference</h3>
<p>That Conference is where I was originally bitten by the speaking bug - in 2015, when I saw <a href="https://twitter.com/housecor">Cory House</a> give two talks, and was blown away by his command of the room. Getting to speak here in 2017 was amazing. It made me feel like I'd really done what I set out to do - become a conference speaker.</p>
<p>Shortly before my talk, I ran into <a href="https://twitter.com/davidgiard">David Giard</a>, another of my speaker heroes, in the speaker lounge. My voice crackled as I told him how much he inspired me, and how much it meant for me to be speaking at the same event as him. He was extremely gracious, as always - and then asked when and where my talk was, so he could attend. This was not exactly what I was expecting, and suddenly I felt a bit of pressure to impress him. Cut to fifteen minutes into my talk, with him applauding me and practically rolling out of his chair with laughter, and I felt like I did pretty good.</p>
<p>My wife and kids attended my talk, too! This was really fun, and I hope they can be in the audience for more of my talks. I felt like I made them proud.</p>
<h3>MidwestJS</h3>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Borked my phone this morning in the rain. Resorting to 19th century tactics to commemorate a fun speaker dinner <a href="https://twitter.com/midwest_js?ref_src=twsrc%5Etfw">@midwest_js</a>. <a href="https://twitter.com/hashtag/midwestjs?src=hash&ref_src=twsrc%5Etfw">#midwestjs</a> <a href="https://t.co/K3ywDFxQvm">pic.twitter.com/K3ywDFxQvm</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/898008069271166976?ref_src=twsrc%5Etfw">August 17, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>My second trip to the Twin Cities for a conference. I went for a run one morning, and a surprise <em>massive</em> downpour soaked my phone - and turned it into a hunk o' junk.</p>
<p>This was another case of the organizers just being fantastic. One thing I think is really important for speakers is to be very gracious and thankful with the organizers. When they do as great a job as MidwestJS did, it makes it easy to be gracious.</p>
<h3>Dev Up</h3>
<p>In October, we took the kids out of school for a couple days to make a family trip to Dev Up, in St. Louis. We spent a weekend being tourists, and had a ton of fun.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Got to <a href="https://twitter.com/devupconf?ref_src=twsrc%5Etfw">@devupconf</a> a little early and spent some family time being tourists. 630 feet of fun. <a href="https://t.co/TMEXwdx2bh">pic.twitter.com/TMEXwdx2bh</a></p>— Steven Hicks (@pepopowitz) <a href="https://twitter.com/pepopowitz/status/919330189657673728?ref_src=twsrc%5Etfw">October 14, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I had a new talk to deliver here - <a href="https://steven-j-hicks-speaking.netlify.com/do-scary-things/">Maximize Professional Growth By Doing Scary Things</a>. This is a talk I'd had floating around in my head for at least a year. I wanted to convince people to do something scary, in hopes that it would change their life - the way that getting into speaking has changed mine.</p>
<p>I was emotionally drained during the conference, because I was just so nervous about this talk. It is the most vulnerable thing I've done. It took me a lot of energy to build myself up.</p>
<p>It went awesome, though.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">One of my favorite <a href="https://twitter.com/hashtag/devup2017?src=hash&ref_src=twsrc%5Etfw">#devup2017</a> talks was from <a href="https://twitter.com/pepopowitz?ref_src=twsrc%5Etfw">@pepopowitz</a> talking about fear. Feeling inspired. <a href="https://t.co/9VFZACcWSD">pic.twitter.com/9VFZACcWSD</a></p>— Scott Addie (@Scott_Addie) <a href="https://twitter.com/Scott_Addie/status/920336252280475649?ref_src=twsrc%5Etfw">October 17, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>It was great to have <a href="https://twitter.com/davidpine7">David Pine</a> and <a href="https://twitter.com/Scott_Addie">Scott Addie</a> - two friends from home - in the front row for this talk. I needed the encouragement, and they delivered.</p>
<h2>MKE DOT NET</h2>
<p>This was year number 3 for MKE DOT NET. I've helped out with all three - but never as much as this year. I was significantly involved from start to finish of the planning this year.</p>
<p>And I love it. The day of MKE DOT NET is like Christmas for me. I love the adrenyline of getting to the event early in the morning, and discovering that a marathon is blocking traffic from getting there. It sucks that this kind of thing happens..but it does, every time. We never know what the problem is going to be - but something is going to go wrong, and we need to figure out how to work around it. Dealing with all of the logistical details of a conference gives me a buzz.</p>
<h2>I Got A New Job</h2>
<p>For the last few years, Centare has been an incubator for me. I've grown so much, thanks largely to the amazing team around me.</p>
<p>In late 2017, I found myself looking for new work. I moved on to Northwestern Mutual - a place that I think is poised to make some noise. I'm really excited about my new team and new role. I'm doing full-stack JavaScript. This is a change, after spending almost my whole career in .NET development. It is a welcome change.</p>
<p>I will continue to help out with MKE DOT NET.</p>
<h2>2018 - A Look Ahead</h2>
<p>I've got a ton of ideas for 2018. I won't be able to follow through on all of them, I'm sure. There are just too many.</p>
<h3>Not As Much Speaking</h3>
<p>I'd really like to keep the momentum with speaking at conferences, but it looks like I won't be submitting to nearly as many as I'd like this year. It seems as if there are basically four weeks that all conferences are scheduled this year.</p>
<h3>More Writing</h3>
<p>I say this every year. But I re-wrote my blog using <a href="https://github.com/11ty/eleventy/">eleventy</a> last week, hoping that it provides less friction for writing than Metalsmith did. Metalsmith was a bit flaky, and I would often have to restart the dev server just to get it to reload a content change. So far, eleventy has been great.</p>
<p>I've already published one story to Medium - <a href="https://medium.com/@pepopowitz/chekhovs-gun-and-better-unit-tests-96fb439e6d0e">Chekhov's Gun And Better Unit Tests</a> - and I'm hoping to write at least 6 Medium-worthy stories this year. Hold me to it, internet!</p>
<h3>Self Improvement</h3>
<p>I'm looking to cut back on self-deprecation. I rely on it a lot to make sure people don't think I'm full of myself - but I've found this year that it affects me negatively. I think I believe the self-deprecation a little too much. In 2018, I'm hoping to cut back on this habit significantly.</p>
<p>I've also realized one of my biggest hurdles to feeling comfortable in a social setting with strangers is that I just don't know how to start conversations. I'm looking into specific ideas to improve this, in the hopes that it makes me feel more confident. Being at a conference where I don't know anyone can be really draining for me. If I know someone there, or meet someone, it's a different story - I could talk for hours. I'd like to make it easier for myself to get to that stage.</p>
<h3>Something Scary</h3>
<p>I'm not totally sure what, yet. I've eliminated live-streaming, as I don't really understand how it is interesting. Writing is part of this, but I want to do something even more scary. I'm deciding between starting a podcast, and making videos for YouTube.</p>
<p>I'm also going to submit a workshop idea to conferences. I've got a basic idea, but I really need to work out more details before I can submit it. I have ideas on how to make a good workshop - but I'm not sure yet how to write an abstract that will get it accepted.</p>
<p>I'd also like to speak at an international conference this year. I got myself a new passport late in 2017, in hopes that I'd get selected soon. Nothing yet but I've got a couple open submissions, and more to come.</p>
<p>Here's looking forward to 2018. Let's get better!</p>
Chekhov's Gun And Better Unit Tests2018-01-28T12:00:00+00:00https://stevenhicks.me/blog/2018/01/chekhovs-gun-and-better-unit-tests/<p><img src="../chekhovs-gun.jpg" alt="Chekhovs Gun & Unit Testing Illustration"></p>
<h2>Chekhov's Gun</h2>
<p><a href="https://en.wikipedia.org/wiki/Anton_Chekhov">Anton Chekhov</a> was a Russian playwright who died in 1904. He wrote several hundred stories by the time he was 26, many of them highly regarded. Chekhov is often credited with being a major influence on the modern short story.</p>
<p>One of his most famous contributions is something called Chekhov's Gun. This is a principle in dramatic writing that suggests that all elements in a story must be relevant in some way. If an element doesn't have relevance, it should be removed. Irrelevant elements might distract the audience from the intended story.</p>
<p>The origin of Chekhov's Gun comes from several related quotes from his writings. One example:</p>
<blockquote>
<p>Remove everything that has no relevance to the story. If you say in the first chapter that there is a rifle hanging on the wall, in the second or third chapter it absolutely must go off. If it's not going to be fired, it shouldn't be hanging there.</p>
</blockquote>
<h2>What does this have to do with unit tests?</h2>
<p>Aside from a couple of classes in high school, I’ve never studied writing. I don’t remember the first time I heard of Chekhov’s Gun. It resonated with me though, as an analogy of how I feel about writing unit tests — especially when it comes to test setup. If a line of setup code isn’t important to describe a specific test, then I don’t want it to distract me. I’d like to be able to focus on what makes the test unique.</p>
<p>Imagine I have an app where users can submit new beers to a curated list of "the best beers ever." Here's a test that I might write for a function that validates that the user submitted the ABV (alcohol by volume) for a beer. We'll call it <code>Noisy Test</code>.</p>
<pre><code class="language-javascript">describe("validateBeer", () => {
it("returns invalid for beers with no abv", () => {
const beer = {
id: 87983,
brewery: {
id: 12332,
name: "Central Waters",
location: "Amherst, WI",
overallRating: 5,
},
name: "Mudpuppy Porter",
beerStyle: "Porter",
abv: undefined,
};
const result = validateBeer(beer);
expect(result).toEqual(false);
});
});
</code></pre>
<p><code>Noisy Test</code></p>
<p>This test is pretty decent. I like that the name of the test tells me exactly what it is testing. I like that there aren't a ton of assertions, and it's pretty clearly testing only one thing.</p>
<p>What I don't like about it is the distracting setup code. This test verifies that a beer with no ABV is considered invalid. It doesn't specify <code>abv: undefined</code> until the 11th line of the test. This means that I have to read through 10 lines of irrelevant setup code before I get to what actually makes this test unique - the submitted beer doesn't have an ABV. This is a violation of Chekhov's Gun, in unit test form.</p>
<p>What I'd rather see is something like this - we'll call it <code>Better Test</code>:</p>
<pre><code class="language-javascript">describe("validateBeer", () => {
it("returns invalid for beers with no abv (less setup)", () => {
const beer = makeMeABeer({
abv: undefined,
});
const result = validateBeer(beer);
expect(result).toEqual(false);
});
});
</code></pre>
<p><code>Better Test</code></p>
<p>Here, I get a beer from an extracted function named <code>makeMeABeer</code>. This function would generate a typical beer, with values that are usual and not edge-cases. I pass in only the values that are unique to this test - in this case, <code>abv: undefined</code>.</p>
<p>Extracting my setup of a beer, as in the second example, improves my signal to noise ratio for this test. The signal is "what am I trying to test?" The noise, in the first example, is "setup code that doesn't impact this test." The more I can increase the signal, the easier it is for a reader of my test to understand my intentions.</p>
<h2>Why do I care about the reader of this test?</h2>
<p>A question: who do we write our unit tests for? It's ourselves, right?</p>
<p>If we're doing TDD, yes, we get a lot of value out of the tests we're writing, in the moment.</p>
<p>But aside from TDD, who benefits the most from our tests? It's not you, when you write the test. You've got all the context about the test being written. You most likely understand the code that you are writing. If you'd just finished writing the <code>validateBeer</code> function, and I asked you what the system was supposed to do with a beer that didn't have an ABV, you'd likely be able to tell me the answer - without looking at any tests.</p>
<p>But if a teammate makes a change a few months from now and this test suddenly starts failing, that teammate will have no context. They won't know why this test was written in the first place. They will not know details of how the test was made to pass. They might not even know that ABV is a thing.</p>
<p><code>Noisy Test</code>, a test that is filled with irrelevant setup code, will distract them from discovering why this test exists. It will take them time to determine what makes this test unique compared to the others. They might have to compare it to several other tests to find the difference.</p>
<p><code>Better Test</code>, on the other hand, removes the irrelevant setup details. This allows them to determine more quickly what makes this test unique. They might not know immediately why it is failing, but at least they'll know why this test exists, and how it is different.</p>
<h2>Optimize for the reader, not the author</h2>
<p>Chekhov's Gun is all about optimization of information. Anton Chekhov wants your writing to be as efficient as possible - so that readers can consume it with minimal effort and distraction.</p>
<p>When we're writing code, we are communicating with our teammates as much as we are communicating with the compiler. We are authors, and they are our readers. If we write our code in a way that optimizes for our writing experience, our readers will struggle. It won't take long for them to throw their hands up and say "let's do a rewrite, because I don't understand this."</p>
<p>If we write our code in a way that optimizes for the reading experience, our readers will thank us. With less cognitive load from trying to decipher our code, they'll be able to focus on implementing their changes. Tests that minimize irrelevant setup are one simple way we can help the reader. Optimizing your code for the reader will help you build a more maintainable codebase.</p>
I'm going to be busy2017-04-19T12:00:00+00:00https://stevenhicks.me/blog/2017/04/im-going-to-be-busy/<p>Earlier this year, I decided to become more involved in speaking at local meetups and events. In 2016 I got the speaking bug, but struggled a little to get into conferences. For 2017, I thought a good way to get experience was to engage at the local level.</p>
<p>So I scheduled a few meetup dates. Those events have gone really well.</p>
<p>In the meantime, I submitted a good amount of abstracts to local and regional conferences, hoping to get into one or two.</p>
<h2>And then I got into more than one or two</h2>
<p>I started to get accepted to speak at conferences way more quickly than I expected. In 2016, my issue with conference submissions was that I couldn't get in. In 2017, my issue is the opposite. I have actually started retracting submissions from conferences, for fear of getting accepted and not having any time for my family and my training. A lucky problem to have!</p>
<h2>My upcoming schedule</h2>
<h3>Chicago Code Camp</h3>
<h4>April 29, Chicago</h4>
<p>I'll be presenting my "Getting Started With React" talk at <a href="https://www.chicagocodecamp.com/">Chicago Code Camp</a> at the end of April.</p>
<p>One thing that is interesting about speaking is that you never really know what talks will get picked up. My "Getting Started With React" talk was never really intended to happen - it came out of a workshop I did for our internship program at <a href="http://www.centare.com">Centare</a>, and I was reluctant to submit it anywhere. But after Chicago Code Camp, I will have given this talk more than any other talk. Crazy how that works out.</p>
<h3>Self.conference</h3>
<h4>May 19-20, Detroit</h4>
<p>I will be doing my favorite talk - "Code Is Communication" - at <a href="http://www.selfconference.org">Self.conference</a> in May.</p>
<p>This is a really fun talk, and I'm excited to get to do it again. I'm also really excited about the other talks at this conference. Getting accepted to Self.conference was my first big surprise.</p>
<h3>Open Source North</h3>
<h4>June 8, Twin Cities</h4>
<p>In June, I'll be doing a revamp of my oldest talk - "The Static Web Revolution" - at <a href="http://opensourcenorth.com">Open Source North</a>.</p>
<p>I'm excited about some visual updates I'll be making for the slides for this talk, playing on the "revolution" idea.</p>
<h3>That Conference</h3>
<h4>August 7-9, Wisconsin Dells</h4>
<p>Oh man, this is the grandaddy of them all. This conference (that conference) is the reason I wanted to start speaking. I saw Cory House speak two years ago, and realized this was what I wanted to do. Getting into <a href="https://www.thatconference.com">That Conference</a> was the first speaking goal I set.</p>
<p>So yeah, I'm pretty stoked about this one. I'll be doing "The Static Web revolution" here as well.</p>
<h3>Midwest JS</h3>
<h4>August 16-18, Twin Cities</h4>
<p>Back to the Twin Cities again, this time for <a href="http://midwestjs.com/">Midwest JS</a>. I'd heard great things about this conference in 2016, so I was planning on attending even if I hadn't gotten in as a speaker.</p>
<p>I'll be doing a brand new talk here - "Unit Testing Your React App". This talk was born out of the work I've been doing over the last year or so, as well as a desire to learn more about Jest and Enzyme.</p>
<h2>Lessons Learned</h2>
<p>A few things I've learned from this year of craziness, so far.</p>
<h3>Keep submitting.</h3>
<p>I was frustrated last year, and early this year, with not getting accepted into conferences. Admittedly, some of my very early submissions were not very good. But I thought a lot of the stuff I was submitting last year and early this year was pretty good.</p>
<p>It turns out it was, because all of the talks I had picked up this year were things that had been submitted and rejected many times. Especially early on, I think the ratio of rejected to accepted is at least 5 to 1. I just knew I wanted to speak at conferences, so I pushed through the rejection, and it is paying off.</p>
<h3>You never know which talks will be accepted.</h3>
<p>It blows my mind how many times I'll be giving my intro to React talk. I hesitated to submit that talk every single time. I thought "this is such a basic thing, they must get a ton of these, why would mine get picked?"</p>
<p>I'm not sure why. But it did, several times. So, yeah - you never know which talks are interesting to organizers. It might be the one you least expect.</p>
<h3>Don't spread yourself too thin.</h3>
<p>For as many talks as I'll be doing this summer, I've been pretty lucky with which ones have been selected. Considering the range of talks I'd submitted to these events, I could have found myself with a brand new talk for every event. Luckily, I didn't, so I get to focus on nailing down just a few of them. And I only have one brand new one that I have to start from scratch.</p>
<p>For next year, I'm going to make sure that I'm submitting talks that I have a pretty good start on. I don't want to get stuck having to write 5 new talks over the summer. This can be a hard thing to balance when you're first starting - I just wanted to get into conferences, so I was submitting anything I thought was a good idea. But it probably makes sense to restrain those ideas a little, to make sure you don't get swamped with accepted talks that you haven't written.</p>
Mocking ES2015 modules2017-01-05T12:00:00+00:00https://stevenhicks.me/blog/2017/01/mocking-es2015-modules/<p>If you're unit testing code that is using ES2015 modules [<a href="#footnotes">1</a>], you'll probably point want to mock dependencies at some point. For the most part this is just like mocking things in any code. My tool of choice right now is <a href="https://github.com/jupiter/simple-mock">simple-mock</a>, but there are gajillions of options.</p>
<h2>Test-induced damage</h2>
<p>There is some test-induced damage that my team has come to accept with simple-mock, and I am guessing it would be an issue with other mocking libraries like it. While simple-mock is great for mocking functions that are properties on exported objects, it seemed difficult to mock standalone/named exported functions from dependencies.</p>
<p>So we found this to be easy: [<a href="#footnotes">2</a>]</p>
<pre><code class="language-javascript">//dependency.js
export default {
someAction: function() {
//....
},
};
//test.js
import simple from "simple-mock";
import dependency from "./dependency";
describe("feature", function() {
afterEach(function() {
simple.restore();
});
it("calls a dependency", function() {
//Arrange
simple.mock(dependency, "someAction");
//Act
systemUnderTest.execute();
//Assert
expect(dependency.someAction.callCount).to.equal(1);
});
});
</code></pre>
<p>But the <code>simple.mock</code> function is looking for an object, and a property on that object. So we found this to be difficult:</p>
<pre><code class="language-javascript">//dependency.js
export function someAction() {
//....
}
//test.js
import simple from "simple-mock";
// Notice the difference in how we are importing...
import { someAction } from "./dependency";
describe("feature", function() {
afterEach(function() {
simple.restore();
});
it("calls a dependency", function() {
//Arrange
// ¯\_(ツ)_/¯
// How would we mock 'someAction'? It isn't a property on an object.
//Act
systemUnderTest.execute();
//Assert
expect(someAction.callCount).to.equal(1);
});
});
</code></pre>
<p>As a result of our inability to solve this problem, we had accepted that we would always export a default object from our code, with functions on it as properties.</p>
<p>So while we wanted to export something like this:</p>
<pre><code class="language-javascript">//dependency.js - the desired way
export function someAction() {
//....
}
export function otherAction() {
//...
}
</code></pre>
<p>We just always accepted that we would do this:</p>
<pre><code class="language-javascript">//dependency.js - the undesired way
export default {
someAction: function() {
//....
},
otherAction: function() {
//...
},
};
</code></pre>
<h2>Why does it matter?</h2>
<p>There's a reason this was not ideal. Modern bundlers are able to remove the unused exports out of your code, to reduce your bundle size. But code is only removed if it isn't imported. By exporting a single object with a bunch of functions on it, the entire object must be imported by the calling code.</p>
<p>So in the first example, if your code called <code>someAction</code> but not <code>otherAction</code>, your final bundle would only include <code>someAction</code>. But in the second example, even if you are only ever calling <code>someAction</code>...you are importing the entire object, and therefore the unused <code>otherAction</code>.</p>
<p>In our app, that ends up not being a problem, because we are using all of the code we write. But if we were writing a library, or some code that was intended to be shared by other apps and therefore might not use everything, this would not be cool.</p>
<h2>But there is a better way to import the dependency.</h2>
<p>Today, we discovered the answer to our problems. The "import * as" syntax allows you to import all exports from a dependency, expanding them onto an object - and therefore allowing you to mock a named/standalone function from your dependency. [<a href="#footnotes">3</a>]</p>
<p>So all that changes is how we import dependencies into our tests:</p>
<pre><code class="language-javascript">//dependency.js
export function someAction() {
//....
}
//test.js
import simple from "simple-mock";
// vvvvvvvvvvvvvvv Notice the difference in how we are importing
import * as dependency from "./dependency";
// ^^^^^^^^^^^^^^^
describe("feature", function() {
afterEach(function() {
simple.restore();
});
it("calls a dependency", function() {
//Arrange
// and now we can pass simple.mock a property on an object.
simple.mock(dependency, "someAction");
//Act
systemUnderTest.execute();
//Assert
expect(dependency.someAction.callCount).to.equal(1);
});
});
</code></pre>
<p>Hooray, JavaScript! Hooray, mocking! Boo, test-induced damage!</p>
<hr>
<div id="footnotes"></div>
<p>[1] If you aren't unit testing your JS, you should be! It is not as scary as it sounds. You'll see great improvements to the quality of your front-end code. As an added bonus, the guilt that you probably feel for not testing your JS will disappear.</p>
<p>[2]<a href="http://mochajs.org/#arrow-functions">why aren't you using es2015 arrow functions in your tests?</a></p>
<p>[3] I ain't gonna lie, the discovery of this functionality was, as usual, the result of a <a href="http://stackoverflow.com/a/38414160/1585069">stackoverflow search</a> that I definitely tried a long time ago, but must have narrowly missed.</p>
My 2016 recap2017-01-03T12:00:00+00:00https://stevenhicks.me/blog/2017/01/my-2016-recap/<p>2016 was a ...year that happened. Love it or hate it, it happened. At a personal level, I had a pretty fantastic 2016.</p>
<h2>I discovered what I want to do when I grow up</h2>
<p>Nevermind that I am a few weeks from being able to say <a href="https://www.youtube.com/watch?v=jXEde89WC44">I'm a man, I'm 40!</a> It was 2016 when I realized what it is I want to do with my career - I want to speak.</p>
<p>When I was an intern at State Farm in the summer of 1998, I was forced to attend Toastmasters on a regular basis. I hated it. It seemed like a bunch of old people who liked to hear themselves talk.</p>
<p>But I was not bad at it. One of our assigments was to give a speech about..well I'm not sure what, actually....and in silent protest I gave my speech about all the ways I procrastinated writing it. It went over really well, and I was voted my group's finalist, which meant there was a chance I would have to give it in front of the entire group of interns. My instructor told me I was "quite a wordsmith" (+1 for vocabulary), but I could use some practice.</p>
<p>I ended up not being picked to speak in the finals, which I was pretty excited about. I left that summer with a bad taste in my mouth for Toastmasters - though to be fair, I was young and hated everything.</p>
<p>But I also left with the knowledge that I didn't hate the actual act of speaking. Throughout my career I've been happy to speak to coworkers in internal lunch-and-learns. But until recently, I had never gone much further than that.</p>
<p>In late 2015, having done a lunch-and-learn on TDD, my boss asked if I was interested in getting out and speaking outside of the company. My TDD lunch-and-learn was not great - lots of bullet points, and too much information crammed into tiny spaces. But I enjoyed it, so I said yes.</p>
<p>So in 2016, my goal was really to get out and speak. And when I did - I realized I love it. I love the amount of learning I do when I research a topic to death, because I don't want to look foolish. I love telling jokes that not everyone gets. I love telling stories that reframe technical ideas in terms of real life. And despite the 10 minutes of "oh crap this is really happening" I feel as the talk is about to start, I love the rush I feel 15 minutes into the talk when the audience is engaged, and laughing at my dumb jokes, and listening to my dumb stories about grocery shopping.</p>
<h2>So I did some speaking in 2016</h2>
<p>I spoke at Milwaukee meetups twice in 2016. Once on static site generation, and once on clean code, as a lightning talk. These were my first two times speaking outside of my job. The thing I noticed the most was that people were actually engaged and listening. I thought speaking in front of co-workers would have been easier, because they were people I know...but since so many people are just there to pass some time while they eat their lunch, the engagement level isn't very high. When you are speaking outside of work, you are speaking to people who actively sought out your talk - so the engagement level is way higher.</p>
<p>I also got lucky enough to get into four conferences/camps in 2016!</p>
<p>My first conference speaking opportunity ever - <a href="/blog/2016/06/chicago-coder-conference">at Chicago Coder Conference</a> - was at a conference that I didn't know anyone on the selection committee. That felt pretty good. I did a variation of my static site generation talk. I met some great people, and had a really fun time in downtown Chicago for a couple days.</p>
<p>Later in the year, I spoke at <a href="/blog/2016/11/october-recap">MKE DOT NET and Milwaukee Code Camp</a>. Each time I've spoken, it has gotten easier. At MKE DOT NET, I got a ton of great reviews in our follow-up survey.</p>
<p>I also got to speak at another Centare-run event in December - QA Camp. This was an event for QA professionals, and I tag-teamed a talk with my co-worker <a href="http://thefoxgang.com/">Tyler Evert</a>, called "Tight Feedback Loops with TDD and DevOps." This was my first tag-team talk, and it was really fun. Again, we got a ton of great reviews in the follow-up survey. I feel like I am on the right track.</p>
<p>I really wanted to get into That Conference in 2016, but it didn't happen. Looking back at the abstract I submitted, I am not surprised. It was wordy. I was crafting it as if the reader had nothing to do on a cold winter Saturday, poured themself a cup of coffee, kicked their feet up in front of the fireplace, and read my abstract.</p>
<h2>I got some great speaking advice at That Conference</h2>
<p>Probably the best part of That Conference is the Open Spaces, in which anyone can propose any topic, and anyone can attend. I facilitated one on "becoming a tech speaker", and it was amazing. I had tons of great speakers stop by to give advice and answer questions. It was in this session that I realized why I probably had my abstract rejected. I am still trying to figure out how to get my abstracts selected, I feel like it is a game that I just haven't figured out yet.</p>
<p>Some of the other great things I learned about speaking:</p>
<ul>
<li>It's okay to reach out to organizers for feedback on why your abstracts weren't selected. I have used this advice several times already, and no one so far has declined my request for more info.</li>
<li>Have backups for everything! This was a theme for me this year. At Milwaukee Code Camp, I thought I had accounted for everything...until the projector stopped working. At MKE DOT NET, Scott Hanselman worked with us to make sure we had backups of everything, including a projector...but we somehow missed a backup microphone, so obviously that's the one thing that started to die five minutes in. At QA Camp, the batteries in Tyler's clicker died halfway through. So yeah....backups for everything.</li>
<li>Have a checklist of all the things you want to verify during the setup of your talk. <a href="https://www.evernote.com/shard/s8/sh/bceb64d8-717f-4027-aaaa-6cd73d071ffc/be44ae852ddc8208">Cory House</a> has a very thorough example. This is incredibly helpful in those ten minutes before you start up, when your brain is freaking out a little bit.</li>
<li>Find what works for you. You will get advice from everyone about how to get started speaking, or get better at speaking. It turns out...you and that person may not agree on what makes a good speaker. Or you may learn differently than they do. Much like the adage about the most effective gym workout being the one that you will not quit - the best speaking tips are the ones that work for you. Which reminds me....</li>
</ul>
<h2>I stopped hating Markdown</h2>
<p>As early as a few months ago, I despised Markdown. It was too cryptic for me. But I realized that a weird combination of PowerPoint and OneNote weren't really working for me, for developing talks. I discovered that there were plenty of web-based alternatives, which would allow me to write my notes as an outline from the start, and iteratively build them up. The feedback I got from other speaking coworkers was that they sucked.</p>
<p>But I settled on <a href="https://remarkjs.com">RemarkJS</a> and tried it anyway, and it completely works for me. I have written several talks with it, and will continue to write my talks that way.</p>
<p>Like always, different strokes for different folks.</p>
<p>As a result, I've now written talk outlines in Markdown enough times that I don't need a cheatsheet to do everything. Me from three months ago would not have believed this would happen.</p>
<h2>Looking Forward to 2017</h2>
<p>I am really happy with my career right now. I am submitting to conference CFP's any time I can. I am not being accepted a bunch -- yet. I love that <a href="http://www.centare.com">Centare</a> is giving me a ton of opportunity to speak.</p>
<p>In 2017 I plan to speak a bit more at local meetups. I have a talk scheduled in late January with the local ReactJS meetup group, and I think spreading my brand locally is a great way for me to get regional conferences to notice me.</p>
<p>And speaking of brand, I know this is something I need to work on in 2017. I am a bad self-marketer, and I need to get better.</p>
<p>I am also hoping to get into several regional conferences this year. I'm submitting like crazy. It's not happening as quickly as I want it to, but I know this is what I want to do, so I'm going to keep submitting.</p>
SSIS Is Annoying2016-12-01T12:00:00+00:00https://stevenhicks.me/blog/2016/12/ssis-is-annoying/<p>The project I'm currently working on requires a spreadsheet to be imported into the database. SSIS seemed like the logical solution to this.</p>
<p>But one month after dealing with SSIS, I am done with it. SSIS is like 85% awesome - but 15% "OMG I hate you". <a href="https://www.youtube.com/watch?v=QfzDUpB88x4&t=0m24s">I wrote a song about it, like to hear it here it goes</a>.</p>
<h2>SSIS doesn't work consistently across machines</h2>
<p>The gist of the package we are building is that it takes an Excel spreadsheet, sanitizes the data, and upserts it into a database.</p>
<p>Some of the data was a little bit long. We built some columns to handle 512 characters in the database. Everything seemed like it was working fine.</p>
<p>But then one of the team members said it was failing for him, complaining about having to truncate data. We were all using the same spreadsheet, so this didn't make sense.</p>
<p>After some investigation, it turns out all of the columns coming from the spreadsheet were capped at 255 characters. That is strange in itself, but we'll get to that. But why was it not failing for me?</p>
<p>I still don't know the answer. Because everything in the package said that any value over 255 characters long should have been failing with an error. And that's what it was doing for my coworker - but not for me!</p>
<p>My bigger concern was this 255 character limit.</p>
<h2>SSIS and Excel are just the worst</h2>
<p>SSIS thinks it is soooo smart. It can predict the widths of your columns for you, so you don't have to define them! Hooray!</p>
<p>Except it only predicts the widths based on the first 8 rows of data. And if there is nothing very long in a column, it will set its max length to 255.</p>
<p>This wouldn't be the worst thing, if you could change the columns it got wrong. But you can't! You can try...but it will just blow away your changes when it re-samples the spreadsheet.</p>
<h3>But you can change the default sample size from 8 rows to something else!</h3>
<p>Um...<a href="http://stackoverflow.com/a/8629065/1585069">in your registry</a>. That's a super fun way to make things fail differently for you than the rest of your team! And not a solution I plan on ever using.</p>
<h3>Hey, didn't you read the rest of that answer? You can put a dummy row with long values in it, to trick SSIS!</h3>
<p>Yup, you can. Then you have to alter your package to ignore it...and hope that no one accidentally deletes that row one day. This is the least bad option...but still bad.</p>
<p>One side-effect of adding the dummy row is that now every column that is more than 255 characters long goes from a <code>DT_WSTR(255)</code> type to a <code>DT_NTEXT</code> type. That makes sense.</p>
<h2>But SSIS is bad at determining the length of text</h2>
<p>For all of those columns that were fine as a <code>DT_WSTR(255)</code> type, the <code>LEN(x)</code> function is gravy.</p>
<p>But for all of those columns that became <code>DT_NTEXT</code>, SSIS will suddenly start doubling the column lengths. After the crazy pills wear off, you might find <a href="https://technet.microsoft.com/en-us/library/ms141797(v=sql.130).aspx">this</a> -</p>
<blockquote>
<p>If the argument passed to the LEN function has a Binary Large Object Block (BLOB) data type, such as DT_TEXT, DT_NTEXT, or DT_IMAGE, the function returns a byte count.</p>
</blockquote>
<p>Ugh. I could see the argument for that with <code>DT_IMAGE</code>. But <code>DT_TEXT</code> and <code>DT_NTEXT</code>? They have "text" in the name! Why wouldn't I want the length of the text?</p>
<p>Turns out you have to do the conversion yourself. <code>LEN( (DT_WSTR, 600) X)</code> Thanks, SSIS!</p>
<h2>SSIS sucks for other reasons, too</h2>
<p>Deployment is bad. Our timebox ended before we could figure out if we could run this all off of a server.</p>
<p>It crashes often - at least with Visual Studio 2015. Especially when you pull latest on the repo and it has to reload the project.</p>
<p>When it crashes, it leaves a process running (SSIS Debug Host) that you have to kill before you can run it again.</p>
<p>You can only have one Data Flow task open at one time. This is incredibly frustrating when you want to "copy" functionality across tasks.</p>
<h2>Conclusion</h2>
<p>SSIS, consider me unimpressed.</p>
<p>My advice for someone who needs to import data from a spreadsheet:</p>
<ul>
<li>Build a command-line project.</li>
<li>Have that command-line project use SqlBulkCopy to move all excel rows into a working table in your database.</li>
<li>Have that command-line project then execute a bunch of T-SQL that does all your ETL things.</li>
<li>Hug yourself for avoiding SSIS.</li>
</ul>
October Recap2016-11-12T12:00:00+00:00https://stevenhicks.me/blog/2016/11/october-recap/<p>October was a crazy month! Aside from being peak soccer season for my kids, I spoke at Milwaukee Code Camp on the 15th, and MKE DOT NET at the end of the month.</p>
<p>First off, thanks to everyone who attended my sessions! I had a great time. I really enjoy the storytelling part of speaking. It is quickly becoming clear to me that stories are how you draw people in.</p>
<h2>Milwaukee Code Camp</h2>
<p>At Milwaukee Code Camp, I presented my static site/JAM stack talk, in the "Backends? Where we're going we don't need...backends..." iteration. This talk has undergone constant revision since I first gave it, as I've been learning how to do technical speaking. It will change before the next time, too. I had some great feedback from people, and I also noticed some things on my own that I am looking forward to improving.</p>
<p>I battled some issues with the projector right as my talk was about to start. While I was waiting for help to arrive, I decided that I could get started without slides - and realized before help arrived that I could probably do this talk entirely without a projector. This was an encouraging feeling.</p>
<h2>MKE DOT NET</h2>
<p>After Milwaukee Code Camp, the crazy ramped even higher. Aside from speaking at MKE DOT NET, I was also fairly involved in helping organize the conference. The end of October was a crazy busy time. Thankfully, I work with a ton of really great people who volunteered to make the event run silky smooth, given some scheduling challenges from the venue.</p>
<p>My talk at MKE DOT NET almost couldn't have gone better. I had a great crowd, including some old friends. Everyone seemed really engaged - at one point I wanted to take a picture of literally the entire crowd looking up at a code sample, but the moment had passed by the time I got my phone out.</p>
<p>Slides for this talk can be found <a href="https://steven-j-hicks-speaking.netlify.com/code-is-communication/">here</a>.</p>
<p>One experiment from this talk that went really well was the use of a JavaScript-based presentation framework, instead of PowerPoint. I find that I spend a lot of time moving things around in PowerPoint, and I really wanted something where I could use CSS to keep my slides consistent. I also wanted to be able to keep my slides in a git repo. I did a little research and found that <a href="https://remarkjs.com">remark</a> most closely met the vision in my head.</p>
<p>It worked great! My plan moving forward is to use remark to build all my decks. I am not the biggest fan of markdown but I love being able to outline my talk in code, and build it out from there.</p>
<p>There are a lot of great conversations I got out of these two events, but right up near the top was an opportunity to work with Scott Hanselman to prepare the A/V equipment for his keynote. I got some great tips from him on speaking, as well as making MKE DOT NET more diverse next year.</p>
<p><img src="/static/img/mkedotnet.jpg" alt="Steven Hicks and Scott Hanselman"></p>
<h2>Up Next</h2>
<p>My next scheduled talk will be at QA Camp in Milwaukee, on December 8th. I'll be speaking with a colleague on tightening the feedback loop with TDD and DevOps.</p>
<p>I've also got a couple proposals that I'm awaiting responses from, and I'm looking forward to submitting to as many CFPs as I can in the near future.</p>
Getting Started With TDD2016-08-19T12:00:00+00:00https://stevenhicks.me/blog/2016/08/getting-started-with-tdd/<h2>Background</h2>
<p>Recently, a coworker asked me to send him some good resources for introducing a team to TDD. Rather than hoarding the info, this seemed like something I should probably put somewhere for the next person that asks. Much of this is information that I talked about in my "You down with TDD? Yeah you know me" talk that I wish I could give more often.</p>
<p>There is entirely too much information to fit into one place, so I've broken the info down a bit.</p>
<ul>
<li><a href="/blog/2016/08/my-tdd-sales-pitch">My TDD sales pitch</a></li>
<li><a href="/blog/2016/08/getting-started-with-tdd">Getting started with TDD (this post!)</a></li>
</ul>
<h2>Getting Started</h2>
<p>Right, so there are a couple different ways to handle this.</p>
<h3>Cold Turkey</h3>
<p>There are many who will say that cold turkey is the way to go to learn TDD. TDD all the things, on all the projects, all the time! The only way to truly learn it is to be thrown into the fire, with no way to escape but a fireman's axe labelled "TDD".</p>
<p>Maybe this works for you. If it does...I apologize, I don't have any advice, because this would never work for me.</p>
<h3>Baby Steps</h3>
<p>Things generally work better for me if I dip my toes in the water first. TDD for me took a couple years of biting off a little more each time. It went something like this:</p>
<ol>
<li>
<p><strong>Consume resources</strong>. Watch videos. Read blogs. Learn what TDD is all about.</p>
</li>
<li>
<p><strong>Practice.</strong> Start messing around with some meaningless code, just to see what TDD feels like. <a href="http://codekata.com/">Code katas</a> are a great medium for this.</p>
</li>
<li>
<p><strong>Introduce TDD into one project, in one feature.</strong> Sometimes there is a task on a project that just feels perfectly made for TDD. Something where you need to do some calculations, for example. The test scenarios practically write themselves. It is here that you can start to introduce TDD into your development flow.</p>
<p>Sometimes the features will end up being more difficult to TDD than you expected - that's okay. If you need to stop, you can stop. You also might find yourself feeling like you are taking to long to get things done, the TDD learning curve is just fighting you. Again - I stopped here many times myself, and abandoned TDD until I felt like I was back on schedule. <strong>It's okay to introduce the practice a little bit at a time.</strong></p>
</li>
<li>
<p><strong>Introduce TDD into one project, in many features.</strong> Alright, so now you're starting to build momentum and confidence. Start TDD'ing more!</p>
</li>
<li>
<p><strong>TDD all the things!</strong> Okay, not really. There are things that are just not worth testing or TDD'ing, in my opinion. The cost is just too high for the value you get. But now you can go into a project saying "yeah, we're going to TDD the snot out of this thing."</p>
</li>
</ol>
<h2>Resources</h2>
<h3>The man himself</h3>
<p>If there's anyone who knows anything about TDD, it's <a href="https://twitter.com/KentBeck">Kent Beck</a>. These resources would be a good place to start if you would like to learn from "the source" of TDD.</p>
<ul>
<li><a href="https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530/ref=sr_1_1"><strong>He wrote a book.</strong></a> I would be lying if I said I read it but I would like to.</li>
<li><a href="https://pragprog.com/screencasts/v-kbtdd/test-driven-development"><strong>He made some videos.</strong></a> I would be lying if I said I've watched them but I would like to.</li>
</ul>
<h3>Mark Seemann</h3>
<p>I'll talk about this more in another post, but the most significant way to improve your ability to TDD is to improve your ability to write tests. <a href="http://blog.ploeh.dk/about/">Mark Seemann</a> is full of good tips on writing autonomous tests that follow principles of clean software design.</p>
<ul>
<li><a href="https://app.pluralsight.com/library/courses/advanced-unit-testing/table-of-contents"><strong>He has a really good Pluralsight video on Advanced Unit Testing</strong></a>. I have watched this and it has significantly improved my test code.</li>
<li><a href="https://app.pluralsight.com/library/courses/outside-in-tdd/table-of-contents"><strong>He has another Pluralsight video called Outside-In TDD.</strong></a> I have not watched this but I want to. There are a couple strategies to TDD - outside-in, and then I guess obviously inside-out. Inside-out seems to be more natural for people new to TDD. You build the inner-most components (i.e. data-access) first, then work your way out. Outside-in is, in my opinion, the correct way to TDD. Build the outer-most component (i.e. the UI) first, then work your way in. The big advantage to outside-in is that it allows you to shape the interfaces between components as the caller would want to use them.</li>
</ul>
<h3>Chess TDD series</h3>
<p>I would be lying if I said I watched all 60-some episodes, but Erik Dietrich put together a really nice <a href="href='http://www.daedtech.com/tag/chesstdd/'">series of screencasts</a>, which captured him using TDD to build a chess game. It is neat to see it in action.</p>
<h3>A good testing checklist</h3>
<p><a href="https://dzone.com/articles/unit-testing-checklist">This is a checklist</a> that helps you write good tests. Your tests are as important as your system code, so you should put effort into writing good, clean test code.</p>
<h2>TDD is an investment</h2>
<p>Hopefully the above resources can help you get started with TDD. Like any other skill, TDD takes practice and patience. It took me years to get from TDD newb to feeling totally comfortable with it, so don't feel discouraged if it feels difficult at first.</p>
My TDD Sales Pitch2016-08-18T12:00:00+00:00https://stevenhicks.me/blog/2016/08/my-tdd-sales-pitch/<h2>Background</h2>
<p>Recently, a coworker asked me to send him some good resources for introducing a team to TDD. Rather than hoarding the info, this seemed like something I should probably put somewhere for the next person that asks. Much of this is information that I talked about in my "You down with TDD? Yeah you know me" talk that I wish I could give more often.</p>
<p>There is entirely too much information to fit into one place, so I've broken the info down a bit.</p>
<ul>
<li><a href="/blog/2016/08/my-tdd-sales-pitch">My TDD sales pitch (this post!)</a></li>
<li><a href="/blog/2016/08/getting-started-with-tdd">Getting started with TDD</a></li>
</ul>
<h2>Intro to TDD</h2>
<p>First off, <a href="https://en.wikipedia.org/wiki/Test-driven_development">the wikipedia article</a> has a good summary of what TDD is. A summary -</p>
<ul>
<li><strong>Every system change that you make requires a failing test first.</strong></li>
<li>This is often boiled down to the slogan <strong>"Red/Green/Refactor"</strong>. Red = write a failing test, Green = make the test pass by writing "just enough" code, and Refactor "just enough" that you are happy with the result.</li>
<li><strong>TDD encourages simplicity.</strong> "Just enough" is in that last bullet point twice. TDD wants to prevent you from over-designing.</li>
<li><strong>TDD is a discipline.</strong> It is not easy to learn, and it is easy to fall out of habit. It takes practice. I worked on incorporating TDD into my dev flow for probably 2 years before it finally stuck.</li>
</ul>
<h2>Why use TDD?</h2>
<p>Some of the reasons I like TDD are found in the code.</p>
<ul>
<li><strong>TDD can lead to simpler designs.</strong> Emphasis on >>><strong>CAN</strong><<<. Not will. It is kind of a chicken vs egg situation - if you do TDD, your designs can be simpler...if you simplify your designs, it makes TDD easier. You can still do TDD with complex designs but that is often the type of scenario that causes a person to stop doing TDD.</li>
<li><strong>TDD can lead to smaller units of code/looser coupling/cleaner interfaces.</strong> Similar to the previous point, this is not a guaranteed result.</li>
<li><strong>TDD can improve your code coverage.</strong> You are writing tests before each line of system code - so you are usually getting pretty close to 100% coverage on anything you are using TDD for. I will not get into the discussion of whether 100% coverage is valuable....but if a high coverage number is important to you, TDD can help.</li>
</ul>
<p>My favorite reasons that I like TDD are found beyond the code.</p>
<ul>
<li><strong>TDD questions your understandings of the requirements.</strong> The moment in TDD when you are deciding what tests to write for your component is a great moment to reflect on what you do and don't know about the system. It is also a great moment for you and your team to talk, to figure out how to handle the things you don't know about the system.</li>
<li><strong>TDD documents the requirements.</strong> TDD gives you thorough tests, which tell the next person looking at the code what you expected the system to do. They are also great for when a Quality Engineer asks you what should happen in a specific scenario, and you can't exactly remember.</li>
<li><strong>TDD is a good guide for code reviews.</strong> Without TDD, code reviews that I've done are generally aimless and we end up meandering through the code. When I am using TDD to write something, I start with the tests to guide me through the code review. We can talk about all the things that were and weren't covered by my changes easily, by following the tests.</li>
<li><strong>TDD gives you confidence</strong> that your code works as you expected it to. If I make a change, I know I will have a broken test telling me if an unintended side-effect occurred.</li>
<li><strong>TDD reduces mental clutter.</strong> This is my favorite. When my wife sends me to the store for a couple things, there is like a 90% chance that I will screw it up. If I'm supposed to get milk, eggs, and butter, I will come home with milk, eggs, bacon, and beer....but no butter. My brain can't be trusted to remember a list of three groceries. It <strong>definitely</strong> can't be trusted to remember hundreds of different test scenarios for the code I'm working on. TDD gets me the confidence to know when I broke something that I didn't think I would break, rather than having to fumble manually through some scenarios that I half remember.</li>
</ul>
<h2>Why not use TDD?</h2>
<p>There are lots of arguments people, including myself, have made to not use TDD.</p>
<ul>
<li><strong>It is hard.</strong> Yeah...at first. Like any skill, it takes practice, and eventually it becomes easier. The benefits outweigh the learning curve.</li>
<li><strong>It is slow.</strong> Yeah...at first. Again, you eventually get faster. Plus, bugs are generally easier to figure out because you're writing simpler code, so you spend way less time in a debugger.</li>
<li>I have tried and <strong>my tests were just breaking any time I wanted to change anything</strong>. Again, you get better at this over time, too. When I started out, my tests were big. They had a lot of setup, and a lot of assertions...because I wasn't writing very small units. Over time, you learn to write smaller units of code, which require less test setup. When small tests for small units of code break, they are easier to fix, so it's not that big of a deal that you broke a test.</li>
<li><strong>I don't need all that test coverage.</strong> Maybe. I agree that 100% coverage is unreasonable. There definitely seems to be diminishing returns after about 80%. But sometimes I think we convince ourselves that we don't need coverage on something because we just aren't sure how to test it. As you get better at writing simpler units of code, it's hard to convince yourself not to cover stuff, because it's a lot easier than it used to be.</li>
<li><strong>I test after.</strong> First off, good for you. I know I have a tendency to pass on testing if I am doing it after, and I suspect you may be similar. Also - when you're testing after, it is often at a point in the current sprint/iteration/whatever that you don't really want to be making changes to the underlying code, because you don't want to break anything that's already been tested. So you write your tests around what is easy to test. Or #yolo, you refactor the code to be more testable, and don't tell anyone you made the changes, because there's no time to test them. (Guilty.) In either case....wouldn't it be cool if you could refactor while covered by tests, instead of refactoring to write tests?</li>
<li><strong>All that stuff <a href="http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html">that DHH said</a></strong>. The phrase I have best heard to describe DHH's post is "test-induced damage". Abstractions just for the sake of testing, for example. To me, I see it more as "test-induced compromise" than damage. I can write unit tests for all my things just by making a few small design changes? Sign me up.</li>
<li><strong>TDD is not a silver bullet.</strong> Yeah, it's not. Nothing is. But that doesn't mean it doesn't have value.</li>
</ul>
<p>That's my sales pitch. Next up I'll list <a href="/blog/2016/08/getting-started-with-tdd">a few good resources for getting started with TDD</a>.</p>
Back-ends? Where we're going, we don't need...back-ends.....2016-06-10T12:00:00+00:00https://stevenhicks.me/blog/2016/06/chicago-coder-conference/<p>I got to present at this year's Chicago Coder Conference, about static site generators and the "JAM stack." This is a talk I've been doing variations of here and there for about six months. It was the first time I've been lucky enough to present at a conference.</p>
<p>I had a great time! I was a bit disheartened by the fact that I'd been time-slotted up against Uncle Bob, and I even told everyone I met the first day of the conference that they should go listen to him instead of me. It was an honor to be speaking at the same venue as him, he is an amazing speaker and a role model for me. I just wish I hadn't had to compete with him for listeners....</p>
<p>Still, I had about 15 people come hang out and listen to me. I had great conversation with several of them before and after my talk. I've been going into these things with the thought that if I get two people to legitimately listen to what I have to say, and I can legitimately help them, it is a win. In this case, I think I definitely met that goal, and I am pretty hungry to speak again soon.</p>
<p>As much as I hate taking selfies, I did convince myself to take one on my way out of town. The squatty looking building in the background, the Gleacher Center, is where I spoke.</p>
<p><img src="/static/img/chicagocoderconf.jpg" alt="Steven Hicks at Chicago Coder Conference"></p>
Maybe I was wrong about React2016-04-07T12:00:00+00:00https://stevenhicks.me/blog/2016/04/maybe-i-was-wrong-about-react/<h2>So many places to start</h2>
<p>There are a lot of examples of how to set up a project with React, and very little direction on the best route. The first time I tried to do this, I ended up with a whole lot of analysis paralysis, and couldn't figure out where to even start.</p>
<h2>It depends on how you learn</h2>
<p>A lot of the advice you get on how to learn React and its ecosystem is to learn each individual piece, one thing at a time. Definitely DO NOT start with a "starter kit", they say. That isn't going to teach you anything, they say.</p>
<p>But suddenly in retrospect I'm realizing, that is why I struggled so much the first time I tried to learn React. I am not that kind of learner. I am a "give me a real live working example and I will tweak things until they break and learn from all the mistakes I make" kind of learner.</p>
<p>And what is the best way to fire up a working example of something? Yeoman. Find a generator that fits your tech stack, and start there.</p>
<h2>Yo let's do this</h2>
<p>So off to <a href="http://yeoman.io/generators">yeoman.io/generators</a> we go...where we search for 'react', and get....tons....and tons....of results.</p>
<p><img src="/static/img/yeoman-react.gif" alt="Yeoman React search"></p>
<p>Looking through some of the results, I see one that has the things I'm looking for - <a href="https://github.com/stylesuxx/generator-react-webpack-redux">react-webpack-redux</a>, which is really an extension of another generator, <a href="https://github.com/newtriks/generator-react-webpack">react-webpack</a>. The things I want out of the box, and the things this generator offers out of the box, are...</p>
<ul>
<li>Redux</li>
<li>ES2015</li>
<li>Unit testing</li>
<li>Sass or Less</li>
</ul>
<h2>The moment of truth</h2>
<p>So following the directions, we install the generator via npm, then generate a new site with yeoman.</p>
<p><code>yo react-webpack-redux</code></p>
<p>After answering a few questions for setup, comes the moment of truth. If all goes according to plan, I should be able to just start the app with</p>
<p><code>pre npm start</code></p>
<p>and HOLY CRAP I CAN AND IT JUST WORKS OH MY GEEZ HOW MUCH TIME DID I WASTE TRYING TO DO THIS THE LAST TIME!!!</p>
<img src="/static/img/dancing_banana.gif" style="width:200px">
<h2>The verdict - Oh my goodness this actually is kind of easy!</h2>
<p>Well, jeez. This was so much easier than the first time I tried to set up a React project. I am very pleased. I am beyond very pleased. I am actually in complete disbelief and wish I had realized it could be this easy this the first time. This makes learning React a much more tolerable proposition.</p>
Ember looks neat2016-03-24T12:00:00+00:00https://stevenhicks.me/blog/2016/03/emberLooksNeat/<h2>I got 99 problems and 98 of them are javascript MV* frameworks</h2>
<p>I am in the market for a new MV* framework in Javascript. I've done Angular 1, many times. There are things I like about it - dependency injection is easy, meaning unit testing is easy; there is <a href="https://github.com/johnpapa/angular-styleguide">good guidance</a> on style and best practices; documentation is pretty great. There are also things I don't like about it - it can be slow; the digest cycle can make me want to rip my hair out at times; writing directives can be confusing and sometimes feels a little like throwing darts at a dartboard to see what sticks. But that ship has sailed, and there are too many other frameworks to stick with Angular 1 for life. I am a breadth of knowledge guy, not depth of knowledge - and the longer I stick with one framework, the more I feel like I'm falling behind.</p>
<h3>Angular 2</h3>
<p>The logical framework for me is Angular 2, right? I have all that Angular 1 experience. They've now provided good <a href="https://angular.io/docs/ts/latest/guide/upgrade.html">guidance on migration</a>,which is way better than when it was announced, and we all thought we'd be rewriting apps from scratch. But to be honest, I haven't looked into Angular 2 a whole lot, because when I look at the examples, the views look atrocious.</p>
<pre><code class="language-html"><li *ngFor="#hero of heroes" (click)="onSelect(hero)">
<span class="badge"></span>
</li>
</code></pre>
<pre><code class="language-html"><some-component [prop]="someExp" (event)="someEvent()" [(twoWayProp)]="someExp"></show-title>
</code></pre>
<p>Good gracious, look at all those symbols. Maybe I'm being a crybaby, but I can't imagine myself staring at views like that all day without (a) needing a cheat-sheet to tell me which symbol to use when, and (b) throwing up. I despise Markdown for its use of semantic symbols; the symbols in Angular 2 seem just as confusing to me.</p>
<h3>That React, it's so hot right now!</h3>
<p>First of all, most of what I've learned so far about React, I've learned from <a href="https://medium.com/@housecor">Cory House</a> - <a href="https://www.pluralsight.com/courses/react-flux-building-applications">his Pluralsight course</a> is a great place to start. This dude is an amazing speaker, and I highly recommend checking him out if you get a chance.</p>
<p>I really like a lot of things about React.</p>
<ul>
<li>The virtualDom diffing is cool. Better performance is nothing to complain about.</li>
<li>I like the concept of uni-directional flow. I've played with both Flux and Redux, and before you get lawyery on me I know that's not technically part of React, but it is part of the ecosystem - and if you're going to build something with React, you're going to use either Flux or Redux because all of the examples do too. Flux is okay, but has too much boilerplate repetition for my tastes. Redux I find to be pretty neat - I like the <a href="http://redux.js.org/docs/introduction/ThreePrinciples.html">guiding principles</a> - a single source of truth, read-only state, and the use of pure functions.</li>
<li>The language is simple, and it's pretty easy to figure out what's going on in a component.</li>
<li>Jsx is awesome. I love the fact that you get a compile error when you typo something in a component - as opposed to something silently failing in most other frameworks.</li>
</ul>
<p>The dislikes list is much shorter....but the one item on the list is huge. Javascript tools fatigue is a real thing, and React is the ultimate example of it. I hope you like to have opinions about everything - cuz you're gonna need to. Setting up a project in React is a painful experience. React embraces the micro-library culture of npm - so you need to pick your own libraries for pretty much everything. If you're the kind of person who wants control over every..single..feature when you buy a new car, then React is going to make you happy. I am not one of those people. I am pretty happy to have three packages to choose from. I don't want to fill out a form for two hours choosing between 2 cupholders and 3. I want a car, and I want to drive it, and I don't much care for the details. Getting up and running with React can be a challenge, and one that makes me not want to use React.</p>
<h3>Ember</h3>
<p>Which brings me to Ember. I don't know a whole lot about Ember. In fact, most of what I know, I learned from <a href="http://hanselminutes.com/516/ambitious-ux-and-ambitious-apps-with-ember-and-lauren-tan">one episode of HanselMinutes</a>.</p>
<p>What I think I like the most about Ember is that it provides opinions for you. This is my biggest issue right now with JavaScript development - you need to have an opinion about everything. I am a breadth of knowledge guy, rather than depth of knowledge - as a result, I have more interest in learning the opinions prescribed by a framework than in forcing my own opinions onto a framework. I want my MV* framework to have opinions for me so that I can focus on building something.</p>
<p>Heck, <a href="http://emberjs.com/">the slogan on their site</a> is "more productive out of the box". That is a slogan I can get behind.</p>
Static Site Generators Are The New WordPress2016-03-10T12:00:00+00:00https://stevenhicks.me/blog/2016/03/staticgen/<h2>Static Site Generators Are The New WordPress</h2>
<p>I recently presented at the MKE Web Pros meetup group, about static site generators, and how you can build stupid simple websites with them.</p>
<h3>Summary</h3>
<p>The dynamic CMS like WordPress is no longer the defacto answer to the statement "I need a website". Static site generation can easily handle the needs of simple sites with infrequently changing content. Services are becoming more prevalent for adding interactive functionality to an otherwise static site - for example, Disqus for comments. And in many cases, where services can't be dropped directly onto your static site, a little technical courage and some javascript can get you what you need.</p>
<p>Statically generated sites are much more performant and secure than a site run on a dynamic CMS. There are fewer moving parts, and since your content is literally served as static html files, fewer ways your site can be attacked.</p>
<p>I would love to say static site generation is the answer for all sites. But it's not quite ready for your non-technical relative's site. Until content editing is more user-friendly, static site generation is probably only for your simplest sites or your most technical friends' websites. As a general rule, if the content editor is not capable or ready to write content using a language like Markdown, static site generation might not be the answer.</p>
<p>The good news is, we're headed toward a future where even your non-technical relative can publish content on a statically generated site. Services like Contentful and CloudCannon and plugins like Netlify-CMS are pushing in that direction. I don't think we're too far from a future where your non-technical relative can edit content in a WYSIWYG editor, click a button, and push changes to their statically generated site.</p>
<p>Check out <a href="http://www.staticgen.com">staticgen.com</a> to get started with static site generation. There are way too many generators for me to recommend "the perfect one". It is up to you to determine which generator works best for your scenario. For me, the answer is either HarpJs or Metalsmith. For you, the answer could be anything. Find the static generator for you, and let's start moving the web to a more performant and less vulnerable place.</p>