erock's bloghttps://erock.io2022-06-29T00:10:06ZTechnical writings by Eric BowererockTypescript is terrible for library developers2022-08-23T15:57:12Zhttps://erock.io/typescript-terrible-for-library-developers<p>I love typescript as an end-developer. I feel like it dramatically reduces the
need to manually write automated tests. It cannot be overstated how much work is
involved with writing and maintaining good automated tests so anything that can
reduce its utility is a huge boon to productivity.</p>
<p>However, as a library developer, I hate typescript. There are a lot of reasons
why typescript sucks for library developers, but at the end of the day it
reduces developer productivity. In effect, we are shifting complexity from
end-developers to library developers. This places a huge burden on us to be
experts with how typescript works.</p>
<h2 id="documentation"><a class="anchor" href="#documentation" rel="nofollow">#</a> Documentation</h2>
<p>There's great documentation and blogs for end-developers but there's very little
for library-developers. The closest thing I can find targeting library
developers is the section on
<a href="https://www.typescriptlang.org/docs/handbook/2/types-from-types.html" rel="nofollow">Type Manipulation</a>.</p>
<p>I think the underlying assumption here is that there is no difference between a
library developer and an end-developer, which I reject.</p>
<p>Why are there no guides on the typescript site about library developers? What
about a guide on the recommended tools for a library developer?</p>
<p>The kind of hoops I have to jump through to get types "just right" in a web app
versus a library is dramatically different. It's rare in a web app for me to
need constructs like conditional types, type operators, and overloads. As a
library developer, they are <strong>heavily</strong> used. These constructs are highly
dynamic and embed logic into your types. This leads into my next frustration
with typescript which is debugging.</p>
<h2 id="debugging"><a class="anchor" href="#debugging" rel="nofollow">#</a> Debugging</h2>
<p>How do library developers debug their highly dynamic and heavy use of
conditional types, overloads? They bang their head against the keyboard and hope
it works. It seems like the only debugging tool available is the typescript
compiler itself and expertise. You make a change and see what the end result is
for that type. As far as I know there are no better solutions besides having an
intuition about how it all works.</p>
<p>The only tool that I know library developers use heavily is the
<a href="https://www.typescriptlang.org/play" rel="nofollow">Typescript playground</a> so they can isolate
discrete parts of their type logic to figure out why typescript is resolving to
a certain type. It also lets you change the typescript version and config
easily.</p>
<p>What am I missing here? I'm writing this post not only out of frustration but
also because I'm hoping I've missed something.</p>
<h2 id="complexity"><a class="anchor" href="#complexity" rel="nofollow">#</a> Complexity</h2>
<p>I spend a decent amount of time in the redux world so <code>redux-toolkit</code> is a great
library to see how types are done <strong>correctly</strong> in a real codebase. To be clear,
they do a fantastic job with types, but the level of complexity is pretty
startling:</p>
<ul>
<li><a href="https://github.com/reduxjs/redux-toolkit/blob/4ab8c42cb20ae1e6f7b84a8ac0070eee54775b79/packages/toolkit/src/createAction.ts#L58" rel="nofollow">createAction #1</a></li>
<li><a href="https://github.com/reduxjs/redux-toolkit/blob/4ab8c42cb20ae1e6f7b84a8ac0070eee54775b79/packages/toolkit/src/createAction.ts#L193" rel="nofollow">createAction #2</a></li>
</ul>
<p>That is just one example but the codebase is riddled with complex types. Also,
when you look around, note the amount of types vs actual code. Purely for
demonstration purposes -- and ignoring imported code -- <strong>only ~10%</strong> (35/330
LoC) of that file is code that will be transpiled to js.</p>
<p>It's pretty common in style guides to never nest ternaries. In typescript,
that's the only way to narrow types based on other types. It's a mess!</p>
<h2 id="testing"><a class="anchor" href="#testing" rel="nofollow">#</a> Testing</h2>
<p>Because types can be generated from other types and the highly dynamic nature of
those types, there's a new class of tests that are required for any serious
typescript project: testing your types. It's not enough to test your types
against the latest version of the typescript compiler either, you also need to
test against previous versions.</p>
<p>This new class of tests are in their infancy and there's a barren wasteland of
tools that are now deprecated or partially maintained. I've used these libraries
previously:</p>
<ul>
<li><a href="https://github.com/microsoft/DefinitelyTyped-tools" rel="nofollow">DefinitelyTyped-tools</a></li>
<li><a href="https://github.com/SamVerschueren/tsd" rel="nofollow">tsd</a></li>
<li><a href="https://github.com/microsoft/dtslint" rel="nofollow">dtslint (moved)</a></li>
<li><a href="https://github.com/danvk/typings-checker" rel="nofollow">typings-checker (deprecated)</a></li>
</ul>
<p>There's a lot of churn in this space and I have projects that are still using
deprecated libraries because it's a huge pain to migrate.</p>
<p>It also seems like there are two recommended tools to use: dtslint and tsd. Why
do we need two tools to do roughly the same job? It's confusing to figure out
and frustrating to deal with.</p>
<h2 id="maintenance"><a class="anchor" href="#maintenance" rel="nofollow">#</a> Maintenance</h2>
<p>Types add a lot of code to your library. When first attempting to contribute to
a project, one must grok the application logic as well as the type logic. This
adds mental and code overhead to a project which really sucks. As an example, I
help maintain <a href="https://github.com/redux-saga/redux-saga" rel="nofollow">redux-saga</a>, and most
of our latest pull requests and issues are specifically related to types.</p>
<blockquote>
<p>I spend more time tweaking types than I do writing library code.</p>
</blockquote>
<p>I'm proficient with typescript but I'm no expert. It's frustrating because after
spending years writing typescript, I still do not have the necessary knowledge
to work with typescript as a library developer. Mastery seems like a
prerequisite. Types make it much harder to maintain a js library and especially
difficult to contribute to them.</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion" rel="nofollow">#</a> Conclusion</h2>
<p>I love typescript and think the team working on it are incredible. Typescript
has completely changed the FE landscape and wouldn't want to dismiss its
contributions.</p>
<p>But as a library developer, we need:</p>
<ul>
<li>better documentation,</li>
<li>better tooling, and</li>
<li>to spend less time making <code>tsc</code> happy.</li>
</ul>
<p>I shouldn't have to read the typescript compiler source code in order to figure
out why it's resolving a piece of my code to a specific type.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
We spend a disproportionate amount of time on typesinternet points2022-07-13T01:54:15Zhttps://erock.io/internet-points<p>I've spent the past decade chasing internet points on Reddit, Github, and
Twitter. I've found that focusing on internet points doesn't actually yield
favorable results. I spent so much time trying to figure out what other people
want to see that I've lost sight of what I want to see.</p>
<p>In the early days of reddit I used to spend hours constructing well thought out
and cited posts. I wanted to get karma because it somehow validated how smart I
am. Whenever I would get downvoted or receive a confrontational response, I
would feel deflated. Negative responses would scare me. It got to the point
where I would feel an intense amount of anxiety whenever I opened my inbox. What
are they going to say about my post? Was it well received? I would obsess over
the points I had accrued over time. I justified that this was a training process
to figure out how to construct well written posts. To some extent I think that's
true, but the reality is my focus on internet points ultimately led to an
unfavorable result. I was merely trying to please people while sacrificing my
own thoughts, ambitions, or desire to express my mind.</p>
<p>Then along came Github. Github stars are a seductive drug. The more stars you
have the more internet clout you have as a developer. The issue is compounded by
the fact that popular projects open doors for you as a software developer. I've
spent years chasing stars only to slowly realize yet again that internet points
mean nothing. They say nothing about you or the project you are working on
besides a popularity contest. Why do I want to be popular so badly? If I ever
get what I secretly desire, will it bring me happiness?</p>
<p>Recently I've come to the realization that social networking sites are
reinforcing this need to be popular and now that I've put a name to the concept
I've been trying to recuse myself of it entirely. It's tough because it feels
like you can't create software projects without being on Github. Sourcehut has
been a great outlet for me lately. The whole philosophy behind Sourcehut really
resonates as I lash out against all things social networking.</p>
<p>Who knows, maybe this is just an ad-hoc rationalization for why my projects
never seem to gain any traction.</p>
<p>Even if that is true, I know that focusing on internet points is
counter-productive. From now on I'm only going to build projects or products
that I want to use.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
internet points are a distractionOn github as a social network2022-07-05T13:45:05Zhttps://erock.io/github-as-social-network<p>Ever since I became jaded by social networks, I've been examining the sites I
visit and why I continue to visit them. We all know the addictive behavior
driving social networking "engagement." Ever since I first discovered facebook I
have been fighting against the constant drip of accumulating social currency.
Slowly I began to realize that these social networking sites were echo-chambers
that really didn't mean much at all. When you talk to people in person, there's
a sense of restraint. In person, people have manners, can read the room, and
understand what they should vocalize to their listeners. Perhaps most important
of all, they know when to keep thoughts to themselves.</p>
<p>It was only on these social networking sites, where people have a podium, that I
noticed quite a change in discourse. All of a sudden, you could read family and
friends' thoughts on all types of subjects that are never uttered in person.
Through these sites, I learned that some things are best left unsaid. And so I
left.</p>
<p>However, there's still a few social networking sites that I use. There's one in
particular, that is cloaked under the guise of productivity. Hidden behind both
my personal and professional life. It's one of the first pages I open during the
day, and one of the last ones to be closed before bed. Notifications from this
site are important because they could be a ping from a colleague who needs me to
review what they wrote. Or it could be a new question raised on one of my
projects. Github is the worst kind of social networking site. Like a virus, it
has infected my mind and is constantly getting me to engage with it. It's a
social networking site that I have to use for work. Software engineers stand no
chance against github. How did a site with such good intentions turn into one
that preys so easily upon our weaknesses?</p>
<p>"Github is the software engineer's resume, it will help you get a job" That's
what I have convinced myself is true. Is it actually true, though? I imagine
it's true for the people who have accumulated a lot of social currency. However,
it has been my experience that many companies do not even look at your resume.
No, these companies have a rigid process for potential employees and they rarely
deviate from the norm.</p>
<p>Now, with github sponsors, it's easier than ever to become popular and get paid
to work on open source projects. Popular, being the operative word. There's even
more reason to use github because the platform could help suppliment your
income.</p>
<p>Why do I get so excited when I receive stars on my projects hosted on github? I
am constantly trying to come up with new projects to build, but do I actually
want to build them? Or do I just want social currency? I'm not sure I can tell
the difference anymore.</p>
<p>Github isn't just a code repository, it's a social networking site. The network
effect makes it really hard to host other open source projects anywhere else. I
catch myself typing in a somewhat generic phrase, looking for a OSS project, and
append the search with "github." Ultimately, github makes it easier for people
to discover your work. People that want to share their work and have people use
it gravitate towards github. No other code repository site has as much mindshare
as github and it's all thanks to the human desire to accumulate and compare with
peers. Humans yearn for social clout and recognition.</p>
<p>Why was my judgment so clouded?</p>
<p>I really like <a href="https://sr.ht" rel="nofollow">sourcehut</a> and will be moving everything there.
There are no "engagement" features like stars or trending projects. Drew, more
than anyone, has made me realize that you don't have to participate in social
networking in order to have people use your projects.</p>
<p>For now, I'm going to maintain a mirror on github of my popular code
repositories, but I think I need to start distancing myself from the most
addictive social network in my life.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
A shining star, fades behind the cloudsreply -- the balance has shifted away from SPAs2022-07-05T13:45:08Zhttps://erock.io/shifting-away-from-spa<p>A recent article popped up on hackernews[0] that I figured I would respond to
here.</p>
<p><a href="https://news.ycombinator.com/item?id=31459316" rel="nofollow">[0] The balance has shifted away from SPAs</a></p>
<h1 id="new-frameworks-are-focusing-on-mpa--0kb-js-by-default"><a class="anchor" href="#new-frameworks-are-focusing-on-mpa--0kb-js-by-default" rel="nofollow">#</a> New frameworks are focusing on MPA + 0kb JS by default</h1>
<p>I definitely see a trend where more and more web frameworks are trying to
leverage multi-page applications with minimal or no javascript on the front-end.
I welcome these changes because not everything needs to be an SPA. I recently
built lists.sh[1] which doesn't have any javascript in its stack. Everything
works perfectly fine a simple document browser. It was a lot of fun to build and
didn't require any heavy lifting on the front-end. But here's the thing, it
didn't require any interactivity. One of its features was no javascript. At the
core of the site is a blog. Blogs do not need javascript and in fact, tend to
make the reading experience worse. This is one reason why I'm enjoying gemini so
much. Gemini is great for what it is: a document browser.</p>
<p><a href="https://lists.sh" rel="nofollow">[1] lists.sh</a></p>
<p>In the comments someone mentioned the concept of holotypes[2], which classifies
each use case of the modern browser in a category.</p>
<p><a href="https://jasonformat.com/application-holotypes/" rel="nofollow">[2] application holotypes</a></p>
<p>Some categories are better suited as an SPA vs an MPA. So let's organize the
list that way:</p>
<h2 id="spa"><a class="anchor" href="#spa" rel="nofollow">#</a> SPA</h2>
<ul>
<li>Social networking destinations</li>
<li>Social media applications</li>
<li>PIM applications</li>
<li>Productivity applications</li>
<li>Media players</li>
<li>Graphical editors</li>
<li>Media editors</li>
<li>Engineering tools</li>
<li>Immersive / AAA games</li>
<li>Casual games</li>
</ul>
<h2 id="mpa"><a class="anchor" href="#mpa" rel="nofollow">#</a> MPA</h2>
<ul>
<li>Storefronts</li>
<li>Content websites</li>
</ul>
<p>I really like this list because it helps us understand why SPAs are so
prevalent. Does everything need to be an SPA? No, but there are a lot of use
cases where it just makes sense.</p>
<p>In this context, it's pretty clear that SPAs are not going anywhere.</p>
<h1 id="new-browser-apis-to-support-mpas"><a class="anchor" href="#new-browser-apis-to-support-mpas" rel="nofollow">#</a> New browser APIs to support MPAs</h1>
<p>The article continues to discuss that browser's have been implementing more and
more features to make MPAs more usable:</p>
<ul>
<li>Paint holding</li>
<li>Back-forward caching</li>
<li>Service workers</li>
<li>Shared element transitions</li>
</ul>
<p>Honestly, I don't think these features are particularly compelling. When I look
at this list, I don't think "game changer." These are polish features to make
MPAs are little more pleasant, but that's about it.</p>
<h1 id="control"><a class="anchor" href="#control" rel="nofollow">#</a> Control</h1>
<blockquote>
<p>Choosing an MPA is a big architectural decision that may effectively cut off
the future possibility of taking control of the page in cases where the
browser APIs are not quite up to snuff. At the end of the day, an SPA gives
you full control, and many teams are hesitant to give that up.</p>
</blockquote>
<p>That's it. He described exactly why I like building SPAs: control. As a software
engineer who has built a career on SPAs, the control I have when building
applications is critical. Control is the primary characteristic I think about
when architecting a web app. This is especially important when working at a
startup where the business requirements are vague. Making big decisions about an
application that might stick around for 5+ years while at the same time not
fully understanding the business requirements puts us in a tough position.</p>
<p>At the end of the day, SPAs provide us with control over our application. I can
make an SPA do anything I want and that's really all I care about. Do I
particularly enjoy building massive javascript applications? Not really. That's
why in my free time I'm focusing less on javascript and more on products that
doesn't require it.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
SPAs are here to stayThe duality of software production2022-07-05T13:45:08Zhttps://erock.io/the-duality-of-software-production<p>My ideal setup is shockingly similar to a web application itself: I have a
front- and back-end computer for all personal software development. As I reflect
on this, I find it humorous that I've aligned myself with a setup that resembles
so closely to how I write web apps.</p>
<p>My back-end "server" is an old gaming rig that I have repurposed. It sits in my
basement with an ethernet connection placed right next to my home network. It is
connected to a UPC so brownouts don't interrupt development. I have also changed
a BIOS setting so my server will turn back on automatically whenever I lose
power. All of this hardware helps keep my server online and always available.</p>
<p>The benefits of having a server for software development is multi-faceted:</p>
<ul>
<li>I can have as many front-end clients as I want and can pickup exactly where I
left off (continuity)</li>
<li>I don't need to install any software on my client computers except for ssh
(consistency)</li>
<li>Any heavy processing doesn't cause my client to overheat and scorch my thighs
(power)</li>
<li>The specs of the client doesn't really matter that much (flexibility)</li>
</ul>
<p>The other really amazing feature of this setup is since I have a server to host
my dev environment, it doesn't matter what front-end I use, I'll always pick up
exactly where I left off.</p>
<p>It also makes long-running tasks easier to handle since I don't have to worry
about my computer going to sleep while it's processing. For example, I can have
a full ethereum node running in a tmux session and I don't have to worry about
it getting interrupted.</p>
<p>My front-end clients consist of a few machines. I have a framework laptop that I
purchased late last year.
<a href="https://erock.io/2021/11/01/framework-vs-mbp.html" rel="nofollow">It is a fantastic ultra thin laptop</a>
that I would highly recommend to anyone interested in a similar setup. I have my
primary gaming rig that I'll sometimes use when I don't want to switch
computers. I also have an iPad that I can be perfectly productive using with
this setup.</p>
<p>In order to make this workflow feasible, I need to move as much of my software
tools into the terminal.</p>
<p>I accomplish this goal with a couple of tools:</p>
<ul>
<li>mosh (ssh alternative that allows for intermittent connectivity)</li>
<li>tmux (tabs and window panes for terminals)</li>
<li>neovim (IDE, tabs and window panes)</li>
<li>zerotier (so I can connect to my server from anywhere)</li>
</ul>
<p>These tools are essentially all I need to be productive. The only thing missing
is the browser which I have to run on my client machines.</p>
<p>Overall this setup has served me well for years and its benefits outweigh the
initial setup.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
How the I develop softwareOn decoupled loaders2022-07-05T13:45:07Zhttps://erock.io/on-decoupled-loaders<p>It's pretty common on the FE to track the status of data being loaded, which I
often refer to them as loaders.</p>
<p>This provides the ability to display loading indicators when data is being
fetched as well as return other meta data like errors or other relevant
information about the data that was fetched. It can also help assist in flow
control when there is some hairy business logic that needs to wait for an
operation to complete before proceeding to the next step.</p>
<p>More times than not, it's common to colocate the data with the status.</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">posts</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="nx">status</span><span class="o">:</span> <span class="s1">'loading'</span><span class="p">,</span> <span class="c1">// or 'error' or 'success'
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span> <span class="nx">error</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"> <span class="nx">meta</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"> <span class="nx">data</span><span class="o">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>On the surface this seems like a great way to ensure the data and the loading
state stays in sync. When inspecting your state, it's easy to see at-a-glance
the current status of your data. However, there are a couple issues with this
pattern.</p>
<h2 id="data-is-now-nested-deeper-in-your-state-object"><a class="anchor" href="#data-is-now-nested-deeper-in-your-state-object" rel="nofollow">#</a> Data is now nested deeper in your state object</h2>
<p>I keep quoting the zen of python over and over again because it often guides how
I think about code design and architecture:</p>
<pre><code>$ python
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
**Flat is better than nested.**
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
</code></pre>
<blockquote>
<p>Flat is better than nested</p>
</blockquote>
<p>In <code>redux</code>, the kernel of this rule is embedded in the following recommendation:</p>
<ul>
<li><a href="https://redux.js.org/style-guide/style-guide#normalize-complex-nestedrelational-state" rel="nofollow">normalize complex state</a></li>
</ul>
<p>When I'm debugging some code that relates to my state data, I want to be able to
inspect the data quickly. I want the data to be plain JSON so it's easy to read
in the console, and I would prefer to not traverse a bunch of nested objects to
find the relevant information.</p>
<p>By colocating a loader with the data that is being loaded, it adds unnecessary
nesting. Also, now that the data is nested, every query or debug session
involves traversing the object tree. This also requires a slew of existential
checks to ensure the objects even exist.</p>
<p><strong>Aside: I wrote an article on preventing existential checks,
<a href="https://erock.io/2020/08/12/death-by-thousand-existential-checks.html" rel="nofollow">check it out!</a></strong></p>
<h2 id="loading-state-logic-requires-a-data-object"><a class="anchor" href="#loading-state-logic-requires-a-data-object" rel="nofollow">#</a> Loading state logic requires a data object</h2>
<p>The logic for loading states is now tightly coupled to the data that is being
loaded. Like I said previously, on the surface this seems great, but what if you
want loading states that are not a 1:1 to a single piece of data?</p>
<p>What if you want a loading state for multiple pieces of data being loaded? To
solve that problem with a couple of loaders, you have to create custom logic to
wait for all loaders to be completed before proceeding. What happens when of the
loaders fail or never finish? This is all ad-hoc logic and complexity stapled on
top of a design for something that it was never intended to solve.</p>
<h2 id="enter-decoupled-loaders"><a class="anchor" href="#enter-decoupled-loaders" rel="nofollow">#</a> Enter decoupled loaders</h2>
<p>Instead, what I do in my projects with global state, like <code>redux</code>: I create a
separate data store for all my loaders:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">posts</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">loaders</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">posts</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">status</span><span class="o">:</span> <span class="s1">'loading'</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">error</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">meta</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>In this example, the <code>posts</code> data is at the top of the state data and all of my
loaders are in a separate slice.</p>
<p>Here is an example of how I would manage the loading state:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">async</span> <span class="nx">fetchPosts() {</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">startLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'posts'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s1">'/posts'</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">result</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">errorLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'posts'</span><span class="p">,</span> <span class="nx">error</span><span class="o">:</span> <span class="s1">'failed'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="kr">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">successLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'posts'</span><span class="p">,</span> <span class="nx">meta</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">post.id</span> <span class="p">}</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>A react component might look something like this:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">fetchPosts</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"./api"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useLoader</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"./hooks"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">const</span> <span class="nx">Page</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="kr">const</span> <span class="nx">loader</span> <span class="o">=</span> <span class="nx">useLoader</span><span class="p">(</span><span class="s2">"posts"</span><span class="p">);</span> <span class="c1">// find loader by id
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">fetchPosts</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="p">},</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nx">loader</span><span class="p">.</span><span class="nx">loading</span><span class="p">)</span> <span class="k">return</span> <span class="p"><</span><span class="nt">Spinner</span> <span class="p">/>;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="p">{</span><span class="nx">posts</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">p</span><span class="p">)</span> <span class="o">=></span> <span class="p"><</span><span class="nt">div</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">p</span><span class="p">.</span><span class="nx">id</span><span class="p">}>{</span><span class="nx">p</span><span class="p">.</span><span class="nx">title</span><span class="p">}</</span><span class="nt">div</span><span class="p">>)}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>The logic is exactly the same, but the beauty of decoupling the logic of the
loading state is you can now use the loaders for any asynchronous actions in our
app.</p>
<p>For example, what if we want to have an initial loader to fetch a bunch of data:</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="nx">fetchInitialData() {</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">startLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'initial-data'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"> <span class="k">await</span> <span class="nx">fetchUser</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"> <span class="k">await</span> <span class="nx">fetchPosts</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"> <span class="k">await</span> <span class="nx">fetchComments</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"> <span class="nx">successLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'initial-data'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Or what if we have to do some CPU intensive task</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">async</span> <span class="nx">intensity() {</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">startLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'intensity'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"> <span class="k">while</span> <span class="p">(</span><span class="nx">notFinished</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"> <span class="c1">// create the universe
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"> <span class="nx">successLoader</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="s1">'intensity'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>You can now reuse the same component logic as you do for loading single pieces
of data!</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">loader</span> <span class="o">=</span> <span class="nx">useLoader</span><span class="p">(</span><span class="s2">"intensity"</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// or useLoader('initial-data');
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// or useLoader('posts');
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// it's all the same!
</span></span></span></code></pre><p>In this way, our loaders become generic status trackers for any operation the
user or app might trigger. This provides way more flexibility than a loader that
is tightly coupled to fetching single pieces of data.</p>
<p>For all my react/redux projects, I generally use
<a href="https://github.com/neurosnap/robodux" rel="nofollow">robodux</a> which has a slice helper called
<a href="https://github.com/neurosnap/robodux/blob/master/docs/basic-concepts.md#createloadertable" rel="nofollow">createLoaderTable</a>
which encapsulates this decoupled loading logic.</p>
<p>As a result, every project can reuse the same decoupled loaders. It works well
for small projects as well as large enterprise ones.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
A common interface for loading statesnvim.sh2022-07-05T13:45:07Zhttps://erock.io/nvimsh-release<p>The older I get the more time I spend in the terminal. I really appreciate
services that accommodate accessing information from the terminal.</p>
<p>For example, I am a huge fan of <a href="https://cht.sh" rel="nofollow">https://cht.sh</a> to quickly access docs for
programming languages and <a href="https://wttr.in" rel="nofollow">https://wttr.in</a> to get a nice display of the weather.</p>
<p>I thought it would be fun to be able to quickly search for neovim plugins within
the terminal. Since I already have a curated list of neovim plugins via
<a href="https://neovimcraft.com" rel="nofollow">https://neovimcraft.com</a>, I thought it would be a fun project to work on for the
day.</p>
<p>So I'm happy to announce <a href="https://nvim.sh" rel="nofollow">https://nvim.sh</a> - a website that makes it easy to find
neovim plugins.</p>
<p>Using the service is easy and the default response is <code>text/plain</code>.</p>
<p>To figure out how the service works simply <code>curl</code> the website:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl">curl https://nvim.sh
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">nvim.sh - neovim plugin search from the terminal
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">api description
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">https://nvim.sh <span class="nb">help</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">https://nvim.sh/s <span class="k">return</span> all plugins in directory
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">https://nvim.sh/s/:search search <span class="k">for</span> plugin within directory
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">https://nvim.sh/t list all tags within directory
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">https://nvim.sh/t/:search search <span class="k">for</span> plugins that exactly match tag within directory
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">powered by: https://neovimcraft.com
</span></span><span class="line"><span class="ln">12</span><span class="cl">source: https://github.com/neurosnap/nvim.sh
</span></span></code></pre><p>Finding a plugin using <code>/s/:search</code> will look something like this:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl">$ curl https://nvim.sh/s/git
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Name Stars OpenIssues Updated Description
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">sindrets/diffview.nvim <span class="m">671</span> <span class="m">7</span> 2021-12-17T16:10:43Z Single tabpage interface <span class="k">for</span> easily cycling through diffs <span class="k">for</span> all modified files <span class="k">for</span> any git rev.
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">tveskag/nvim-blame-line <span class="m">121</span> <span class="m">1</span> 2021-07-27T18:59:55Z A small plugin that uses neovims virtual text to print git blame info at the end of the current line.
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">TimUntersberger/neogit <span class="m">1056</span> <span class="m">57</span> 2022-01-08T08:04:27Z magit <span class="k">for</span> neovim
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">tanvirtin/vgit.nvim <span class="m">265</span> <span class="m">12</span> 2022-01-08T06:55:57Z Visual Git Plugin <span class="k">for</span> Neovim
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">kdheepak/lazygit.nvim <span class="m">255</span> <span class="m">7</span> 2022-01-04T23:44:54Z Plugin <span class="k">for</span> calling lazygit from within neovim.
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Iron-E/nvim-highlite <span class="m">134</span> <span class="m">1</span> 2022-01-06T18:55:05Z A colorscheme template that is <span class="s2">"lite"</span> on logic <span class="k">for</span> the developer.
</span></span><span class="line"><span class="ln">10</span><span class="cl">onsails/vimway-lsp-diag.nvim <span class="m">71</span> <span class="m">5</span> 2021-12-28T07:58:29Z Live render workspace diagnostics in quickfix with current buf errors on top, buffer diagnostics in loclist
</span></span><span class="line"><span class="ln">11</span><span class="cl">onsails/diaglist.nvim <span class="m">71</span> <span class="m">5</span> 2021-12-28T07:58:29Z Live render workspace diagnostics in quickfix with current buf errors on top, buffer diagnostics in loclist
</span></span><span class="line"><span class="ln">12</span><span class="cl">lourenci/github-colors <span class="m">29</span> <span class="m">1</span> 2021-10-04T13:53:35Z Yet another GitHub colorscheme
</span></span><span class="line"><span class="ln">13</span><span class="cl">lewis6991/gitsigns.nvim <span class="m">1066</span> <span class="m">19</span> 2021-12-30T11:54:10Z Git integration <span class="k">for</span> buffers
</span></span><span class="line"><span class="ln">14</span><span class="cl">tversteeg/registers.nvim <span class="m">335</span> <span class="m">4</span> 2022-01-03T08:48:42Z 📑 Neovim plugin to preview the contents of the registers
</span></span><span class="line"><span class="ln">15</span><span class="cl">f-person/git-blame.nvim <span class="m">247</span> <span class="m">4</span> 2022-01-04T23:20:49Z Git Blame plugin <span class="k">for</span> Neovim written in Lua
</span></span><span class="line"><span class="ln">16</span><span class="cl">ruifm/gitlinker.nvim <span class="m">148</span> <span class="m">7</span> 2021-12-23T15:11:12Z A lua neovim plugin to generate shareable file permalinks <span class="o">(</span>with line ranges<span class="o">)</span> <span class="k">for</span> several git web frontend hosts. Inspired by tpope/vim-fugitive<span class="err">'</span>s :GBrowse
</span></span><span class="line"><span class="ln">17</span><span class="cl">projekt0n/github-nvim-theme <span class="m">559</span> <span class="m">5</span> 2022-01-08T11:29:31Z Github theme <span class="k">for</span> Neovim, kitty, iTerm, Konsole, tmux and Alacritty written in Lua
</span></span><span class="line"><span class="ln">18</span><span class="cl">winston0410/range-highlight.nvim <span class="m">97</span> <span class="m">5</span> 2021-08-03T07:19:30Z An extremely lightweight plugin <span class="o">(</span>~ 120loc<span class="o">)</span> that hightlights ranges you have entered in commandline.
</span></span><span class="line"><span class="ln">19</span><span class="cl">m00qek/plugin-template.nvim <span class="m">29</span> <span class="m">0</span> 2022-01-05T20:16:10Z A template to create Neovim plugins written in Lua
</span></span><span class="line"><span class="ln">20</span><span class="cl">rktjmp/highlight-current-n.nvim <span class="m">16</span> <span class="m">1</span> 2021-10-26T12:16:53Z Highlights the current /, ? or * match under your cursor when pressing n or N and gets out of the way afterwards.
</span></span></code></pre><p>Searching for a plugin uses a fuzzy matching algorithm
(<a href="https://en.wikipedia.org/wiki/Levenshtein_distance" rel="nofollow">Levenshtein distance</a>) to
find the best matches based on the name and description.</p>
<p>Since all of the plugins are tagged, you can also search for plugins matching
the tag with <code>/t/:search</code></p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl">$ curl https://nvim.sh/t/comment
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Name Stars OpenIssues Updated Description
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">folke/todo-comments.nvim <span class="m">607</span> <span class="m">24</span> 2021-12-11T09:13:48Z ✅ Highlight, list and search todo comments in your projects
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">numToStr/Comment.nvim <span class="m">572</span> <span class="m">8</span> 2022-01-02T12:51:22Z :brain: :muscle: // Smart and powerful comment plugin <span class="k">for</span> neovim. Supports treesitter, dot repeat, left-right/up-down motions, hooks, and more
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">b3nj5m1n/kommentary <span class="m">471</span> <span class="m">13</span> 2021-12-03T13:54:42Z Neovim commenting plugin, written in lua.
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">terrortylor/nvim-comment <span class="m">207</span> <span class="m">7</span> 2022-01-04T23:21:12Z A comment toggler <span class="k">for</span> Neovim, written in Lua
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">danymat/neogen <span class="m">149</span> <span class="m">6</span> 2022-01-08T12:34:58Z A better annotation generator. Supports multiple languages and annotation conventions.
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">winston0410/commented.nvim <span class="m">93</span> <span class="m">2</span> 2021-11-14T06:00:20Z Neovim commenting plugin in Lua. Support operator, motions and more than <span class="m">60</span> languages! :fire:
</span></span><span class="line"><span class="ln">10</span><span class="cl">glepnir/prodoc.nvim <span class="m">42</span> <span class="m">0</span> 2021-01-23T07:01:54Z a neovim comment and annotation plugin using coroutine
</span></span><span class="line"><span class="ln">11</span><span class="cl">s1n7ax/nvim-comment-frame <span class="m">37</span> <span class="m">0</span> 2021-09-14T06:54:29Z Detects the language using treesitter and adds a comment block
</span></span><span class="line"><span class="ln">12</span><span class="cl">gennaro-tedesco/nvim-commaround <span class="m">33</span> <span class="m">0</span> 2021-12-16T11:39:07Z nvim plugin to toggle comments on and off
</span></span></code></pre><p>The data source for this service is <a href="https://neovimcraft.com" rel="nofollow">https://neovimcraft.com</a> and it is
automatically refreshed once per hour.</p>
<p>I'd be happy to read any feedback or suggestions to make this service even more
useful!</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
neovim plugin search from the terminalOn autoloading2022-07-05T13:45:07Zhttps://erock.io/on-autoloading<p>Autoloading is a paradigm in some programming languages or frameworks that
allows code to be automatically imported into any developer's source code file.
This has a few benefits, like removing the need to have the header of a file
contain a series of import statements.</p>
<p>Autoloading appears to be the most popular in scripting languages like
<a href="https://guides.rubyonrails.org/autoloading_and_reloading_constants.html" rel="nofollow">Ruby (via Rails)</a>
and <a href="https://www.php.net/manual/en/language.oop5.autoload.php" rel="nofollow">PHP</a>.</p>
<p>At <a href="https://aptible.com" rel="nofollow">Aptible</a>, a significant portion of our infrastructure
is a set of services written using Rails. After spending the better part of a
couple years working with rails, I would argue that autoloading <strong>negatively</strong>
impacts the developer experience as well as the productivity of our engineers.</p>
<p>Autoloading is the worst feature in rails and getting rid of it entirely would
make the framework much more enjoyable. Even the creator of ruby wrote a post
about how he
<a href="https://bugs.ruby-lang.org/issues/5653" rel="nofollow">strongly discourages the use of autoloading</a>.
Granted their reasons are unrelated to what I'm interested in discussing and
later in the post they rescinded their stance.</p>
<p>For this post, I'd like to focus on how autoloading impacts the developer
experience of a novice.</p>
<p>As someone who was first getting introduced to ruby <em>and</em> rails, I found it
extremely difficult to figure out where classes or functions were defined in the
codebase. Since there are no import statements at the top of the file, it was a
guessing game to figure out where source code was located. Is <code>SomeClass</code> a
class we created in one of our repos? Is it a third-party class? I have no clue
just by looking at how the class is being used. This makes onboarding to a
codebase frustrating and confusing. There are
<a href="https://guides.rubyonrails.org/v5.2/autoloading_and_reloading_constants.html#autoload-paths-and-eager-load-paths" rel="nofollow">conventions</a>
that help you correctly guess, but I found the best way to find the source code
was to grep for it.</p>
<p>Here was my basic search algorithm for finding the source to <code>SomeClass</code>:</p>
<ul>
<li>Grep codebase <code>class SomeClass</code>; If nothing was found then</li>
<li>I would grep through any gems that we built in our Gemfile or gemspec</li>
<li>I would scan the Gemfile for third-party libraries that sound similar</li>
<li>I would start reading third-party documentation and source code</li>
<li>I would give up and ask a colleague</li>
</ul>
<p>It was not uncommon for me to give up entirely. Autoloading made it more
difficult to figure out how something worked and left me feeling defeated.</p>
<p>Even after searching for the right way to do this and asking colleagues, it
still feels like there's no great solution to this problem that virtually every
other modern programming language avoids by having import statements.</p>
<p>There are IDEs like RubyMine or an LSP that make finding the source code one
click away but <code>solargraph</code> -- the LSP I use with <code>neovim</code> -- still comes up
empty sometimes. And at least for <code>solargraph</code> it doesn't even bother searching
the source from my installed gems. There's also the
<a href="https://railsguides.net/find-method-source-location/" rel="nofollow">source_location method</a>
which we can use at runtime to find the source, but it's also not full-proof and
a little awkward. My immediate reaction is: Do I really need to run <code>rails c</code> in
order to figure out where the source code is for a piece of functionality?</p>
<p>Coming from python and typescript, the entire experience feels foreign,
backwards, and confusing.</p>
<p>I think when people say that rails feels "magical," autoloading is a significant
part of the magic.</p>
<p><code>fxn</code> -- the creator of the current autoloading implementation in rails called
<code>Zeitwerk</code> --
<a href="https://bugs.ruby-lang.org/issues/5653#note-40" rel="nofollow">commented about the reasons why he likes autoloading</a>.</p>
<p>I'll focus on the arguments they claim result in a better developer experience:</p>
<blockquote>
<ol>
<li>Being able to reload code is handy in web applications development. That is
replacing the objects stored in the autoloaded constants, not reopening
classes by reevaluating the files.</li>
</ol>
</blockquote>
<p>Every other development web server I've used solves this by watching files and
reloading the server. It is rarely, if ever, an issue. This argument doesn't
convince me in the slightest.</p>
<blockquote>
<ol>
<li>In any non-trivial project, getting the require calls right is difficult,
you always forget some and gives load order bugs.</li>
</ol>
</blockquote>
<p>The error shows up immediately, you fix it, and then move on with your life.
Again, this is not a very convincing argument as I would much rather fix an
easily traceable error once than perform my crude search algorithm every single
time I want to inspect a definition.</p>
<blockquote>
<ol>
<li>If you structure your project in a conventional manner in which file paths
match constant paths, the requires don't feel DRY. You are repeating
something all the time that could be automated.</li>
</ol>
</blockquote>
<p>I have another rant about DRY that deserves its own post. I'll keep it brief
here: the obsession with DRY in the rails community is a virus that has infected
the minds of engineers. I know DRY wasn't invented with rails but it certainly
popularized it. All aspects of structuring code need to be evaluated based on
their merits and DRY is not always the correct decision. I would also argue in
some cases it produces far <strong>less</strong> readable and maintainable code.</p>
<blockquote>
<ol>
<li>Being able to work as if all your classes and modules are just available
everywhere (as in Rails) is a great user experience.</li>
</ol>
</blockquote>
<p>Fair enough, I do see this as a positive and I'm not blind to the benefits of
having all modules, classes, and functions automatically loaded. Once you've
memorized them you can really cut down on the preamble of writing code. I have
also spent a significant amount of time refactoring JS code to fix imports
because I wanted to move some code to another location.</p>
<p>I remember when I first started to learn python and
<a href="https://stackoverflow.com/a/1494402" rel="nofollow">stumbled across this stackoverflow post</a>
about auto importing modules in python. I think it provides some interesting
arguments <strong>for</strong> having import statements:</p>
<blockquote>
<ol>
<li>They serve as a sort of declaration of <strong>intent</strong>.</li>
<li>Imports serve as a proxy for the <strong>complexity</strong> of a module.</li>
</ol>
</blockquote>
<p>So much information and complexity is hidden by removing the import statements.</p>
<p>If we want rails to feel less magical, we should start by looking at removing
autoloading.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
A rant about railsDeath by a thousand existential checks2022-07-05T13:45:04Zhttps://erock.io/death-by-thousand-existential-checks<p>Existential checks are when we have to detect whether or not a variable has a
value - that is, checking to see if a variable exists. If the value is <code>null</code>,
<code>undefined</code> or otherwise falsy, then it fails the check. This usually takes the
form of an if-statement.</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">thingThatExists</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="c1">// do something with `thingThatExists`
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre><p>They are a natural -- and often necessary -- part of codebases. However, their
over abundance can make the readability of the codebase difficult. When
existential checks are nested within existential checks, it becomes difficult to
understand the context of the code we are trying to read.</p>
<p>In this article I will try to demonstrate, <strong>where</strong> they are used has a
dramatic effect on code reuse, readability, and maintainability.</p>
<h1 id="whats-the-problem-with-existential-checks"><a class="anchor" href="#whats-the-problem-with-existential-checks" rel="nofollow">#</a> What's the problem with existential checks?</h1>
<h2 id="it-increases-code-nesting"><a class="anchor" href="#it-increases-code-nesting" rel="nofollow">#</a> It increases code nesting</h2>
<p>As code structure determines its function, the graphic design of code determines
its maintainability. Indentation -- while necessary for visualizing the flow
control a program -- is often assumed to be merely an aesthetic appeal. However,
what if indentation can help determine unnecessary code complexity?
<a href="http://hjemmesider.diku.dk/~jyrki/Course/Software-development-2008/Slides/pretty-code.pdf" rel="nofollow">Abrupt code indentation tends to convolute control flow with minor details.</a>
Linus Torvalds thinks that greater than three levels of indentation is a code
smell which is part of a greater design flaw</p>
<blockquote>
<p>Now, some people will claim that having 8-character indentations makes the
code move too far to the right, and makes it hard to read on a 80-character
terminal screen. The answer to that is that if you need more than 3 levels of
indentation, you're screwed anyway, and should fix your program.</p>
</blockquote>
<p>Jeff Atwood thinks that nesting code has a high
<a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity" rel="nofollow">cyclomatic complexity</a>
value which is a measure of how many distinct paths there are through code.
<a href="https://blog.codinghorror.com/flattening-arrow-code/" rel="nofollow">A lower cyclomatic complexity value correlates with more readable code, and also indicates how well it can be properly tested.</a></p>
<h2 id="deeply-nested-structures-are-a-bad-idea"><a class="anchor" href="#deeply-nested-structures-are-a-bad-idea" rel="nofollow">#</a> Deeply nested structures are a bad idea.</h2>
<p>I started my career in software engineering by trying many different programming
languages. I took something along with me after diving into writing <em>pragmatic</em>
and <em>idiomatic</em> code for each language.</p>
<p>For python, <a href="https://www.python.org/dev/peps/pep-0020/" rel="nofollow">PEP 20</a> describes a set
of design principles that every python developer should think about when
architecturing a codebase. There's one line in this principle that I think about
all the time:</p>
<blockquote>
<p>Flat is better than nested</p>
</blockquote>
<p>This guiding principle has led me to what I believe is more maintainable,
readable code. When we apply this principle to data structures, that means we
should avoid deeply nested data structures.</p>
<p>Deeply nested structures are difficult to understand -- even when strongly typed
-- and they tend to promote cases where a nested object could be empty. This has
the consequence of requiring developers to make many existential checks,
especially if the data is used often.</p>
<p>Redux also has an great set of recommendations for organizing application state
that also
<a href="https://redux.js.org/style-guide/style-guide#normalize-complex-nestedrelational-state" rel="nofollow">strongly recommends normalizing state</a>.</p>
<p>There are many articles about
<a href="https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape" rel="nofollow">how to normalize state</a>,
but the tldr is to think of an application's state like a relational database:
each object is a database table where the key is the <code>id</code> and the value is the
object data. This makes data easier to query, update, and reuse.</p>
<h2 id="it-sets-poor-expectations-for-other-developers-and-makes-it-harder-for-them-to-grok-the-codebase"><a class="anchor" href="#it-sets-poor-expectations-for-other-developers-and-makes-it-harder-for-them-to-grok-the-codebase" rel="nofollow">#</a> It sets poor expectations for other developers and makes it harder for them to grok the codebase.</h2>
<p>When objects <em>can</em> be empty, it sets terrible expectations for the end
developer. Here are some questions it can raise:</p>
<ul>
<li>When do these data get populated?</li>
<li>How do I ensure we will have the data when I need to use them?</li>
<li>What do I do when we don't have the data?</li>
</ul>
<p>With any project-size, it is important for us to set clear expectations with our
code. Setting expectations leads to more readable and manageable code. When
interfaces are littered with optional or nullable properties, we set terrible
expectations. Therefore, we make a concerted effort to minimize the number of
optional or nullable properties in our front-end codebase.</p>
<h2 id="it-usually-means-we-forgot-an-important-step-in-our-data-pipeline"><a class="anchor" href="#it-usually-means-we-forgot-an-important-step-in-our-data-pipeline" rel="nofollow">#</a> It usually means we forgot an important step in our data pipeline.</h2>
<p>When it comes to web development, I regularly have to build a pipeline to
consume an API that is usually a separate service, constructed by a set of HTTP
endpoints that we have to
<a href="https://en.wikipedia.org/wiki/Extract,_transform,_load" rel="nofollow">extract, transform, and load</a>
into front-end applications. When I first started building front-end web
applications, I got into a really bad habit of skipping the <em>transform</em> step. I
would take the API response and load it directly into my application state. By
ignoring the transformation step, it made it harder to make updates to the
codebase when an API endpoint changed. APIs are not always built with the
consumers in mind. They are built in terms of being RESTful, with strict rules
on how data should be formed and sent to their consumers.</p>
<p>Another side-effect of ignoring the transformation step is pushing optional
properties to the view layer (e.g. react components). There's no quicker way to
complicate a react component than to make a bunch of existential checks inside
the render body even if it uses the new syntax sugar of
<a href="https://github.com/tc39/proposal-optional-chaining" rel="nofollow">optional chaining</a>.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useSelector</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'react-redux'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">interface</span> <span class="nx">Author</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">username</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kr">interface</span> <span class="nx">Blog</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="nx">body</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">author</span>: <span class="kt">Author</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="nx">blogId</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kr">const</span> <span class="nx">selectBlogs</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">blogs</span> <span class="o">||</span> <span class="p">{};</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="kr">const</span> <span class="nx">selectBlogById</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="nx">selectBlogs</span><span class="p">(</span><span class="nx">state</span><span class="p">).[</span><span class="nx">id</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="kr">const</span> <span class="nx">BlogArticle</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">blogId</span> <span class="p">}</span><span class="o">:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="kr">const</span> <span class="nx">blog</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="o">=></span> <span class="nx">selectBlogById</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">blogId</span> <span class="p">})</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">blog</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"> <span class="k">return</span> <span class="p"><</span><span class="nt">div</span><span class="p">></span><span class="nx">Could</span> <span class="nx">not</span> <span class="nx">find</span> <span class="nx">blog</span> <span class="nx">article</span><span class="p"></</span><span class="nt">div</span><span class="p">>;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"> <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">>{</span><span class="nx">blog</span><span class="p">.</span><span class="nx">body</span><span class="p">}</</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"> <span class="nx">written</span> <span class="nx">by</span><span class="o">:</span> <span class="p">{</span><span class="nx">blog</span><span class="p">.</span><span class="nx">author</span><span class="o">?</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>This example is meant to demonstrate how code can become more complicated when
there are existential checks inside our react components. We have made no
guarantees for the data we are sending to the view layer and as a result we have
to make many existential checks and fallbacks to accommodate.</p>
<h1 id="how-do-we-avoid-existential-checks"><a class="anchor" href="#how-do-we-avoid-existential-checks" rel="nofollow">#</a> How do we avoid existential checks?</h1>
<h2 id="make-optional-properties-the-exception-not-the-rule"><a class="anchor" href="#make-optional-properties-the-exception-not-the-rule" rel="nofollow">#</a> Make optional properties the exception, not the rule</h2>
<p>Instead of accepting what the backend sends us, we should instead create a
consistent and reliable set of data structures that our app uses. Optional
properties should be an exception, not the rule. Let's build some <em>ideal</em>
interfaces without optional or nullable properties and then figure out how to
build our state with it.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">interface</span> <span class="nx">Author</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">username</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kr">interface</span> <span class="nx">Blog</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">body</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">author</span>: <span class="kt">Author</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>It's a pretty simple exercise, we go through and remove the possibility of a
property not existing or the property being <code>null</code>. This is great, but how do we
make this interface a reality with the data we are being provided?</p>
<h2 id="build-entity-factories"><a class="anchor" href="#build-entity-factories" rel="nofollow">#</a> Build entity factories</h2>
<p>The general approach to retrieving data from our redux store is to always send
the object that the user is requesting. Instead of our selector potentially
returning <code>null</code> or <code>undefined</code>, we can return a default blog object, with sane
defaults for each property. This is a concept inspired by golang. Variables
without an initial value are
<a href="https://golang.org/ref/spec#The_zero_value" rel="nofollow">set to their zero value</a>. This
means that every entity in our front-end codebase has an entity creation
function that accepts a partial of that entity and does a simple merge.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">defaultAuthor</span> <span class="o">=</span> <span class="p">(</span><span class="nx">author</span>: <span class="kt">Partial</span><span class="p"><</span><span class="nt">Author</span><span class="p">></span> <span class="o">=</span> <span class="p">{})</span><span class="o">:</span> <span class="nx">Author</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">""</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">username</span><span class="o">:</span> <span class="s2">""</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s2">""</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">...</span><span class="nx">author</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kr">const</span> <span class="nx">defaultBlog</span> <span class="o">=</span> <span class="p">(</span><span class="nx">blog</span>: <span class="kt">Partial</span><span class="p"><</span><span class="nt">Blog</span><span class="p">></span> <span class="o">=</span> <span class="p">{})</span><span class="o">:</span> <span class="nx">Blog</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">""</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="nx">body</span><span class="o">:</span> <span class="s2">""</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="nx">author</span>: <span class="kt">defaultAuthor</span><span class="p">(</span><span class="nx">blog</span><span class="p">.</span><span class="nx">author</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="p">...</span><span class="nx">blog</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="cm"> console.log(
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="cm"> defaultBlog({ id: '123', body: 'blog content!' })
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="cm"> );
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="cm"> {
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="cm"> id: '123',
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="cm"> body: 'some content!',
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="cm"> author: {
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="cm"> id: '',
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="cm"> username: '',
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="cm"> name: '',
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="cm"> }
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="cm"> }
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="cm">*/</span>
</span></span></code></pre><p>This concept of creating default entities or
<a href="https://www.fabricationgem.org/" rel="nofollow">fabricators</a> is a concept used in ruby,
primarily for specs but also can be used for anything.</p>
<p>By spending a little up-front time building entity factories, we save a ton of
time for every end-developer that needs to create a new entity. It seems
tedious, but we've been able to scale this concept to even massive entities with
good ROI. Our human labor <em>will</em> pay off. We're not using a library to do this
for us - it's straight-forward and easy to copy/paste.</p>
<p>Default entity functions help:</p>
<ul>
<li>Guarantee all objects and properties exist</li>
<li>Does the heavy lifting to create sane defaults for all entities and their
properties</li>
<li>Create a central place to generate the entity</li>
<li>Create an entity while also passing in other property values</li>
<li>Test in a type-safe manner by avoiding casting to <code>any</code> simply because
building an entity object manually in every test is tedious</li>
</ul>
<h2 id="transform-data-from-http-requests"><a class="anchor" href="#transform-data-from-http-requests" rel="nofollow">#</a> Transform data from HTTP requests</h2>
<p>Back to the fundamentals of ETL, it is imperative that we do <strong>not</strong> skip
building the <em>T</em> in <em>ETL</em>. The way we do this is by creating a deserializer for
each entity in our API responses.</p>
<p>You can see in our responses where our original interfaces came from: this is
what the API is sending us. We shouldn't continue the trend of maybe having
properties or maybe having an object.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">interface</span> <span class="nx">AuthorResponse</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">user_name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kr">interface</span> <span class="nx">BlogResponse</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">body</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">author</span>: <span class="kt">Author</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// always type yor API responses!
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="kr">interface</span> <span class="nx">BlogCollectionResponse</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="nx">blogs</span>: <span class="kt">BlogResponse</span><span class="p">[];</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1">// always create a deserializer for each entity
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">deserializeBlog</span><span class="p">(</span><span class="nx">blog</span>: <span class="kt">BlogResponse</span><span class="p">)</span><span class="o">:</span> <span class="nx">Blog</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="nx">id</span>: <span class="kt">blog.id</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"> <span class="nx">body</span>: <span class="kt">blog.body</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="nx">author</span>: <span class="kt">deserializeAuthor</span><span class="p">(</span><span class="nx">blog</span><span class="p">.</span><span class="nx">author</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="kd">function</span> <span class="nx">deserializeAuthor</span><span class="p">(</span><span class="nx">author</span>: <span class="kt">AuthorResponse</span> <span class="o">|</span> <span class="kc">null</span><span class="p">)</span><span class="o">:</span> <span class="nx">Author</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">author</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"> <span class="k">return</span> <span class="nx">defaultAuthor</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"> <span class="c1">// you can see here that we change
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c1"></span> <span class="c1">// the API response from `user_name` to `username`
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"> <span class="nx">id</span>: <span class="kt">author.id</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"> <span class="nx">username</span>: <span class="kt">author.user_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"> <span class="nx">name</span>: <span class="kt">author.name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">fetchBlogs() {</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"> <span class="kr">const</span> <span class="nx">resp</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">"/blogs"</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">resp</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"> <span class="c1">// TODO: figure out error handling
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1"></span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"> <span class="kr">const</span> <span class="nx">data</span>: <span class="kt">BlogCollectionResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"> <span class="kr">const</span> <span class="nx">blogs</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">blogs</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">deserializeBlog</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"> <span class="c1">// TODO: save to redux
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre><p>This seems tedious, but a developer writes it once and now we have:</p>
<ul>
<li>A fully typed API response</li>
<li>Fully typed deserializers for every entity that we are going to use</li>
<li>A set of transformers where we can change the structure or format of the data
being returned from the API</li>
<li>Sane defaults for the data we receive</li>
</ul>
<p>This ETL structure is the basis of our front-end business logic and has scaled
well to date.</p>
<h2 id="avoid-existential-checks-in-react-components"><a class="anchor" href="#avoid-existential-checks-in-react-components" rel="nofollow">#</a> Avoid existential checks in react components</h2>
<p>All of the work in the previous sections should pay off now, let's see what it
looks like.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useSelector</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"react-redux"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">interface</span> <span class="nx">Author</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">username</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kr">interface</span> <span class="nx">Blog</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="nx">body</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">author</span>: <span class="kt">Author</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="kr">const</span> <span class="nx">fallbackBlog</span> <span class="o">=</span> <span class="nx">defaultBlog</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="kr">const</span> <span class="nx">selectBlogs</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">blogs</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// here we use a fallback blog for when
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1">// we cannot find the blog article
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">selectBlogById</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="nx">selectBlogs</span><span class="p">(</span><span class="nx">state</span><span class="p">)[</span><span class="nx">id</span><span class="p">]</span> <span class="o">||</span> <span class="nx">fallbackBlog</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kr">const</span> <span class="nx">BlogArticle</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">blogId</span> <span class="p">}</span><span class="o">:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="kr">const</span> <span class="nx">blog</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">((</span><span class="nx">state</span><span class="p">)</span> <span class="o">=></span> <span class="nx">selectBlogById</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">blogId</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">>{</span><span class="nx">blog</span><span class="p">.</span><span class="nx">body</span><span class="p">}</</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="nx">written</span> <span class="nx">by</span><span class="o">:</span> <span class="p">{</span><span class="nx">blog</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>What did we accomplish?</p>
<ul>
<li>We have removed branching logic inside our component</li>
<li>We removed all existential checks from our component</li>
<li>We removed the need to have multiple <code>x || ''</code> inside of our component</li>
<li>This component is now completely safe to render even if we know we don't have
any blog articles yet!</li>
</ul>
<p>This last point is interesting: we don't <strong>need</strong> a loader to prevent this code
from throwing an error, it's safe to use while data is being fetched. We can
defer implementing loading states until later.</p>
<p>One could argue showing an empty blog post isn't much of an improvement. But the
point isn't that we are missing a critical message to the user, it's the fact
that we can defer the decision to handle the <code>blog not found</code> error case until
later. It's hard to articulate with a trivial example the impact this change has
on a codebase, but let's say we want to add messaging. In this case, we will
need at least one existential check. What we normally do is create a helper
function that performs the check for us and then use that inside the react
component.</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">hasBlog</span> <span class="o">=</span> <span class="p">(</span><span class="nx">blog</span>: <span class="kt">Blog</span><span class="p">)</span><span class="o">:</span> <span class="kr">boolean</span> <span class="o">=></span> <span class="nx">blog</span><span class="p">.</span><span class="nx">id</span> <span class="o">!=</span> <span class="s2">""</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasBlog</span><span class="p">(</span><span class="nx">blog</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="k">return</span> <span class="p"><</span><span class="nt">div</span><span class="p">></span><span class="nx">Could</span> <span class="nx">not</span> <span class="nx">find</span> <span class="nx">blog</span> <span class="nx">article</span><span class="p"></</span><span class="nt">div</span><span class="p">>;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// ...
</span></span></span></code></pre><h1 id="conclusion"><a class="anchor" href="#conclusion" rel="nofollow">#</a> Conclusion</h1>
<p>The goal of flattening our objects is not to save on lines of code; rather, it's
to build a scalable, readable, and maintainable architecture that is predictable
to use.</p>
<p>Architecting code is hard. With a little planning and pushing a few existential
checks to the transform layer of ETL, we end up with a repeatable pattern for
dealing with optional or nullable properties, and making our view layer easier
to build. You can keep your optional chaining.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
You can keep your optional chaining3 Rules for Refactoring Functions2022-07-05T13:45:08Zhttps://erock.io/three-rules-for-refactoring-functions<ol>
<li>Return early</li>
<li>Return often</li>
<li>Reduce levels of nesting</li>
</ol>
<p>These rules are the heartbeat to how I write code. Whenever I write a function
there is nothing more important for readability and maintainability. Whenever I
approach a function that needs be refactored it's because the function did not
subscribe to these rules.</p>
<p>Let's try to build a function that lets a user sign up for our service:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">interface</span> <span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"success"</span> <span class="o">|</span> <span class="s2">"error"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="nx">any</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">function</span> <span class="nx">register</span><span class="p">(</span><span class="nx">email</span><span class="o">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">pass</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nx">isEmailValid</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">isEmailAvailable</span><span class="p">(</span><span class="nx">email</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nx">isPassValid</span><span class="p">(</span><span class="nx">pass</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="kr">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">createUser</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"success"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"password must be 8 characters long"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"email is invalid"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"unknown error occurred"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>All the named functions are stubs, the actual implementation is irrelevant to
this demonstration. Before we can create the user, we need to make sure that the
email and password provided to us are valid and the email address has not
already been created. There are a couple issues with the current code that can
be improved. First, the main purpose of the function's code is nested within
multiple <code>if-statements</code>. This makes it harder to understand how a function's
core <em>responsibility</em> gets activated. Because we want to return meaninful errors
in this function, we have to create accompanying <code>else-statements</code> which are not
co-located next to the logic that controls them and makes them hard to read.
<em>Max indendation is 4</em>.</p>
<h2 id="return-early"><a class="anchor" href="#return-early" rel="nofollow">#</a> Return early</h2>
<p>The general idea is to figure out all the ways to exit a function as quickly as
possible. This reduces cognitive overhead when reading a function. When you are
hunting a bug and a function returns early for your use-case, you can move on
quickly because you avoid the cognitive load required to understand the rest of
the function.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">function</span> <span class="nx">register</span><span class="p">(</span><span class="nx">email</span><span class="o">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">pass</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isEmailValid</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="nx">isEmailAvailable</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="nx">isPassValid</span><span class="p">(</span><span class="nx">pass</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"validation failed"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="kr">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">createUser</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"success"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>This seems better. We have removed the need to read all the code when we know
our email or pass is invalid. The <code>if-statement</code>/<code>else-statement</code> separation in
the previous example is gone: the condition is co-located with the error state.
Also the primary goal of the function's code is in the main body of the
function. However, we lost something in this iteration: meaningful error
messages. We no longer know what piece of validation failed because they are all
inside one <code>if-statement</code>. <em>Max indentation is 3</em>.</p>
<h2 id="return-often"><a class="anchor" href="#return-often" rel="nofollow">#</a> Return often</h2>
<p>Don't wait until the end of a function to return. Based on experience, waiting
until the end to return from a function ends up creating a lot of branching
logic that a developer has to keep track of in their head when traversing a
function's code.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">function</span> <span class="nx">register</span><span class="p">(</span><span class="nx">email</span><span class="o">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">pass</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isEmailValid</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="nx">isEmailAvailable</span><span class="p">(</span><span class="nx">email</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"email is invalid"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isPassValid</span><span class="p">(</span><span class="nx">pass</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"password must be 8 characters long"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="kr">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">createUser</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"success"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Now we have separated the email validation from the password validation and have
better error messages by returning early and often. This is clear to understand
and at each step of the function we filter out parameters that would prevent the
core of our function from running. However, there are still some improvements we
could make. We have an <code>if-statement</code> that requires two condititions to be met
before exiting the function. This logic seems straight-forward, but when given
the opportunity, I almost always split the condition into separate
<code>if-statements</code>. Another side-effect of having one statement handle two
conditions is the error messaging is a little misleading. <em>Max indentation is
3</em>.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">function</span> <span class="nx">register</span><span class="p">(</span><span class="nx">email</span><span class="o">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">pass</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isEmailValid</span><span class="p">(</span><span class="nx">email</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"email is invalid"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isEmailAvailable</span><span class="p">(</span><span class="nx">email</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"email is already taken"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isPassValid</span><span class="p">(</span><span class="nx">pass</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="s2">"password must be 8 characters long"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="kr">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">createUser</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"success"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Using all of the rules we have the final result. This seems readable, traceable
error messaging, and reduces the level of indentation. Now it may seem like this
is lengthier than the other examples, which is true. Lines of code is not really
a concern to me and is not something I consider a valuable metric to compare
code for readability. <em>Max indentation is 3</em>.</p>
<p>Could we improve this code even more? Let's try to refactor some of the error
handling by making the validation function return the error message.</p>
<h2 id="dynamic-returns"><a class="anchor" href="#dynamic-returns" rel="nofollow">#</a> Dynamic returns</h2>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cm"> We switch the validation functions from returning if the email is valid to
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="cm"> returning the error message if the email is not valid. e.g.:
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="cm"> isValidEmail(string): boolean -> validateEmail(string): string
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">function</span> <span class="nx">register</span><span class="p">(</span><span class="nx">email</span><span class="o">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">pass</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Response</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="kr">const</span> <span class="nx">validations</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">validateEmail</span><span class="p">(</span><span class="nx">email</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">validateEmailAvailable</span><span class="p">(</span><span class="nx">email</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">validatePass</span><span class="p">(</span><span class="nx">pass</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="p">];</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">validations</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="kr">const</span> <span class="nx">err</span> <span class="o">=</span> <span class="nx">validations</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"error"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="nx">err</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="kr">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">createUser</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="nx">type</span><span class="o">:</span> <span class="s2">"success"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="nx">payload</span><span class="o">:</span> <span class="p">{</span> <span class="nx">userId</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Is this example more readable than the previous one? There are definitely some
positives to this version. It's easier to add more validations because we can
add the functions to the <code>validations</code> array and it should just work. There are
only two return statements in the function which is less than the previous
example.</p>
<p>However, we have introduced a <code>for-loop</code> to iterate over the validation
functions and we will return if any of those validations fail. Even though this
function abstracted the concept of validation and is more scalable to adding
more validations, it strikes me as very dynamic. Also, every function must
subscribe to handling validations the same way, if they don't, figuring out the
bug is going to be tricky to figure out because the error is going happen inside
a <code>for-loop</code>.</p>
<p>To me, this function is less readable because of its dynamic nature. The
end-developer now needs to mentally iterate over the <code>validations</code> array which
is a significant amount of cognitive load. At this point it really depends on
what is more important: readability or scalability.</p>
<p>My instincts tell me to use the previous example until it is unbearable to
continue to write the validation checks one after another.</p>
<h2 id="reduce-levels-of-nesting"><a class="anchor" href="#reduce-levels-of-nesting" rel="nofollow">#</a> Reduce levels of nesting</h2>
<p>As code structure determines its function, the graphic design of code determines
its maintainability. Indentation, while necessary for visualizing the flow
control a program, is often assumed to be merely an aethestic appeal. However,
what if indentation can help determine unnecessary code complexity? Abrupt code
indentation tends to convolute control flow with minor details.
<a href="http://www.perforce.com/resources/white-papers/seven-pillars-pretty-code" rel="nofollow">1</a>
Linus Torvalds thinks that greater than three levels of indentation is a code
smell which is part of a greater design flaw.</p>
<blockquote>
<p>Now, some people will claim that having 8-character indentations makes the
code move too far to the right, and makes it hard to read on a 80-character
terminal screen. The answer to that is that if you need more than 3 levels of
indentation, you're screwed anyway, and should fix your program.
<a href="https://www.kernel.org/doc/Documentation/CodingStyle" rel="nofollow">2</a></p>
</blockquote>
<p>In python, indentation is a rule not a guideline, any python script with
misaligned code nesting will result in an
<code>IndentationError: expected an indented block</code>. Again in python if you
<code>import this</code> you'll get "The Zen of Python," otherwise known as
<a href="https://www.python.org/dev/peps/pep-0020/" rel="nofollow">PEP20</a>, here is snippet from it:</p>
<blockquote>
<p><strong>Flat is better than nested.</strong></p>
</blockquote>
<p>Studies by Noam Chomsky suggest that few people can understand more than three
levels of nested ifs
<a href="http://www.amazon.com/Managing-structured-techniques-Strategies-development/dp/0917072561" rel="nofollow">3</a>,
and many researchers recommend avoiding nesting to more than four levels
<a href="http://www.amazon.com/Software-Reliability-Principles-Glenford-Myers/dp/0471627658" rel="nofollow">4</a>
<a href="http://www.amazon.com/Software-Engineering-Concepts-Professional-Vol/dp/0201122316%3FSubscriptionId%3D0JRA4J6WAV0RTAZVS6R2%26tag%3Dworldcat-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0201122316" rel="nofollow">5</a></p>
<p>Jeff Atwood thinks that nesting code has a high
<a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity" rel="nofollow">cyclomatic complexity</a>
value which is a measure of how many distinct paths there are through code. A
lower cyclomatic complexity value correlates with more readable code, and also
indicates how well it can be properly tested.
<a href="http://blog.codinghorror.com/flattening-arrow-code/" rel="nofollow">6</a></p>
<p>Why is this important? Rarely does readability seem to play an important role in
most undergraduate discussions when it can in fact be one of the most important
characteristics of widely used and maintainable code. Code that is only
understood by one person is code not worth maintaining -- and as a result poorly
designed.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
Simple steps to guarantee cleaner functions