<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Shane Brinkman-Davis Delamore on Medium]]></title>
        <description><![CDATA[Stories by Shane Brinkman-Davis Delamore on Medium]]></description>
        <link>https://medium.com/@shanebdavis?source=rss-3e2ccd7daec------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*30jjSxuW7FELcpQg-f_xSw.jpeg</url>
            <title>Stories by Shane Brinkman-Davis Delamore on Medium</title>
            <link>https://medium.com/@shanebdavis?source=rss-3e2ccd7daec------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 17 Jul 2026 08:12:40 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@shanebdavis/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Embracing Software Agility]]></title>
            <link>https://shanebdavis.medium.com/embracing-software-agility-a32a90132c81?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/a32a90132c81</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[modular-design]]></category>
            <category><![CDATA[code-quality]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Thu, 24 Oct 2024 09:02:55 GMT</pubDate>
            <atom:updated>2026-04-10T03:59:14.130Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UMqOWfLnryr8mAku4MOb6Q.png" /><figcaption>How to make Code that can Agilely respond to any new requirement</figcaption></figure><p>In my last article (<a href="https://shanebdavis.medium.com/code-quality-and-correctness-considered-harmful-what-are-the-true-pillars-of-software-c3bffe94ccc3">Forget “Code Quality” and “Correctness” — Verifiability and Agility are the True First Principles of Software Engineering</a>), I challenged conventional wisdom regarding code quality and correctness. Instead, I argued that <strong>verifiability </strong>and<strong> agility</strong> are the true first principles from which everything else stems. Now, let’s move from theory to practice.</p><p>This article focuses on agility<strong> </strong>— how can we build codebases that adapt to ever-changing requirements quickly, with minimal effort and <em>never accumulate code debt</em>. By mastering the techniques of code-size reduction and modular design, we can keep our projects flexible, scalable, and manageable over time.</p><h3>Problem Statement</h3><p>When I use the word agility, I’m not referring to “agile methodologies.” I’m talking about the dictionary definition: the ability to move quickly and easily. That’s what we want. We want our codebases to quickly and easily adapt to future, unforeseen requirements. But what does that mean exactly? We’re going to work toward a very specific goal:</p><blockquote><strong>The Agility Goal</strong>: Minimize the cost — in terms of developer hours — needed to deliver the next new requirement, averaged over the lifetime of the code.</blockquote><p>There are two main challenges with this goal. First, how do you minimize the effort to meet any specific new requirement? Second, how do you set up the codebase to respond well to all future, unpredictable, and sometimes unimaginable requirements?</p><p>To answer these questions, we’ll work backward. Let’s assume we’ve just finished modifying the codebase to meet the latest requirements. By looking at the git history, we can see exactly what changes were made. The challenge wasn’t just making those edits — it was determining <strong>what</strong> needed to be changed.</p><p>To quantify the challenge of designing those edits, the first question is what knowledge you needed to do that design work. Let’s assume that, knowing the requirements, you already had a good guess at the algorithms and strategy you needed to solve the problem. The next and hardest task was figuring out how to implement that solution in actual code. To do this, you needed to understand all parts of the codebase that could possibly be relevant. Specifically, you had to grasp everything your changes might depend on, and everything that might depend on your changes. In other words, for both the edits you finalized and the exploratory steps you took, you needed to understand all the upstream and downstream dependencies involved.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iZx6eNMUf0WKrTCvN8si1g.png" /><figcaption>the “surface area” of a change — everything you need to know to successfully change a codebase</figcaption></figure><p>I call this the <strong>“surface area”</strong> of an edit. Imagine zooming out until you can see your entire codebase as a 2D image. Now, highlight all the upstream and downstream implications of your edit. That highlighted region represents the surface area you had to understand to meet the new requirements — and ensure you didn’t break anything else along the way.</p><blockquote><strong>The Surface Area of a Change (“surface area” for short):</strong> the set union of all code-points a) that you changed or considered changing, b) all code-points those changes could depend on and c) all code-points that could be affected by your changes, directly or indirectly.</blockquote><p>The real complexity emerges from the fact that any pair of code points in that surface area could potentially interact with each other. If they could further interact with something outside that surface area, you have to expand your surface area to include that too. You have to consider all <em>potential</em> upstream and downstream dependencies, not just the actual ones. For one, it’s impossible to fully understand all the actual dependencies in just one screenful of code, let alone 10, 100, or more screens of code for a more typical “surface area” of a change. For two, it’s not just “what is” that matters, but also “what you could potentially change it to be” that you have to contend with.</p><p>The complexity of a change is proportional to the number of potential interaction across the surface area of your change. This forms a fully connected graph of potential dependencies, and the cost of understanding all these interactions scales <em>quadratically</em>. If the surface area doubles in size, the complexity quadruples. If it’s 10x larger, the complexity multiplies by 100x. That is why this simple measurement, the size of your change’s surface area, <em>will dominate all other software engineering considerations.</em></p><p>This is the fundamental nature of <em>code debt.</em> As code bases grow, if nothing is done to combat the problem, the cost of each subsequent change <em>will grow quadratically</em>. Consequently, productivity will plummet over time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_hrvHzG-gVzoI-SIrt_V5w.png" /><figcaption>By default, complexity scales quadratically with code size</figcaption></figure><p>This is the true “cost” of meeting new requirements. The cost scales quadratically: <strong>O(n²)</strong> where “n” is the size of the surface area. Therefore, to minimize the cost of meeting future requirements, the dominant factor is minimizing the size of the surface area of the required changes.</p><blockquote><strong>Code Debt:</strong> Ever increasing surface-area for each subsequent change resulting in the collapse of productivity over time.</blockquote><p>Let’s take a moment to consider what other software engineering considerations might matter. How about using a more functional vs a more stateful language or style? Being more functional reduces side effects. In other words, it reduces what other parts of the code your changes could impact. Which means it is simply one strategy for reducing the surface area of future changes.</p><p>How about well formatted, consistent and linted code? While some of that does help with human comprehension, it is just a constant-factor in the O(k*n²) equation. It doesn’t reduce the complexity of code-code interactions. It’s worth doing, <em>but not if it increases </em><strong><em>n</em></strong><em>! </em>Any technology, tool or coding standard that increases the surface area of the next change <em>will slow you down</em>.</p><p>So… how <em>do</em> we minimize the surface area of changes due to future, unknowable requirements? We need a strategy.</p><h3>Surface Area Minimization Strategy</h3><p>To overcome the challenges of complexity and minimize the surface area of future changes, we need two complementary strategies: <strong>code-size reduction</strong> and <strong>modular design</strong>. Each plays a key role in keeping codebases agile.</p><h4>1. Code-Size Reduction</h4><p>The first obvious way to reduce the surface area of future changes is to reduce our code size in general. Obviously, this will help, and since each eliminated token reduces the base of the quadratic function, it has more impact than you might think. A 10% decrease in code size results in a 20% decrease in complexity. A 2x decrease in code size results in a 4x reduction in complexity.</p><p>It gets even better. As you eliminate each extra token and line, you’ll uncover new opportunities for further code-size reductions that were previously hidden in the noise. Sometimes this can lead to cascade unraveling of code complexity. Let’s say you’ve got two different data structures for users. This happens pretty often actually: you’ll have one for the user record in your database that your ORM provides and you’ll have one for your API. Now, having two different but similar data structures means your code needs to handle both. You’ll need to start writing functions that can take both. You’ll need to add converter functions between the two, etc. All due to the fact that you have extra complexity due to having two data-types when one would suffice. Now you’ve added mountains of additional complexity to manage it. If instead you standardized on just one user data-type (typically the API one) and immediately convert to and from it when reading from the database, all other code in your code-base becomes much simpler.</p><p>The general process is, as you work, you’ll begin to spot repeated patterns in your code (like two nearly identical but slightly different user-record types). You’ll remember the cardinal rule: <strong>“Don’t Repeat Yourself” (DRY)</strong>. When you DRY up a chunk of code, not only do you greatly decrease your code size, but you end up with a reusable abstraction. These “baby abstractions” are the first step toward an even more powerful strategy for minimizing code complexity: modular design.</p><blockquote>Every single token you eliminate from your code matters. Not only does it decrease the base of quadratic-complexity scaling, but it often unlocks new, previous invisible opportunities to simplify and reduce your code. <strong>When in doubt between two versions of code, always select the smaller one.</strong></blockquote><h4>2. Modular Design</h4><p>While reducing code size is an essential first step, it’s not enough by itself. When code-size reduction hits its limits, <strong>Modular design</strong> can give you nearly boundless scaling potential. Modular design doesn’t just reduce the size of your code — it fundamentally changes how your code interacts with itself. By encapsulating implementation behind interfaces, you can reduce the surface area of a change by 10x or more. This happens because the outside world only needs to interact with a slim interface rather than the whole implementation, instantly cutting down complexity.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I4PMC0AxnlyO50TVkNZZ2w.png" /><figcaption>Fully applied Modular Design transforms complexity scaling from quadratic to logarithmic</figcaption></figure><p>The magic doesn’t stop there. Modular design is recursive. Once you wrap one module behind an interface, you can repeat the process, creating modules within modules. This recursive approach means that the 10x reduction becomes exponential. Take 10 modules, each with an interface 10% the size of their implementation, and wrap <em>them</em> in a module. Now you have an interface which is a mere 1% the size of your implementation — a 100x100 = 10,000x decrease in complexity! And, for exactly the same reasons why operations on tree data structures take O(ln n) time, fully modularizing a codebase can convert complexity scaling from quadratic to sweet, sweet <em>logarithmic scaling</em>.</p><p>And before you object to this as an absurd example and that in the real world it can’t be that good… consider that your current project has about <em>100 million lines of code</em>. You read that right. I’m not exaggerating. We are all creating projects who’s total size, if you include the entire engineering stack that makes it possible, the OS, the CPU, the language, the database, the runtime, the dozens of 3rd party modules not to mention the cloud infrastructure, is easily that much code. How can we use that much code if complexity scales quadratically with code size? We can use it because all those technologies are exceptionally well honed modules that hide most their complexity behind much, much simpler interfaces. Modular. Design. Works.</p><h4>3. Anticipating Unknown, and Unknowable Future Requirements</h4><p>Above, we established two goals we are attempting to solve:</p><ol><li>minimize the time it takes to make a specific change — which means minimizing the surface area of that change</li><li>set up our codebase to easily adapt to endless, unanticipatable changes — which means setting up the codebase such that future changes’ surface areas will be minimal</li></ol><p>We’ve established that in general, minimizing our overall codebase size and maximizing modular isolation can work to minimize the surface area of a change, and that is by far the most effective way to minimize the time it takes to implement it. What we haven’t looked at quite so closely is how this helps us prepare for the future. We know there will be more requirements, and while we might be able to guess at some of them, we know there will be just as many unforeseen ones.</p><p>It turns out that even though we may not know what future requirements may be exactly, for most projects, most future requirements come in similar shapes. If you are building a database backed website, it’s highly likely that future requirements will either require that you add new tables or columns to the database and/or new web pages or web-page elements. Knowing that, you can measure and minimize how much code (measured in language tokens) it takes to add a new web page or new record type to your codebase. Minimize how many files you need to touch, how many lines you need to change in those files and how many new lines you need to add. The difference between an agile code base and an everyday code base under these simple metrics can be 10x or more.</p><p>More generally, our two strategies from above for minimizing surface-areas will work well for any future requirement. Keeping the overall code-size minimal decreases the amount of code that could possibly be in the next change’s surface-area. Modular design, though, is even more powerful. I’ll put it in terms of the <a href="https://shanebdavis.medium.com/the-5-essential-elements-of-modular-software-design-6b333918e543">5 Essential Elements of Modular Design</a>:</p><ul><li>Purpose: If we maximize the clarity of purpose for every module in our system, new requirements are less likely to feel disconnected from our existing work. Instead, they will naturally align with the meta-structure we’ve established, often requiring changes to just one or two modules.</li><li>Interface: A well defined interface together with strong encapsulation means that usually, if you are altering just one module, you can completely ignore all downstream dependencies — as long as you are only making minor or patch changes.</li><li>Encapsulation: If your modules’ encapsulation is strong, i.e. your abstractions aren’t very leaky, you can truly ignore their internals. If, on the other hand, you have weak encapsulation, then “the surface area of the next change” will often include internals of your modules negating most of the advantages of having modules in the first place.</li><li>Connection: A well modularized codebase is like Lego bricks. Under more extreme new requirements, which seemingly require larger refactors, a modular codebase can be reorganized like Legos. Many of the internals of modules won’t need to be changed nor contemplated — i.e. they will be outside the surface-area of the change. Further, a well modularized codebase is like a well-honed toolbox. When a new, unanticipated requirement comes in, you’ll have a wealth of tools to tackle the problem.</li><li>Implementation: A well-architected module, one that is strong along the other 4 elements, gives you the opportunity to invest in a stronger implementation: performance, reliably, extensible. This transforms a module from being a nice place to hide some complexity to being an invaluable tool in your arsenal against ever-changing needs.</li></ul><p>Finally, a bit of hard honesty. Though this method will greatly reduce the need for major refactors, nothing can completely eliminate them. Fundamental choices made at the beginning of a project, which may have been right at the time, may now be the wrong answer, especially after a major pivot. Still, if you’ve modularized your system, you have the best chance of reusing at least some of your code untouched.</p><h3>The Path of Mastery</h3><p>When I tell people all software engineering comes down to just one thing: <em>write less code</em>, they either don’t believe me. How could it be as simple as just making your code smaller? I hope now you have some sense that not only is it the dominant factor in any project’s chances of success, but also it isn’t such a simple idea after all. It’s a path of mastery. It’s something you get better and better at the more you practice it, without limit. Using “write less code” as a honing tool can be invaluable at guiding you down this path. It is a simple, objective way to measure your progress and to evaluate new ideas. Each time, you can ask “does this new technique actually reduce code-size, or more specifically, the surface-area of my next change?”, compare the two chunks of code, measure their size, choose the smaller one, and continue down the path of endless, objective improvement.</p><p>Anecdotally, I’ve found that decreasing code-size, one token at a time, is often like pulling on the thread of a sweater. At first progress is slow, but suddenly large parts of code collapse into far smaller and simpler solutions. Complexity begets complexity. Once you have overly complex code you must write more code to manage that complexity, compounding the problem. Conversely, just a little extra simplicity can often reveal even greater opportunities for simplification.</p><p>This insight has been critical to me in honing my own craft. I now evaluate all software technologies and methodologies against how well they minimize the surface-area of adapting to requirements.</p><p>I’ve written extensively on modular design and code-size reduction. If you are interested in further reading, I recommend <a href="https://shanebdavis.medium.com/the-5-essential-elements-of-modular-software-design-6b333918e543">The 5 Essential Elements of Modular Design</a>, which outlines the 5 measures of a module and your overall modular system. I also recommend my more recent <a href="https://shanebdavis.medium.com/organic-modular-design-8a7258a44d8f">Organic Modular Design</a> as a practical guide how to do modular design in the real world. If you’d like a concrete example of DRYing up a system, making it more modular and dramatically reducing it’s overall complexity (and size), check out <a href="https://shanebdavis.medium.com/modular-redux-a-design-pattern-for-mastering-scalable-shared-state-82d4abc0d7b3">Modular Redux — An Example of Applied Modular-Design for Mastering Scalable, Shared State in React</a>.</p><h3>Agility in the Age of LLMs</h3><p>I feel I can’t really talk about code agility without addressing the elephant in the room: LLMs like ChatGPT and Claude are changing the game… and also not changing the game. They are tremendous at accelerating code generation, but so far, you still need to understand the code they generate. They screw up, they get lost and they still don’t really understand what they’re doing. As long as we, as software engineers, need to understand our code, everything I’ve said above applies.</p><p>I suspect that even when we don’t need to look at our code line by line, the ability to think systematically about problems and break them up into well-encapsulated sub-problems will still be essential to human-ai collaboration. While writing small code, token-by-token may matter less, mastering modular design will likely be eternally useful.</p><blockquote>2026 Update: Even today, with Claude Code, Cursor and Codex, LLMs are just as constrained by the n-squared complexity scaling problem as humans are. Regularly coaching your coding agents to keep the code-base well modularized, organically, is still a critical, winning move. Share this article with them and ask them to suggest opportunities for improvement.</blockquote><h3>Conclusion</h3><p>If you care about actually creating real world impact as a software engineer, then you care about how quickly you can deliver that impact. Everything we call “code quality” is really just code agility in disguise. Many so-called code-quality tools and practices actually hurt agility and, in my opinion, should be discarded.</p><p>Agility is essential to continually delivering success. By obsessively reducing code size and embracing continuous, organic modular design, you can create systems that remain flexible and manageable, even as they grow. Through strong encapsulation and recursive modular structures, you transform the quadratic scaling of complexity into something much more tractable. By making these practices a daily habit, you’ll ensure your codebase remains agile, scalable, and ready for the ever-evolving demands of real-world projects.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a32a90132c81" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Organic Modular Design]]></title>
            <link>https://shanebdavis.medium.com/organic-modular-design-8a7258a44d8f?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/8a7258a44d8f</guid>
            <category><![CDATA[javascript-tips]]></category>
            <category><![CDATA[modular-design]]></category>
            <category><![CDATA[cto]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Sun, 13 Oct 2024 16:52:44 GMT</pubDate>
            <atom:updated>2024-10-21T12:08:13.650Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q7-4-WlbjMSuGkcaWTVIWg.jpeg" /></figure><p>We’ve all been on projects bogged down by extreme technical debt — where productivity grinds to a halt, and no one wants to touch the code for fear of breaking something in production. It’s no mystery why projects slow down over time: in codebases where any piece of code can interact with any other, complexity scales quadratically with code size. I’ve talked a lot about this topic (links below), and the solution to this n-squared complexity growth is clear: modular design. But I realized I’ve never talked about <em>how</em> to do modular design in practice.</p><p>Modular design isn’t just for architects and principal engineers — it’s something every developer should be doing daily. Rather than being a rigid upfront exercise, modular design should evolve naturally alongside your code. I call my approach Organic Modular Design (OMD). It’s a practical, incremental way to discover modular opportunities as you build, ensuring that the modules you create are both relevant and useful to the project at every stage — and it helps keep your project maximially modular throughout, squashing the dreaded n-squared scaling and replacing it with smooth logarithmic sailing.</p><blockquote>Also available on my <a href="https://shows.acast.com/code-and-optimism/episodes/organic-modular-design">Code &amp; Optimism podcast</a> and <a href="https://youtu.be/tAl1gQ6K__4">youtube.com/@code-and-optimism</a> channel.</blockquote><h3>The Modular Lifecycle</h3><p>At its heart, Organic Modular Design is about the modular lifecycle. At any given moment, your codebase is a garden of modules, each at various stages of maturity. I’ve talked a great deal about the <a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">5 Essential Elements of Modular Design</a>: Purpose, Interface, Encapsulation. Connection and Interface (PIE/CI— more resources linked at the end). These metrics provide an objective way to measure the quality of a module. With OMD, our goal as engineers is to continuously improve the modules we encounter in our daily work, nudging them forward in their lifecycle by enhancing one or more of the PIE/CI metrics. In this way, we can keep our codebase maximally modular throughout the development process.</p><h3>Lifecycle Stage 0: Modular Genesis</h3><p>Every module begins with the recognition of patterns. As you add new features or functionality, you’ll start to notice repetition or similarities in how tasks are structured. These emerging patterns signal the opportunity to introduce modularity.</p><p>The key is to act as soon as these patterns appear. Don’t wait until the repetition becomes overwhelming. When you spot recurring logic, it’s time to DRY (Don’t Repeat Yourself) up the code by extracting it into a function. This is the first step in Organic Modular Design — the function is the smallest possible module and represents the genesis of modularity in your codebase. You just created the seed of a new module. This is the first step toward reducing complexity as part of the modular lifecycle.</p><blockquote><strong>Pro Tip</strong>: DRY up patterns as soon as you see them; don’t wait.</blockquote><h3>Lifecycle Stage 1: Function Modules</h3><p>After DRYing up your first repetition, you’ve created the smallest form of a module — a function. Functions are inherently strong modules because they come with language-enforced encapsulation. The implementation is hidden behind the function’s interface (its inputs and outputs), ensuring a separation between what the function does and how it does it.</p><p>At this stage, the function’s purpose — its name and stated intent — might still be unclear, and that’s perfectly fine. You don’t need to get the purpose perfect right away. As the function’s role in the codebase becomes clearer over time, you can refine its name and scope. As related functions start to emerge, you’ll naturally gather them into a file — leading to the next step in the modular lifecycle.</p><h3>Lifecycle Stage 2: File Modules</h3><p>As you accumulate DRYed-up functions, you naturally begin organizing them into files. And just like functions, files are modules. In fact, the file itself becomes the next level of modularity in your codebase.</p><p>At this stage, you’ll start applying the PIE/CI principles more explicitly. Every time you touch a file, think about its purpose — is the file serving a clear, cohesive role? The name of the file should reflect that purpose. It’s fine to start with something generic like lib, but as your codebase grows, more specific names like stringLib are better, and names like dateFormatters are even more precise. The more specific the name, the better it defines the module&#39;s boundaries—what it does and what it doesn&#39;t do. This specificity is crucial for clear modular design.</p><p>It’s perfectly OK to start by exporting all the functions you’ve gathered into the file. However, the real benefit of modular design comes from increasing the ratio of interface to implementation. A smaller, well-defined interface compared to a larger implementation leads to a massive reduction in complexity for clients of that module. For instance, if the interface is just 10% of the implementation’s size, it results in a 100x reduction in complexity. This leverage is the key to achieving logarithmic scaling and avoiding the quadratic complexity that can cripple projects.</p><p>Note, though exports are explicit in JavaScript, other languages like Python export everything by default. Given the power of “defaults,” Python typically takes a bit more intentional effort to minimize the exported interface.</p><p>As you accumulate a few of these file-modules, you may notice a group of related files. When that happens, you’ll start thinking about the next step in OMD: organizing them into a folder.</p><h3><strong>Lifecycle Stage 3: Folder Modules</strong></h3><p>As you accumulate a few file-modules, you might notice that several of them are related. This is when you’ll naturally start thinking about grouping them into a folder.</p><p>Up until now, I’ve covered concepts that most engineers are familiar with — treating functions and files as modules. However, in most JavaScript projects, folders are treated as loose collections of files, with no defined interface or encapsulation. This is where projects lose modularity and fall into the trap of growing complexity. In typical JavaScript codebases, any file can import from any other file, leading to a fully connected graph of dependencies. The result? Complexity scales quadratically with the number of files.</p><p>To avoid this, it’s critical to treat every folder as a module: define its purpose, explicitly specify its exports, and treat its files and subfolders as private — nothing outside your folder should import them directly.</p><p>Thankfully, JavaScript (and other languages) provides tools to support folders as modules. In JavaScript, you can use an index.js file to define the public interface of the folder. While JavaScript doesn’t enforce encapsulation for folder-modules, you and your team can adopt the rule to never import from private files inside a folder. For example, importing from &quot;../../utils&quot; is fine, but &quot;../../utils/stringLib&quot; breaks the module&#39;s encapsulation. Avoid adding a / after a word character in import strings.</p><p>It’s fine to start with an index.js that exports everything using export *. Sometimes, keeping export *s forever makes sense—especially for files whose job is to define the interface. As the module evolves, the goal is to minimize the number of exports over time, by either removing entire files from exports or exporting only what&#39;s necessary.</p><p>Another pattern to consider is the aggregate export, where the index.js file imports specific items—like route handlers—and exports a single variable, such as an array containing all the items. This approach works well when the client only cares about the set as a whole, allowing you to expand the module without requiring client changes.</p><p>The most common way to limit exports is to export each item individually. However, this is often more tedious than necessary, especially in nested folder structures. One of the above methods is often more efficient.</p><p>The key is that you don’t have to be perfect. You just have to make the folder a little more modular each time. As the module’s purpose becomes clearer, you’ll naturally reduce the exports and strengthen its boundaries.</p><blockquote><strong>Pro Tip</strong>: Treat every folder as a module. Never import from internal files — always go through the folder’s public interface.</blockquote><h3><strong>Lifecycle Stage 4: Packaged Modules</strong></h3><p>At some point, you might want to consider packaging up your module. There are two main reasons for doing this. The first is straightforward: your module may mature enough that you want to reuse it in other projects or share it with the larger community by open-sourcing it. As the module gets used in new contexts, it can reveal important shortcomings or even bugs that were previously missed.</p><p>The second reason may be even more important. Packaging a module is the final step in the modular lifecycle and presents a major opportunity for improvement along each of the <strong>PIE/CI</strong> metrics:</p><ul><li><strong>Purpose</strong>: Step back and carefully consider the module’s name. If you’re going to publish it, you’re committing to it long-term. The name should clearly reflect the module’s purpose, be short, and memorable. This is also the time to refine the README, with a concise pitch, a quick-start tutorial, and example uses.</li><li><strong>Interface</strong>: It’s time to document the interface, not just in code but in words. Provide a detailed explanation of how to use your module in the README, or even consider creating dedicated documentation.</li><li><strong>Encapsulation</strong>: Packaging enforces the strongest modular boundaries. As you package the module, you may discover unexpected dependencies on other parts of your project or third-party libraries. You might also find areas where your project is violating the module’s encapsulation by relying on private files or sub-folders.</li><li><strong>Connection</strong>: Packaging makes your upstream dependencies explicit in the package.json, giving you a chance to streamline or eliminate unnecessary dependencies. It’s also the perfect time to implement semantic versioning, ensuring clients can safely upgrade without encountering breaking changes.</li><li><strong>Implementation</strong>: With the module isolated, improving test coverage, code quality, and iterating on the implementation becomes easier, all without impacting the main project.</li></ul><p>Packaging a module takes modular design to its highest level. It fully enforces boundaries, clarifies the module’s purpose, and ensures that it’s ready for use in contexts beyond its original project.</p><h3><strong>Embracing The Modular Lifecycle</strong></h3><p>As we’ve discussed, modular design starts with recognizing patterns in your code and DRYing them up — this is the foundation. The moment you spot repetition, you should immediately refactor it into a function. As your functions accumulate, the next step is to organize them into files. It’s perfectly fine to start with a broad interface where you export everything, but over time, work to refine that interface — reducing the number of exports and improving the ratio of interface to implementation. From there, related files naturally group into folders. Every folder should be treated as a module, with explicit exports and internal files and subfolders kept private. Like with files, it’s okay to start by exporting everything, but you should incrementally improve it. Eventually, some folders will mature into full-fledged packages. At this level, you create a fully mature module, complete with documentation, semantic versioning, and maximum encapsulation, ensuring a clean interface for external use.</p><p>The modular lifecycle is exactly that — a continuous cycle within your project. Once a module matures into a file, folder, or package, new “baby” modules start to emerge inside them. As your project evolves, you’ll discover new patterns within existing modules, which give rise to smaller, nested modules that will grow and eventually follow the same lifecycle. Mature modules give birth to new ones, and this cycle continues as long as the project is alive and growing. In this way, modular design is not a one-time task — it’s an ongoing, organic evolution of your codebase.</p><p>While it’s best to adopt Organic Modular Design (OMD) from the beginning of a project, any project — no matter how messy or old — can adopt OMD and begin the journey of controlling complexity through modular design, one day at a time.</p><p>By embracing Organic Modular Design, you can continuously improve your codebase in small, manageable steps. Each stage of the modular lifecycle brings increasing clarity, flexibility, and scalability to your project. The key is to treat modularity as an ongoing process that evolves alongside your project. Without modular design, complexity in a project will inevitably scale quadratically with its code size, making it harder to maintain. But by maximizing modularity, complexity can scale logarithmically instead, making your codebase more manageable and adaptable over time. As a bonus, you’ll also produce highly reusable packages that accelerate development in both this and future projects.</p><h3>Additional Resources on Modular Design</h3><ul><li>From my YouTube Channel, “Code and Optimism”: <a href="https://youtu.be/V_mOC8I86ic">5 Essential Elements of Modular Design</a>, <a href="https://youtu.be/4u6Z_lLeoyI">More about Purpose</a>, <a href="https://youtu.be/j7rNwl_3RG8">More about Interface</a>, <a href="https://youtu.be/JRYEVh8xHPY">More about Encapsulation</a>, <a href="https://youtu.be/lk9bg2JOVVQ">More about Connection</a>, and <a href="https://youtu.be/4io3gL-6V7U">More about Implementation</a>. <a href="https://youtu.be/2P3VWz4s38I">About Modular Testing</a>.</li><li>More from me on Medium: <a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">5 Essential Elements of Modular Design</a> and <a href="https://shanebdavis.medium.com/modular-redux-a-design-pattern-for-mastering-scalable-shared-state-82d4abc0d7b3">A Concrete, Step by Step Example of Modularizing Existing Code</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8a7258a44d8f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[“Code Quality” and “Correctness” Considered Harmful — What are the True Pillars of Software…]]></title>
            <link>https://shanebdavis.medium.com/code-quality-and-correctness-considered-harmful-what-are-the-true-pillars-of-software-c3bffe94ccc3?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3bffe94ccc3</guid>
            <category><![CDATA[static-analysis]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[code-quality]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Sat, 05 Oct 2024 12:17:26 GMT</pubDate>
            <atom:updated>2024-11-14T07:43:21.209Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HyEDV9dJ9HOaHYt6RFIw5g.png" /></figure><h3><strong>Forget “Code Quality” and “Correctness” — Verifiability and Agility are the True First Principles of Software Engineering</strong></h3><p>You wouldn’t be challenged for saying great software engineering revolves around code quality and correctness, yet you’d be profoundly wrong. “Code quality” and “correctness” are inherently meaningless. What do these terms even mean? Every software team comes up with its own answer to these questions, usually based on a mix of dogma, personal opinions, and prejudice. The result often leads to decisions and practices that actively reduce a project’s chances of success.</p><blockquote>If your principles are not based in reality, you are in serious danger of doing more harm than good.</blockquote><p>As senior engineers, we’ve all experienced the frustration of trying to apply so-called ‘best practices’ only to find they don’t seem to work for our particular project. We end up questioning ourselves, wondering if we’re implementing them incorrectly. The reality is, the ‘best practice’ might not actually be ‘best’ for every situation — or at least, not for the specific challenges we face.</p><p>So, if best practices are unreliable, and “code quality” and “correctness” are meaningless, what truly matters? The answer can be expressed under two first principles: <strong>code verifiability</strong> and <strong>code agility</strong>. In this article, we’ll explore why thinking in terms of “code quality” and “correctness” is flawed and how it leads many engineering teams astray, ultimately hurting their ability to deliver valuable software in a timely manner. From there we’ll examine why verifiability and agility are better models by which to evaluate our software engineering decisions.</p><h3><strong>Principle #1: Forget Correctness — Strive for Verifiability</strong></h3><p>The problem with “code correctness” is that it doesn’t define <em>what</em> correctness is or <em>why</em> it matters. Without that guidance, engineers fill in the blanks themselves, creating arbitrary ideas of what correctness should be. They turn to tools that promise to enforce “correctness,” but without a clear measure, these tools often end up making it harder to build valuable software.</p><p>The whole point of building software is to solve real-world problems, and solving real-world problems means establishing real-world requirements. It begins by selecting a good problem to solve, then devising an effective strategy for solving it. Only then can we identify the set of essential, real-world requirements needed to solve the problem.</p><p>These requirements must be both essential and realistic. A set of requirements is <em>essential</em> if it is minimal yet sufficient to solve the problem. <em>Realistic</em> requirements can’t demand 100% accuracy or 100% reliability. Instead, they must specify the minimal number of “nines” — such as 99%, 99.9%, or 99.99% — of accuracy and reliability required. Since each additional “nine” costs an order of magnitude more to achieve, it is critical to select the minimal level of accuracy and reliability that is just enough to solve the problem.</p><p>The only thing that matters when building software is that it meets the strategy’s specific set of essential, realistic requirements. No one cares what the inside of the software looks like (other than the engineers). Much of what passes for the dogma of “software correctness” is irrelevant to any specific project and its unique requirements.</p><p>What matters isn’t that the software is “correct” in some arbitrary or abstract sense, but rather: a) does it meet the specific set of requirements, and b) how do we know? Thus, when it comes to software engineering — the practice of making all the millions of design decisions, from the top-level architecture down to the name of every variable — what ultimately matters is <strong>verifiability</strong>. How easy is it to verify if the software we built meets its requirements?</p><blockquote><strong>Verifiable</strong> (adjective): able to be checked to be true or accurate</blockquote><p>Arbitrary notions of correctness often make it more difficult to validate that the code does what it needs to do. <strong>Verifiability</strong> puts correctness into context, asking: “Does this code fulfill the requirements?” That’s the only form of correctness that matters. <em>Every single decision, big or small, should be measured by how easily we can verify if it helps us meet requirements.</em></p><h3>Principle #2: Forget Quality — You Need Agility</h3><p>As we’ve established, the goal of building software is to solve real-world problems. In reality, though, both the problem and the strategy for solving it evolve over time. As a result, the requirements for the software also change. Because of real-world competition and a constantly changing problem landscape, we must deliver our solution in a timely manner to achieve the impact we desire.</p><p>Therefore, as engineers, we must not aim to solve a fixed set of requirements but design our code to quickly adapt to ever-changing demands. <em>We must engineer our code to be agile.</em></p><blockquote><strong>Agile (</strong>adjective)<strong>: </strong>able to move quickly and easily.</blockquote><p>Code agility is how quickly and easily you can modify your software to meet new needs. However, the common fixation on “code quality” often undermines this goal. The problem with “code quality” is that it doesn’t define <em>what</em> quality means or <em>how</em> it supports the software’s evolving purpose. Without clear guidance, engineers fill this void with arbitrary standards, often prioritizing aesthetics or trendy best practices that can actually hinder agility.</p><p>Engineers, in their pursuit of “quality,” may shape their code to fit an image of neatness and elegance, adding layers of abstraction or complexity. While these choices might look “clean” or “well-structured,” they often come at the cost of making the software harder to change when new requirements arise. In reality, what the code <em>looks</em> like doesn’t matter unless it impacts how quickly and efficiently the software can adapt to changes.</p><p><strong>Agility</strong> provides a far clearer target. It isn’t about crafting beautiful or fashionable code; it’s about building software that can evolve with minimal friction. The faster you can adapt your software to meet changing requirements, the more valuable it becomes. That’s the only kind of “quality” worth pursuing: the kind that keeps your software light, flexible, and ready to meet whatever new challenges come its way.</p><h3>Code is a Liability</h3><p>Code is not an asset; it’s a liability. Every line of code brings a cost — the cost of engineering time and money needed to maintain it. The real asset is the value that the code delivers in the real world. No one outside of the engineering team cares what’s inside the code: not what cloud resources are used, what language was selected, which libraries are included, or even how the CI/CD pipeline is set up. All of that is just noise.</p><p>What truly matters is <strong>verifiably</strong> creating the impact we want in the real world, and doing it <strong>in a timely manner.</strong></p><p>Because code itself is a liability, we as engineers must set aside our pride and personal preferences. The choices we make are not about satisfying aesthetic tastes or showcasing technical prowess. Instead, we need to continuously and objectively evaluate every software engineering decision. This means everything: the architecture, the language, the services, the libraries we use, right down to each variable name, file name, and how we structure functions, classes, and modules. If it doesn’t have a clear, net positive impact on verifiability and agility, it’s the wrong decision.</p><h3><strong>Software is a Powerful Tool for Solving Real-World Problems</strong></h3><p>While code itself may be a liability, software is a powerful tool for delivering real-world impact. Every decision must align with solving the problem at hand. It’s not just about solving it; it’s about solving it <strong>verifiably</strong> and with <strong>agility</strong>. We should focus on building software that delivers value, adapts easily to evolving requirements, and can be verified against the goals we’ve set. Code is merely a means to an end — a tool that must be wielded carefully to minimize its liability while maximizing the value it delivers.</p><p>As senior engineers, we have a unique responsibility and opportunity to shape our teams’ approach to software development. By proposing <strong>verifiability</strong> and <strong>agility</strong> as the benchmarks for decision-making, we can help our teams make effective, objective choices that are aligned with maximizing our project’s chances of success. In the end, it’s not about chasing an abstract idea of “code quality” or “correctness.” It’s about building software that meets real-world requirements, adapts to change, and consistently delivers value.</p><h3>Further Reading…</h3><p>I’m working on follow-up articles on this topic. The first one is out:</p><ul><li><a href="https://shanebdavis.medium.com/embracing-software-agility-a32a90132c81">Embracing Software Agility</a></li></ul><p>Much of my previous work is also related. You can start with this one if your are curious how to put Agility into action:</p><ul><li><a href="https://shanebdavis.medium.com/organic-modular-design-8a7258a44d8f">Organic Modular Design</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3bffe94ccc3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rewrite ROI: 5 Scenarios That Justify Modernizing Your Legacy System]]></title>
            <link>https://shanebdavis.medium.com/rewrite-roi-5-scenarios-that-justify-modernizing-your-legacy-system-f9617a83e002?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/f9617a83e002</guid>
            <category><![CDATA[regulatory-compliance]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[modernize-legacy-system]]></category>
            <category><![CDATA[scalability]]></category>
            <category><![CDATA[cloud-native]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Thu, 23 Jun 2022 19:21:14 GMT</pubDate>
            <atom:updated>2022-06-23T19:25:49.748Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_SLIUv7E926E1mu4.jpg" /></figure><p>Technology moves fast. So do customer expectations. Keeping an application up to date in this dynamic environment requires continuous innovation. However, the increasing pace of change in both technology and user expectations means that obsolescence is always nipping at your heels.</p><p>At some point, even the most well-designed and well-maintained products become unacceptably expensive and time-consuming to maintain. Beyond these hard costs, an outdated product can result in shrinking market share, ballooning costs, security risks, performance issues, or all of the above. At some point, the cost of a major rewrite becomes justified.</p><p>A well-done rewrite offers many opportunities. Discarding out-dated assumptions embedded into your product and tuning it for the modern market can reinvigorate your competitive edge. Taking advantage of modern hardware and software platforms can decrease operational costs while increasing scalability, reliability and security. A ground-up modernizing of your software can radically improve your agility, performance, and profitability.</p><p>Determining the right moment to pursue this level of change can be challenging. However, in our experience, the ROI tipping point often comes sooner than you might think-especially when taking into account the opportunity costs of keeping a less-competitive product alive and missing out on accelerated innovation.</p><p>Below are five common business scenarios that can show a compelling ROI for modernizing your software with a major rewrite. In each section, I’ll provide an overview of the problem, some typical symptoms, and some diagnostic questions to ask. Then, I’ll offer a few tips on how to get started thinking about your rewrite and what ROI goals you might want to target.</p><h3>1. Reliability and Performance</h3><p>Even a system that was once top-of-the-line can become a lumbering dinosaur a few years down the road. Whether customer expectations have grown or the technology just wasn’t built to handle today’s workloads, it’s showing its age. It might be slow, behave in unpredictable ways, or incur unacceptable levels of downtime.</p><p><strong>Symptoms:</strong></p><ul><li>Excessive downtime</li><li>Excessive customer complaints about reliability and performance</li><li>High average latencies on requests</li><li>Excessive alarms waking your team up at night</li></ul><p><strong>Diagnostic questions:Solution:</strong></p><p>Assess your application from the inside out. If you were to rewrite the core using today’s technologies, how much better would it be? Depending on how much of the back end needs rewriting, you might also consider re-inventing the front-end product experience as well. Somewhere around the 50% mark rewriting the entire application becomes easier than rewriting just half of it.</p><p><strong>ROI goals:</strong></p><ul><li>Turn detractors into enthusiastic supporters</li><li>Redirect your engineering team from fighting fires to adding value to the product</li><li>Sleep through the night</li></ul><h3>2. Scalability Limits</h3><p>Your technology is a victim of success. You’re growing like crazy, but your service architecture can’t keep up. Sometimes your service scaled in a completely different way than you planned for. Sometimes there just wasn’t time to build a robust scalable platform for your MVP. Perhaps you performed a “lift-and-shift” of your on-premises infrastructure without taking full advantage of your cloud provider’s scaling technologies. To avoid flattening your growth curve and driving away customers, you might need to invest in a major modernization of your infrastructure.</p><p>Alternatively, perhaps you are courting a new enterprise customer that is an order of magnitude larger than any previous customer you’ve worked with. Perhaps your multi-tenant architecture was designed to scale in the number of tenants but was never designed to scale individual tenants to very large sizes. To service these larger customers, you’ll need whole new scaling capabilities.</p><p><strong>Symptoms:</strong></p><ul><li>Can’t keep up with growing demand</li><li>Web servers’ latency spiking as they reach 100% utilization during peak hours</li><li>Database servers falling over, requiring manual intervention to get back up and running</li><li>Spending an excessive amount of time manually scaling your infrastructure to manage load</li><li>Courting a big, new client, but the system architecturally can’t support a client that large</li></ul><p><strong>Diagnostic questions:</strong></p><ul><li>If you experienced overnight 10x growth in your customer base, could your app keep up?</li><li>Do you find yourself worrying that you’re going to land a customer that’s TOO big?</li><li>Is your current architecture impacting customers and throttling your growth?</li><li>Is your app particularly intensive in terms of storage, computation, concurrency, or other specific factors?</li><li>Are you ready to scale well under your wildest best-case scenarios?</li></ul><p><strong>Solution:</strong></p><p>Estimate your realistic, best-case, and amazing-case scaling needs for the next 5 years. Draft the best modern architecture to meet your needs. Now that you know where you are going, you need to figure out how to get there. Estimate how long it will take to build the new architecture, taking into account you’ll still be maintaining the existing infrastructure in the interim, and most importantly make a well thought through migration plan. The best migration plans include running both systems side-by-side for a substantial period.</p><p><strong>ROI goals:</strong></p><ul><li>Accelerate business growth by uncorking your bottleneck</li><li>Easy, effortless, stress-free scaling</li><li>Support larger customers</li></ul><h3>3. New Opportunities for Fueling Growth</h3><p>You see a major opportunity to serve the emerging needs of existing customers or to pivot to a new market altogether. Your current app gets you part of the way there, but there’s a big gap between where it is today and what you envision. Whether you need to optimize it for a different customer size, usage pattern, or feature set, rewriting the app may be more cost-effective than bolting on new functionality.</p><p><strong>Symptoms:Diagnostic questions:Solution:</strong></p><p>Rewrite the app with the new market in mind, prioritizing the ability to make future change easier. By decomposing the app into discrete services using containers, APIs, microservices, and other modern architecture, you’ll be positioned for greater agility going forward. You’ll also be able to reuse those components in other solutions. Make sure you engage with the target user base early and often to validate the business case and optimize the value proposition in real time.</p><p><strong>ROI goals:</strong></p><ul><li>Open new markets or revenue streams</li><li>Support larger customers</li><li>Increase profitability with a more valuable product or by entering a more lucrative market</li><li>Reinvigorate your product with an agile, modern platform that can quickly respond to ever changing business requirements</li></ul><h3><strong>4. Operational Costs</strong></h3><p>The problem is simple-you’re overspending on operations. The solution might be simple, too-like turning off unused servers or changing which cloud instances you use. More often, chronically high operational costs are a symptom of sub-optimal architecture as it relates to the underlying platform. The bill can add up quickly, dwarfing what it would have cost to refactor the app in the first place. By the time you are spending six figures or more a month, a strategic rewrite can often pay for itself in a few months.</p><p><strong>Symptoms:Diagnostic questions:</strong></p><ul><li>Which aspects of your infrastructure are driving the cost, and which are driving the value</li><li>Have you already performed significant cost optimization and are finding that you are still paying too much?</li><li>Estimating pre- and post-rewrite operational costs, how long before the rewrite pays for itself?</li><li>Are you using high-cost SaaS services when it might be cheaper to build an in-house solution?</li></ul><p><strong>Solution:</strong></p><p>Audit your app to understand where the majority of your spending resides. Determine if it’s best to optimize your application code, refactor your cloud infrastructure or do both. The key is often tailoring your app to the pricing structure of your cloud provider. Be sure to understand their pricing model thoroughly before proceeding with your plan. As mentioned above in the “Scaling” scenario, you’ll need a good migration strategy so you can seamlessly swap over to the new back-end without affecting customers.</p><p><strong>ROI goals:</strong></p><ul><li>Slash your cloud-bill in half, three quarters or more</li><li>Increase profitability</li><li>Structure your cloud usage to maintain profitability even as you scale</li></ul><h3>5. Regulatory Change</h3><p>Regulatory complexity continues to grow, especially in compliance-heavy industries like healthcare and finance. In some cases, new regulations can stymie the growth trajectory of an existing application. Or, you may want to expand into a new market with stricter requirements and find that your app was not designed to meet them. Retrofitting an existing app to meet compliance requirements may not be possible, especially when those requirements are related to the technology stack itself.</p><p><strong>Symptoms:Diagnostic questions</strong></p><ul><li>What compliance issues will come into play if you expand into new markets or territories?</li><li>What is the opportunity cost of delaying entry into those opportunities?</li><li>What aspects of compliance are pro forma and which require demonstrating specific capabilities?</li><li>How easily can you adapt to future regulatory changes?</li></ul><p><strong>Solution:</strong></p><p>Start by understanding the regulatory requirements thoroughly. Consulate with compliance experts to verify your assumptions. Next, audit your existing solution to determine the increment of change required. This can range from updating the encryption settings on your cloud infrastructure to fully rewriting the application. If deeper change is required, take the opportunity to evaluate your product’s design as a whole and re-tune it for modern market conditions.</p><p><strong>ROI goals:</strong></p><ul><li>Protect your current markets by staying current with regulation</li><li>Move into new markets with new regulatory capabilities</li><li>Win more business by demonstrating your commitment to compliance rigor</li></ul><h3><strong>The High Cost of the Status Quo</strong></h3><p>There’s almost always a way to keep something running a little longer or hack in a solution for a given business need. But it’s important to evaluate the opportunity cost of burning cash running in place versus investing in the future with a larger, more thoughtful rewrite. Technical debt eats away at the agility of an aging system and eventually comes due in the form of downtime, high costs, and missed revenue. Development gets more and more expensive and innovation slows to a crawl.</p><p>The decision to rewrite an application is rarely an easy one. It can be expensive, complex, and time-consuming. But it can also reduce costs over the long term, increase profitability, and open up new markets.</p><p>The good news is major rewrites don’t have to be the high-risk projects they are infamous for. By definition you’ve already done the hardest part of any project-fitting a product to a market need. Armed with your deep understanding of that market and a few critical risk-management strategies, a well-managed rewrite project has an excellent chance of success.</p><p>At GenUI, we specialize in modernizing complex applications in ways that deliver ongoing value and measurable business results. We can evaluate your existing situation and recommend the most cost-efficient way to achieve your business goals. If a rewrite is justified, we’ll help you get it done faster, with less risk, and with an eye toward your future. <a href="https://www.genui.com/contact">Let’s talk about getting your app up to speed</a>.</p><p><em>Originally published at </em><a href="https://www.genui.com/resources/modernizing-your-legacy-system"><em>https://www.genui.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f9617a83e002" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[S3P — Massively Parallel S3 Copying]]></title>
            <link>https://shanebdavis.medium.com/s3p-massively-parallel-s3-copying-9a9e466d0d74?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/9a9e466d0d74</guid>
            <category><![CDATA[cloud-storage]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-s3]]></category>
            <category><![CDATA[cloud]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Thu, 20 Aug 2020 22:47:32 GMT</pubDate>
            <atom:updated>2024-05-06T12:01:53.728Z</atom:updated>
            <content:encoded><![CDATA[<h3>S3P — Parallel S3 Listing &amp; Copying</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*y8d1ieqdCH5VK1Gu.jpeg" /></figure><blockquote><em>S3P’s unique, </em>parallel<em> S3 bucket listing algorithm can list </em><strong><em>35,000 items/second </em></strong><em>and copy up to </em><strong><em>9 gigabytes / second</em></strong><em>.</em></blockquote><p><a href="https://aws.amazon.com/s3/">AWS S3</a> offers industry-leading scalability, data availability, security, and performance. It’s also relatively easy to work with, at least when working with one file at a time. However, once you load a bucket with terabytes of data and millions of files, doing anything over the whole bucket becomes unwieldy. Even getting statistics about file counts and file sizes becomes challenging.</p><p>We created S3P to solve two problems: large-bucket content discovery and cross-bucket copying. In our tests S3P was 15x to 100x faster than <a href="https://docs.aws.amazon.com/cli/latest/reference/s3/index.html">AWS CLI</a>. We sustained item listing speeds of 35,000 items per second and copy speeds of nearly 9 gigabytes per second.</p><h3>Key Takeaways</h3><ul><li>S3P uses a massively parallel algorithm for scanning S3 buckets</li><li>S3P scans S3 buckets up to <strong>15x faster</strong> than aws-cli</li><li>S3P copies S3 buckets up to <strong>100x faster</strong> than aws-cli</li></ul><h3>How S3P Came to Be</h3><p>We needed to refactor a client’s AWS infrastructure into a HIPAA compliant environment. As part of work, we had to move about 500 terabytes of data into new S3 buckets. Since we only had a month to finish the whole project. We knew right away this was going to be trouble.</p><p>After some preliminary tests with aws s3 sync we found we could get a max of about 150 megabytes/second throughput. A little back-of-the-envelope math shows it would take over 40 days to complete. Clearly this wasn&#39;t going to work.</p><p>We also had another problem. The client had dozens of buckets and no good information on the contents of those buckets. Not only did we need to transfer a large amount of data in a small amount of time, we had no idea exactly what data needed transferred and no idea what the actual file counts were. By default, S3 doesn’t tell you anything about bucket size or file count. <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/cloudwatch-monitoring.html">S3 + CloudWatch</a> tells you a little, but it isn’t very detailed.</p><p>The S3 API is structured around listing items in the bucket sequentially. Just listing 5 million files with aws s3 ls at about 1400 files per second would take an hour.</p><p>We had two problems to solve:</p><ul><li>List S3 items at least 10x faster to aid discovery</li><li>Copy S3 items between buckets in the same region at least 10x if not 100x faster.</li></ul><p>We solved the listing problem with a cleaver use of <a href="https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm">divide-and-conquer</a> and AWS S3 SDK’s startAfter option introduced with <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html#API_ListObjectsV2_RequestSyntax">listObjectsV2</a>. This was the hardest part of the project. Doing efficient bisection over an unknown key distribution can be tricky, but we made it work. It’s blazing fast, performing 100 or more S3 list-object requests in parallel.</p><p>Copying S3 items was more straightforward. We just needed to fire off simultaneous calls to copy-object. We overcame copy-object’s lack of support for objects larger than 5gigabytes by shelling out to aws s3 cp for these larger files. What seemed like a cludge at first ended up being a performance win since it allowed us to leverage multiple CPU cores.</p><p>All of this aggressive parallel copying needed limits however. Without some control over the concurrency, the poor NodeJS instance ran out of memory and crashed. There is a sweet spot for parallel listing as well. Too many parallel list-object requests and performance declined. We built tools and options for controlling the concurrency of listing, copying ‘small files’ with the SDK and copying ‘large files’ with the aws-cli.</p><p>Though the current implementation of S3P tops out at about 35,000 items/second for listing and 9 gigabytes/second for copying, we believe there is room for even more performance improvements by forking NodeJS or even distributing the work across multiple machines. There is no documentation about the maximum performance S3 can provide, but whatever it is, S3P’s techniques should allow us to reach it.</p><p>If you have S3 data in the petabytes range, and you want an even faster S3P to solve your problems, let us know, we’d love to help you.</p><blockquote><em>Instead of a 40 day aws-sync, with S3P we were able to copy our client’s 500 terabytes of S3 data in less than 24 hours.</em></blockquote><h3>Alternatives</h3><p>Before building our own tool, we investigated several alternative solutions to accelerating same-region cross-bucket copying. This Stack Overflow thread nicely sums up the options:</p><ul><li><a href="https://stackoverflow.com/questions/39149171/fastest-way-to-sync-two-amazon-s3-buckets">Fastest way to sync two Amazon S3 buckets — Stack Overflow</a></li></ul><p>The two main pure-Amazon options were:</p><ul><li><a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-commandrunner.html">Command Runner — Amazon EMR</a></li><li><a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html">Amazon S3 Transfer Acceleration — Amazon Simple Storage Service</a></li></ul><p>I thought EMR would do the trick. Throwing a bunch of parallel hardware at a problem seemed like the right idea. I followed the steps and did a test with 10 large EC2 instances. Shockingly, it still only produced about 150 megabytes/second throughput. It should have been at least 10x faster. I’m not an expert on Amazon-flavored map reduce (EMR), and the Amazon-provided “Command Runner” tooling had almost no doc. I suspect the problem was in part due to the fact that listing S3 buckets is slow. Regardless, we had a short deadline on this project and didn’t have time to dive in and debug this option. It turns out that was a very good choice, because S3 speed, in this case, doesn’t require huge hardware.</p><p>I also experimented with S3 transfer acceleration endpoints, but I was never able to significantly improve on the 150 Mb/s transfer rate. The documentation of S3 transfer acceleration focuses almost exclusively on uploading data /into/ s3, not transferring between s3 buckets. Reading between the lines, I don’t think this actually accelerates transfers between buckets within the same region. Another dead end.</p><p>The remaining suggestions basically amounted to parallelizing the copy operations within a single machine. These techniques do accelerate copying, but without solving the serial listing speed problem, they, too, have limitations.</p><h3>Try Out S3P</h3><p>S3P is written in JavaScript and is available as an NPM package. If you have <a href="https://nodejs.org/en/download/">NodeJS</a> installed, you can run it directly from the command line:</p><pre><em># run s3p and get the top-level help page <br></em><strong>npx s3p</strong>  </pre><pre><em># run s3p and get a summary of the contents of your bucket: <br></em><strong>npx s3p summarize</strong> --bucket my-bucket-name</pre><blockquote><em>NOTE: S3P uses the same credentials system as the AWS Cli. More on how to configure your AWS-CLI credentials: </em><a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html"><em>Configuring the AWS CLI — AWS Command Line Interface</em></a></blockquote><p>S3P supports many useful operations right out of the box:</p><ul><li><strong>compare</strong>: Compare two buckets and produce a summary of their differences.</li><li><strong>copy</strong>: Copy all files from one bucket to another bucket.</li><li><strong>list</strong>: List all matching files.</li><li><strong>list-buckets:</strong> List all your S3 buckets.</li><li><strong>summarize:</strong> Scan all items in one bucket and produce a summary of all the items.</li><li><strong>sync</strong>: Only copy files which do not exist in the target bucket.</li></ul><p>S3P also supports powerful options such as specifying custom key re-writing functions in JavaScript, but on the command-line:</p><pre><em># copy an entire bucket and add .backup to the end of every file <br></em><strong>npx s3p copy</strong> --bucket my-from-bucket --to-bucket my-to-bucket --to-key &quot;js:(key) =&gt; key + &#39;.backup&#39;&quot;</pre><p>You can also iterate over specific sub-ranges of the bucket either by specifying a prefix or a combination of start-after and stop-at keys:</p><pre><em># summarize all items with keys starting with &quot;my-folder/&quot; <br># Example match: &quot;my-folder/2015-05-23.log&quot; <br></em><strong>npx s3p summarize</strong> --bucket my-from-bucket --prefix my-folder/  </pre><pre><em># summarize all items with keys &gt; &quot;2018&quot; and &lt;= &quot;2019&quot;.  <br># Example match: &quot;2018-10-12-report.txt&quot; <br></em><strong>npx s3p summarize</strong> --bucket my-from-bucket --start-after 2018 --stop-at 2019</pre><p>S3P has an API you can call directly from JavaScript. The API gives you programatic access to all S3P’s massively parallel S3 listing capabilities.</p><blockquote><em>You can read more about alternative installation options and using S3P as a library in the </em><a href="https://github.com/generalui/s3p/"><em>S3P readme</em></a><em>.</em></blockquote><h3>S3 Costs</h3><p>With all these massively parallel S3 operations you may be wondering how much this might drive up your S3 bill. The key thing to note is that S3 pricing is not based on speed. The cost is purely based on how much you do, not how fast you do it.</p><p>S3P uses two S3 operations: list-objects and copy-object. Currently both operations cost $.005 per 1000 requests, or 200,000 requests per $1 (US-East, June 2020).</p><p>List-objects can return up to 1000 items per request, but because we can’t assume any a priori knowledge about the distribution of keys, S3P’s divide-and-conquer algorithm must make educated guesses which aren’t perfect. S3P always requests 1000 items per list, but often requests overlap. In practice, S3P fetches a little more than 500 unique items per request overall.</p><p>Effectively, S3P costs about $.01 to scan one million items. To copy one million items costs about $5 (assuming they are all smaller than 5 gigabytes and staying within one AWS region). If each file was just under 5 gigabytes, that’s about $1 per petabyte.</p><p>In other words, though S3P uses approximately twice as many list-object calls to feed it’s massively parallel listing algorithm, that cost is small compared to the copying costs. S3P uses exactly the same number of copy requests AWS-cli or any other tool would. The only real cost danger is, since S3P is so fast, you might use it more, and that might drive up your S3 bill.</p><blockquote><em>S3 pricing is based on how much you use, </em>not how fast you use it<em>.</em></blockquote><h3>Conclusion</h3><p>S3P is an essential tool for working with large S3 buckets. The ability to scan buckets 15x faster and copy data 100x faster opens up all new capabilities. It allowed us to move half a petabyte of data between buckets in less than a day instead of more than a month. Perhaps even more important, it allowed us to inspect the client’s S3 buckets in detail to fully understand their data patterns and know with confidence what we needed to do to meet their requirements. Without S3P we would have been blindly doing large, slow aws-syncs, and often getting it wrong as we slowly learned about the data.</p><p>S3P’s key innovation is the ability to divide-and-conquer any arbitrary S3 bucket regardless of what keys were used (as long as they only use ASCII characters). This allowed us to break free of the standard, slow linear-list-object algorithm and open up a world of unbounded parallel possibilities.</p><ul><li>source code: <a href="https://github.com/generalui/s3p">github.com/generalui/s3p</a></li><li>published: <a href="https://www.npmjs.com/package/s3p">npmjs.com/package/s3p</a></li></ul><p><em>Originally published at </em><a href="https://www.genui.com/resources/s3p-massively-parallel-s3-copying"><em>https://www.genui.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9a9e466d0d74" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Modular Redux — a Design Pattern for Mastering Scalable, Shared State]]></title>
            <link>https://shanebdavis.medium.com/modular-redux-a-design-pattern-for-mastering-scalable-shared-state-82d4abc0d7b3?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/82d4abc0d7b3</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[redux-thunk]]></category>
            <category><![CDATA[redux]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Tue, 04 Feb 2020 17:53:30 GMT</pubDate>
            <atom:updated>2024-10-13T15:52:35.476Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pGJ-ghIlu-_oknZiQPTn5g.jpeg" /></figure><h3>Modular Redux — An Example of Applied Modular-Design for Mastering Scalable, Shared State in React</h3><p>Modular Design Part 2 — A step-by-step example of applying the 5 Essential Element of Modular Design to existing code in 3 levels of difficulty.<br>(<a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">Modular Design Part 1 — The 5 Essential Elements of Modular Design</a>)</p><blockquote>Summary: Applying Modular Design, using Redux as an example, the resulting “Modular Redux” is a new design pattern, not a new dependency. It only uses standard Redux and React. It takes <strong>less than half the code </strong>compared to traditional Redux/Redux-Toolkit and <strong>decreases complexity by almost 3x</strong>. Modular Redux is an easy, powerful and scalable way to manage shared state in React applications. This is largely due to the power of Modular Design.</blockquote><p>The day of massive, single-page-applications is upon us. Websites are looking more and more like applications and less like collections of pages. The median JavaScript size for the <a href="https://freshysites.com/web-design-development/the-20-most-popular-websites-for-2019/">most popular 20 websites of 2019</a> is 3.5 megabytes. All but two had at least 1 megabyte of JavaScript.</p><p>Single page apps require a new way of thinking about web design. Instead of storing client session state on the server, more and more is stored in the client. State is one of the trickiest things to manage in a growing code-base. It’s particularly tricky when the state is shared across multiple components in your client-side application. Thankfully there are tools that can help. Shared-state libraries help with understanding and debugging the state across multiple components.</p><p>In this article I’m going to focus on Redux, one of the most popular solutions to managing shared client side state. Redux really shines when it comes to understanding and debugging shared state:</p><ul><li>Redux’s <strong>serializable, immutable, shared state</strong> is easy to inspect, persist, and restore</li><li>Redux’s <strong>serializable, atomic, dispatched updates</strong> are easy to inspect, record and playback</li></ul><p><a href="https://redux.js.org">Redux</a> is the de facto standard for <a href="https://reactjs.org">React</a> shared-state, and there’s good reason. With centralized serializable state, serializable actions, atomic updates in the form of reducers, not to mention great middleware, there’s a lot to recommend Redux. State predictability and debuggability is the name of the game, and Redux is a best-of-class solution for shared state management… almost.</p><p>Redux is a pain to use and doesn’t scale. Even on the smallest projects it takes an excessive amount of ‘boilerplate’ code to get up and running. On large projects, it only gets worse. Redux’s complexity scales poorly with project size. Except… it’s not actually Redux that’s the problem. It’s the way it is used. Both the traditional Redux and newer <a href="https://redux-toolkit.js.org">redux-toolkit</a> design patterns are inherently complex and don’t scale.</p><p>Thankfully, Redux can be used in a way that is both easy <em>and</em> scales. We can have our cake and eat it too. It’s possible to have all the wonderful properties of Redux and do it with svelte, clean, scalable code. We just need a change of design pattern.</p><blockquote><em>Redux is a best-of-class solution for shared state management</em></blockquote><p>The current state-of-the-art redux-toolkit was a good start, but in this article I’ll show how to take your Redux code to the next level. I will show how to write Redux code that not only scales to very large applications but also how to reduce your Redux-related code by half. Best of all, I’m going to do it with no new dependencies. I’m only going to use plain React and Redux.</p><p>The key is modular design.</p><h3>Outline</h3><p>This article is organized in the following sections:</p><ul><li>The 5 Essential Elements of Modular Software Design</li><li>What is Modular Redux?</li><li>To-Do-List Tutorial Using Modular Redux</li><li>Comparing Modular Redux with Traditional Redux</li><li>Comparing Modular Redux with React Toolkit</li><li>Streamlining Modular Redux with Hooks-for-Redux (H4R)</li><li>Modular Redux Scales — It’s also Just Easier</li><li>Where to Start with Modular Redux</li></ul><h3>The 5 Essential Elements of Modular Software Design</h3><p>Modular Design Part 1 — Concepts</p><p>This article is a follow-up to my previous article on the elements of modular design. I’ll be referring back to it from time to time. I recommend reading it if you want to learn more about the principles of scalable, modular design:</p><p><a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">The 5 Essential Elements of Modular Software Design</a></p><p>Where the previous article applied to software engineering in general, this article is all about the nitty-gritty of real-world React-Redux applications. My goal here is to show how to write better, more scalable Redux code, and also to use Redux as a real-world case-study of the benefits and techniques of modular design. I’ll compare three different real-world applications of increasing complexity showing for each in turn how Modular Redux is vastly superior to the status quo.</p><h3>What is Modular Redux?</h3><p>Modular Redux is the result of consistently applying good, modular design to applications using Redux. Modular Redux is a design pattern. It’s a re-usable solution to using Redux in real world applications. My focus is on React applications, but Modular Redux can apply to any platform.</p><p>From the point of view of modular design, there were four main opportunities for improving Redux design patterns:</p><ul><li><strong>Encapsulate</strong> Redux modules with respect to each other: Reducers typically have access to <em>all</em> redux state and <em>all</em> dispatched actions. This means every part of the application that touches Redux is potentially dependent on any other part. This is why global variables are so bad, and why Redux, when used improperly, can get out of control. Redux is a global state, but it doesn’t have to be used that way. We can modularize Redux slices so each slice only has access to its own sub-state and actions.</li></ul><blockquote><em>Redux is a global state, but it doesn’t have to be used that way.</em></blockquote><ul><li><strong>Consolidate</strong> all code related to one Redux slice into one module: All reducers, action-types, subscriptions, getters and dispatchers can be consolidated for each Redux slice resulting in code reduction, stronger encapsulation and greatly reduced inter-dependencies.</li><li><strong>Isolate</strong> Redux from components: Components should not be directly dependent on Redux. If we need to refactor how we manage shared state, we shouldn’t have to rewrite every component that uses that shared state. Instead, we’ll implement the simplest possible API for each Redux-module, taking care not to expose the fact that we are using Redux as the underlying implementation.</li><li><strong>Minimize</strong> component shared-state dependencies: Often I see people adding Redux dependencies to a parent component only to do nothing with them other than pass them to children components via props. This can make sense in scenarios where the sub-component is reused with different state-sources. However, it’s overkill if the sub-component only accesses one slice of Redux state. In general, if a component doesn’t directly use or update shared state, it shouldn’t be dependent on it. (Build your components with <a href="https://www.essenceandartifact.com/2016/02/yagni-and-building-in-house-frameworks.html">ZEN</a> — zero extra nuts.)</li></ul><blockquote><em>If a component doesn’t directly use or update shared state, it shouldn’t be dependent on it.</em></blockquote><p>To see how apply modular design principles to Redux, and get the most out of the opportunities listed above, let’s do a tutorial.</p><h3>To-Do-List Tutorial Using Modular Redux</h3><p>I’ll use the classic ‘to-do’ app for this tutorial. First I’ll go through the source code and explain how each part works. In the next section I’ll compare and contrast Modular-Redux with the same app implemented using the traditional Redux design pattern. I’m assuming familiarity with <a href="https://reactjs.org/">React</a> and <a href="https://reactjs.org/docs/hooks-intro.html">React Hooks</a>.</p><p>I’ve included all the JavaScript code here, but you can also run and explore the code in your browser on CodeSandbox:</p><ul><li><a href="https://codesandbox.io/s/modular-redux-tutorial-t1mkz">https://codesandbox.io/s/modular-redux-tutorial-t1mkz</a></li></ul><h4>To-Do-List Tutorial Files</h4><p>This example consists of 5 files. These are the app’s modules. I’ll explain them one at a time:</p><ul><li>components/ToDo.js</li><li>components/ToDoItem.js</li><li>redux/list.js</li><li>redux/store.js</li><li>index.js</li></ul><h4>index.js</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/95b7d5144209989747592660fff4edcf/href">https://medium.com/media/95b7d5144209989747592660fff4edcf/href</a></iframe><p>The root file of the app is about as simple as it can be. We are importing our root component, ToDo, and rendering it as a child of the ‘root’ element. Notice there is no Provider nor any other Redux-related dependencies. There is no need for index.js to be dependent on Redux, so it shouldn’t be. This is one of the essential elements of modular-design: <strong>minimize inter-dependencies</strong>.</p><h4>ToDo.js</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7f1405e05d37354364e37772478556b9/href">https://medium.com/media/7f1405e05d37354364e37772478556b9/href</a></iframe><p>The main component shows a list of ToDo items returned from <em>useList</em>. It also presents a simple HTML form for adding new items via <em>addItem</em>. The call to <em>useState</em> and the <em>createNewToDoItem</em> function are used to capture the current value in the DOM’s input field.</p><p>Experienced Redux developers will notice an important difference here compared to other Redux design patterns. We are importing simple methods directly from our <em>redux/list</em> module. These are simple functions. There is no manual dispatching, action-creating or other complications. This component has no dependencies on Redux at all. It only accesses an API which provides exactly what the component needs to function and nothing more.</p><p>The <em>redux/list</em> module’s API is the simplest solution possible given the needs of the application:</p><ul><li><em>useList</em> returns an array of items</li><li><em>addItem </em>takes a single<em> </em>item as input, an object with a single ‘text’ property</li></ul><h4>ToDoItem.js</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6635c98f0d3f2ff1ec3e99ce8a998419/href">https://medium.com/media/6635c98f0d3f2ff1ec3e99ce8a998419/href</a></iframe><p><em>ToDoItem</em> is the only other component in this simple app. It, too, imports exactly what it needs from <em>redux/list</em> and nothing more: <em>deleteItem</em>. You might notice that we didn’t pass <em>deleteItem</em> into <em>ToDoItem</em> from <em>ToDo</em> as is common in other Redux patterns. Doing that makes two components dependent on <em>deleteItem</em> instead of only one.</p><h4>redux/list.js</h4><p>This module is where the magic happens. It solves a well defined problem, manage the to-do list, with the simplest possible solution: an API consisting of three functions with the simplest possible inputs and outputs. Defining the <strong>essential problem</strong> and the <strong>simplest solution</strong> possible are two essential elements of modular design.</p><p>If we stubbed the list module, it might look like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ec02dff797529d9cd6a3ef49a2ed5897/href">https://medium.com/media/ec02dff797529d9cd6a3ef49a2ed5897/href</a></iframe><p>Now I’ll go through the actual implementation from top to bottom, explaining it chunk-by-chunk:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7b9265a2d5183f4afefbda2d07d796a6/href">https://medium.com/media/7b9265a2d5183f4afefbda2d07d796a6/href</a></iframe><p>At the top of the file we define the basic logic of the list state:</p><ul><li><em>storeKey</em> defines the name of the Redux store slice where the list will live</li><li><em>getUniqueId</em> takes a list of to-do items and returns an id not in the list</li><li><em>initialState</em> pre-populates the list with two to-do items</li><li><em>reducers</em> is a map from action-names to functions. These reducers take in the current state of the list-slice (importantly, <em>not</em> the whole Redux state), and a payload. Then they return a new list with the change applied.</li></ul><p>Notice that reducers do <em>not</em> have access to the full Redux state, yet another essential element of modular-design: <strong>encapsulation</strong>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ade8c94d99bbf9929488d5f91b6dd377/href">https://medium.com/media/ade8c94d99bbf9929488d5f91b6dd377/href</a></iframe><p>Next are a few helper functions. These make it easy to interact with just our slice of the Redux state instead of the entire, global state.</p><ul><li><em>getState</em> returns the current list</li><li><em>subscribe</em> takes a function as an input. That function gets called with the current list whenever the list changes. Subscribe also returns a function for unsubscribing.</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cc8925d0a41f414a3ec9003f1290b9a0/href">https://medium.com/media/cc8925d0a41f414a3ec9003f1290b9a0/href</a></iframe><p>Finally, we replace our stubs with working implementations:</p><ul><li><em>useList</em> uses standard React hooks to re-render any component it is used in, returning the current list every time it’s called.</li><li><em>addItem</em> dispatches a Redux action to trigger the <em>addItem</em> reducer. Notice that the dispatcher only takes an item as input; that’s all it needs to know to do its job.</li><li><em>deleteItem</em> also dispatches a Redux action, this time to trigger the <em>deleteItem</em> reducer.</li></ul><p>Notice that <em>addItem</em> and <em>deleteItem</em>, the dispatchers, only take exactly what they need as inputs — the item to add and the item to delete respectively. There is no need to pass in action-names nor construct action data-structures.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/934925843467cbd8e1b5e6907ec15453/href">https://medium.com/media/934925843467cbd8e1b5e6907ec15453/href</a></iframe><p>Last, we need to add our reducers to the redux store. This may seem a little backwards compared to the traditional Redux pattern. Usually you define all the reducers and then call createStore, passing them in. In order to minimize dependencies, we moved everything related to this Redux slice into one file. Therefor we need to have access to the store in the same file where we define the reducers. This is why in Modular-Redux, the Redux-modules register themselves with the store rather than the other way around.</p><p>As it turns out, this helps us not only further reduce dependencies by eliminating the need for the traditional Redux <em>reducers</em> file (see below), the self-registration method of defining Redux modules also allows us to do cool things like lazy loading and hot reloading.</p><blockquote>Note: If an application needs control over which store a modular Redux slice is registered with, simply wrap the entire contents of this file in a function that takes the store as an input. Then invoke the function with the store you want to bind it to.</blockquote><h4>store.js</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e48d3e46f1af039fd091c51bc6146929/href">https://medium.com/media/e48d3e46f1af039fd091c51bc6146929/href</a></iframe><p>The last file of the to-do list example defines the Redux store. It creates a basic store with an injectReducer method. This pattern is described in detail on the Redux.js website:</p><h4>Tutorial Wrap-Up</h4><p>If you haven’t already, try the working tutorial example and explore the code: (same as linked above) <a href="https://codesandbox.io/s/modular-redux-tutorial-t1mkz">https://codesandbox.io/s/modular-redux-tutorial-t1mkz</a></p><p>This example seems almost trivially simple. The key is consistently applying the 5 elements of modular design to each file: define a single, narrow and <strong>focused problem</strong>, create a well-defined, complete and <strong>minimal solution</strong>, ensure <strong>watertight encapsulation</strong>, implement a correct, performant and <strong>minimal foundation</strong>, and do it with <strong>minimal inter-dependencies</strong>.</p><h3>Comparing Modular Redux with Traditional Redux</h3><p>Here’s why modular Redux really matters: it can reduce the complexity of shared state by two to three times. Let’s compare two equivalent implementations. One is basically what we just finished in the tutorial — with styling added. The other is functionally identical but written in the traditional redux way:</p><ul><li>modular Redux: <br><a href="https://github.com/shanebdavis/modular-redux-todo-2020">https://github.com/shanebdavis/modular-redux-todo-2020</a><br><a href="https://codesandbox.io/s/modular-redux-tutorial-t1mkz">https://codesandbox.io/s/modular-redux-tutorial-t1mkz</a></li><li>traditional Redux: <br><a href="https://github.com/sunil-sandhu/redux-hooks-todo-2019">https://github.com/sunil-sandhu/redux-hooks-todo-2019</a><br><a href="https://codesandbox.io/s/github/sunil-sandhu/redux-hooks-todo-2019">https://codesandbox.io/s/github/sunil-sandhu/redux-hooks-todo-2019</a></li></ul><p>Modular redux slashes the dependencies between modules. The traditional implementation has 19 dependencies between modules:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FbXy4YbZ_k1cnsZojpigCg.png" /><figcaption>Traditional Redux — todo list — dependency diagram</figcaption></figure><blockquote>Note: CSS, react, and react-dom imports are omitted since they are identical in both projects.</blockquote><p>The solid arrows show explicit dependencies in the form of JavaScript import statements. The dashed arrows show implicit dependencies. A module is implicitly dependent if it needs updating when the implicitly-dependent-on module is updated. Let me explain the implicit dependencies one at a time:</p><ul><li><em>appActions</em> depends on redux because it implements part of the redux dispatch API.</li><li><em>appReducer</em> similarly depends on Redux since it implements the redux-specific reducer API.</li><li>further, <em>App</em> depends on Redux since it explicitly uses the Redux dispatch API.<br> In all three of the above cases, if you swap out redux, you’d have to complete rewrite or replace these files.</li><li><em>appActions</em> depends on <em>appReducer</em> since every action in <em>appActions</em> must exactly parallel the reducers defined in <em>appReducer</em>. If you add or change your reducers, you’ll also have to update appActions.</li><li><em>ToDoItem</em> and <em>ToDo</em> both implicitly depend on <em>appActions</em> since they implicitly use <em>appActions</em>. If you change the signature of any of those actions, <em>ToDoItem</em> or <em>ToDo</em> will need to be updated.</li></ul><p>Now, compare that with the Modular-Redux solution’s mere 7 dependencies:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dsBtg4nay54zzDRcA9khHg.png" /><figcaption>Modular Redux — todo list — dependency diagram</figcaption></figure><p>There are zero indirect dependencies. Each module can be fully swapped out without needing to update other modules. The relationships are clear and easy to understand. Minimized dependencies maximize code scalability.</p><p>Modular Redux dramatically cuts down on the number of files and lines of code and has 63% less inter-dependencies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qd57b6iJcTwR0qvwClKC0w.png" /><figcaption><strong>Modular Redux vs Traditional Redux</strong></figcaption></figure><ul><li><strong>2.7x improvement: </strong>7 dependencies vs 19</li><li><strong>1.8x improvement: </strong>5 files vs 9</li><li><strong>1.6x improvement: </strong>104 lines of code vs 170</li></ul><h3>Comparing Modular Redux and React Toolkit</h3><p>In the tutorial above I compared Modular Redux with the standard Redux pattern. I did this because it is the Redux pattern most people are familiar with. This is somewhat unfair, however, as the official Redux recommendation is to use the newer <a href="https://redux-toolkit.js.org">Redux Toolkit</a>. Redux Toolkit does make good progress over the traditional approach, but let’s see how Modular Redux can bring things to the next level. The Redux Toolkit website provides a <a href="https://redux-toolkit.js.org/tutorials/basic-tutorial">basic tutorial</a>, an <a href="https://redux-toolkit.js.org/tutorials/intermediate-tutorial">intermediate tutorial</a> and an <a href="https://redux-toolkit.js.org/tutorials/advanced-tutorial">advanced tutorial</a>. I’m going to skip over the basic tutorial and dive directly into a detailed comparison using the intermediate and advanced tutorials.</p><h4>Intermediate Tutorial: To-Do List with Filter</h4><p>This version of the To-Do list uses two Redux slices. In addition to adding and listing to-do items, you can also filter the item list.</p><p>First, let’s start with the Redux-Toolkit-based implementation. I recommend opening the CodeSandbox to get a frame of reference:</p><ul><li><a href="https://codesandbox.io/s/rtk-convert-todos-example-uqqy3">interactive Redux Toolkit solution (codesandbox)</a></li><li><a href="https://github.com/reduxjs/rtk-convert-todos-example">Redux Toolkit solution source (github)</a></li><li><a href="https://redux-toolkit.js.org/tutorials/intermediate-tutorial">Redux Toolkit Intermediate Tutorial Documentation</a></li></ul><p>Let’s jump right into the Redux Toolkit dependency diagram:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FFlwmCYFUENUuY2WRYTjQQ.png" /><figcaption>Redux Toolkit — to-do list with filter — intermediate tutorial — dependency diagram</figcaption></figure><p>There’s quite a bit going on compared to our first tutorial. There are three external dependencies on redux-related libraries instead of one: <em>redux-toolkit</em>, <em>react-redux</em> and <em>redux</em> itself. While there are a lot of interdependencies, there were only two implicit ones: <em>Link</em> and <em>TodoList</em> use the <em>filterSlice</em> and <em>todoSlice</em> APIs indirectly, respectively.</p><p>Redux-Toolkit does a good job of eliminating the need for multiple files to define each redux-slice. There is just one file for the to-do list and one file for the filter. However, Redux-related dependencies are still littered throughout the components.</p><p>Now let’s look at the Modular-Redux solution for the same To-Do-List-with-Filter:</p><ul><li><a href="https://codesandbox.io/s/github/shanebdavis/rtk-convert-todos-example-modular-design-conversion">interactive Modular Redux solution (codesandbox)</a></li><li><a href="https://github.com/shanebdavis/rtk-convert-todos-example-modular-design-conversion">Modular Redux solution source (github</a>)</li></ul><p>The Modular Redux dependency diagram:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kkLGTQF1dWh6mMfpuP_FKA.png" /><figcaption>Modular Redux — to-do list with filter — intermediate tutorial — dependency diagram</figcaption></figure><p>The above diagram shows what a dramatic improvement modular design can bring to a project. We cut the number of dependencies from 27 down to just 13.</p><p>You may notice there are less components in this version. As I was rewriting the code, simplifying it, additional simplifications suggested themselves. This is why it is so important to always strive to make things as simple as possible. Complexity breeds complexity. If a system is overly complex, it will tend to grow even more complex. The complexity itself necessitates ever more complexity to manage it.</p><blockquote>Complexity breeds complexity.</blockquote><p>When should a module not be a module? The main sign a module might not be necessary is when it has only one other module dependent on it. In that case, one needs to determine if the module is substantively different from the module that depends on it, and it the code is simpler or more complex with the sub-module.</p><p>In this tutorial, <em>FilterLink</em> and <em>Link</em> were intimately tied up with <em>Footer</em> and the <em>filter</em> options and weren’t being used anywhere else. I was able eliminate 38 lines of code, two files and only add 3 lines of code to <em>Footer</em>. If moving code into modules causes a <em>12x increase in code-size</em>, it’s probably not the best solution.</p><p>I also added one module. There was some redundant code in the <em>filter</em> and <em>todo</em> modules. Since the code was used in two different places, I was able to create a new module, <em>modularRedux</em>, simplify both the original modules and reduce overall code-size.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h4ozNJ7KAXMnOThLlqVwcw.png" /><figcaption><strong>Intermediate Tutorial-Modular Redux vs Redux Toolkit</strong></figcaption></figure><ul><li><strong>2.1x improvement: </strong>13 dependencies vs 27</li><li><strong>2.0x improvement: </strong>193 lines of code vs 393</li><li><strong>30% improvement: </strong>10 files vs 13</li><li><strong>30% improvement: </strong>134k minimized JavaScript build vs 170k</li></ul><h4>Advanced Tutorial: Github Issue Browser</h4><p>The final Redux Toolkit tutorial is a styled Github issue browser. It adds asynchronous requests, TypeScript, limited testing, hot-reloading, styling and has four different redux-slices: issuesDisplay, repoDetails, issues and comments.</p><p>You can learn about the Redux Toolkit implementation here:</p><ul><li><a href="https://codesandbox.io/s/rtk-github-issues-example-03-final-ihttc">interactive Redux-Toolkit Advanced Tutorial (codesandbox)</a></li><li><a href="https://github.com/reduxjs/rtk-github-issues-example">Redux-Toolkit Advanced Tutorial source (github)</a></li><li><a href="https://redux-toolkit.js.org/tutorials/advanced-tutorial">Redux-Toolkit Advanced Tutorial Documentation</a></li></ul><p>The module-dependency diagram is significantly more complex than anything we’ve looked at so far:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FNrTPf1lQaOSV0F68rDAiA.png" /><figcaption>Redux Toolkit — github issue browser — advanced tutorial — dependency diagram</figcaption></figure><p>The Redux Toolkit Advanced Tutorial implementation had a ton more implicit dependencies. For example, <em>showIssueComments</em> is imported in App, but it is passed through <em>IssuesListPage</em> through <em>IssuesList</em> before it is finally actually used in <em>IssuesListItem</em>. It also contains a complicated set of inter-dependencies between the Redux modules themselves.</p><p>Now let’s look a the Modular Redux implementation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J8LDNIScD0csUuPfuK9xMA.png" /><figcaption>Modular Redux — github issue browser — advanced tutorial — dependency diagram</figcaption></figure><p>As you might expect, Modular Redux dramatically decreases the number of inter-dependencies. The total dependencies decreased from 63 to 34, but that only tells part of the story. This time the React modules were already optimal, so the component dependency structure is identical in both implementations. All 28 eliminated dependencies came from improving how Redux is used. The Redux-related dependencies decreased by more than half from 48 to just 19.</p><p>The right-hand side is still pretty busy, but even without knowing the contents of the files, the diagram helps illuminate what’s going on in the application. For example <em>comments</em>, <em>issues</em>, and <em>repoDetails</em> all update when <em>issuesDisplay</em> changes, and that’s clearly reflected in the dependency diagram.</p><p>You’ll notice there is no dependency on <a href="https://www.npmjs.com/package/redux-thunk">redux-thunk</a>. Each of the four Redux modules manage their own asynchronous loading internally. Subscriptions and promises sufficiently and elegantly manage the asynchronous requests. There is no need for the added complexity of thunks.</p><p>I also added TypeScript support for <em>modularRedux</em>. Typing works very well with the Modular Redux pattern. You’ll notice there is very little explicit typing in any of the Redux modules. Eliminating the pass-through, indirect dependencies in the components also cleaned up huge swaths of unnecessary typing code.</p><p>In the end, the most advanced, most challenging, and most up-to-date example of Redux Toolkit was outdone by using plain JavaScript and good software engineering in the form of modular design:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u9X-3RaKf6XeeKsfrq2wRA.png" /><figcaption><strong>Advanced Tutorial-Modular Redux vs Redux Toolkit</strong></figcaption></figure><ul><li><strong>2.4x improvement: </strong>20 redux-related dependencies, vs 48</li><li><strong>1.8x improvement: </strong>35 overall dependencies, vs 63</li><li><strong>1.7x improvement: </strong>685 lines of code, vs 1169</li><li><strong>10% improvement: </strong>244k minimized JavaScript build, vs 271k</li></ul><h4>Redux Toolkit’s Missed Modular Design Opportunities</h4><p>H4R address all four of the Redux modularization opportunities I discussed above, but Redux Toolkit only addresses one and a half out of the four:</p><ol><li><strong>Redux Toolkit addressed:</strong> Encapsulating slices with respect to each other. Each reducer only handles the state of the slice, not the entire state. Reducer-routing is handled automatically, so a particular slice’s reducers aren’t allowed unfettered access to all actions.</li><li><strong>Redux Toolkit partially addressed:</strong> Redux Toolkit does consolidate reducers, action-types and action-creators into one file, but it still leaves dispatching, subscribing and getting the current state to the individual components.</li><li><strong>Redux Toolkit missed:</strong> Redux Toolkit still fully mingles Redux-specific code throughout every component in the project. This is such a disaster that, when rewriting the Advanced Example with Modular-Redux, I ended up rewriting it from scratch rather than refactoring the existing code. This is why it’s so important for modules to not expose their own dependencies. Redux-Toolkit’s slice-modules expose their Redux dependency making it near impossible to refactor on anything but trivial projects.</li><li><strong>Redux Toolkit missed:</strong> Both the intermediate and advanced Redux Toolkit examples continue to repeat the broken pattern of importing dependencies in a parent component only to pass them to a child component without ever using them. This isn’t a direct failure of Redux Toolkit, but by repeating this error in the tutorials they are propagating bad design to the broader Redux community. The Advanced tutorial is especially guilty as the showIssueComments function is actually passed not to a child component, but all the way through to a great-grand-child component before it is actually used creating four dependencies when one would have sufficed.</li></ol><p>While Redux-Toolkit is better than the traditional Redux design pattern, it comes at the expense of adding its own complexities. It adds new concepts but doesn’t successfully abstract away Redux base concepts. The result is you have to deeply understand both to successfully use the toolkit.</p><h3>Streamlining Modular Redux with Hooks-for-Redux (H4R)</h3><p>I said I was going to show how to use Modular Redux with no extra dependencies, and I have, but you’ll find a certain amount of reusable code across all three of the examples above. That code should go in its own NPM module. That NPM is <a href="https://github.com/generalui/hooks-for-redux">hooks-for-redux</a>.</p><p>If you decide to use the Modular Redux pattern, I recommend using hooks-for-redux. In some of the diagrams above there is a light-grey rectangle with ‘H4R’ in the lower right-hand corner. H4R is a drop-in replacement for those rectangles. It is a tiny library (currently just 90 lines of code). It implements the common parts of the Modular Redux design pattern in a well-tested and fully TypeScripted way.</p><p>I’ve implemented each of the tutorials above using H4R. You can view them here:</p><ul><li>To-Do Tutorial with Modular Redux and H4R: <br>(<a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019">github</a>) (<a href="https://codesandbox.io/s/github/shanebdavis/hooks-for-redux-todo-2019">codesandbox</a>)</li><li>RTK Intermediate Tutorial with Modular Redux and H4R: <br>(<a href="https://github.com/shanebdavis/rtk-convert-todos-example-h4r-conversion">github</a>) (<a href="https://codesandbox.io/s/github/shanebdavis/rtk-convert-todos-example-h4r-conversion">codesandbox</a>)</li><li>RTK Advanced Tutorial with Modular Redux and H4R:<br>(<a href="https://github.com/shanebdavis/rtk-github-issues-example-h4r-conversion">github</a>) (<a href="https://codesandbox.io/s/github/shanebdavis/rtk-github-issues-example-h4r-conversion">codesandbox</a>)</li></ul><p>Read more about hooks for Redux:</p><ul><li><a href="https://medium.com/@shanebdavis/how-i-eliminated-redux-boilerplate-with-hooks-for-redux-bd308d5abbdd">How I Eliminated Redux Boilerplate with Hooks-for-Redux (H4R)</a></li></ul><h3>Modular Redux Scales — It’s also Just Easier</h3><p>Redux is a very simple idea. It lets you define shared state, methods for atomic updates and subscriptions to changes. Where Redux really shines is the wealth of middleware available you can attach to a redux store to persist, restore, playback and inspect your state. Because of all those reasons, Redux should scale very well, but it typically doesn’t. The problem isn’t Redux itself. The problem is the recommended ways of using Redux introduces a ton of unnecessary complexity.</p><p>The Modular Redux design pattern dramatically simplifies using Redux. While making things easier is great, Modular Redux shines most as you scale your application. Modular Redux kept the complexity under control for each of the three applications presented above even as they increased in size. This comes from the systematic application of the <a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">essential elements of modular software design</a>. Modular Design helps ensure each module solves a <strong>focused problem</strong>, with a well-defined, complete and <strong>minimal solution</strong>, using <strong>watertight encapsulation</strong>, supported by a performant, correct and minimal <strong>foundational implementation</strong>, and most of all, with <strong>minimal inter-modular connections</strong>.</p><p>With Modular Design, Redux becomes a Best-of-Class solution to React shared state. You can have Redux’s centralized serializable state, serializable actions, atomic updates, great middleware and you can do it with svelte, clean, scalable, modular code.</p><blockquote>With Modular Design, Redux becomes a Best-of-Class solution to React shared state</blockquote><h3>Where to Start with Modular Redux</h3><p>To get started, I recommend either taking one of the examples above, or you can dive into the <a href="https://github.com/generalui/hooks-for-redux#tutorial">Hooks-for-Redux Tutorial</a> which is a little more detailed and will guide you to towards the best way to use Modular Redux in your application.</p><h3>Further Reading</h3><ul><li><a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">Part 1: The 5 Essential Elements of Modular Software Design</a></li><li>More about H4R — recommended library for building modular-redux applications:<br><a href="https://medium.com/@shanebdavis/how-i-eliminated-redux-boilerplate-with-hooks-for-redux-bd308d5abbdd">How I Eliminated Redux Boilerplate with Hooks-for-Redux (H4R)</a></li></ul><p><em>Originally published at </em><a href="https://www.genui.com/insights/modular-redux"><em>https://www.genui.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=82d4abc0d7b3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The 5 Essential Elements of Modular Software Design]]></title>
            <link>https://shanebdavis.medium.com/the-5-essential-elements-of-modular-software-design-6b333918e543?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b333918e543</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Mon, 27 Jan 2020 17:09:06 GMT</pubDate>
            <atom:updated>2024-10-09T08:12:51.024Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kX5Yxj_eK9wPeyVdRRZCqA.jpeg" /></figure><h3>The 5 Essential Elements of Modular Software Design (PIE-CI)</h3><p>Why do we care about modular design? Let me tell you a story about my friend Mike Kelly. In 2016 he took a side job to build a simple course platform. Since it was a side job, and Mike was bored, he offered to do this job at a deeply discounted rate if he could retain rights to the source code. It was a brilliant move. That client had reach, and other people started asking about his work. “This is a really nice platform. It’s really easy to use. It’s fast, and it works well. What is it?”</p><p>Mike decided to test the waters. His plan was to package up the product so it would be easier to custom-install on each clients’ hardware. He quickly realized it was going to be a nightmare to maintain the software in each client’s environment. Instead, he moved towards a software-as-a-service solution, hosting each client’s course in one place for a monthly fee. Thus <a href="https://membervault.samcart.com/referral/membervault/LMl4e4KjiC5KacxA">MemberVault.co</a> was born.</p><p>MemberVault was a success. More and more people signed up. Mike quit his day job and, together with his wife, started working full time on marketing, sales, customer support, bugs and new features. There was just one problem. In the rush to get things out the door, he didn’t have time to change his original design: each client required their own database. Each new customer, free or paying, added another database to his MySQL server.</p><p>Fast forward to 2019 and MemberVault’s MySQL server had thousands of databases. MySQL is not designed to scale this way, and performance and memory problems were starting to creep up. Mike had to solve this problem soon. He was proverbially tied to the train-tracks and the locomotive was barreling down on him.</p><p>This is where I came into the story. I helped Mike think through the possibilities and we came up with a simple solution. We decided it was best to stick with MySQL, but refactor the code to use a centralized database. It would need a new schema with an added clientId field for most tables. The question was how to refactor the existing code-base, migrate old customers, minimize downtime, minimize development time, and avoid having to maintain two codebases for the different database schemas during the transition.</p><p>The solution ended up being astonishingly simple (the best solutions always are). Mike added the clientId field to the existing databases even though they didn’t need it. He could refactor the code in place, test it and deploy it incrementally before he introduced the new, centralized database. Once everything was working on the existing databases, he could add a small tweak to his existing database routing logic to route all new clients to the central database, which would have the exact same schema. He could worry about actually migrating the old clients later, at his leisure, and still only have to maintain one code-base.</p><p>The problem was Mike’s code was a mess. It wasn’t modular. It had grown organically as he struggled to keep up with all the needs of a successful, growing SAAS company. For example, instead of being wrapped up in a single, well-designed module, the code for adding users was duplicated and spread across multiple code-bases: member-user-signup, member-admin, membervault-admin, cron jobs, the API and other external integrations.</p><p>Modular design could have reduced the amount of code Mike needed to update by a factor of 5 and dramatically reduced the complexity of the overall refactor. Instead of taking months, the refactor could have been done in a couple weeks. That’s the power of modular design. Modular design allows code to remain agile in the face of ever-changing requirements.</p><blockquote>Modular design allows code to remain agile in the face of ever-changing requirements.</blockquote><h3>Modular Design</h3><blockquote>Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality — wikipedia</blockquote><p>As with all software engineering, the ultimate goal of modular design is to maximize developer productivity. We want to create maximum software value for minimum development cost. Modular design amplifies developer productivity by managing complexity.</p><p>Complexity is the primary killer of developer productivity. Software complexity can scale exponentially with size. Thankfully, modular design gives us complexity-fighting superpowers. It allows us to decompose big, seemingly complex systems into small, manageable parts. The strength of a project’s modular design will ultimately determine how large it can scale. Without modular design, it would be impossible to manage the complexity of all but the smallest, most incremental projects.</p><blockquote>Modular design gives us complexity-fighting superpowers.</blockquote><p>With good modular design, there is no limit to how high and how far we can go with software. Modules give us the super power of abstracting arbitrary complexity behind clean, simple interfaces. Modular design can, when wielded by masters of the craft, transform a project where complexity scales exponentially with size to a project where complexity scales sub-linearly. It is even possible for a project to get easier to add functionality as the project grows.</p><h3>The 5 essential Elements of Modular Design</h3><p>The goal of modular design is to manage complexity. To do this we must simultaneously minimize the complexity of each individual module and minimize the complexity of the overall system. Practically, the goal is to make each module as easy as possible to design, implement, test, deploy, upgrade and maintain. The 5 elements of modular design are essential to achieving this goal.</p><blockquote>Make each module as easy as possible to design, implement, test, deploy, upgrade and maintain.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hL58Y213YzbmnFw_kuUJ3A.png" /></figure><ol><li><strong>purpose:</strong> A module is an abstraction with purpose. Its purpose should be crystal-clear. It should have a <strong>single, exclusive responsibility</strong>. A module’s responsibility should be <strong>narrow and focused</strong>, and no two modules’ purpose should overlap.<br>(<a href="https://en.wikipedia.org/wiki/Single_responsibility_principle">single responsibility principle</a>)</li><li><strong>interface:</strong> A module’s interface should be easy to use, easy to understand and easy to ensure correctness. It should offer all this without needing to understand any of its implementation details.<br>To achieve this, a module’s API should be <strong>well-defined</strong> and <strong>documented</strong>. The API should be <strong>complete</strong> and <strong>minimal</strong>. It should have exactly what is needed and nothing more. Last, it should be <strong>hard to misuse</strong>. The easiest way to use a module should also be the correct way.<br>(<a href="https://nordicapis.com/the-three-principles-of-excellent-api-design/">Arnaud’s three principles of excellent API design</a>)</li><li><strong>encapsulation:</strong> A module’s <strong>implementation is private</strong>. Modules should expose as little as possible. They should not expose their functional structure, data-structure nor their own dependencies. Any implementation detail of a module should be changeable without affecting a single client.<br>This is perhaps the most important element of modular design. The other four elements could all be expressed in terms of maximizing the isolation as much as possible of the internal implementation from the outside world. As an abstraction, each module should be as <strong>watertight</strong> as possible. <em>Leaks become accidental, hidden parts of the public API</em>. Without strong encapsulation, you end up with implicit dependencies which can be disastrous to scaling projects.<br>(<a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/">Joel’s law of leaky abstractions</a>)</li><li><strong>connection:</strong> In order to truly make our software scalable, it’s not enough to just minimize the complexity of an individual module. We must also minimize the complexity of the entire ecosystem in which the module functions. The dependencies between modules define a network which adds its own complexity. A well designed modular system minimizes the dependencies between all modules. To minimize the connections between modules, <strong>minimize each module’s dependencies</strong>.</li><li><strong>implementation:</strong> In order to ensure the module is easy to use, it must work well. A module with the best-designed purpose, interface and encapsulation will still fail without good implementation. A module’s implementation should be <strong>correct</strong>, <strong>performant</strong>, <strong>tested</strong> and <strong>minimal</strong>. <em>The worse the implementation, the leakier the abstraction.</em></li></ol><blockquote>Here’s an acronym I use to help me remember the 5 elements: PIE-CI. I pronounce it “pie-see-eye.”</blockquote><h3>What Shouldn’t Be a Module</h3><p>As with any technique, modular design can go too far. If you factor a 100,000 line program into 20,000, 5-line modules, you may be creating just as much mess and headache as putting all 100,000 lines in one file.</p><p>Even if a chunk of code meets the five elements of modular design, it may still be overkill to factor it into its own module. There are two things to be wary of if you feel like you are making too many modules:</p><ul><li><strong>mono-dependency:</strong> If a module is only used once in another module, a parent, it may not make sense to make it its own module. It’s ok, even good, to have modules which have only one dependency. These are often called sub-modules. As long as they solve a distinct sub-problem for their parent, as well as adhere to the other essential elements of good, modular design, they can be essential to managing the complexity of that parent.<br>On the other hand, if the code is used two or more times, especially in different modules, it should almost certainly be a module in order to keep things DRY.</li><li><strong>mostly-ceremony:</strong> If you have a module that you think may be unnecessary, ask how much code it would save to merge it into its parent. If it is a mono-dependent module, you will almost always save a little code, but that alone isn’t a good reason to de-modularize. However, if you save something closer to 90% of the module’s code size just by folding it into the parent, that’s probably a <a href="https://martinfowler.com/bliki/CodeSmell.html">code-smell</a> you don’t want to ignore.</li></ul><blockquote>A module with two or more dependencies should almost certainly be a module in order to keep things DRY.</blockquote><h3>Subjective Reality of Modularization</h3><p>The goals of modularization is always to decrease complexity and increase clarity. These are ultimately subjective judgements. Everything I’ve discussed in this article is secondary to your team’s well considered judgement for what works on your particular project.</p><blockquote>Learn the rules well so you know how to break them properly — Dalai Lama</blockquote><h3>Benefits of Modularization</h3><p>The primary benefit of modularization is mastering the art of managing software complexity. When done well, good modular design leads to some very powerful, practical benefits:</p><ol><li><strong>understandability:</strong> A well-modularized system is much easier to reason about, think about and communicate to others.</li><li><strong>improvability:</strong> Strongly encapsulated modules maximizes your ability to fix or improve individual module implementations without needing to update any other, dependent modules.</li><li><strong>refactorability:</strong> The less inter-dependencies in a project, the easier it is to make large changes across multiple modules.</li><li><strong>reusability:</strong> Modules with the best-conceived purposes are fully reusable. Whenever you have the same problem again, you can simply reuse the old solution.</li><li><strong>testability:</strong> Modules with good APIs and minimal inter-dependencies are easier to test. Well-designed APIs can be easily unit-tested. Modules with minimal inter-dependencies can be tested without the need for mocks or more difficult integration-testing. Modules allow you to write tests once, ensure correctness, then reuse the module without the need for further testing.</li><li><strong>scalability:</strong> All of which adds up to the most important benefit of modules: They let our applications scale. It’s impossible to build large applications without good modularization. Without modules, complexity will destroy your productivity.</li></ol><blockquote>Without modules, complexity will destroy your productivity.</blockquote><h3>Higher, Further, Faster</h3><p>Modules allow us to climb higher, go further and build faster. Modules are the building blocks of the virtual landscape. We’d be nowhere without modules. We can only build the amazing software possible today because of the amazing foundation of existing modules we can draw upon. A programming language is a module. Operating systems are collections of modules. The most successful languages have vast module libraries: <a href="http://www.modulecounts.com/">module counts by language</a>. Some modules have endured decades of use and continue to be key to our collective success. That is the power of good modular design.</p><blockquote>Modules allow us to climb higher, go further and build faster.</blockquote><p>So get out there! Take your modular design skills to the next level and build something awesome!</p><blockquote>Higher, further, faster, baby! — Captain Marvel</blockquote><h3>Further Reading</h3><p>In part 2 I apply the 5 elements of modular design to a real world problem — how to write scalable client-side applications with complex state:</p><ul><li><a href="https://medium.com/@shanebdavis/modular-redux-a-design-pattern-for-mastering-scalable-shared-state-82d4abc0d7b3">Part 2: Modular Redux — A Design Pattern for Mastering Scalable, Shared State</a></li></ul><blockquote>2021 Update: I could never remember the names I came up with for the five elements. That isn’t a good sign! So, I adjusted their names and now the five are much easier to remember with the acronym: PIE-C-I. Do you agree? — SBD</blockquote><p><em>Originally published at </em><a href="https://www.genui.com/insights/5-essential-elements-of-modular-software-design"><em>https://www.genui.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b333918e543" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Debuggers are Broken — Become a console.log-Samurai]]></title>
            <link>https://shanebdavis.medium.com/debuggers-are-broken-become-a-console-log-samurai-f352accd9ef6?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/f352accd9ef6</guid>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[logging]]></category>
            <category><![CDATA[chrome]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Mon, 30 Dec 2019 16:52:00 GMT</pubDate>
            <atom:updated>2021-06-03T02:52:32.990Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*drWviZT8xMH7BeqLL3foWQ.png" /></figure><h3>Debuggers are Broken — Become a console.log-Samurai</h3><blockquote>This is a re-post of <a href="https://medium.com/@shanebdavis/debuggers-are-broken-become-a-console-log-samurai-8c7df6901943">my reply</a> to <a href="https://medium.com/better-programming/please-stop-using-console-log-its-broken-b5d7d396cf15">Please Stop Using console.log — It’s Broken</a> by <a href="https://medium.com/@luc.claustres?source=post_page-----b5d7d396cf15----------------------">Luc Claustres</a>.You can read his <a href="https://medium.com/@luc.claustres/dear-shane-its-a-pleasure-to-read-such-a-deep-and-constructive-feedback-imho-it-would-deserve-a-242186f446b6">thoughtful response here</a>.</blockquote><p>Debuggers are useful, but logging has capabilities that debuggers can’t compete with: universality, temporality and turing-completeness. My title is entirely a tongue-in-cheek response to the source article maligning console.log. <em>Both</em><strong><em> </em></strong>debuggers and logging are useful tools for solving problems. Personally though, over 30 years of coding, I’ve found logging to be dramatically more effective for finding tricky bugs. Here’s why:</p><h4>Debuggers ARE Always Breaking; Logging is Universal</h4><p>Across languages, debuggers are notoriously unreliable, particularly for compiled code. When they work, they can be very useful, but often they refuse to connect to your application, refuse to show you the value of an object, or dump you into an assembly-code listing. In interpreted languages this tends to be less of a problem, but if you need to do remote debugging, say of a web-page on a physical device, debuggers still fail to connect all the time.</p><p>On the other hand, logging is available in every language and every environment, and (usually) just works.</p><h4>Too Much Information</h4><p>Debuggers attempt to show you everything about the current state of the running application. It can be overwhelming and distracting. While it’s true when you log from your inner-loops you’ll get overwhelmed with output, log statements have another advantage. They are written in a turing-complete programming language. That means you can filter your logging to show exactly, and only what you need to see.</p><ul><li>Try wrapping your log statements in conditionals to only log information close to your error.</li><li>One dumb, but effective technique is to count how many times the log statement is called and only output up to a certain count, or starting with a certain count… etc.</li><li>Another technique is to put a unique string in your data — a tracer — and only log when that particular piece of data is being processed.</li></ul><h4>Too Little Information</h4><p>Debuggers <em>attempt</em> to show you everything about the current state of the running application. In reality debuggers show less than 0.01% of your application’s current state. At most, they only show 1–2 kilobytes of information. Debuggers typically show the current local variables, the current call-stack, and let you inspect only one object at a time. Your application probably has megabytes if not gigabytes of state.</p><blockquote>Debuggers show less than 0.01% of your application’s current state.</blockquote><p>Debuggers are relatively fixed in their output. Logging, however, is under your full, unbounded control. Logging can perform arbitrary computation, summarizing and distilling avery aspect of the running state into exactly what you need to know to inspect your code.</p><ul><li>Leverage your language to extract and log just what you need to know.</li><li>One trick I love in JavaScript-ES6 is, with the help of object-structuring, you can quickly log a labeled-list of the locals: <br><strong>console.log({data, i, j, sesson})</strong></li></ul><h4>No Temporal Information</h4><p>This is where debuggers really fall short. They only show you a snapshot of your running code. Runtime state is dynamic — it changes over time. Debugging with a debugger is like solving a whodunnit movie from a single frame.</p><p>“Wait!” you say, “You can step through your code with debuggers!” Of course you can. You can also watch a movie one frame at a time. It should only take about a week to find out if Professor Plum killed Mrs Scarlett in the ballroom with the candlestick.</p><p>Logging gives you temporal context. A well crafted log statement can summarize the plot for you. You can see the key transformations of your running state over the lifetime of your application.</p><blockquote>Debugging with a debugger is like solving a whodunnit movie from a single frame.</blockquote><ul><li>If you are trying to scan a lot of log-output for patterns, try column-aligning your output. The human eye is very good at finding anomalies in otherwise regular data:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/934/1*rujKIvfj_IjXpiiqLEBu3A.png" /><figcaption>Use column-alignment to make the log scannable</figcaption></figure><ul><li>Change the color of your output to highlight important data or events.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/998/1*CMNNx6nL8sKGrCLlsXvtgg.png" /><figcaption>Log with color to enhance readability</figcaption></figure><h4>Turing-Complete vs Fixed-Function</h4><p>The key difference between logging vs using a debugger is the difference between a full, turing-complete language and a fixed-function calculator. Logging has no theoretical limitations. Debuggers are limited to only the functions their designers put into them.</p><p>In 30 years, I’ve found time and again that I solve my bugs faster with well-crafted logging. In part it’s because I’ve invested time to get good at logging. I’ve spent comparatively less time mastering debuggers, though I am fairly proficient at them. My point is you can get <em>very</em> good at logging. It is an exceptionally powerful tool for debugging.</p><p>Debuggers have fundamental limitations that logging does not:</p><ul><li>Logging is simple</li><li>It works everywhere</li><li>and most of all, <em>you can use the full power of your programming language to inspect your state</em></li></ul><h4>Enhanced Logging</h4><p>The techniques above work universally in every programming environment I’ve ever seen. If you can code it, you can log it. However, there are tools that can make logging even better. Several of them come built into your browser:</p><ul><li><strong>collapsable logged-objects</strong> — web browsers let you collapse and expand logged objects to let you explore your log — getting the best of both worlds, debugger and logger</li><li><strong>inspection framework </strong>— languages like Ruby come with a concept of inspecting objects — objects have built-in methods to output their state cleanly and succinctly for loggers. I embrace this heavily whenever I can. If the language doesn’t have a predefined system of inspection, I’ll add my own inspect methods to every object I define. <br>Pre-defining user-friendly views of objects throughout your code amplifies the effectiveness of logging. It means each time you add a new log statement, you get awesome output for free, even if you don’t know the types of the objects you are logging.</li><li><strong>dynamic filtering and search</strong> — web browsers also let you filter and search your log. I’ll often turn on all my logging, but also turn on a filter for just what I’m looking for. Once I find it, I might turn off filtering to see the related logs near-by.</li></ul><h4>Downsides of Logging</h4><p>I have my own list of logging downsides — at least in contemporary languages:</p><ul><li>They clutter your code. There’s no easy way to add them, turn them on and off, without leaving litter all over. As a result I typically add them to debug, but then I remove them later to clean up. However, I sometimes have a similar bug later and I end up re-adding the same log statements. Some way to hide, remove and/or restore log statements at the source-level would be welcome.</li><li>They can compound bugs. Your code is different when you add logs. Sometimes that affects the runtime sufficiently to make your bug disappear, or for new, spurious bugs to appear. Debuggers get a big win here. That said, as you improve your log-fu, you can master this shortcoming.</li><li>They impact performance (and so do debuggers). Sometimes adding a logger can slow down the app so much it isn’t practical to run your tests. However, since you have turing-completeness at your side, you can solve this problem through careful logging.</li></ul><h4>No One-Size-Fits-All Solution</h4><p>Ultimately, there is no one-size-fits-all solution. Debuggers have their time and place, and so does logging. It’s all about using the right tool for the job. Logging, however, is an extremely powerful tool:</p><ul><li>logging is always available and rarely breaks</li><li>logs give you an evolution-over-time view of your application, not a single-frame, point-in-time snapshot.</li><li>but most of all, turing-completeness means total control over filtering, presentation, and summarization of your application’s state</li></ul><blockquote>Written at <a href="https://www.GenUI.com">GenUI.com</a> — software engineers, designers and strategists for bringing visions to life</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f352accd9ef6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Eliminated Redux Boilerplate with Hooks-for-Redux]]></title>
            <link>https://shanebdavis.medium.com/how-i-eliminated-redux-boilerplate-with-hooks-for-redux-bd308d5abbdd?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/bd308d5abbdd</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[hooks]]></category>
            <category><![CDATA[redux]]></category>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Wed, 04 Dec 2019 17:14:45 GMT</pubDate>
            <atom:updated>2020-02-08T06:33:22.644Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1mbW-fkf8CoslyLeBnakEQ.jpeg" /></figure><p>Redux is great. It lets you persist, restore, log, and investigate your application’s state in a reliable, predictable way. Further, there are lots of great libraries out there like <a href="https://react-redux.js.org/">react-redux</a>, <a href="https://github.com/reduxjs/redux-devtools">redux-devtools</a> and <a href="https://www.npmjs.com/package/redux-logger">redux-logger</a> which make working with Redux a pleasure… almost.</p><p>There’s just one thing. Redux takes an excessive amount of boilerplate to code. The standard way to use Redux is a highly manual, redundant process of dispatching, routing and reducing your state. But it is just that: standard — and that means automatable.</p><p>After working on several projects using Redux, and noticing these patterns, I started designing a tool to automate it. I’m not the first person to observe this: <a href="https://blog.jakoblind.no/reduce-redux-boilerplate">reduce-react-boilerplate</a>, <a href="https://medium.com/@jeromefranco/reducing-boilerplate-in-redux-with-redux-starter-kit-c5bd9115e75b">reducing-boilerplate-in-redux</a> and <a href="https://dev.to/comscience/minimize-redux-boilerplate-with-these-4-lines-of-code-5ak0">minimize-redux-boilerplate-with-4-lines-of-code</a>, nor the first to try to address this: <a href="https://www.npmjs.com/package/reduxless">reduxless</a>, <a href="https://www.npmjs.com/package/redux-actions">redux-actions</a>, <a href="https://www.npmjs.com/package/redux-arc">redux-arc</a> and <a href="https://github.com/generalui/hooks-for-redux#prior-work">many more</a>. However, these previous attempts each fail in one of two ways:</p><ol><li>They offer a non-standard solution that doesn’t integrate well with the Redux ecosystem</li><li>Or they fall short of removing all the redundancy of the standard Redux pattern.</li></ol><p>So I created <a href="https://www.npmjs.com/package/hooks-for-redux">hooks-for-redux</a>. I liked the way hooks cleaned up React, so I decided to use a similar API to clean up Redux. I’ll explain how H4R works with an example:</p><h3>How I Used H4R to Eliminate 52% of the Code in the React-Hooks-Todo App</h3><p>I recently read Sunil Sandhu’s great post detailing how well React-Hooks cleans up how you use Redux in components. I‘m going to use his result as my starting point:</p><ul><li><a href="https://medium.com/javascript-in-plain-english/how-i-reduced-the-amount-of-code-in-my-redux-app-by-using-redux-hooks-b19c926419ea">how-i-reduced-my-redux-code-using-redux-hooks</a> (<a href="https://github.com/sunil-sandhu/redux-hooks-todo-2019">github</a>)</li><li><a href="https://github.com/shanebdavis/redux-hooks-todo-2019/blob/master/comparison/todo-plain-redux.js">all 177 lines of JavaScript from Sunil’s code in one file</a></li></ul><p>For those who don’t want to wait, you can see the final results here:</p><ul><li><a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019">github.com/shanebdavis/hooks-for-redux-todo-2019</a></li><li><a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019/blob/master/comparison/todo-hooks-for-redux.js">all 85 lines of the H4R JavaScript in one file</a></li></ul><h3>Essential Logic of the To-Do App</h3><p>Before we dive into the code it is useful to understand the essential logic of the app. An app can be no simpler than its essential logic. That gives us a target to strive for as we attempt to clean up and reduce the code size of the To-Do app.</p><p>Starting with the user-interface, the React components pseudo-code might look like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/060829c268aae3b351e8fe2c3285c97c/href">https://medium.com/media/060829c268aae3b351e8fe2c3285c97c/href</a></iframe><p>As you can see, there are just two components. ToDo consists primarily of a list of ToDoItems, one for each item from <strong>useList</strong>. The ToDo component also has a text-box and a button for adding new ToDo items via <strong>addItem</strong>. ToDoItems show each item’s text and a <strong>deleteItem</strong> button.</p><blockquote>This psuedo-code is almost functional except <strong>addItem</strong> and <strong>deleteItem</strong> aren’t getting passed the correct parameters.</blockquote><p>The rest of the application is managing the todo list itself. Here’s the pseudo-code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/45691d88c77d0f2332636911c6d7cdee/href">https://medium.com/media/45691d88c77d0f2332636911c6d7cdee/href</a></iframe><p>At the top of the pseudo-code you can see the structure of the list and its initial state. Lines 7 and 8 describe the logic behind our two list operations: <strong>addItem</strong> and <strong>deleteItem</strong>. These are expressed as reducers — they take the current list’s state, plus an item, and return a new list with the change applied.</p><p>Last, there are three stubs for the three functions used in the components above:</p><ul><li><strong>useList</strong> returns the current value of the list and re-renders the component it is used in whenever the list changes</li><li><strong>deleteItem</strong> deletes the item from the list</li><li><strong>addItem</strong> adds the item to the list</li></ul><p>The entire, near-working essential logic of the ToDo app is just 30 lines of code. Real apps have more code to manage styling and robustness, but the goal is to get as close to this minimal form as possible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZkzQ3jiiD8GCQBzhIoXbQQ.jpeg" /></figure><h3>Let’s Get Started</h3><p>I’m going to take it one file at a time starting with the root index.js. I’ll show the before and after and explain what changed.</p><blockquote>I’m using lines to measure code size. Though it’s not my preferred method, it is easiest. More on <a href="https://github.com/caffeine-suite/caffeine-script/wiki/Counting-Tokens">my thoughts on counting code in tokens here</a>.</blockquote><h4>src/index.js (22% less lines)</h4><p>This is the root file for the application.</p><p>index.js before (14 lines):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9eb1e1d3a068fef5c07caf0743f3be68/href">https://medium.com/media/9eb1e1d3a068fef5c07caf0743f3be68/href</a></iframe><p>index.js after (11 lines):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/93300e89a27cc17fc8562b52f8970914/href">https://medium.com/media/93300e89a27cc17fc8562b52f8970914/href</a></iframe><p>You’ll notice two changes to src/index.js. First, there is no call to <strong>configureStore</strong>, and second, there is a new <strong>Provider</strong> which doesn’t require a store parameter. Hooks-for-redux is designed to add reducers to an existing store rather than require all reducers be defined before the store is created. This allows us to use a default store, which is sufficient for many applications. That way you don’t have to explicitly create it or bind to it throughout your application. Of course, you can override the default store as needed.</p><h4>src/App.js (67% less lines)</h4><p>App is the root React Component for the application.</p><p>App.js before (15 lines):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/60c0f5e8a7b425a4ce39b3720169287d/href">https://medium.com/media/60c0f5e8a7b425a4ce39b3720169287d/href</a></iframe><p>App.js after (5 lines):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b2346e2a804797e16bc3ff1a613d4feb/href">https://medium.com/media/b2346e2a804797e16bc3ff1a613d4feb/href</a></iframe><p>Simplifying App.js is pretty straight forward. App.js shouldn’t be dependent on the List state at all. Among other problems, that unnecessary dependency causes App to re-render whenever the list changes, which is wasteful. Only ToDo needs to re-render in that case. In general, always minimize dependencies between modules.</p><h4>src/components/ToDo.js (42% less lines)</h4><p>ToDo is the main component for presenting the todo-list. This is the biggest JavaScript file, and there are many changes. Most are small improvements in how React is used. Some of these are subjective, but my goal is always to reduce code without sacrificing clarity.</p><p>ToDo.js before: (72 lines)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43f252018a1fdffa53e47f26e36e6ba3/href">https://medium.com/media/43f252018a1fdffa53e47f26e36e6ba3/href</a></iframe><p>ToDo.js after: (42 lines)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5455183d3a5d5fdaf7fdb88c0971e1c3/href">https://medium.com/media/5455183d3a5d5fdaf7fdb88c0971e1c3/href</a></iframe><p>React code cleanup:</p><ul><li>I’m using export-const, not export-default — I generally avoid export-default. I thought it would save code, which undoubtedly is the point, but I’ve found it actually tends to increase code-length and fragility. One problem with export-default is when you import a default you can assign any name you want, and that opens up the possibility of inconsistent naming in a project. However, my biggest problem with export-default is it doesn’t work with “import *” or “export *” — which are very powerful constructs for reducing code redundancy.</li><li>I inlined a few of the shorter functions. I find that while adding new, named things can help with clarity, excessive naming can also hurt readability. In general, I try to not name things that will only ever be used once. That way you can read them in-place where they are used and know exactly what’s going on. If you do name something, it’s important for it to add clarity. Names like ‘handleInput’ tell you nothing about what the function does while significantly decreasing readability. A better name would have been ‘captureTextUpdate’.</li></ul><p>There are more substantial changes as well:</p><ul><li>I imported <strong>addItem</strong> and <strong>useList</strong>. As noted in the App.js changes, they shouldn’t be passed in as props, so now they are imported directly.</li><li>I removed <strong>generateId</strong>. It is not the ToDo component’s responsibility to create and guarantee unique ids. It’s the responsibility of the list model.</li><li>I removed <strong>deleteItem</strong>. ToDo.js never uses deleteItem, so it shouldn’t be dependent on it.</li></ul><h4>src/components/ToDoItem.js (35% less lines)</h4><p>These changes are pretty straight forward:</p><p>ToDoItem.js before: (17 lines)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7812a511b24511f7f2e40f931d5a135b/href">https://medium.com/media/7812a511b24511f7f2e40f931d5a135b/href</a></iframe><p>ToDoItem.js after: (11 lines)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d8aabfa145bbd82c18c349a33240f13/href">https://medium.com/media/1d8aabfa145bbd82c18c349a33240f13/href</a></iframe><ul><li><strong>deleteItem </strong>is imported directly rather than coming from props</li><li>export-const (same as ToDo.js)</li><li>props-destructuring directly in the argument list allows us to make the entire render function an expression</li></ul><h4>redux/list.js (74% less lines, 80% less files!)</h4><p>Now for the real magic. H4R’s <strong>useRedux</strong> eliminates all the boilerplate needed to manage your Redux store, and it does this without sacrificing any power or compatibility. It allows you to put all your Redux logic in one file.</p><blockquote>Note, on larger projects I recommend creating one file per data-model. In this simple app, though, there is only one data-model: list.</blockquote><p>5 redux-related files before: (61 lines)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ed81de1f7e01a9f3fe4a764054a657e/href">https://medium.com/media/1ed81de1f7e01a9f3fe4a764054a657e/href</a></iframe><p>Just list.js after: (16 lines)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f868d43fd0368e199ad6116bfed4604e/href">https://medium.com/media/f868d43fd0368e199ad6116bfed4604e/href</a></iframe><p><strong>getUniqueId</strong> is basically the same as <strong>generateId</strong> originally in ToDo.js. I moved it out of the React-view and into the Redux-model where it belongs.</p><p>The rest of the file is the call to <strong>useRedux</strong>. Each call to useRedux defines a slice of named Redux state. It takes three main arguments:</p><ol><li><strong>storeKey</strong>, a string, is the property name where your state will be stored in the Redux store.</li><li><strong>initialState </strong>can be anything</li><li><strong>reducers</strong> is an object of named reducers <br>(the names are the action names used during dispatch)</li></ol><p>And it returns, ready to use, an array containing the following:</p><ol><li>A React hook for getting the current state and re-rendering the component whenever the state changes.</li><li>An object of named <strong>dispatchers</strong> matching the provided <strong>reducers</strong>.</li></ol><blockquote>Note: There are some additional inputs and outputs not shone in this example. Refer to the <a href="https://www.npmjs.com/package/hooks-for-redux#useredux">README</a> for more.</blockquote><p>This is how hooks-for-redux works. It takes the essential information needed to define your state — name, initial state and reducers, and it returns the essential methods for interacting with that state. Internally it’s doing exactly what you’d be doing manually yourself — in a clean, efficient and most importantly, well-tested way.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3y5__fXfNE5eILtTGK0tVA.jpeg" /></figure><h3>Finished! How Did We Do?</h3><p>The original source was <a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019/blob/master/comparison/todo-plain-redux.js">177 lines of JavaScript</a>. With hooks-for-redux, it <a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019/blob/master/comparison/todo-hooks-for-redux.js">streamlined down to just 85</a>. That’s 48% the size of the original line-count. We also reduced the number of files from 9 down to 5.</p><p>How did we do compared to the <a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019/blob/master/comparison/todo-pseudocode.js">30-line essential solution</a>? If you merge the H4R version into one-file you can remove most of the import and export lines. If you further remove all the styling, <a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019/blob/master/comparison/todo-essential-h4r-solution.js">it slims down to just 45 lines</a>. Compared to the essential, not-quite-working solution, the extra 15 lines account for handling text input, initializing the React app and a few other details required to make it actually work. I don’t think you can make it much simpler:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/03b891a3b1adba0b840469adf68c1f86/href">https://medium.com/media/03b891a3b1adba0b840469adf68c1f86/href</a></iframe><h3>Hooks-for-Redux and Redux</h3><p>Redux is a great extensible, client-side state management platform. I want to give plenty of credit to Dan Abramov and Andrew Clark. Redux’s extensibility is what makes H4R possible.</p><p>However, Redux falls short from the application-author point of view. The standard pattern for using Redux is highly repetitive. Not only does it take more time to write the same code over and over, but it also creates more surface area for bugs to occur. The standard pattern litters dependencies all over your code and encourages mixing your Redux logic with your React components.</p><p>Almost all good software engineering comes down to one thing: Don’t Repeat Yourself. This is what hooks-for-redux does for you. It eliminates the repetition without sacrificing any power. You still define your state. You still define your reducers, but the rest is taken care of for you.</p><blockquote>Some more of <a href="https://www.essenceandartifact.com/2016/06/write-less-code.html#dry">my thoughts about DRY and writing less code can be found here</a>.</blockquote><p>Best of all, H4R is small. The core function, <strong>useRedux</strong>, is <a href="https://github.com/generalui/hooks-for-redux/blob/master/src/useRedux.js">about 10 lines of code</a>, and the entire library is just 90. That means, even with a tiny project like this, not only is your source code smaller and more robust, but your total code-size is also smaller. The savings only grow as your project grows.</p><h3>What to Read Next</h3><p>I have two follow-up articles to this original H4R announcement. I dive deeper into the good software engineering, modular design, and how to write better Redux with or without H4R.</p><ul><li><a href="https://medium.com/@shanebdavis/the-5-essential-elements-of-modular-software-design-6b333918e543">The 5 Essential Elements of Modular Software Design</a></li><li><a href="https://medium.com/@shanebdavis/modular-redux-a-design-pattern-for-mastering-scalable-shared-state-82d4abc0d7b3">Modular Redux — a Design Pattern for Mastering Scalable, Shared State</a></li></ul><h3>Resources and Acknowledgments</h3><ul><li><a href="https://www.npmjs.com/package/hooks-for-redux">Get started with hooks-for-redux on npmjs.org</a></li><li><a href="https://github.com/generalui/hooks-for-redux#how-it-works">Learn more about how hooks-for-redux works</a></li><li>input source: <br><a href="https://github.com/sunil-sandhu/redux-hooks-todo-2019">github.com/sunil-sandhu/redux-hooks-todo-2019</a><br><a href="https://github.com/shanebdavis/redux-hooks-todo-2019/blob/master/comparison/todo-plain-redux.js">all 175 lines of JavaScript from Sunil’s code in one file</a></li><li>output source: <br><a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019">github.com/shanebdavis/hooks-for-redux-todo-2019</a><br><a href="https://github.com/shanebdavis/hooks-for-redux-todo-2019/blob/master/comparison/todo-hooks-for-redux.js">all 85 lines of the H4R JavaScript in one file</a></li><li>Thank you <a href="https://medium.com/@sunilsandhu">Sunil Sandhu</a> for encouraging me to write this Medium post.</li><li><a href="https://www.genui.co/">Hooks-for-redux was developed at GenUI.co</a>. I love that GenUI values contributing back to open source, and I’m grateful for the opportunity to create H4R. Thank you to my colleagues at GenUI who helped test and refine H4R.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd308d5abbdd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Medium Image Options Demo]]></title>
            <link>https://shanebdavis.medium.com/medium-image-options-demo-74f6d4e519c4?source=rss-3e2ccd7daec------2</link>
            <guid isPermaLink="false">https://medium.com/p/74f6d4e519c4</guid>
            <dc:creator><![CDATA[Shane Brinkman-Davis Delamore]]></dc:creator>
            <pubDate>Thu, 30 Aug 2018 16:10:17 GMT</pubDate>
            <atom:updated>2018-08-30T16:10:17.834Z</atom:updated>
            <content:encoded><![CDATA[<p>I’m curious what medium’s different image-presentation options look like, aren’t you? Let’s find out!</p><h4>First, the Options</h4><p>There are four image-placement options in Medium. Your image must be at least 1000 px wide for all four options to show up.</p><ul><li>Full column-width images ( recommended &gt;1400 px )</li><li>Out-set images ( recommended &gt; 2040 px )</li><li>Screen-width images ( recommended &gt; 2500 px )</li><li>Out-set, float-left images ( medium doesn’t seem to have an official name for these)</li></ul><blockquote>Source: <a href="https://help.medium.com/hc/en-us/articles/215679797-Images">https://help.medium.com/hc/en-us/articles/215679797-Images</a></blockquote><p>The options look like this (on Desktop browsers):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KXGbP_vEsDTYmCZrACJmZA.png" /></figure><h4>Full Column Width</h4><p>This is the default option. As the name implies, the image is scaled to just fill the width of the column.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*haZTWlJhzNjJb2BHTJNPVQ.jpeg" /></figure><h4>Screen-Width Image</h4><p>These are dramatic, full-scree images inline with your text. Note the extra vertical padding given to them to add to their weight.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*haZTWlJhzNjJb2BHTJNPVQ.jpeg" /></figure><h4>Out-set Image</h4><p>This option is somewhere between column-width and screen-width. It is always at least a little wider than column-width with a max-width of screen-width. Usually it’s about an inch wider than column-width. Try adjusting the width of your browser to see. Also, note that it left-aligns with Out-set, Float-Left images.</p><p>Out-set images have slightly more vertical padding than column-width images.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*haZTWlJhzNjJb2BHTJNPVQ.jpeg" /></figure><h4>Out-set, Float-Left Image</h4><p>I couldn’t find the official Medium-name for this style, so I made one up. This is the only option that lets you place text alongside your image. It is also the smallest presentation option.</p><p>These images have the same vertical padding as the other out-set image option.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*haZTWlJhzNjJb2BHTJNPVQ.jpeg" /></figure><blockquote>Atticus said to Jem one day, “I’d rather you shot at tin cans in the backyard, but I know you’ll go after birds. Shoot all the blue jays you want, if you can hit ’em, but remember it’s a sin to kill a mockingbird.” That was the only time I ever heard Atticus say it was a sin to do something, and I asked Miss Maudie about it. “Your father’s right,” she said. “Mockingbirds don’t do one thing except make music for us to enjoy. They don’t eat up people’s gardens, don’t nest in corn cribs, they don’t do one thing but sing their hearts out for us. That’s why it’s a sin to kill a mockingbird.</blockquote><blockquote>— Harper Lee, To Kill a Mockingbird</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=74f6d4e519c4" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>