<?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 Scott Gangemi on Medium]]></title>
        <description><![CDATA[Stories by Scott Gangemi on Medium]]></description>
        <link>https://medium.com/@scottgangemi?source=rss-c893f51f53ac------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*FPbnZ6IzypPIXINJ.</url>
            <title>Stories by Scott Gangemi on Medium</title>
            <link>https://medium.com/@scottgangemi?source=rss-c893f51f53ac------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 03:46:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@scottgangemi/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[Make pages load lightning fast by shipping less JavaScript]]></title>
            <link>https://medium.com/mitti-engineering/make-pages-load-lightning-fast-by-shipping-less-javascript-c3431a0f3922?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3431a0f3922</guid>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Tue, 26 Sep 2023 00:02:32 GMT</pubDate>
            <atom:updated>2023-09-26T00:02:32.390Z</atom:updated>
            <content:encoded><![CDATA[<p>Modern frontend applications are great because they abstract away complexities, and let us focus on making functionality for our customers. Reusable components? Cool! Lots of developers working on a codebase? Sublime! Crappy performance because our customers have to download lots of JavaScript? Not so good 🙁. This is one of the tradeoffs of modern JavaScript development. The compiled code from tools like Webpack is very different from the code we write, and in my experience, this can lead to performance degradations. This is because this code is designed for computers to understand, not humans, and this makes it hard to know what has and hasn’t been included.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vlW6aGDLR7Ppy-jNXh3Ifw.png" /><figcaption>Read on to see just how much faster I got this page to load</figcaption></figure><p>Bloated JavaScript causes a page to load slowly as your customer’s device has to download, parse and execute every byte, so every byte counts. As developers, we often don’t feel this performance pain because we write and test our code on high powered devices, but our customers may use older, slower devices with unreliable network connections. Some performance improvements like compression help by reducing the file download time, but uncompressed, the file is just as large, and the device still has to deal with these large files. Therefore, sending less JavaScript makes our pages load faster.</p><p>In this blog post, I’ll present a performance problem, and how I improved it. One of our pages allows people to <a href="https://help.safetyculture.com/en-US/001617/">create an issue by scanning a QR code</a>. Except, we found that less than half of the people who scanned the QR code completed the flow. Our team thought that due to the poor performance of this page, our customers didn’t want to wait for the page to load. To fix this problem, I identified and made some small changes to improve the performance, which significantly increased the retention rate. When I work through performance improvements I think of it as a series of experiments (like in this <a href="https://medium.com/p/46be8c3be0a8">previous</a> blog post) so that I can measure the impact of each change, and that’s how this blog post is set out as well.</p><h3>Starting Point</h3><p>When I first started looking at this page it had a <a href="https://developer.chrome.com/docs/lighthouse/overview/">Lighthouse score</a> of 48, so there was a lot of room to improve! My primary focus was to reduce the JavaScript we sent, and to improve the way the page loaded. I felt confident we could get this much higher. Along with Lighthouse score, I focused on improving <a href="https://web.dev/fcp/">First Contentful Paint (FCP)</a> and <a href="https://web.dev/tti/">Time To Interactive (TTI)</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/631/1*s0asKUU9Dfp1Tl9DcA6pWw.png" /></figure><h3>Experiment 1 — Proper bundle splitting</h3><p>This page was designed to be used by unauthenticated users. However, when this page was first created, it was not split from the normal authenticated SPA due to a deadline. The side effect of rushing this work out was that we were making our customers download a lot of code that they didn’t need. Whenever someone scanned the QR code, their browser downloaded a lot of code, parsed it, and then discarded it. This impacts performance by slowing down the network request and giving the device more work to do.</p><p><strong>Hypothesis: </strong>If this standalone page is split from the main application, a lot of this unnecessary code won’t be downloaded. This will decrease download, parse and execution time.</p><p><strong>Method: </strong>This set of changes ended up being fairly simple. I moved this page from the main bundle into it’s own package, untangled some internal dependencies and then set it up to be deployed as part of a separate pipeline.</p><p><strong>Result: </strong>This simple change led to a significant improvement by improving the Lighthouse score from 48 to 78. More importantly, these results validated the approach of sending less JavaScript to improve performance. By splitting the page out from the main SPA we removed unnecessary API calls, reduced the size of downloaded code bundles, and simplified the network waterfall. By doing so, this change improved all of the core metrics that we measured.</p><p><strong>Bundle Size: </strong>2642kb =&gt; 2012kb<br><strong>Lighthouse: </strong>48 =&gt; 78<br><strong>FCP:</strong> 2.6s =&gt; 1.1s<br><strong>TTI:</strong> 3.7s =&gt; 2.3s</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/621/1*iJljv8MRMfsYCk7KasLD2w.png" /></figure><h3>Experiment 2 — Improving the waterfall</h3><p>In Experiment 1, I observed that the network waterfall became simpler by reducing the requested JavaScript. I wanted to pull on this thread to see if we could simplify the waterfall even more.</p><p><strong>Hypothesis:</strong> Below is a screenshot of the page’s network waterfall from <a href="https://www.webpagetest.org/">WebPageTest</a>. The network waterfall is a representation of the network events that occur to load a page. It generally consists of downloaded JavaScript bundles, as well as other network calls that are used to fetch data. In the below image, the orange bars represent JavaScript, and the pink bar after them is the execution of that JavaScript.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lOY2deaZ778PZPHUh8y9-A.png" /></figure><p>The flow of this page works like this.</p><ol><li>The initial index.html is downloaded (position 1).</li><li>It downloads the vendors~boot.js bundle (position 2) and the boot.js bundle (position 3) because they’re included in the &lt;head&gt; tag of index.html.</li><li>Once these two bundles are downloaded, parsed, and executed, boot.js executes a request for some more required JavaScript and parallel network requests to fetch this are instantiated (lines 6–9).</li><li>Once these bundles have been processed, one of the bundles initiates an API call to fetch some data to display on the page (position 11). This API call happens <strong>8 whole seconds after the page is first loaded.</strong></li></ol><p>If these separate bundles can be combined, the application will download the same amount of code, but the amount of time spent on network and execution calls will be reduced. Therefore, the API call that fetches data will occur sooner, and the page should load quicker.</p><p><strong>Experiment: </strong>Because this page now has a separate pipeline, we don’t need a generic boot.js file for the SPA and the required code can be sent to the client in a single bundle. To do this, I changed the Webpack configuration for this page to include all of the required code in one file instead of splitting this into multiple files.</p><p><strong>Result:</strong> As expected, the overall bundle size didn’t change much. However, I was hoping that the network waterfall would be simplified. In the below screenshot, a similar amount of JavaScript gets downloaded, but the API call happens a lot earlier. Now, it’s moved up to position 4, and is initiated at around 5s, instead of 8s in the previous waterfall. Additionally, the network waterfall is much simpler; only two JS bundles are downloaded instead of 6.</p><p>Even though the bundle size only shrank by about 50kb, simplifying the network waterfall improved the Lighthouse score from 78 =&gt; 89 which is a big win.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*miSKI9xqnN2JOmdJuVFeWA.png" /></figure><p><strong>Bundle Size: </strong>2012kb =&gt; 1960kb<br><strong>Lighthouse: </strong>78 =&gt; 89<strong><br>FCP: </strong>1.1s =&gt; 1.4s<strong><br>TTI: </strong>2.3s =&gt; 1.4s</p><h3>Experiment 3 — Adding a skeleton loader</h3><p><strong>Hypothesis: </strong>We thought by that adding a hardcoded skeleton to the index.html file, our customers will see something straight away. We weren’t sure what impact this would have on page load, but we hoped that this change would improve <a href="https://developer.mozilla.org/en-US/docs/Learn/Performance/Perceived_performance">perceived performance</a>. One of our reasons for people not using the page was that it was slow to load. This can be addressed by improving the pages performance, but by showing <em>something</em> to the customer we improve the perceived performance and enhance that impact even further.</p><p><strong>Experiment: </strong>This experiment involved replacing the existing generic index.html page with one that was based on the layout of this page. Most SPAs have a generic index.html page that is used as a base for any page that is loaded. This works well for a multi-page SPA, but forces the browser to redraw the page when new elements are rendered. By having a skeleton that looks similar to what the final layout will look like, the browser has less work to do when the actual content is rendered.</p><p><strong>Result: </strong>This change actually increased most of the core metrics by a small amount, however FCP dropped from 1.4s to 0.2s. This is still a useful change, because perceived performance will improve, even if some of the metrics have degraded.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7hFNvtvG4DLDflcRGHlWIg.png" /></figure><p><strong>Bundle Size: </strong>1960kb =&gt; 1960kb<br><strong>Lighthouse Score:</strong> 89 =&gt; 86<br><strong>FCP: </strong>1.4s =&gt; 0.2s<br><strong>TTI: </strong>1.4s =&gt; 1.4s</p><h3>Experiment 4 — Manual tree shaking</h3><p>At this point in time, this application was on Webpack 4 in a Lerna monorepo. Throughout the course of this work, I spent a lot of time staring at JavaScript bundles and trying to understand where certain imports came from. Whilst staring, I came across a number of anomalies that didn’t need to be there like constants from other domains, random images, unnecessary client code etc. Analysing these imports made me realise that Webpack couldn’t always figure out what was needed from a module and what wasn’t. So to be safe, Webpack included everything from that module. This was good for correctness, but bad for performance and indicated that tree shaking wasn’t working correctly in our environment. This particular problem was fixed in Webpack 5, but we were blocked from upgrading due to other reasons.</p><p><strong>Hypothesis: </strong>The workaround in the short term was to manually tree shake by being more specific with our imports to make the bundle smaller. Importing something from the index.js at the top level of the package like this:</p><p>import { someFunction } from &quot;@safetyculture/top-level-package&quot;</p><p>ended up including <strong>everything</strong> from that package in the bundle 😳. This led to some pretty extreme blowouts in package sizes. Updating the import to look like the below should shrink the bundles by giving Webpack better cues on what it can eliminate, and letting it discard everything that it didn’t need.</p><p>import { someFunction } from &quot;@safetyculture/top-level-package/a/much/more/specific/directory&quot;;</p><p><strong>Experiment:</strong> I wouldn’t recommend manual tree shaking, but fortunately this page was fairly small and I could trace the imports quite easily. I went through and updated the imports to be more specific, and got some good results.</p><p><strong>Result: </strong>After this experiment, I understood why getting automatic tree shaking to work was a big deal. By updating a few imports to be more specific, about 300kb was removed from the overall bundle size and the Lighthouse score increased from 86 to 93.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/850/1*zm85ktQ6wCl-IpeYoFYjoQ.png" /></figure><p><strong>Bundle Size:</strong> 1960kb =&gt; 1664kb<br><strong>Lighthouse Score:</strong> 86 =&gt; 93<br><strong>FCP:</strong> 0.2s =&gt; 0.3s<br><strong>TTI:</strong> 1.4s =&gt; 1.2s</p><h3>Conclusion</h3><p>This series of experiments led to a huge improvement on this page. We saw a large increase in user engagement which proved our overall thesis that <strong>bad performance was driving our customers away from this page</strong>. There are a number of ways you can improve the performance of your page, from decreasing the bundle size to simplifying the network waterfall, and your customers (and the rest of the business) will be grateful you took the time to make these changes.</p><p>If you have some performance improvements that you’re interested in making, I’d encourage you to break it up into small experiments like this. It’s easier to measure, plus you can use the gradual momentum and improvements to convince others that this work is important and worthwhile.</p><h4>Overall improvement</h4><p><strong>Bundle Size:</strong> 2642kb =&gt; 1664kb<br><strong>Lighthouse Score:</strong> 48 =&gt; 93<br><strong>FCP:</strong> 2.6s =&gt; 0.3s<br><strong>TTI:</strong> 3.7s =&gt; 1.2s</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3431a0f3922" width="1" height="1" alt=""><hr><p><a href="https://medium.com/mitti-engineering/make-pages-load-lightning-fast-by-shipping-less-javascript-c3431a0f3922">Make pages load lightning fast by shipping less JavaScript</a> was originally published in <a href="https://medium.com/mitti-engineering">Mitti Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Developing a customer service mindset as a software engineer]]></title>
            <link>https://medium.com/mitti-engineering/developing-a-customer-service-mindset-as-a-software-engineer-1f8896c95e4d?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/1f8896c95e4d</guid>
            <category><![CDATA[customer-experience]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Tue, 12 Sep 2023 00:02:13 GMT</pubDate>
            <atom:updated>2023-09-12T00:02:13.327Z</atom:updated>
            <content:encoded><![CDATA[<p>There are many skills that make a good software engineer ranging from technical skills — like programming and performance optimisation — to non-technical skills — like communication and <a href="https://medium.com/safetycultureengineering/an-introduction-to-feature-leading-b57906aa6767">feature leading</a>. In my opinion, one of the most underrated skills is approaching your work with a <strong>customer service mindset</strong>.</p><p>A customer service mindset means putting the needs of your customer first and working to improve their experience. Engineers think about their customers, but they’re often forgotten when we start thinking about technical details. Technical details matter, but your business lives and dies on its customers, and having them front of mind will lead to a better product.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T1PTQD8nc_qPpQ4f5RHWSA.jpeg" /><figcaption>Everybody on your team will be this happy when you’ve developed a customer service mindset</figcaption></figure><h4>Shifting your mindset</h4><p>How you define a customer differs depending on your role, for example, on a platform team, your customers are the engineers who use your products. We’ll be focusing on customer facing team where your customers are the end users of the product. In engineering we usually don’t interact with our customers too much. Depending on where you work, you may never interact with your customers. However, there are others in your business, such as sales and customer support, who do. Speaking to these people gives you valuable insights into how your customers are using the product, and give you opportunities to improve it.</p><p>Communicating with more customer facing people in the business helps to shift your thinking to put your customers first. This helps you solve urgent problems sooner, but is a valuable skill to allow you to increase ownership and impact. Doing this opens your mind up to asking questions and trying to do what is best for your customer. I’ve found that work is much more engaging when you can think deeper about the impact you’re having, rather than just working on the next ticket that comes your way. This helps you care more about your work and become a better engineer by really understanding the problems your customers are facing, and influence how to solve them.</p><h4>Technical tasks through a customer lens</h4><p>This also allows you to look at technical work with a different lens and think about the customer impact your changes can have. Being able to express this well helps convince non-technical folk about why a piece of work matters and can help to prioritise it.</p><p>For example, there’s part of your codebase with poor test coverage. The engineers on your team hate working on this area, but nobody has the time to improve the tests. Tying these tests to their customer impact makes it easier to prioritise, and will allow you to think about the kinds of tests you should write. Compare these two approaches:</p><blockquote>I want to write some tests for this component.</blockquote><p>vs</p><blockquote>I want to write some tests for this component because improving the code quality will give the team greater confidence in the code, make it easier to make changes in the future and will provide an opportunity to fix any bugs that customers may be experiencing at the moment</blockquote><p>The second statement is a bit wordier, but explaining the customer impact of this work makes it much easier to get others on board, and get it done.</p><h4>How to adopt a customer mindset</h4><p>Developing a customer service mindset comes from practice like any other skill. However, it’s different from a skill like programming that develops through work; you need to re-train your brain. There’s a number of ways you can do this:</p><ol><li><strong>Answering questions about your product area from around the business</strong>. We have internal Slack channels for each product team, and there’s often questions about our team’s product. Answering these questions helps you understand different issues, and will make you feel a sense of ownership over your product. Plus, your teammates will appreciate your help.</li><li><strong>Reviewing customer facing documentation.</strong> Depending on the size of your company, there may be a team who write customer support documentation. When your team releases a new feature, read the documentation for it. This helps build empathy for those writing the documentation, helps you understand how the product solution is presented to customers, and gives you an opportunity to improve the documentation as well.</li><li><strong>Writing customer facing documentation.</strong> Conversely, you may have opportunities to write customer facing documentation, such as for API endpoints. These opportunities are valuable because they give you a great opportunity to think about how your customers will use the endpoints, and allows you to think about how to best present this information.</li><li><strong>Site visits and customer calls.</strong> Speaking to customers is a great way to build empathy for them, as well as understand how they use the product. These understandings help engineers see the impact of their work, and can be used to make the product even better.</li><li><strong>Investigate metrics for features you’ve worked on.</strong> Once you’ve built something, work with your product manager to look at how it’s performing. This is great validation for the work you’ve completed and can give you some insights into if it was the right solution.</li><li><strong>Read feedback about your product.</strong> Your product team talk to your customers a lot and they may collate this feedback somewhere. Read through this, and see what your customers care about. Some of these may be easy to implement, if so, see the next point.</li><li><strong>Implement quick wins.</strong> This is the best way to do this, and is often the result from the previous points. If you hear about a problem that seems easy to fix like poor scrolling or bad keyboard controls, fix it. Nothing will make people feel heard more than a quick resolution to a problem they’ve raised. Beloved products are built through many, many small changes and this is a great way to improve your product over time.</li></ol><p>To summarise: show up, care, make things better. A customer service mindset allows you to see the opportunities in your product, and quickly addressing these will elevate your product from good to great.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1f8896c95e4d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/mitti-engineering/developing-a-customer-service-mindset-as-a-software-engineer-1f8896c95e4d">Developing a customer service mindset as a software engineer</a> was originally published in <a href="https://medium.com/mitti-engineering">Mitti Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[An introduction to feature leading]]></title>
            <link>https://medium.com/mitti-engineering/an-introduction-to-feature-leading-b57906aa6767?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/b57906aa6767</guid>
            <category><![CDATA[project-management]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Sun, 17 Jul 2022 23:59:54 GMT</pubDate>
            <atom:updated>2022-07-17T23:59:54.533Z</atom:updated>
            <content:encoded><![CDATA[<p>Software engineering is about more than programming. Programming is important, but so is understanding how your software works, how it gets deployed and the machines it runs on. Equally important is successfully leading a feature from an idea to being in your customers hands. Overseeing the delivery of a feature helps you grow as an engineer, gives you a better understanding of the software development lifecycle and helps you grow your impact beyond the code you write. This is often done by a tech lead, but can also be done by a <a href="https://www.thoughtworks.com/en-au/insights/blog/feature-leading-agile-team">feature lead</a>. A feature lead is an individual who is responsible for managing a feature, and helping the team work towards its completion. Feature leading is a great way to develop new skills, but can be quite challenging to get started with. I have been the feature lead on a number of projects, and have helped others on my team feature lead as well. Feature leading is a great opportunity to learn, but can be very daunting at first. In this article, I’ll give you an overview of how to lead a feature, including strategies some of our teams use at SafetyCulture.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g7dVn4Qj8apPS53SM5Xx-A.jpeg" /><figcaption>This is how you and your team will feel after successfully releasing your feature</figcaption></figure><h3>First steps</h3><h4>Think</h4><p>Feature leading has two goals. The first, and most obvious, being to get the feature over the line. The second being to develop a <em>really</em> deep understanding of the feature. Ask lots of questions, write everything down, go for a walk, and then come back to ask more questions. Try to understand the big picture goals and low level minutia of the feature. Think about questions that your teammates might ask to be better prepared to answer them. Try and understand the feature deeply, and cover as many scenarios as possible.</p><h4>Plan</h4><p>When I first started programming, I’d always jump right into a problem; I’d start hacking something together, only to realise that my initial attempt wasn’t quite right. As I gained more experience, I learned to stop and form a plan before making any large code changes. It’s the same with feature leading. Jumping in without a plan will lead to edge cases, and make completing the work much harder. Time spent in planning is never wasted, and leads to better outcomes.</p><h4>Find the why</h4><p>An important question to figure out for this feature is ‘why are we building this?’. Growing as an engineer involves lifting your focus from only code, to thinking about what you’re building and why. Code is a tool used to solve a problem, and that problem is the one solved by the product. Thinking about the why ensures that the solution solves the problem. Finding the why, and lifting your focus beyond code makes a better engineer, and will make you a more effective feature lead.</p><h4>What do you need?</h4><p>Before you start planning, do you have everything you need? Do you have all the required designs? What do the different user flows look like, including error states? What are the required changes? When is the projected release date? Make sure you have everything you need before you start planning. This ensures you haven’t missed something critical that could put the feature at risk.</p><h4>Who needs to know?</h4><p>This feature may interest different people or departments; find out who they are. Think about the different people usually involved in new product releases. Does the customer support team have enough lead time to write customer documentation? Are marketing running a new campaign? Will any of the code changes have an impact on other parts of the platform? Part of the role of a feature lead keeping everybody in the loop about the feature. When you’ve thought about who needs to know, this becomes much easier.</p><h3>Measuring success and determining scope</h3><p>What’s the goal of this feature, and how will the team know when they’ve achieved it? If you’ve set out to do one thing but do another, the feature may not have achieved its goal. With a goal in mind, it’s the priority of a requirement can be identified. Knowing the goal helps to determine the <em>scope</em> of the feature.</p><p>The requirements of the feature make up its scope. If a requirement is “In scope”, it must be completed. Similarly, “out of scope”, means the requirement won’t be completed. Some requirements will be neither in or out of scope, and are “nice to have”. Scope is important because the team will only have limited <em>time</em> to deliver this feature.</p><p>Time, scope and cost are the three parts of <a href="https://en.wikipedia.org/wiki/Project_management_triangle">the project management triangle</a>. This says that the competing demands of scope, time and cost determine the total quality of a project. For example, cutting scope could lead to an earlier completion date, but may lead to an incomplete feature that doesn’t achieve the set goals.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6FrllwEAtLeJirAu.png" /><figcaption>Project management triangle, borrowed from <a href="https://en.wikipedia.org/wiki/Project_management_triangle">Wikipedia</a></figcaption></figure><p>Therefore, a feature lead’s job is to maximise quality, while managing scope, time and cost. One of the most powerful ways to do this is by changing and negotiating which requirements are in scope. This also means that some requirements will be left out. This is necessary to ensure the product is of high quality, and completed within a reasonable timeframe. Cutting scope can help to focus a feature. If a feature is too large and there isn’t enough time to complete it, this will often lead to a rushed and buggy product.</p><p>For example, these hypothetical requirements are to add some new functionality to a form.</p><p><strong>Requirement:</strong> Update the form with a new redesign.<br><strong>Response:</strong> This will add an extra two weeks, so this is <em>out of scope</em>. Maybe we can do it as a separate feature, because the work is complex.</p><p><strong>Requirement:</strong> Update the colours to fit the new brand theme.<br><strong>Response:</strong> This will only take an extra two days, so this should be possible. Because it’s not completely necessary, this work is nice to have. We’ll try and fit this in if we have time.</p><p><strong>Requirement:</strong> Add the new form field and connect it to the back end. <strong>Response:</strong> This is in scope, because without it the feature doesn’t achieve its goals.</p><p>If a feature is too large and there isn’t enough time to complete it, this can lead to a rushed and buggy product. Therefore, managing what will and will not get completed in a given timeframe is critical. Some requirements can be rejected during planning, and your team can help reject others later on when your estimates are firmer. It sounds scary to push back during planning, but this is expected of a feature lead. The feature lead has the most context on the technical side of things, and is in charge of the feature. They are therefore best positioned to help steer the feature in the right direction. If the feature lead doesn’t push back where appropriate, they’ll be under pressure to get the feature back on track if it starts to fall behind. It’s easy to add extra requirements if there’s spare time, but much harder to remove them if the timeline gets tight and promises have been made. The team will be grateful if unnecessary requirements can be trimmed early. Once this information has been finalised, add it to a product page in <a href="https://www.atlassian.com/software/confluence">Confluence</a> or somewhere similar. This will be the source of truth going forward, and will be often referred to during the course of the feature.</p><h3>Communicate to the team</h3><p>Once the engineering manager and product manager agree on the requirements, bring in the team who will be doing the work.</p><p>At SafetyCulture, we do this in a Product Kickoff meeting. This meeting brings in product, design and engineering to align everybody on the goals and outcome of the feature. During this session, the team will ask questions that might not have been covered yet. This is a great chance to make sure that any questions are answered and all requirements are documented. Make sure to document anything new in your product page.</p><h3>Design the technical implementation</h3><p>Next, you’ll need to plan the system design for the implementation. It’s important that the implementation is sensible, performant and scalable. This is important, because changing the implementation before anybody has begun coding is much easier than changing it after.</p><p>System design is a large topic that’s out of scope (get it?) of this article. There’s many resources out there that focus on it, so check those out to learn more. This article will instead discuss how to present your design.</p><p>At SafetyCulture we write about the design in a <a href="https://www.atlassian.com/software/confluence">Confluence</a> document. This way, your team and other teams can reference this page as required. It’s also useful as a historical document for the engineering team’s reference in the future. When you’re writing a design out, think about these points:</p><ol><li><strong>Outline the problem.</strong> Your technical solution exists to solve a problem. Ensure that whoever is reading this document knows what that problem is. Write this for an audience that’s broader than your team. Your team may know the problem in depth, but others may not.</li><li><strong>Write for a particular audience.</strong> I try to keep my CTO in mind when I’m writing up a design. They understand technology and the business, but may not have a deep understanding of implementation specific details. This helps me think about what’s relevant, what to expand on and what doesn’t need as many details.</li><li><strong>Start broad and narrow down.</strong> Start with the big picture, and then separate out larger portions into their own section. Don’t start from a narrow context, as this makes it harder for you to communicate the complete picture.</li><li><strong>Keep details relevant.</strong> Only include relevant details. Specifying that the feature is using Postgres and then doing a deep dive into Postgres itself isn’t helpful. However, outlining a new database table and its columns and indexes would be very helpful.</li><li><strong>Discuss any alternatives.</strong> During the design process, there may have been some alternatives that weren’t chosen. Mention these, as someone else may think about this solution too. It’s helpful for them to know that these possibilities have been thought about and disqualified. This also gives the team confidence that the proposed solution is the best way forward, and not the first idea you came up with.</li></ol><p>After you’ve done this, get feedback on your design. Start with your team, and then open it up to a wider audience. SafetyCulture runs a design review session where teams present their designs to collect feedback from engineers outside of their immediate team. This session helps to refine a design, and helps shape them into better solutions. If your organisation doesn’t do this yet, organise it for your next design. Others within engineering will benefit from this as well.</p><h3>Figure out and plan tasks</h3><p>After you’ve incorporated any feedback into the design, <strong>break the requirements down into stories</strong>. At the outset, be super granular with the aim of combining some tickets later on. I do this in a rough document before moving the tickets to <a href="https://www.atlassian.com/software/jira">Jira</a>. It’s easier to get feedback on the tasks, as well as consolidating them if needed. Doing this in Jira means a lot more clicks to change or combine a task.</p><p><strong>Think about this in broad tasks</strong> like, “Set up new microservice”, or “Show new information to customers”. These broad tasks can now be broken down into the specific changes that they need. “Display new information to customers” might include updating your web, iOS and Android clients, updating the endpoint to return new data and adding end-to-end tests. These five tasks are easier to communicate, assign and parallelize between the right people, as opposed to the one big task.</p><p><strong>Work vertically instead of horizontally. </strong>It’s easy to think about tasks as “backend” tasks, or “mobile” tasks. A better approach is to split the work up so that smaller pieces of work can be tested and verified early. Let’s say we are creating a new endpoint and modifying an existing one. A horizontal approach would be to work on all of the backend tickets for each endpoint, then mobile tickets to support these changes, and so on. The problem is that it becomes harder to test a set of changes until the feature is close to completion. By working to finish one endpoint before the other, it’s easier to de-risk the feature and build confidence in the testing plan as early as possible. This means less testing at the end because large portions have already been tested</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/511/1*dCBjiX_JveHKnE5aX_SAIw.png" /><figcaption>Working vertically makes it much easier to verify and de-risk the feature compared to working horizontally</figcaption></figure><p><strong>Sequence the work to make things smoother. </strong>When looking at the work involved, the order that functionality is completed can make a huge difference. For example, part of a new feature work involves adding a new data type to an existing data structure. If the vertical slice that allows that data to be added via the UI is done first, it’s much easier to work on the slice that focuses on fetching and displaying that data because the team can add the data in the same way a real customer would. If this was done the other way around, the team will probably be adding data manually to the database. This works, but is slow, error prone, and much harder to find edge cases. By figuring out the correct sequence of work the implementation of the entire feature is much simpler.</p><p>Next,<strong> go through the tasks and make sure that the implementation works if the tasks are executed as outlined</strong>. Try to find any gaps, missed areas or flaws. Do this with a teammate to ensure it makes sense, and make sure that nothing has been missed. Add any missed tasks in, or tweak the design if needed.</p><p><strong>Add some estimates to the tickets to get a rough feel for how much time this work will take</strong>. Bring this to your engineering manager and get their feedback. If the estimates are higher than the allotted time, you might need to work together to cut some scope or revise the target delivery date. With these estimates in hand, I’d recommend visually planing out the tasks. I’m a big fan of Jira’s <a href="https://confluence.atlassian.com/jiraportfolioserver/what-is-a-plan-952623577.html#:~:text=A%20plan%20is%20a%20roadmap,committed%20to%20delivering%2C%20and%20more.">planner functionality</a>. It includes functionality to assign tasks to people, add blockers and visualise the flow of the feature. This way the work can be spread evenly amongst the team so that nobody is stretched too thin. Visualising the tasks is optional, but it’s a helpful way to see how the feature is going, and correct course if it begins to to slip behind schedule.</p><h3>Align your team and start</h3><p>With your plan and tickets, it’s time to bring your team together and get the feature underway. At SafetyCulture we do this as part of an engineering kickoff. The purpose of this session is to make sure the team is ready to start working on the feature. The team should have already read through the implementation, but go through it with them to ensure that everyone is on the same page. When everybody understands the big picture, go through the tasks and get the team’s estimates. Hopefully the estimates are similar, but if your teammates have different estimates, add theirs to the ticket. Their estimates should give good insight into how long this feature will take.</p><p>From here, you and your team can start working on this feature. During the course of the feature you may find some problems or gaps. This is okay! Your job is to facilitate fixing these problems, and finding ways to plug any gaps. Provide solutions to these problems and be open and communicative to your stakeholders.</p><p>The bottom line is to be honest and as communicative as possible. If the feature is slipping behind schedule, talk about it. Your manager and team want to know when things are going off course so that they can help you fix it. Hiding information only make things worse, even if being outspoken feels difficult.</p><h3>Test and deploy</h3><p>Once the work is complete, run a testing session to ensure that everything works as expected. Come up with scenarios that cover happy paths, and various edge cases. This will give you confidence that when your customers are using it, you won’t get any nasty surprises. You’ll only be able to test the whole thing when the work is complete, but you can still test the feature while your team is working on it, especially if you’ve broken the work into testable vertical slices. This will help you find bugs and missed edge cases before your customers do.</p><p>Once you’re confident with your testing, switch on the feature flags and progressively roll it out. Keep an eye on alarms, logs and performance metrics, and don’t be afraid to halt deployment if you find something.</p><h3>Reflect and retro</h3><p>Congratulations! That’s your first successful feature completed. Schedule a feature retro, and find out how your team felt about it. T<strong>ake any feedback on board</strong> and work on their suggestions next time. Find some time to self reflect and give yourself some feedback too. You’ll have learnt a lot, and now is a great time to consolidate these learnings.</p><p><strong>Make sure to document any cut scope</strong>. Your team may be able to work on this as a quick win sometime now that most of the hard work is out of the way. If the cut scope was a bit larger, it might be an additional feature that needs more work and planning.</p><p>Finally, give yourself a huge pat on the back. Leading your first feature is incredibly challenging, but very rewarding. You’ll have levelled up a lot in your product knowledge, technical expertise and understanding of the software lifecycle. Plus, the greatest reward of all, you’ll be a better engineer afterwards.</p><h4>TL;DR</h4><ol><li>Think and determine scope</li><li>Product kickoff</li><li>Technical design/implementation and feedback</li><li>Task breakdown</li><li>Engineering kickoff with the team</li><li>Plan things out visually</li><li>Test, deploy, retro</li><li>Congratulate self, rest</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b57906aa6767" width="1" height="1" alt=""><hr><p><a href="https://medium.com/mitti-engineering/an-introduction-to-feature-leading-b57906aa6767">An introduction to feature leading</a> was originally published in <a href="https://medium.com/mitti-engineering">Mitti Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Analyzing and improving memory usage in Go]]></title>
            <link>https://medium.com/mitti-engineering/analyzing-and-improving-memory-usage-in-go-46be8c3be0a8?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/46be8c3be0a8</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[memory-management]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Sun, 11 Jul 2021 00:27:13 GMT</pubDate>
            <atom:updated>2021-07-11T00:27:13.775Z</atom:updated>
            <content:encoded><![CDATA[<p>The last article I wrote <a href="https://medium.com/safetycultureengineering/an-overview-of-memory-management-in-go-9a72ec7c76a8">focused on how the garbage collector (GC) worked in Go</a>. The next step was to combine this with my love of performance optimisation. The intent of this article is to show you how to optimise the memory used in your Go applications.</p><p>When exploring this space, I found that a lot of articles focused on contrived examples, which made it difficult to apply this to production code. This article uses a real-world application that I work on every day, and explores some improvements I made to it. By the end of this article, I hope you’ll have some new knowledge about how to identify and tune performance bottlenecks.</p><p>The application that we’ll focus on powers the <a href="https://safetyculture.com/issues-and-observations/">Issues product at SafetyCulture</a>. It, and all of our other services, report application stats to Grafana, including GC pause time. Before any improvements were made, the average GC pause time for this service was about 16ms, with the max GC pause time spiking from around 500ms — 1s.</p><p>As <a href="https://groups.google.com/g/golang-dev/c/Ab1sFeoZg_8/m/_DaL0E8fAwAJ">Go can do sub-millisecond GC pauses</a>, this seemed high. However, considering this is a constantly running webserver handling a large volume of HTTP and gRPC requests, I wouldn’t expect numbers that low. In any case, I wanted to see how I could reduce this max GC pause time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v3_lNEPjebCTUKWE7mPuFA.png" /><figcaption>GC pause time before any improvements were made</figcaption></figure><h3>Analysis with pprof</h3><p>After finding an area to investigate, I ran a pprof analysis. <a href="https://golang.org/pkg/net/http/pprof/">pprof is a tool that’s baked into the Go language that allows for analysis and visualisation of profiling data collected from a running application</a>. It’s a very helpful tool that collects data from a running Go application and is a great starting point for performance analysis. I’d recommend running pprof in production so you get a realistic sample of what your customers are doing.</p><p>When you run pprof you’ll get some files that focus on goroutines, CPU, memory usage and some other things according to your configuration. We’re going to focus on the heap file to dig into memory and GC stats. I like to view pprof in the browser because I find it easier to find actionable data points. You can do that with the below command.</p><p>go tool pprof -http=:8080 profile_name-heap.pb.gz</p><p>pprof has a CLI tool as well, but I prefer the browser option because I find it easier to navigate. My personal recommendation is to use the flame graph. I find that it’s the easiest visualiser to make sense of, so I use that view most of the time. The flame graph is a visual version of a function’s stack trace. The function at the top is the called function, and everything underneath it is called during the execution of that function. You can click on individual function calls to zoom in on them which changes the view. This lets you dig deeper into the execution of a specific function, which is really helpful. Note that the flame graph shows the functions that consume the most resources so some functions won’t be there. This makes it easier to figure out where the biggest bottlenecks are.</p><p>When you’re selecting the sample you want to view, these are your options:</p><ul><li><strong>inuse_space</strong>: See memory size in use at time of profiling that hasn&#39;t been released.</li><li><strong>inuse_objects</strong>: See object count in use at time of profiling and that hasn&#39;t been released.</li><li><strong>alloc_space</strong>: See allocated memory size across the lifetime of the application.</li><li><strong>alloc_objects</strong>: See allocated object count across the lifetime of the application.</li></ul><p>To analyze GC, we want to look at the alloc_objects option. This will help us understand how memory objects are allocated over the lifetime of the application. When analyzing the flame graph, there was one function that was responsible for a third of all memory allocation, which we’ll call fetchData. This function seemed like a great place to begin investigating.</p><p>When I’m investigating performance improvements I frame it as an experiment, and pprof gave me enough data for my first experiment.</p><h3>Experiment 1</h3><p><strong>Objective: </strong>Reduce the amount of memory being allocated by fetchData.</p><p><strong>Hypothesis: </strong>After reading through the code that calls fetchData, I don’t think we need every field that is requested. If we remove this from the request, the amount of allocated memory might be reduced.</p><p><strong>Investigation: </strong>At SafetyCulture, <a href="https://safetyculture.com/create-inspection-checklist-template/">inspections</a> are a big part of how our customers use our product. Because of this, bringing inspections into other parts of the product is really valuable. fetchData gets inspections data from an API in JSON format. The response size varies, but the largest responses can be quite big, at around 20MB. Unmarshaling and parsing a 20MB JSON object uses up <em>a lot</em> of memory. This is correlated by the stack trace of fetchData mostly consisting of a large number of <a href="https://golang.org/pkg/encoding/json/">encoding/json</a> functions. As mentioned in the hypothesis, it looks like a requested field is no longer used. It’s still referenced in the code, but in a path that looks like it will never be executed. Can we remove this field, and will it improve memory usage?</p><p>We now have a few questions to answer:</p><ul><li>Is the suspected code actually dead?</li><li>Are we able to remove the suspect field from the request without breaking anything?</li><li>Will removing this field from the request improve memory usage?</li></ul><p><strong>Method: </strong>Since this field looks unused, I wanted to find out <em>why </em>it was unused. I went back to the PR that first added this field and traced the history of the file. I found a merged PR from a few months ago (2 years after the initial commit!) that made this field redundant. I now felt confident that removing this field would be safe. I deleted the field from the request, deleted the code referring to it, did some testing and merged it.</p><p>I monitored the application in production and everything looked ok. It seemed that the part of my hypothesis about the unneeded field was correct. Now, we need to see if the amount of memory allocations is reduced.</p><p><strong>Results:</strong></p><ul><li><strong>Memory utilisation had reduced. </strong>On the left, you can see memory use before the changes, and afterwards on the right. The constant blue line is what’s the Go runtime uses, and the coloured spikes are from our application. As you can see, the memory use on average is lower, and the spikes in memory usage are far smaller as well. Before, spikes in memory usage were about ~1018MB, whereas afterwards, the biggest spikes were about ~649MB. On average, memory use before was between 100MB — 250MB, whereas afterwards, it was between 80MB — 120MB. These changes caused less memory to be used and reduced the variance significantly.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P-Y5oWigtKAd8saR1TLSag.png" /></figure><ul><li><strong>Allocated objects for </strong><strong>fetchData had reduced. </strong>Before, 33% of allocated objects were going towards this function. After this had dropped to around 28%.</li><li><strong>The 99th percentile latency for the endpoint. </strong>The changes were deployed on Wednesday 26 (which isn’t displayed on the graph). The bigger spikes in the 99th percentile have dropped (except for the one weird spike on Thursday 27). The 99th percentile latency is now around 800ms, down from 1.2s</li><li><strong>The max GC pause time has dropped (a bit). </strong>It’s not a huge drop, but it’s still a drop. Since deploying on the 26th, the biggest spikes in max GC pause time have dropped, and the volume of large spikes have reduced as well.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dG7Ur0J-K9Amz32wIROj5A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FVBRpNDfoJ_7gKIZWsRhXA.png" /></figure><p>Overall, I was really happy with these results! Memory usage was reduced, and the customers who experience the 99th percentile response time will now receive a better experience.</p><p>But, can we do more?</p><h3>Experiment 2</h3><p>The fetchData function is still responsible for 28% of all memory allocations. This is because the big JSON object is put into memory and unmarshaled into a Go struct. This takes us to the second experiment.</p><p><strong>Objective: </strong>Reduce the amount of memory allocations from unmarshaling the JSON object.</p><p><strong>Hypothesis: </strong>If the process of extracting data from JSON can be improved, the number of memory allocations might be reduced.</p><p><strong>Investigation:</strong> I wanted to understand why this function uses so much memory. First, a POST request fetches the JSON and then the calling function receives the decoded body via json.NewDecoder(response.body).Decode(). Then, json.Unmarshal converts the JSON into a suitable struct.</p><p>This is fine for handling most JSON responses, but this JSON response can be massive (up to 20MB). This means that this is a bottleneck for this particular code path. Under the hood, json.Decode writes the entire JSON object to memory, where it is immediately unmarshaled. Because this JSON object can be so big, it’s no surprise that this function uses up so much memory.</p><p><strong>Method: </strong>I was reading through the docs for the <a href="https://golang.org/pkg/encoding/json/">encoding/json</a> package to see what other approaches were available. I came across the <a href="https://golang.org/pkg/encoding/json/#Decoder.Token">Token</a> function that decodes the JSON object as a stream. This means the application can decode the JSON on the fly, instead of writing the entire JSON object to memory. The Token() function lets you manually step through the body of the JSON object to extract the data you need. This method has some tradeoffs. If the JSON response schema changes, your parsing code won’t work anymore. It’s also more verbose and complex when compared to json.Unmarshal. However, if this approach is used sparingly, it can lead to some performance improvements. There’s a number of examples available online, <a href="https://stackoverflow.com/a/61231111">such as this post</a>. As you can see, the code becomes much more complex than json.Unmarshal. If you take this approach, I’d recommend annotating the code so that your colleagues can understand it in the future.</p><p><strong>Results:</strong></p><ul><li><strong>Memory utilisation has reduced even further: </strong>As before, left is the memory use before the changes were deployed, and afterwards is on the right. Experiment 1 resulted in memory usage around 80–120MB and this has now dropped to 70MB — 90MB. The big spikes are much less severe as well.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uvuCcGvJ5rpFJlRoOayc8Q.png" /></figure><ul><li><strong>The 99th percentile latency for the calling function has dropped even further. </strong>In the below screenshot, my changes went out just before 12PM in between Tue 22 and Wed 23, corresponding to the dip in the 99th percentile. Experiment 1 brought the 99th percentile down to around 800ms, but after Experiment 2 this has come dropped even further to 500ms.</li><li><strong>The max GC pause time has dropped. </strong>The code changes went live in the below screenshot at around 8:30 AM. After this time, the max GC pause time is lower for all garbage collections, and many of the GC pauses take around 100ms. This brings the max GC pause time from around 300ms-400ms to around 100–300ms. There are still some high spikes here, but the max GC pause time is much lower.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yr6nHnhfO6iuWE7SQtDp6g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j1Nlmnb5a0vLFTu8ked0TA.png" /></figure><h3>Overall Results</h3><ul><li><strong>Average memory usage has dropped </strong>from around 100MB-250MB to 70–90MB, and spikes in memory usage have dropped from around 1018MB to 120MB.</li><li><strong>Max GC pause time has dropped. </strong>When we first started investigating, the max GC pause time was around 400ms. It’s now around 300ms but is often much quicker. We’re now seeing GC pause time events of 100ms, which never happened before these experiments. There were also occasional spikes in GC pause time that would take a second, and these no longer appear.</li><li>Most importantly, <strong>the endpoint using </strong><strong>fetchData has had a big drop in 99th percentile response time</strong>. At the start, the 99th percentile was around 1.2s but now sits around 500ms. As far as I’m concerned, this is the most impactful change. It’s easy to lose sight of why performance improvements are important. If you focus on your customer and their experience of using your product, you’ll always head in the right direction.</li></ul><p>I hope that after reading this you’ll have more confidence exploring your application’s performance. First, figure out which pieces of your application take up the most resources. Then you can start asking questions about why they use up these resources. You can then take measurable steps towards improvements and improving your customers’ experience.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=46be8c3be0a8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/mitti-engineering/analyzing-and-improving-memory-usage-in-go-46be8c3be0a8">Analyzing and improving memory usage in Go</a> was originally published in <a href="https://medium.com/mitti-engineering">Mitti Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[An overview of memory management in Go]]></title>
            <link>https://medium.com/mitti-engineering/an-overview-of-memory-management-in-go-9a72ec7c76a8?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/9a72ec7c76a8</guid>
            <category><![CDATA[go]]></category>
            <category><![CDATA[garbage-collection]]></category>
            <category><![CDATA[memory-management]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Sun, 09 May 2021 21:49:29 GMT</pubDate>
            <atom:updated>2021-05-11T08:48:24.603Z</atom:updated>
            <content:encoded><![CDATA[<p>As programs run they write objects to memory. At some point these objects should be removed when they’re not needed anymore. This process is called <strong>memory management</strong>. This article aims to give an overview of memory management, and then dive deeper into how this is implemented in Go by using a garbage collector. Go has seen many changes to its memory management over the years, and will most likely see more in the future. If you’re reading this and you’re using a version of Go later than 1.16, some of this information may be outdated.</p><h4>Manual Memory Management</h4><p>In a language like C the programmer will call a function such as malloc or calloc to write an object to memory. These functions return a pointer to the location of that object in heap memory. When this object is not needed anymore, the programmer calls the free function to use this chunk of memory again. This method of memory management is called <strong>explicit deallocation</strong> and is quite powerful. It gives the programmer greater control over the memory in use, which allows for some types of easier optimisation, particularly in low memory environments. However, it leads to two types of programming errors.</p><p>One, calling free prematurely which creates a <strong>dangling pointer</strong>. Dangling pointers are pointers that no longer point to valid objects in memory. This is bad because the program expects a defined value to live at a pointer. When this pointer is later accessed there’s no guarantee of what value exists at that location in memory. There may be nothing there, or some other value entirely. Two, failing to free memory at all. If the programmer forgets to free an object they may face a <strong>memory leak</strong> as memory fills up with more and more objects. This can lead to the program slowing down or crashing if it runs out of memory. Unpredictable bugs can be introduced into a program when memory has to be explicitly managed.</p><h4><strong>Automatic Memory Management</strong></h4><p>This is why languages like Go offer <strong>automatic dynamic memory management</strong>, or more simply, <strong>garbage collection</strong>. Languages with garbage collection offer benefits like:</p><p>- increased security</p><p>- better portability across operating systems</p><p>- less code to write</p><p>- runtime verification of code</p><p>- bounds checking of arrays</p><p>Garbage collection has a performance overhead, but it isn’t as much as is often assumed. The tradeoff is that a programmer can focus on the business logic of their program and ensure it is fit for purpose, instead of worrying about managing memory.</p><p>A running program will store objects in two memory locations, the <a href="https://en.wikipedia.org/wiki/Memory_management#HEAP"><strong>heap</strong></a> and the <a href="https://en.wikipedia.org/wiki/Stack-based_memory_allocation"><strong>stack</strong></a>. Garbage collection operates on the heap, not the stack. The stack is a LIFO data structure that stores function values. Calling another function from within a function pushes a new <a href="https://en.wikipedia.org/wiki/Call_stack"><strong>frame</strong></a> onto the stack, which will contain the values of that function and so on. When the called function returns, its stack frame is popped off the stack. You might be familiar with the stack from when you’re debugging a crashed program. Most language compilers will return a stack trace to aid in debugging, which displays the functions that have been called leading up to that point.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/350/0*IQrCG7vc-lS55LDK.png" /><figcaption>Stacks can have values “pushed” onto the top, or “popped” from the top in a LIFO (last in, first out) fashion. <a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)">Image from Wikipedia.</a></figcaption></figure><p>In contrast, the heap contains values that are referenced outside of a function. For example, statically defined constants at the start of a program, or more complex objects, like Go structs. When the programmer defines an object that gets placed on the heap, the needed amount of memory is allocated and a pointer to it is returned. The heap is a graph where objects are represented as nodes which are referred to in code or by other objects in the heap. As a program runs, the heap will continue to grow as objects are added unless the heap is cleaned up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/551/1*U6e2x93Rk_4l99K-vbQipg.png" /><figcaption>The heap starts from the roots and grows as more object are added.</figcaption></figure><h4>Garbage Collection in Go</h4><p>Go <a href="https://groups.google.com/g/golang-nuts/c/KJiyv2mV2pU/m/wdBUH1mHCAAJ">prefers to allocate memory on the stack</a>, so most memory allocations will end up there. This means that Go has a stack per goroutine and when possible Go will allocate variables to this stack. The Go compiler attempts to prove that a variable is not needed outside of the function by performing <strong>escape analysis</strong> to see if an object “escapes” the function. If the compiler can determine a variables <a href="https://www.memorymanagement.org/glossary/l.html#term-lifetime">lifetime</a>, it will be allocated to a stack. However, if the variable’s lifetime is unclear it will be allocated on the heap. Generally if a Go program has a pointer to an object then that object is stored on the heap. Take a look at this sample code:</p><pre>type myStruct struct {</pre><pre>  value int</pre><pre>}<br></pre><pre>var testStruct = myStruct{value: 0}<br></pre><pre>func addTwoNumbers(a int, b int) int {</pre><pre><em>  return</em> a + b</pre><pre>}<br></pre><pre>func myFunction() {</pre><pre>  testVar1 := 123</pre><pre>  testVar2 := 456</pre><pre>  testStruct.value = addTwoNumbers(testVar1, testVar2)</pre><pre>}<br></pre><pre>func someOtherFunction() {</pre><pre>  // some other code</pre><pre>  myFunction()</pre><pre>  // some more code</pre><pre>}</pre><p>For the purposes of this example, let’s imagine this is part of a running program because if this was the whole program the Go compiler would optimise this by allocating the variables into stacks. When the program runs:</p><p>1. testStruct is defined and placed on the heap in an available block of memory.</p><p>2. myFunction is executed and allocated a stack while the function is being executed. testVar1 and testVar2 are both stored on this stack.</p><p>3. When addTwoNumbers is called a new stack frame is pushed onto the stack with the two function arguments.</p><p>4. When addTwoNumbers finishes execution, it’s result is returned to myFunction and the stack frame for addTwoNumbers is popped off the stack as it’s no longer needed.</p><p>5. The pointer to testStruct is followed to the location on the heap containing it and the value field is updated.</p><p>6. myFunction exits and the stack created for it is cleaned up. The value for testStruct stays on the heap until garbage collection occurs.</p><p>testStruct is now on the heap and without analysis, the Go runtime doesn’t know if it’s still needed. To do this, Go relies on a garbage collector. Garbage collectors have two key parts, a<strong> mutator </strong>and a<strong> collector</strong>. The collector executes garbage collection logic and finds objects that should have their memory freed. The mutator executes application code and allocates new objects to the heap. It also updates existing objects on the heap as the program runs, which includes making some objects unreachable when they’re no longer needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/551/1*DqBH5Crvv6yjuTbzdhu_Sw.png" /><figcaption>The object at the bottom has become unreachable due to changes made by the mutator. It should be cleaned up by the garbage collector.</figcaption></figure><h4>The implementation of Go’s garbage collector</h4><p>Go’s garbage collector is a <strong>non-generational concurrent, tri-color mark and sweep garbage collector</strong>. Let’s break these terms down.</p><p>The <a href="https://www.memorymanagement.org/glossary/g.html#term-generational-hypothesis">generational hypothesis</a> assumes that short lived objects, like temporary variables, are reclaimed most often. Thus, a generational garbage collector focuses on recently allocated objects. However, as mentioned before, compiler optimisations allow the Go compiler to allocate objects with a known lifetime to the stack. <a href="https://groups.google.com/g/golang-nuts/c/KJiyv2mV2pU/m/wdBUH1mHCAAJ">This means fewer objects will be on the heap, so fewer objects will be garbage collected</a>. This means that a generational garbage collector is not necessary in Go. So, Go uses a non-generational garbage collector. <a href="https://github.com/golang/go/blob/master/src/runtime/mgc.go#L7">Concurrent means that the collector runs at the same time as mutator threads</a>. Therefore, Go uses a non-generational concurrent garbage collector. Mark and sweep is the type of garbage collector and tri-color is the algorithm used to implement this</p><p>A mark and sweep garbage collector has two phases, unsurprisingly named <strong>mark</strong> and <strong>sweep</strong>. In the mark phase the collector traverses the heap and marks objects that are no longer needed. The follow-up sweep phase removes these objects. Mark and sweep is an indirect algorithm, as it marks live objects, and removes everything else.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/125/0*Xt8YNYDE5U7kr4UA.gif" /><figcaption>A visualisation of a mark and sweep collector, <a href="https://spin.atomicobject.com/2014/09/03/visualizing-garbage-collection-algorithms/">borrowed from here</a>. There’s visualisations of other kinds of garbage collectors too if you’re interested.</figcaption></figure><p><a href="https://github.com/golang/go/blob/master/src/runtime/mgc.go#L24">Go implements this in a few steps</a>:</p><p>Go has all goroutines reach a garbage collection safe point with a process called <strong>stop the world</strong>. This temporarily stops the program from running and turns a <strong>write barrier</strong> on to maintain data integrity on the heap. This allows for concurrency by allowing goroutines and the collector to run simultaneously.</p><p>Once all goroutines have the write barrier turned on, the Go runtime <strong>starts the world</strong> and has workers perform the garbage collection work.</p><p>Marking is implemented by using a <strong>tri-color algorithm</strong>. When marking begins, all objects are white except for the root objects which are grey. Roots are an object that all other heap objects come from, and are instantiated as part of running the program. The garbage collector begins marking by scanning stacks, globals and heap pointers to understand what is in use. When scanning a stack, the worker stops the goroutine and marks all found objects grey by traversing downwards from the roots. It then resumes the goroutine.</p><p>The grey objects are then enqueued to be turned black, which indicates that they’re still in use. Once all grey objects have been turned black, the collector will stop the world again and clean up all the white nodes that are no longer needed. The program can now continue running until it needs to clean up more memory again.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/330/0*u8YPg1zXxynsPJIV.gif" /><figcaption><a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking">This diagram from Wikipedia makes it a bit easier to understand</a>. The colours are a bit confusing, but white objects are light-grey, grey objects are yellow, and black objects are blue.</figcaption></figure><p>This process is initiated again once the program has allocated extra memory proportional to the memory in use. The `GOGC` environment variable determines this, and is set to 100 by default. <a href="https://github.com/golang/go/blob/master/src/runtime/mgc.go#L112">The Go source code describes this as:</a></p><blockquote>If GOGC=100 and we’re using 4M, we’ll GC again when we get to 8M (this mark is tracked in next_gc variable). This keeps the GC cost in linear proportion to the allocation cost. Adjusting GOGC just changes the linear constant (and also the amount of extra memory used).</blockquote><p>Go’s garbage collector improves your efficiency by abstracting memory management into the runtime and is one part of what enables Go to be so performant. Go has built in tooling to allow you to optimise how garbage collection occurs in your program that you can investigate if you’re interested. For now, I hope you learnt a bit more about how garbage collection works and how it’s implemented in Go.</p><p><strong>References</strong></p><p><a href="https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html">Garbage Collection in Go: Part 1</a></p><p><a href="https://blog.golang.org/ismmkeynote">Getting to Go: The Journey of Go’s Garbage Collector</a></p><p><a href="https://medium.com/a-journey-with-go/go-how-does-the-garbage-collector-mark-the-memory-72cfc12c6976">Go: How Does the Garbage Collector Mark the Memory?</a></p><p><a href="https://medium.com/invalid-memory/golang-cost-of-using-the-heap-e70363469754">Golang: Cost of using the heap</a></p><p><a href="https://golang.org/doc/faq#stack_or_heap">Golang FAQ</a></p><p><a href="https://groups.google.com/g/golang-nuts/c/KJiyv2mV2pU/m/wdBUH1mHCAAJ">Google Groups discussion, comment by Ian Lance Taylor</a></p><p><a href="https://hub.packtpub.com/implementing-memory-management-with-golang-garbage-collector/">Implementing memory management with Golang’s garbage collector</a></p><p><a href="https://www.memorymanagement.org/">Memory Management Reference</a></p><p><a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)">Stack (abstract data type)</a></p><p><a href="https://gchandbook.org/">The Garbage Collection Handbook</a></p><p><a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking">Tracing garbage collection: Tri-color marking</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9a72ec7c76a8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/mitti-engineering/an-overview-of-memory-management-in-go-9a72ec7c76a8">An overview of memory management in Go</a> was originally published in <a href="https://medium.com/mitti-engineering">Mitti Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing a (trivial) search engine]]></title>
            <link>https://scottgangemi.medium.com/writing-a-trivial-search-engine-e3256fa3a096?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/e3256fa3a096</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[search-engines]]></category>
            <category><![CDATA[software]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Sat, 16 Mar 2019 06:06:45 GMT</pubDate>
            <atom:updated>2019-03-16T06:11:07.990Z</atom:updated>
            <content:encoded><![CDATA[<p>I was recently made tech lead of a team focusing on search, without knowing much about search. To skill up on how search engines work, I started reading <a href="https://nlp.stanford.edu/IR-book/pdf/irbookonlinereading.pdf">An Introduction to Information Retrieval</a>. FYI, because I didn’t know, search engines and searching fall into the field of study called Information Retrieval. Hopefully that’ll save you some frantic Googling around the lines of “How do search engines work?”.</p><p>After reading a couple of chapters, I decided to write a trivial search engine to understand what I was reading. You can <a href="https://basic-search.herokuapp.com/">find it here</a>. It indexes three Shakespeare texts, and you can search through those 🎉. It’s a bit buggy (try searching for more than two terms 🤷🏻‍♂️), but I’m pretty happy with it for a week’s worth of evenings. My goals in building this were to:</p><ul><li>Read some documents</li><li>Index these documents</li><li>Figure out the term document frequencies</li><li>Develop an inverted index of these documents</li><li>Allow a user to search for multiple search terms and get back some results.</li></ul><p>Let’s break some of this down:</p><p>The material that you can search for within a particular search engine is a document. So my search engine has Shakespeare plays, as Google allows you to search through webpages. Indexing a document involves saving this into a form that you can access later on.</p><p>After you’ve indexed a document, you’ll figure out the term document frequency of each word. This is a (long) list of how many times a particular term will appear in a document. This is accomplished by breaking down the text through tokenisation and stemming. Tokenisation is separating the words in a document into their individual words. Stemming is the process of bringing a word down to it’s “stem” word so that it can be compared to other similar words. For example, the words ‘rebellious’, ‘rebelling’, ‘rebels’, and ‘rebellion’ all come back to the stem word ‘rebel’. You’ll see this in the below examples where some words look a little bit weird. There many stemming algorithms, such as the <a href="https://snowballstem.org/">Snowball</a> and the <a href="https://tartarus.org/martin/PorterStemmer/">Porter</a> stemmer. <a href="https://github.com/Gangezilla/porter-port">As part of this project I did a port of the Porter algorithm, which you can find here if you’re interested</a>. A stemmer basically drills down to the stem of a word through a series of more and more pointed regex trimmings. A snippet of this from Hamlet looks like this:</p><pre>&quot;mind&quot;: 16,</pre><pre>&quot;mine&quot;: 33,</pre><pre>&quot;miner&quot;: 1,</pre><pre>&quot;minist&quot;: 2,</pre><pre>&quot;ministr&quot;: 1,</pre><pre>&quot;minut&quot;: 2,</pre><pre>&quot;miracul&quot;: 1,</pre><pre>&quot;mirror&quot;: 2,</pre><pre>&quot;mirth&quot;: 2,</pre><pre>&quot;mischanc&quot;: 2,</pre><pre>&quot;mischief&quot;: 1,</pre><pre>&quot;miss&quot;: 1,</pre><p>After the term document frequencies for each document is calculated , the search engine will build an inverted index. This sounds a lot scarier than it is. An inverted index is a combined term document frequency for your documents, as well as a record of which documents contain those words. Below, the key “doc_freq” is the combined document frequency of these terms, and the “postings_list” is the list of document ID’s that these terms have appeared in. This inverted index looks something like this:</p><pre>&quot;deep&quot;: {</pre><pre>  &quot;doc_freq&quot;: 13,</pre><pre>  &quot;postings_list&quot;: [</pre><pre>    1,</pre><pre>    2,</pre><pre>    3</pre><pre>  ]</pre><pre>},</pre><pre>&quot;deer&quot;: {</pre><pre>  &quot;doc_freq&quot;: 8,</pre><pre>  &quot;postings_list&quot;: [</pre><pre>    1,</pre><pre>    2,</pre><pre>    3</pre><pre>  ]</pre><pre>},</pre><pre>&quot;defenc&quot;: {</pre><pre>  &quot;doc_freq&quot;: 3,</pre><pre>  &quot;postings_list&quot;: [</pre><pre>    1,</pre><pre>    2<br>  ]</pre><pre>},</pre><p>After you’ve done this you can start searching. My search engine has a very basic search parser. When your user types in a query such as ‘Hamlet sword’, this gets converted to ‘Hamlet AND sword’ so both of these terms are searched for. The upgrade to this is a Boolean search parser. This allows your user to use some Boolean logic in their query, such as: ‘Hamlet AND sword OR cat AND been’. This can be used with a phrase query, which is many words surrounded by double quotes, which will match everything inside those quotes. With a Boolean query, this might look like: ‘Hamlet AND sword OR cat AND “who was that”’.</p><p>At this point, a user will see some results come back and our job. Reading this information was interesting, but confusing. Implementing it, even simplistically, has been a great way of understanding how this works.</p><p>There’s many ways that this could be improved. For example, better performance, indexing more documents, and implementing a ranked search system. Feel free to check out the code for the <a href="https://github.com/Gangezilla/porter-port">porter here</a>, the <a href="https://github.com/Gangezilla/basic-search">search engine here</a>, or view the <a href="https://basic-search.herokuapp.com/">final product here</a>.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e3256fa3a096" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Teaching React at NodeGirls]]></title>
            <link>https://medium.com/domain-tech/teaching-react-at-nodegirls-c3f87c1338af?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3f87c1338af</guid>
            <category><![CDATA[node]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[node-girls]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[education]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Fri, 01 Jun 2018 05:02:46 GMT</pubDate>
            <atom:updated>2018-06-01T05:12:35.739Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IIyd-4V1Gqg27iZJig9jiw.jpeg" /></figure><p>On Saturday 7 May 2018, Jesica Saussay, Erin Campbell and I ran a workshop at <a href="http://nodegirls.com.au/">NodeGirls Sydney</a> teaching React to a classroom full of aspiring programmers. (Shoutout to Jes who did most of the work in coming up with the app to build, and how we’ll teach it. 📣)</p><p>NodeGirls is a non-profit that runs Javascript and Node workshops for women. This is an awesome initiative to improve diversity in software development roles, and being able to help out with teaching is really great. 🚀</p><p>The two other workshops, Intro to Javascript, and an Express/Node were self-guided where you work through the course at your own pace and ask for help when you need it. Conversely, we ran our React workshop as a code-along, building an application at the same time as our students did. This was a really fun style of learning, and it felt like our classroom were really involved with our workshop and with the learning of those around them. Not to mention Jes was making everyone answer questions every 20 seconds. ⏰</p><p>The main goal 🥅 of our workshop was to teach React, and make sure that everyone walked away with a working app. The attendees felt like they had accomplished something and can further iterate on what they had built in their own time if they feel like it. The app we built together was a weather app, which is a really good example of React because of how dynamic it is. And if you’re interested, you can check it out here:</p><p><a href="https://github.com/node-girls-australia/react-workshop">https://github.com/node-girls-australia/react-workshop</a></p><p>🌞🌦️🌀</p><p>Quick side note about how skills develop. When I used to work in Apple Retail 🍏 I ran heaps of workshops teaching people how to use their computers, and really worked on developing patience with people when they didn’t understand things. Then when I was at uni I ended up performing in a number of theatre shows (musicals, plays, revues etc) 💃 and doing this helped me get skills in how to project my voice, have confidence in front of a room full of people and probably other things I’m not aware of. When I left uni and part time retail and moved into full time development, I didn’t think that the skills I’d developed as a student would be useful again. After we ran this workshop and I’d had a bit of time to reflect on it, I realised that the skills I’d worked on had helped me in ways that I hadn’t really thought about. Anyway, never dismiss the things you’ve learned in the past, you never know when you’ll be able to utilise them.</p><p>Now, for some photos: 📷</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/624/0*_ziNCzuSzinnMDXS" /><figcaption>The weather app that we all built</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u5eFNNS42luNTHwEgI5eGA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lf2wmDqF5LsKjH9GGdaJPg.jpeg" /><figcaption>A classroom full of people itching to learn React</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NiQSC9VXN6ASUVoMEgripA.jpeg" /><figcaption>Me!</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5nNoFQx2zJ0JwM4IiBMxaA.jpeg" /><figcaption>Erin kicking butt</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b_DvlH10VHvpz2Cz5EGpPw.jpeg" /><figcaption>Jes talking with her hands</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3f87c1338af" width="1" height="1" alt=""><hr><p><a href="https://medium.com/domain-tech/teaching-react-at-nodegirls-c3f87c1338af">Teaching React at NodeGirls</a> was originally published in <a href="https://medium.com/domain-tech">Tech @ Domain</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learn by building]]></title>
            <link>https://scottgangemi.medium.com/learn-by-building-fbbc4405f53d?source=rss-c893f51f53ac------2</link>
            <guid isPermaLink="false">https://medium.com/p/fbbc4405f53d</guid>
            <category><![CDATA[software]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Scott Gangemi]]></dc:creator>
            <pubDate>Wed, 18 Apr 2018 11:16:57 GMT</pubDate>
            <atom:updated>2018-04-18T11:17:23.295Z</atom:updated>
            <content:encoded><![CDATA[<p>When I started my new job at Domain in 2017, I was eager to learn more about Node, and about how to create big applications. To do this, I thought about solving a problem I had at my old job at a fashion company.</p><p>One of their biggest revenue drivers was sending out EDMs about sales. These always caused revenues to spike, and everyone to feel great that we were making money. FYI, An EDM is a promotional email companies send out for marketing purposes.</p><p>Being the junior developer, I had to build these EDMs, and it was my least favourite part of the job.</p><p>For anyone who hasn’t had to build HTML emails, it’s a painful process. First, imagine a marketing email you’ve received lately. A lot of these are big poster-like images that get divided into pieces; buttons, photos, headers etc . This image gets sliced in Photoshop, exported as HTML and then fleshed out so it will work everywhere.</p><p>This is difficult because HTML support in email clients is very outdated. You have to use antiquated standards to make everything work, like wrapping everything in millions of &lt;table&gt; tags. On top of this, different email clients implement these standards in their own way. Painful, right?</p><p>Like any good engineer, I sought to automate this process. We built a Grunt tool that reduced the time it took to build a simple EDM from ~20 minutes, to 2 minutes. This also enabled us to build complicated emails much easier.</p><p>But, like any good lazy person, my real goal was to make a tool that would mean someone else could do this job that I hated. This would have involved making a technical task accessible to non-technical people.</p><p>At the beginning of my time at this job (and at the beginning of my career), I tried to make something like this, but didn’t get very far. Last year I gave it another shot. I got a bit further, but never completed it. Although I never completed it, there were a few cool things about this project. For example, custom login and user systems, and the ability to send out emails to reset passwords.</p><p>The reason I never got to finish was that I didn’t prioritise well. EDM creation relies on image slicing, which I couldn’t get to completely work. For an app that boils down to image slicing, this is a dealbreaker.</p><p>Because I found this hard, I kept on procrastinating fixing this. This lead to building other features, but got me away from the core of the application. This was pretty dumb, because all the stuff I built would never get used until I fixed the image slicing.</p><p>One of the most important lessons that I got out of this process was to build the core features first. If I had finished the image slicing, I would have gotten this app out, or realised sooner that I wouldn’t finish it.</p><p>When I was studying Product Design at uni, one of the things that I learnt was to make prototypes. Lots and lots of prototypes. By making prototypes you’re able to figure out what works and what doesn’t. You then get the privilege of using these lessons to inform your decisions going forward.</p><p>I should have narrowed my focus to make a prototype to “slice images and convert them into HTML”. Instead I created an unfocused prototype where users could do everything but that.</p><p>It’s important to prioritise the most important things for your user and do those things first. Nobody would care that I had all these other systems built out if the core purpose of my app sucked.</p><p>My users would have come back to create emails, and creating emails should have been the thing that I focused on making awesome.</p><p>When I was building this, I focused on getting this out the door. This means the code sucks. Sorry about that. There aren’t any tests either, which all leads to a lot of mess across the whole repo. I planned on coming back to develop this at some point, but the above meant I didn’t bother. I’m not sure where the line between code quality and a speedy release is, but I should have considered it some more.</p><p>This app isn’t pretty in a lot of places, but I’m still proud of it. It represents a lot of hard work and ambition that would have been great to finish. At least getting it out into the world is better than letting it languish as a private repo.</p><p>Check out the code below. It’s not great, but I definitely learned a lot from writing it. I promise my ability to pull together an application has improved since 😅.</p><p>I never would have learned all this without building something. I definitely encourage you to go and build something yourself; it’s the best way to learn anything.</p><p><a href="https://github.com/Gangezilla/edm-release">https://github.com/Gangezilla/edm-release</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fbbc4405f53d" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>