<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Jake’s Substack: Jake Does Dev]]></title><description><![CDATA[Ramblings of a software developer.]]></description><link>https://www.jakew.ca/s/dev</link><image><url>https://substackcdn.com/image/fetch/$s_!k5tm!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf66df35-58be-4040-94bc-08d971e90917_1282x1284.jpeg</url><title>Jake’s Substack: Jake Does Dev</title><link>https://www.jakew.ca/s/dev</link></image><generator>Substack</generator><lastBuildDate>Sat, 11 Apr 2026 11:31:59 GMT</lastBuildDate><atom:link href="https://www.jakew.ca/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Jake Winters]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[jakewca@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[jakewca@substack.com]]></itunes:email><itunes:name><![CDATA[Jake Winters]]></itunes:name></itunes:owner><itunes:author><![CDATA[Jake Winters]]></itunes:author><googleplay:owner><![CDATA[jakewca@substack.com]]></googleplay:owner><googleplay:email><![CDATA[jakewca@substack.com]]></googleplay:email><googleplay:author><![CDATA[Jake Winters]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Code Generation is Terrible; I Love It]]></title><description><![CDATA[Is the repeating generated code &#8220;DRY&#8221;?]]></description><link>https://www.jakew.ca/p/code-generation-is-terrible-i-love-it-27d2ba2ce68b</link><guid isPermaLink="false">https://www.jakew.ca/p/code-generation-is-terrible-i-love-it-27d2ba2ce68b</guid><dc:creator><![CDATA[Jake Winters]]></dc:creator><pubDate>Sun, 04 Jul 2021 14:31:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f1f8aba8-dd6c-4b9e-a56a-6c856fb00bd0_1024x680.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Is the repeating generated code&nbsp;&#8220;DRY&#8221;?</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hf5Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hf5Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hf5Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hf5Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hf5Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hf5Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hf5Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hf5Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hf5Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hf5Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F295f2211-1950-4402-9af4-0eedc70cda53_1024x680.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@austriannationallibrary?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Austrian National Library</a> on&nbsp;<a href="https://unsplash.com/s/photos/factory-line?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure></div><p>If you haven&#8217;t heard of &#8220;Don&#8217;t Repeat Yourself&#8221; or DRY, then I probably haven&#8217;t reviewed your code. Don&#8217;t write the same code twice. If you need to do the same thing in two places, it&#8217;s time to refactor and extract that code. This is a pretty simple concept but it&#8217;s also incomplete.</p><p>I have worked at companies that didn&#8217;t do this. What you end up with is copy/paste blocks in 10 places that all function slightly different. If one has a bug, you are unsure if the next copy/paste block has same bug. You spend your nights debugging why block Y isn&#8217;t working right just to find out it was actually block&nbsp;Z.</p><p>These days, if I see a request to merge and that merge changes the same line in multiple places, my stomach starts to turn. From time to time, it still happens. The culprit is usually either politics, management, or &#8220;the new guy&#8221;. Lately, the biggest culprit for duplicating code has been code generators, not&nbsp;people.</p><p>My introduction to code generators was in the late 2000s when I experimented with <a href="https://rubyonrails.org">Ruby on Rails</a>. Watching a tutorial &#8220;run this command&#8230;&#8221; and have a full &#8220;Hello, World&#8221; website up in minutes. This was appealing to me at a time when I was starting new projects every week but never finishing them.</p><p>The amazement was short lived, though, when I found out I made a single typo that propagated to 10 different places. Easy to solve: Restart from scratch, doing it right this time. If this had happened, and I had more than a seven-day attention span, the results would have been disaster&nbsp;out.</p><p>Another tool Ruby on Rails used was &#8220;ActiveRecord&#8221; which was my first understanding of dynamic metaprogramming as a&nbsp;concept.</p><p>For the uninitiated, metaprogramming is the concept of using a program as data for another program. Reflection is the subset of metaprogramming where a program uses itself as&nbsp;data.</p><p>The best example of the dynamic runtime reflection of Ruby on Rails is calling a method similar to &#8220;find_by_email&#8221; on an Active Record object. It would see the call to a function that didn&#8217;t exist and generate it <em>dynamically</em> based off the name and object,&nbsp;so:</p><ul><li><p>&#8216;find&#8217;: We want to find a (single) record of this&nbsp;type</p></li><li><p>&#8216;by&#8217;: We want to find it using a field&#8217;s&nbsp;value.</p></li><li><p>&#8216;email&#8217;: The field we want to use is&nbsp;&#8216;email&#8217;.</p></li></ul><p>So now, with enough information to make a guess at what the developer is trying to do, the active record can make it happen. Does this seem to be <strong>DRY</strong>? Absolutely! We&#8217;ve now eliminated the entire collection of similar &#8220;find&#8221; methods down to one function, nested somewhere in a parent&nbsp;object.</p><p>This worked well for Ruby on Rails. Many languages even started to copy it because it works and is super easy to use. This only takes two features of the language to&nbsp;work:</p><ol><li><p>Be able to know at run time when a call is being made to a method that doesn&#8217;t exist;&nbsp;and</p></li><li><p>Be able to know at run time what fields are available on an&nbsp;object.</p></li></ol><p>Few features are required, but a few more features are needed to make it <em>useful</em>. First, Ruby on Rails is a dynamically typed language. While not impossible, the question of &#8220;How do you check the type safety of a method that doesn&#8217;t exist?&#8221; comes to mind. Tools like inheritance are also helpful to distribute these dynamic functions to&nbsp;objects.</p><p>While I absolutely love this type of dynamic metaprogramming as a hobby, it does not do well in big productions. The two big reasons for this are its speed and its reliability. While it can be done well, it can easily be done wrong. This is true of <em>many</em> things in software development, but terrible dynamic metaprogramming doesn&#8217;t show up when you compile your code, it shows up when you <em>run </em>your code. If you&#8217;re lucky, it happens when <em>you</em> run it, and not when your code is running on a production server you might not even have access&nbsp;to.</p><p>Skip forward about 12 years. I&#8217;ve now graduated, I&#8217;m out working for some big computer company writing tiny container packages in Go when I start working on an existing package that uses an interesting tool: <a href="https://github.com/go-swagger/go-swagger">go-swagger</a>. The basics of it are pretty simple: given an API description, generate the rest of the code necessary such that you can just add in the implementation details.</p><p>go-swagger is another example of code generation. The output is executable Go code as the Ruby on Rails example above was Ruby code. The distinction is the input. While Ruby on Rails relied on long bash commands, go-swagger uses a YAML or JSON file for data. With this one minor change, you can now re-run the generation. Changes to the API are likely to happen so this is very&nbsp;helpful.</p><p>It wasn&#8217;t too long after working with go-swagger that a change to an API had to be made. Dozens of files were being changed because of one minor tweak. Is this DRY? Has code generation just removed the idea of &#8220;refactoring when you need to repeat your code&#8221; and exacerbated the problem using automation? And more importantly, why was this a good&nbsp;thing?</p><p>Coincidentally, around the time I was hunting for bugs in copy/pasted blocks A and Z, I read the seminal book &#8220;The Pragmatic Programmer&#8221; by Andy Hunt and Dave Thomas. This book is where &#8220;DRY&#8221; comes from. They actually publish the <a href="http://media.pragprog.com/titles/tpp20/dry.pdf">DRY chapter</a> of their new &#8216;20th Anniversary&#8217; edition as a sample and I&#8217;d definitely recommend reading both it and the&nbsp;book.</p><p>Unfortunately, I had missed a critical idea in the chapter about DRY. It&#8217;s subtle but it&#8217;s even in the definition of&nbsp;DRY.</p><blockquote><p>&#8220;Every piece of knowledge must have a single, unambiguous, authoritative representation within a system&#8221;&#8202;&#8212;&#8202;Andy Hunt and Dave Thomas; The Pragmatic Programmer</p></blockquote><p>Do you see it? It doesn&#8217;t actually mention <em>code</em>. It&#8217;s about <strong>knowledge</strong>. What does that mean? It means this isn&#8217;t about refactoring code. It&#8217;s about refactoring knowledge, which is a superset of your code. More specifically, &#8220;knowledge&#8221; in this case can refer to everything from the SQL you use, the API you&#8217;ve designed, the objects you&#8217;ve programmed and even the documentation you&nbsp;write.</p><p>So how does this apply to our go-swagger generation? Simply put, there is only one &#8220;authoritative representation&#8221; in the system, and it&#8217;s the API documentation. The API documentation should systematically influence the program&#8217;s design when possible.</p><p>Why doesn&#8217;t go-swagger just dynamically read the API description and produce a functioning API at runtime? Go is a very light-weight, performance oriented and statically typed programming language. While it would absolutely be possible of any language, it would not necessarily be useful when considering performance.</p><p>Go has evolved into a language of small, sharp tools. Its focus on small directory sized packages helps to make code reusable and easy to read. Packages are statically typed, and &#8220;nothing is hidden&#8221;. There&#8217;s no magical construct methods that happen to perform random tasks when you create objects. Constructors seemed so obvious that the thought of a language without them seemed almost archaic when I started to learn&nbsp;Go.</p><p>Code generation provides metaprogramming to a statically typed programming language in a performant way. go-swagger does this using a set of library packages for the parts that don&#8217;t change based on API, and a structured set of implementation functions and data models used for the parts that represent your API. This is a great approach as changes to the library packages don&#8217;t need to interrupt the implementation details.</p><p>For people fluent in Go, the idea of loading a monolithic package for slowly building dynamic functionality like Active Record does is completely asinine. Some ORMs do exist for Go, but you can&#8217;t go more than two comments in to a discussion before someone tells you to just use this or that SQL helper and be done with&nbsp;it.</p><p>So what about using go-swagger&#8217;s YAML to Go generation approach to SQL Database management? Well, it exists. There are a few different packages like <a href="https://github.com/lqs/sqlingo">sqlingo</a> or <a href="https://github.com/xo/xo">xo</a> that allow you to generate Go code from your Database scheme. But what if you wanted to use both this SQL platform and go-swagger together? That wouldn&#8217;t be DRY, as now you have <em>two separate</em> bodies of knowledge that can conflict: Your database and your API. I like to think of these as &#8220;outside-in&#8221; approaches, as outside parts of your program dictate or generate what&#8217;s going on&nbsp;inside.</p><p>Recently, I began working on a project that involved <a href="https://kubernetes.io">Kubernetes</a>, and some of the Kubernetes tools generate scaffolding and YAML files. The interesting thing I noticed about these tools was that this was the opposite of the go-swagger approach. They were producing YAML by scanning Go code. You can place &#8220;markers&#8221;&#8202;&#8212;&#8202;fancy comments&#8202;&#8212;&#8202;in your code, and the controller-gen tool reads the comments and related code and produces your&nbsp;YAML.</p><p>The controller-gen approach was helpful because it had one &#8220;authoritative representation&#8221; in the system and that was your code. This was an &#8220;inside-out&#8221; approach, as the code dictated what the YAML would look like, just by existing as&nbsp;code.</p><p>This led me to a thought experiment: What would this &#8220;inside-out&#8221; authoritative representation approach look like if it was done in its entirety? Well, it would start with some simple Go models mixed with tags, markers, and documentation. The documentation and the model would generate into an API description, and the model would generate into a SQL Database and a set of helper functions.</p><p>go-swagger does allow something similar to this. It allows you to have comments in your code that will generate your API description. This approach is very similar but it&#8217;s missing one critical piece. It can&#8217;t get all of the content from your code. It <em>does</em> require you to duplicate the knowledge about which API routes do&nbsp;what.</p><p>As for SQL? I haven&#8217;t yet found a tool that does this. I have found plenty of SQL Builders, I&#8217;ve found a few SQL to Go generates, but I haven&#8217;t found anything to build my SQL for&nbsp;me.</p><h4>Resources</h4><p><a href="https://rubyonrails.org">Ruby on&nbsp;Rails</a></p><p><a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/">The Pragmatic Programmer&#8202;</a>&#8212;&#8202;Andy Hunt and Dave Thomas (20th Anniversary Edition)</p><p><a href="http://media.pragprog.com/titles/tpp20/dry.pdf">DRY chapter</a> of The Pragmatic Programmer, &#8216;20th Anniversary&#8217; Edition</p><p><a href="https://golang.org">The Go Programming Language</a></p><p><a href="https://github.com/go-swagger/go-swagger">go-swagger</a></p><p><a href="https://kubernetes.io">Kubernetes</a></p><div><hr></div><p><a href="https://medium.com/geekculture/code-generation-is-terrible-i-love-it-27d2ba2ce68b">Code Generation is Terrible; I Love It</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded></item><item><title><![CDATA[Minding Your Tabs and Spaces]]></title><description><![CDATA[Technical debt comes in all sizes.]]></description><link>https://www.jakew.ca/p/minding-your-tabs-and-spaces-15a17f2d8e6b</link><guid isPermaLink="false">https://www.jakew.ca/p/minding-your-tabs-and-spaces-15a17f2d8e6b</guid><dc:creator><![CDATA[Jake Winters]]></dc:creator><pubDate>Mon, 24 May 2021 12:42:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f2d366e4-4b68-489c-9019-c689034ed28c_1024x683.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h4>Technical debt comes in all sizes. Some debt pays. Some debt&nbsp;costs.</h4><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hddL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hddL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hddL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hddL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hddL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hddL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hddL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hddL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hddL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hddL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c35feaa-71f8-4182-9212-6354ab1bfd2c_1024x683.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@neonbrand?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">NeONBRAND</a> on&nbsp;<a href="https://unsplash.com/s/photos/peaceful-gardening?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure></div><p>One of my weaknesses is &#8220;gardening.&#8221; No, not the dirt and flowers kind&#8212;though I&#8217;m sure that&#8217;s fun for the same&nbsp;reason.</p><p>&#8220;Gardening&#8221; is the detailed work done after writing and testing code. It is similar to refactoring&#8212;reorganizing code to make it more efficient, reusable, or readable&#8212;but at smaller scales. Gardening helps ensure the code has consistent naming, syntax, and documentation and that the code is in compliance with the style&nbsp;guide.</p><p>Paying attention to the minutiae of the code is a requirement for programmers. One misplaced semicolon or one missing equal sign, and you could spend hours debugging.</p><p>This is not my weakness.</p><p>For me, tending to the code&#8217;s minutiae is fun. I enjoy it immensely. But, this is the problem. Knowing when &#8220;enough is enough&#8221; has always been my weakness. I can spend hours aligning brackets, renaming things, and writing excessive documentation.</p><p>This compulsion to detail has caused simple tasks to grow so big that I give&nbsp;up.</p><p>So, what to&nbsp;do?</p><p>If I don&#8217;t clean up my code, the code becomes an incomprehensible mess. Messy code is hard to understand. Code that can&#8217;t be understood is not able to be maintained. Unmaintainable code is discarded.</p><p>Not cleaning up code impacts development in ways that affect business. The effects range from simple bugs to preventing delivery, all of which are opposed to&nbsp;revenue.</p><p>If gardening is enjoyable for me, removing it from development reduces the reward. I&#8217;m less likely to do software development if I don&#8217;t enjoy at least some aspect of&nbsp;it.</p><p>But if I kept getting stuck in the details, the business results would be affected immensely.</p><p>I needed a&nbsp;system.</p><p>Here is what I came up with: defer the fun stuff. Not indefinitely, but to when my motivation and energy are&nbsp;lowest.</p><p>My energy, focus, and motivation are usually highest before lunch. It slowly weans until around 4 PM, and by the time my kids are in bed, all hope of getting anything productive done has disappeared.</p><p>I tackle the big tasks like designing and implementation before around 2 PM. The last few hours of work or after my kids have gone to bed, when I&#8217;m tired, with a brain as sharp as playdough, I go through the list so I can still contribute.</p><p>The hard part becomes deciding what to&nbsp;defer.</p><p>What do you do to keep yourself moving forward? What systems have you created? Reply and let me&nbsp;know!</p><p>Thanks for&nbsp;reading!</p>]]></content:encoded></item><item><title><![CDATA[Desire Paths: For When Shortcuts Don’t Cut It [macOS]]]></title><description><![CDATA[Summary: You don&#8217;t need expensive keyboards to improve convenience.]]></description><link>https://www.jakew.ca/p/desire-paths-for-when-shortcuts-dont-cut-it-macos-67ade700e3b3</link><guid isPermaLink="false">https://www.jakew.ca/p/desire-paths-for-when-shortcuts-dont-cut-it-macos-67ade700e3b3</guid><dc:creator><![CDATA[Jake Winters]]></dc:creator><pubDate>Tue, 27 Aug 2019 13:00:04 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/991547c4-bcbe-4928-934b-5b13091181b4_780x693.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Summary: You don&#8217;t need expensive keyboards to improve convenience. Complete customizability is excellent, but we can get halfway there with simple soft-hacks. You can use system preferences and software tools to customize your keyboard to make your navigation more comfortable and help prevent&nbsp;errors.</p></blockquote><p>Whether you&#8217;re on a computer all day or just trying to do something quickly, you&#8217;ve probably realized that switching between the keyboard and mouse can take a moment. Added all together, these moments can eat up a chunk of time. This wasted time is terrible for productivity and gets even worse if you touch type and manage to miss home&nbsp;row.</p><p>This latency is a known limitation of most modern operating systems, and as such, there is a solution: keyboard shortcuts. Keyboard shortcuts present problems as well. Most of them are designed to work for general problems and are not optimized. If you&#8217;ve ever typed Command + Tab, Tab, Tab, Tab, Tab, you know what I&nbsp;mean.</p><p>Remembering keyboard shortcuts can be a daunting task. Like most things, the key to remembering them is using them. Generic ones like Copy and Paste are usually pretty easy to remember. Creating custom cheat sheets for each app you use can also be quite handy. Then there&#8217;s the option to customize them.</p><h4>When is a keyboard shortcut not a shortcut?</h4><p>Has this scenario ever happened to you? You&#8217;re switching between applications and closing them as you go. You think you have iTunes open (Did I connect my phone?!) so you go to close it using Command + Q, nice and simple. Except you just closed a window with 5+ tabs open.&nbsp;Oops.</p><p>All of a sudden, that &#8220;shortcut&#8221; isn&#8217;t much of a shortcut anymore. Luckily, there is a way to disable this for specific applications. If you go to System Preferences and click on Keyboard, you&#8217;ll see a Shortcuts tab. If you create a shortcut for the &#8220;Quit [Application Name]&#8221; menu item, any existing shortcut for that menu item is not triggerable. Note that you&#8217;re going to want to make sure this matches the menu item, not the name of the application. For example, when you open iTerm, it shows &#8220;Close iTerm2&#8221;, not &#8220;Close&nbsp;iTerm&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5ckl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5ckl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 424w, https://substackcdn.com/image/fetch/$s_!5ckl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 848w, https://substackcdn.com/image/fetch/$s_!5ckl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 1272w, https://substackcdn.com/image/fetch/$s_!5ckl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5ckl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;System Preferences's Keyboard pane with shortcuts tab loaded. App Shortcuts has added iTerm and Safari shortcuts.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="System Preferences's Keyboard pane with shortcuts tab loaded. App Shortcuts has added iTerm and Safari shortcuts." title="System Preferences's Keyboard pane with shortcuts tab loaded. App Shortcuts has added iTerm and Safari shortcuts." srcset="https://substackcdn.com/image/fetch/$s_!5ckl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 424w, https://substackcdn.com/image/fetch/$s_!5ckl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 848w, https://substackcdn.com/image/fetch/$s_!5ckl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 1272w, https://substackcdn.com/image/fetch/$s_!5ckl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fae81846f-f41d-4fc0-ac0f-761fd7dc1fe9_780x693.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">My limited but exceptionally handy tab-savers.</figcaption></figure></div><p>In my case, I added this to Safari and iTerm. I can sit in my iTerm window and press Command + Q or Command + W and nothing&nbsp;happens.</p><h4>When is a keyboard not <em>just</em> a keyboard?</h4><p>A few years ago, when I was working at a contact centre, I decided to purchase a mechanical keyboard. Mechanical keyboards quickly grew into a hobby, and after about five keyboards I had found my endgame, the beautiful 40% Planck Keyboard from OLKB. When is a keyboard not just a keyboard? When it is a piece of&nbsp;art.</p><p>One of the main benefits of the Planck is that it runs on the QMK firmware which offers endless amounts of customizability including changing the key map. I wrote an article about it, including my keymap. The Planck is a fantastic keyboard, and I learned some of my favourite &#8220;desire paths&#8221; from&nbsp;it.</p><p>My only issue was that I needed to move from my desk to the meeting room. I frequently found that switching between the Planck and the built-in MacBook keyboard became confusing for my hands. However, there was no way to add this functionality to my MacBook keyboard. Or so I&nbsp;thought.</p><h4>When is a key <em>less</em> than a&nbsp;key?</h4><p>If you&#8217;re a terminal junky like me, you probably use the Control key a lot. As said before, keyboard shortcuts are a great way to avoid switching to the mouse, but in the simplified, often mouse-less environment of a terminal, they become even more critical.</p><p>I could write an article on <em>just</em> shortcuts for Bash/Zsh. (That&#8217;s not a bad idea. Look forward to that in the future.) The majority of bash shortcuts, along with many used for terminal programs like tmux and Vim use the Control key. The notable exception is the Escape key for Vim to enter &#8220;Normal&nbsp;Mode&#8221;.</p><blockquote><p>As a side note, Vim&#8217;s &#8220;Enter Normal Mode&#8221; shortcut is generally Escape, but Control + [ also exists which works&nbsp;faster.</p></blockquote><p>So, if the Control key is so important, how come it is such a stretch to reach? If you think you know why, please add a comment below. Terrible placement aside, there is a solution. And a pun, but you&#8217;ll get that in a moment. Deep inside of the System Preferences, Apple does let you adjust&nbsp;this.</p><p>You may have noticed while using terminal-based apps that the Home Row comes up often. The home row is the position where your hands should be at rest. Being able to put the most critical keys on this row is helpful, which is why Vim loves it so much. The Home Row contains one of the most useful keys for terminal use: the Return key, as well as one of the least useful: the Caps Lock&nbsp;key.</p><p>The Caps Lock key is useless in this day and age unless you find yourself in YouTube comments. It&#8217;s likely one of the few keys that&#8217;s actuated more often by accident than intention. I&#8217;m sure you can see where these two areas collide. Let&#8217;s convert that prime real-estate Caps Lock into something <em>actually&nbsp;useful</em>.</p><p>In System Preferences, open the Keyboard panel and click on the &#8220;Modifier Keys&#8230;&#8221; The modal that opens gives us some customizability. We can select to set the &#8220;Caps Lock&#8221; option to the value of &#8220;Control&#8221;. Here&#8217;s a gif, because it&#8217;s not 2005 anymore and static images don&#8217;t cut&nbsp;it.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aLgX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aLgX!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 424w, https://substackcdn.com/image/fetch/$s_!aLgX!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 848w, https://substackcdn.com/image/fetch/$s_!aLgX!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 1272w, https://substackcdn.com/image/fetch/$s_!aLgX!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aLgX!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aLgX!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 424w, https://substackcdn.com/image/fetch/$s_!aLgX!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 848w, https://substackcdn.com/image/fetch/$s_!aLgX!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 1272w, https://substackcdn.com/image/fetch/$s_!aLgX!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c6c3826-d82f-46e7-80af-7b30c2b329be_554x484.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Okay. So we&#8217;ve upgraded some dead space. One other thing you may have noticed is that there was another option in there. You can set the pressing of the Caps Lock key to an &#8220;Escape&#8221; press. Setting the &#8220;Caps Lock&#8221; setting to the &#8220;Escape&#8221; value is very tempting. If you have one of Apple&#8217;s Touch Bar MacBook Pros, &#8220;Escape&#8221; may even be a better choice. I did this for a while, and I can recommend it as much as the &#8220;Control&#8221; option. Of course, we can do one&nbsp;better.</p><blockquote><p>Side note for Apple, if you&#8217;re reading this. I love the Touch Bar. It would do well <em>above</em> the function keys on a pro device&nbsp;though.</p></blockquote><h4>When is a key <em>more</em> than a&nbsp;key?</h4><p>It&#8217;s about time we introduce the real star of this article, Karabiner Elements. Self-described as &#8220;A powerful and stable keyboard customizer for macOS,&#8221; I decided to put it to the test. I enjoyed the outcome, and I think you will as&nbsp;well.</p><p>Karabiner Elements is very similar to the &#8220;Modifier Keys&#8230;&#8221; prompt in the same way a Formula 1 car is very similar to a bicycle. In its &#8220;Simple Modifications&#8221; tab, Karabiner allows you to switch <em>any</em> key to act as <em>any</em> key. Did I mention this was the &#8220;<em>Simple Modifications</em>&#8221; tab?</p><blockquote><p>&#8221;With great power there must also come&#8202;&#8212;&#8202;great responsibility!&#8221; RIP, Uncle&nbsp;Ben.</p></blockquote><p>While there are eight tabs total, the first three (Simple Modifications, Function Keys, and Complex Modifications) are the ones we will discuss. If you wanted to, you could easily repeat the same modification, converting the Caps Lock key to a Control key when&nbsp;pressed.</p><p>The &#8220;Function Keys&#8221; tab is quite helpful as it allows you to change what the functions labelled on the keyboard do. Perhaps you don&#8217;t like Launchpad and want to have a regular F4 without changing the rest of&nbsp;them?</p><p>The &#8220;Complex Modifications&#8221; tab is where things get <em>crazy</em>. What if a key could do different things based on the <em>context</em> of its use? Here are a few examples:</p><h4>Application Specific:</h4><p>Perhaps your IDE forces you to use Shift + F4 but you don&#8217;t use F4 for anything. You can have Karabiner force all F4 presses <em>inside of your IDE</em> to be Shift + F4 for&nbsp;you.</p><h4>Multi-Modifier Keys:</h4><p>If you&#8217;ve read my post on <a href="https://effectivedev.com/alfred-automation/">Alfred</a>, you may have noted that you can make advanced workflows start from a keyboard shortcut. If you&#8217;ve looked through the keyboard shortcuts in your apps, you&#8217;ll notice there aren&#8217;t a lot of options left that give you flexibility.</p><p>For example, if you cycle through the same five apps regularly, all day, your pinky finger may get tired from tabbing between apps. Let&#8217;s add our own: introducing the makeshift Hyper&nbsp;key!</p><p>The makeshift Hyper key is technically a combination of Control + Option + Command + Shift. Pressing Control + Option + Command + Shift + C to open Slack would be kind of annoying. Well, let&#8217;s change a key to be the Hyper key. In this case, let&#8217;s set the right Command key to be our Hyper key. After updating Alfred to open apps using keyboard shortcuts, I have set the following:</p><ul><li><p>Hyper + C to open my Slack&nbsp;<em>C</em>hat.</p></li><li><p>Hyper + K to open my <em>K</em>anban&nbsp;board.</p></li><li><p>Hyper + G to open my&nbsp;<em>G</em>itlab.</p></li><li><p>Hyper + M to open my Spark&nbsp;<em>M</em>ail.</p></li></ul><p>Realistically, your memory is the soft limit for&nbsp;this.</p><h4>Differentiate between modifier and lone key&nbsp;presses:</h4><p>This customization is elegant, and probably my favourite part of Karabiner. A key can be a key when pressed by itself, but another key when used as a modifier. For example, let&#8217;s set our ill-suited Caps Lock key to be Escape when pressed alone, but count as Control when pressed with another&nbsp;key.</p><p>So now pressing the Caps Lock key is equivocal to pressing the Escape key. However, pressing Caps Lock + P is equivocal to pressing Control + P. Just think of the Vim possibilities!</p><p>To do&nbsp;this:</p><ol><li><p>Open the &#8220;Complex Modifications&#8221; tab in Karabiner.</p></li><li><p>Click on &#8220;Add&nbsp;Rule&#8221;.</p></li><li><p>Click on &#8220;Import more rules from the Internet (open a web browser)&#8221;.</p></li><li><p>When the browser opens, search for &#8220;Change caps_lock key (rev&nbsp;4)&#8221;.</p></li><li><p>Click &#8220;Import&#8221; for the option listed. Once imported, it&#8217;ll show you all the available rules to&nbsp;select.</p></li><li><p>Find &#8220;Change caps_lock to control if pressed with other keys, to escape if pressed alone&#8221; and click &#8220;Enable&#8221; next to&nbsp;that.</p></li></ol><p>Done!</p><h4>Even More Craziness</h4><p>There are even more things you can do, as well. Here are some examples:</p><ul><li><p>Double-press a key to result in an&nbsp;event.</p></li><li><p>Press-and-hold a key to result in an&nbsp;event.</p></li><li><p>Use multiple non-modifier keys to result in an&nbsp;event.</p></li></ul><h4>The Ugly Side of Karabiner</h4><p>One thing I&#8217;ve found difficult about Karabiner is that it&#8217;s documentation for Complex Modifiers is hidden under the karabiner.json reference manual menu item. Once you have created a complex modifier JSON file, you need to import it. At the time of this article, there is no way to import JSON files directly. To import the file, you need to use the URL karabiner://karabiner/assets/complex\_modifications/import?url={URL} where the {URL} is the full path to the JSON file starting with file:///, URL-encoded.</p><p>For example, assuming the file is at /Users/username/Documents/Modifier.json, we change it to file:///Users/username/Documents/Modifier.json, and then encode it as file%3A%2F%2F%2FUsers%2Fusername%2FDocuments%2FModifier.json and prefix it with the rest of the URL as karabiner://karabiner/assets/complex\_modifications/import?url=file%3A%2F%2F%2FUsers%2Fusername%2FDocuments%2FModifier.json.</p><h4>Example karabiner.json file</h4><p>&lt;a href="https://medium.com/media/8ebf863e8b25b71d515eacb19089954b/href"&gt;https://medium.com/media/8ebf863e8b25b71d515eacb19089954b/href&lt;/a&gt;</p><h4>Wrapping It All&nbsp;Up</h4><p>These changes represent my own needs. So now we&nbsp;have:</p><ul><li><p>Removed keyboard shortcuts that cause more harm than&nbsp;good;</p></li><li><p>Single keyboard shortcuts to open apps and web&nbsp;pages;</p></li><li><p>Moved some of our most commonly used keys into better locations; and</p></li><li><p>Set up our environment to be able to do even&nbsp;more.</p></li></ul><p>I hope that this article has given you the confidence to create your shortcuts and help speed up your work. If you have created some shortcuts, let me know in the comments&nbsp;below!</p>]]></content:encoded></item><item><title><![CDATA[Alfred: Not Just For Batman]]></title><description><![CDATA[Summary: Alfred is a neat macOS app at first but crazy powerful once you understand it.]]></description><link>https://www.jakew.ca/p/alfred-not-just-for-batman-efa34814419a</link><guid isPermaLink="false">https://www.jakew.ca/p/alfred-not-just-for-batman-efa34814419a</guid><dc:creator><![CDATA[Jake Winters]]></dc:creator><pubDate>Tue, 20 Aug 2019 13:00:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0864df07-ceec-4380-8507-701a750f311e_1024x632.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Summary: Alfred is a neat macOS app at first but crazy powerful once you understand it.</p></blockquote><p>Sometimes the hardest part about getting work done isn&#8217;t the challenging bits. It&#8217;s the boring parts. You know the ones. Writing up a pull request, or navigating to tickets, etc. Sometimes it&#8217;s hard to get rid of these things. If you can, that&#8217;s great. If you can&#8217;t, there&#8217;s&nbsp;<a href="https://www.alfredapp.com">Alfred</a>.</p><p>After using Shortcuts on iOS and discovering my new love of making workflows to automate my busy work, I decided to have a search for a comparable tool for macOS. Automator on macOS is an excellent start but lacks the ease of use of Shortcuts. For years I was convinced that Alfred was a moderately improved Spotlight search and therefore not worth the&nbsp;money.</p><h4>&#129333;First Servings</h4><p>After installing Alfred and starting to configure it, I knew that I was wrong. Where Spotlight does a decent job of searching files, Alfred is a tool for performing actions of many different types, one of which happens to be an ultra-fast search for files. Alfred is highly configurable and going through its exhausting preference panel helps tell you something about the number of actions available.</p><p>Here are some of my favourites. These are built-in by default, and I use them almost&nbsp;daily:</p><p><em>Clipboard History:</em> If you&#8217;ve ever had to deal with copy-and-pasting two things at once, this solves that problem elegantly. After typing in the keyword (default of clipboard, I switched mine to pb), or pressing the keyboard shortcut (Alt + Command + C), you get a list of the most recent things you&#8217;ve copied. Command + 3 pastes the <em>second</em> most recently copied. Alfred is even smart enough not to store passwords from 1Password or Keychain in the clipboard history.</p><p><em>Snippets:</em> The reason that Command + 3 returns your second copy is because of snippets. They&#8217;re the first result. If you&#8217;ve ever had to copy and paste a template before filling out the specific details, you know how annoying it is to do it again. Snippets are the solution to that. You can paste the template into a snippet and then use the keyword snip to search for it. An example of this would be using &#8220;Merge Request&#8221; templates. I can open Alfred Command + Space and then type snip merge and press enter. The field populates.</p><p><em>Web Search:</em> By default, Alfred has searches for many popular websites setup. You can easily search IMDb Avengers to find out more about the Avengers movie. Where this gets <em>really</em> good is that you can add your own custom searches. For example, I have my work Wiki setup using wiki issue login fails on error to search for any issues containing that text. You can make this even more powerful, but we&#8217;ll get into that&nbsp;below.</p><p>Want to add a new web search? Just go to the site you want to search, type in {query} and do a search. Replace the part of the URL on the results page that says %7Bquery%7D with {query} and then add the URL as a new entry to the table of searches. Here&#8217;s an example where I added in a Python search in about a&nbsp;minute:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HPgo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HPgo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 424w, https://substackcdn.com/image/fetch/$s_!HPgo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 848w, https://substackcdn.com/image/fetch/$s_!HPgo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 1272w, https://substackcdn.com/image/fetch/$s_!HPgo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HPgo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The query and title added in for a py keyword to search Python documentation.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The query and title added in for a py keyword to search Python documentation." title="The query and title added in for a py keyword to search Python documentation." srcset="https://substackcdn.com/image/fetch/$s_!HPgo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 424w, https://substackcdn.com/image/fetch/$s_!HPgo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 848w, https://substackcdn.com/image/fetch/$s_!HPgo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 1272w, https://substackcdn.com/image/fetch/$s_!HPgo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa89cf50b-2398-48a1-99f8-78e1a5f367e4_1024x632.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">The query and title added in for a py keyword to search Python documentation.</figcaption></figure></div><p>If you have or plan to add your own search, what will it be for? Comment at the bottom. I&#8217;d love to hear about&nbsp;it!</p><p><em>Web Bookmarks:</em> Similar to Web Search, Alfred can search for and open your bookmarks. Even better than that, it can open them in the browser in which the bookmark exists. If you deal with terrible intranet sites that should have been purged a decade ago, this is amazing. A quick search for (or if you have it set, a keyword followed by) &#254;e olde SVN opens an instance of Chrome when one is warranted. Unfortunately, the bookmark search only works in Safari and Chrome. You can have pages open using Firefox, but we&#8217;ll get into that below. (Is the suspense killing you&nbsp;yet?)</p><p><em>The Rest:</em> Calculator, Dictionary and Contacts are all improved over Spotlight. Alfred also includes actions for controlling iTunes, the entire macOS system and even the ability to run terminal (Terminal.app, iTerm <em>or</em> Hyper, theoretically more) commands directly, opening a new window with the result. Pretty neat, eh? Let&#8217;s get to the cool part though: Workflows.</p><p>If you have used Alfred before, what was your favourite feature? Was it one of the ones I listed or something I may have missed? Comment at the bottom. I want to hear from&nbsp;you!</p><h4>&#127959; Workflows</h4><p>Everything up to this point has been pretty neat. Workflows, though, take this app from nice-to-have to productivity-enhancing-drug. Workflows, as the name implies, are a way of performing one or more actions as easily as possible.</p><p>There are five parts: Triggers, Inputs, Actions, Utilities and Outputs. Triggers and Inputs start a workflow before handing off to Actions or Utilities (which may also trigger other Actions or Utilities), finally ending in an optional&nbsp;Output.</p><p>It seems simple enough. So why all of the fuss? All five of these parts offer a lot of different options.</p><p>&#128678; <em>Triggers</em> can be hotkeys, other Alfred features, or an external trigger (which generally means using AppleScript. Expect more on that in another article!). Think hitting Command + Alt + V or running a Bash command. These don&#8217;t even need to be <em>you</em> running the trigger, either. You can use tools like fswatch to run it whenever a file is&nbsp;updated.</p><p>&#9981;&#65039; <em>Inputs</em> are keyword-based actions that may have a query. Use these when you&#8217;re trying to use Alfred similar to Spotlight. &#8220;Keyword&#8221; works for simple actions like typing in keyword [query...]. For example, I have one set up so git jakew/dotfiles opens github.com/jakew/dotfiles, which makes getting to the project on Github super&nbsp;easy.</p><p>Another significant option for Inputs is a Script Filter. This one allows you to run a script and return the results directly in the Alfred window. It can run Python, PHP, Ruby, Perl, AppleScript or even just a Bash function. Returning JSON or XML in the proper format allows Alfred to show a custom list of results. For example, I have a Python wrapper that returns all of the tickets I own in my organization&#8217;s issue tracker. By typing issues, the list is populated and selecting an option opens the issue in my browser. (Unfortunately, my issue workflow uses proprietary software so I can&#8217;t release it. &#128555; Just know that it would have blown your&nbsp;mind.)</p><p>&#127939;&#8205;&#9792;&#65039; <em>Actions</em> are generally the <em>doing</em> part of the workflow. For example, they might open a specific app, file or URL, or run a system command. An Action can also run an AppleScript script for automation purposes. You can combine it with applications that use x-callback-urls as well (which is itself a topic for another article.)</p><p>&#129520; <em>Utilities</em> are the logic of the workflow. If query is this, then do that. Split arguments. Convert X to Y. Stuff like that. One extra utility available is &#8220;Debug,&#8221; which shows you the current values in your workflow&nbsp;objects.</p><p>&#128250; Lastly, <em>Outputs</em> are how you get the content or notification out of the workflow. It can be as simple as a macOS Notification or sound, or it can itself trigger a key combination. Outputs may also write text where your cursor is currently waiting, or put its result in your clipboard, ready for you to paste. To get even crazier: an output may also call an &#8220;External Trigger&#8221;! Remember when we mentioned those above? Yea, you can have workflows calling workflows. &#128561;</p><h4>&#10084;&#65039; Sharing is Caring; Sharing Automation is Doubly&nbsp;So.</h4><p>&#8220;Okay, you&#8217;ve convinced me,&#8221; you might be thinking, &#8220;but do I want to sit down and make all of these workflows?&#8221; The simple answer is: you don&#8217;t need to. A large community of people are already making a lot of them and are eager to share! You can join in on the <a href="https://alfredforum.com/">Alfred Forum</a> and even share the workflows you&#8217;ve&nbsp;made.</p><p>You can also get workflows from the <a href="https://www.alfredapp.com/blog/">Alfred Blog</a> or even in future articles on Effective Dev. Here is an example of one I&#8217;ve already&nbsp;made:</p><ul><li><p><em>Paste as Code Block</em>: When &#8220;pasted,&#8221; the contents of your clipboard paste with three grave-accents (```) surrounding them to create a code block. Pasting like this is helpful in Markdown on Github/Gitlab, and in&nbsp;Slack.</p></li></ul><p>If this article has convinced you to get Alfred or if you already have it, and you have made some workflows of your own that you&#8217;d like to share, I&#8217;d love to see them in the comments&nbsp;below.</p><h4>&#127937; Conclusion</h4><p>Alfred has been amazing for increasing my productivity. As long as I don&#8217;t spend my entire day making workflows instead of doing work. Alfred can get many things done, so you don&#8217;t have to. Its community is vibrant and offers a ton of resources that you should check out. Of course, your workflows are your own, so dig in and give it a&nbsp;try.</p>]]></content:encoded></item></channel></rss>