<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Doug Donohoe</title>
    <description>The latest articles on DEV Community by Doug Donohoe (@dougdonohoe).</description>
    <link>https://dev.to/dougdonohoe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1014579%2Fa6c312e9-d27b-407f-9a41-97f8f8762290.jpeg</url>
      <title>DEV Community: Doug Donohoe</title>
      <link>https://dev.to/dougdonohoe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dougdonohoe"/>
    <language>en</language>
    <item>
      <title>Always Be Refactoring</title>
      <dc:creator>Doug Donohoe</dc:creator>
      <pubDate>Tue, 06 May 2025 07:36:23 +0000</pubDate>
      <link>https://dev.to/dougdonohoe/always-be-refactoring-157b</link>
      <guid>https://dev.to/dougdonohoe/always-be-refactoring-157b</guid>
      <description>&lt;p&gt;&lt;em&gt;Great engineers refactor constantly, even if they have to slip it into their normal workflow.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;“Always be closing.”&lt;/p&gt;

&lt;p&gt;The hard-nosed sales mantra from &lt;a href="https://www.google.com/search?q=always+be+closing+scene&amp;amp;rlz=1C5CHFA_enUS901US901&amp;amp;oq=always+be+closing+scene&amp;amp;gs_lcrp=EgZjaHJvbWUyCQgAEEUYORiABDIICAEQABgWGB4yCAgCEAAYFhgeMggIAxAAGBYYHjIICAQQABgWGB4yCAgFEAAYFhgeMg0IBhAAGIYDGIAEGIoFMgYIBxBFGDzSAQg0MTAxajBqNKgCALACAQ&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8#fpstate=ive&amp;amp;vld=cid:0c1fc1d3,vid:O6ybfVT9gxA,st:0" rel="noopener noreferrer"&gt;&lt;em&gt;Glengarry Glen Ross&lt;/em&gt;&lt;/a&gt; became shorthand for relentless improvement. In the same spirit (but with much less swearing), a good engineer should &lt;em&gt;always be refactoring&lt;/em&gt;. Here’s why.&lt;/p&gt;

&lt;h2&gt;
  
  
  To Clean or Not to Clean
&lt;/h2&gt;

&lt;p&gt;It’s generally agreed that clean code is easier to understand, cheaper to maintain, and much more extensible. At its core, refactoring is cleaning up existing code without altering its perceived functionality.&lt;/p&gt;

&lt;p&gt;A common objection to refactoring is that there isn’t time to actually do it. Teams see it as a luxury. The relentless drive for new features simply doesn’t allow for refactoring, especially because refactoring changes nothing from an external point of view. That can be a hard sell to your product owner.&lt;/p&gt;

&lt;p&gt;But clean code makes your life easier. Clean code pays for itself and slows the accumulation of technical debt.&lt;/p&gt;

&lt;p&gt;So sneak it in.&lt;/p&gt;

&lt;p&gt;That’s right. I’m suggesting a slight bit of subterfuge. A little misdirection even. Why not clean up a few things in every pull request?&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Vigilant
&lt;/h2&gt;

&lt;p&gt;A good engineer has a vision of where the codebase should be heading. It might take a while to get there, but you know the messy parts, the backlog, the roadmap, the technical debt, the security holes, and the documentation gaps.&lt;/p&gt;

&lt;p&gt;As you go about your regular feature development, be on the lookout for refactorings that can advance your larger agenda. Like one of those hyper-observant TV show detectives surveying a crime scene, pay close attention to all the code you stumble across.&lt;/p&gt;

&lt;p&gt;When you notice a code smell or something slightly off near your current focus, alarm bells should go off: &lt;em&gt;“Don’t let this opportunity pass!”&lt;/em&gt; Take a few minutes and fix it now, before inspiration fades.&lt;/p&gt;

&lt;p&gt;Don’t say it’s not your problem. Don’t pretend you can unsee it. Just roll up your sleeves and get it done.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Example
&lt;/h2&gt;

&lt;p&gt;Refactoring doesn’t necessarily mean changing thousands of lines of code. It can be just a small chunk of code here and there. These little micro-refactorings add up over time. In fact, the more you get into the habit of constant cleanup, the less you will need to do major refactorings in the future.&lt;/p&gt;

&lt;p&gt;To illustrate micro-refactoring, let’s look at an “extract method” Golang example.&lt;/p&gt;

&lt;p&gt;Let’s say you are tackling a feature that requires knowing how many days it has been since a user last logged in. You notice an existing method that determines if a user is dormant by using that same information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func IsDormant(user User, asOf time.Time) bool {
  days := int(asOf.Sub(user.LastLogin).Hours() / 24)
  return days &amp;gt;= 8
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to re-use the last login calculation instead of copying and pasting it, so you take the internal variable, &lt;code&gt;days&lt;/code&gt;, and extracting it into a separate method, &lt;code&gt;DaysSinceLastLogin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func IsDormant(user User, asOf time.Time) bool {
  return DaysSinceLastLogin(user, asOf) &amp;gt;= 8
}

func DaysSinceLastLogin(user User, asOf time.Time) int {
  return int(asOf.Sub(user.LastLogin).Hours() / 24)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the last login logic to be tested and reused. If you write a unit test, you’ll spot an edge case that should be handled (a potential panic if a user has never logged in).&lt;/p&gt;

&lt;p&gt;It also makes future enhancements easier. For example, it might make sense to make &lt;code&gt;IsDormant&lt;/code&gt; and &lt;code&gt;DaysSinceLastLogin&lt;/code&gt; methods on the User struct instead of being standalone. Likewise, consider replacing the hard-coded value &lt;code&gt;8&lt;/code&gt; with something more descriptive like &lt;code&gt;DormantDaysThreshold&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a simple example, just a few lines of code. But that’s the beauty. It shows a small refactoring can add value by revealing a potential bug and pointing towards future improvements.&lt;/p&gt;

&lt;p&gt;To learn more about the craft of refactoring and see all the small ways to improve your code, check out online resources such as the &lt;a href="https://refactoring.com/catalog/" rel="noopener noreferrer"&gt;refactoring catalog&lt;/a&gt; from Martin Fowler’s book or the &lt;a href="https://refactoring.guru/refactoring/what-is-refactoring" rel="noopener noreferrer"&gt;Refactoring Guru&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Refactoring Mindset
&lt;/h2&gt;

&lt;p&gt;Having a vision for your codebase is easy. Knowing how to get it there is harder. Spotting refactoring opportunities takes practice. One way to get started is to consider which categories of refactoring are necessary to transform your code.&lt;/p&gt;

&lt;p&gt;Here are some common refactoring themes to think about:&lt;/p&gt;

&lt;p&gt;🧹 Remove Duplication — Collapse copy and pasted logic into a single function.&lt;/p&gt;

&lt;p&gt;🧰 Extract Shared Utility or Library — Move common logic out of per-service implementations. Normalize integrations (e.g., all services use the same auth client).&lt;/p&gt;

&lt;p&gt;🧱 Add Common Infrastructure — Introduce observability (logging, tracing, metrics). Centralize error handling or retries.&lt;/p&gt;

&lt;p&gt;⚙️ Make Code More Testable — Break apart tightly coupled logic. Extract interfaces so dependencies can be mocked or stubbed.&lt;/p&gt;

&lt;p&gt;🔄 Prepare for a Feature Change — Flatten complex conditionals or nested logic. Reorganize files or classes to fit new responsibilities.&lt;/p&gt;

&lt;p&gt;🏗️ Support a New Architecture — Break apart a monolith. Introduce event-driven patterns, dependency injection, or async processing.&lt;/p&gt;

&lt;p&gt;🚦 Reduce Cognitive Load — Split giant files or classes into logical, focused units.&lt;/p&gt;

&lt;p&gt;🔐 Improve Security or Compliance — Centralize access control logic. Refactor insecure patterns (e.g., manual SQL concatenation → parameterized queries).&lt;/p&gt;

&lt;p&gt;🚀 Improve Performance — Replace naive algorithms with optimized ones. Reduce memory churn or unnecessary computation in hot paths.&lt;/p&gt;

&lt;p&gt;👥 Ease Onboarding or Handoff — Refactor code that only “Pat” understands into something team-readable. Introduce docs/comments/test coverage as part of structural cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20%
&lt;/h2&gt;

&lt;p&gt;Some might object that surely not all refactorings can be snuck in. I’ll grant you that. Let’s apply the 80–20 rule and stipulate that 20% of refactorings need to be done on the up-and-up.&lt;/p&gt;

&lt;p&gt;These should be an easier sell, however, because when you get to the point where you need a full, honest-to-goodness refactor, it is most definitely going to be in service of some larger goal.&lt;/p&gt;

&lt;p&gt;For example, before working on a performance improvement ticket, you first need to add tracing and metrics so you can develop a finer picture of current performance and identify hotspots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring Starts with Testing
&lt;/h2&gt;

&lt;p&gt;It is probably self-evident, but I must point out that refactoring is much easier (and less nerve-racking) if your codebase has an accompanying suite of tests that pass before and after the refactoring. Having tests is also valuable for a &lt;a href="https://medium.com/@DougDonohoe/you-dont-have-time-not-to-test-e82bda121d64" rel="noopener noreferrer"&gt;bunch of other reasons&lt;/a&gt;. If your project doesn’t have strong testing, then add some tests first to unlock future refactorings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Review Considerations
&lt;/h2&gt;

&lt;p&gt;My advice in this article runs counter to the standard guideline to not mix refactoring work and regular feature development. The objection is that it makes it harder for the team to do code reviews if unrelated changes are included. That’s a fair point. When refactoring during feature work, keep it small and (ideally) related to the main thrust of the change.&lt;/p&gt;

&lt;p&gt;A good rule of thumb for any pull request is to limit it to 500 lines of code changes. A second rule of thumb is that ancillary refactoring changes should be no more than 20% of the PR.&lt;/p&gt;

&lt;p&gt;When you do have dedicated refactoring PRs, keep it focused and around this size. Sometimes a PR has to be greater than 500 lines of code, especially when working on a repo-wide refactor. In those cases, coordinate well with your colleagues to prepare them for the change and to minimize merge conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Every Commit Count
&lt;/h2&gt;

&lt;p&gt;Every time you touch the codebase, you have a choice: leave it a little better, or leave it a little worse.&lt;/p&gt;

&lt;p&gt;Refactoring doesn’t have to be a grand event. Small improvements (extracting methods, renaming confusing variables, breaking apart long methods, etc.) add up over time. They prevent technical debt from piling up and make future changes easier and safer.&lt;/p&gt;

&lt;p&gt;If you see something, fix something.&lt;/p&gt;

&lt;p&gt;Always be refactoring!&lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>softwaredevelopment</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>You Don't Have Time Not to Test</title>
      <dc:creator>Doug Donohoe</dc:creator>
      <pubDate>Thu, 24 Apr 2025 06:58:04 +0000</pubDate>
      <link>https://dev.to/dougdonohoe/you-dont-have-time-not-to-test-382i</link>
      <guid>https://dev.to/dougdonohoe/you-dont-have-time-not-to-test-382i</guid>
      <description>&lt;p&gt;&lt;em&gt;Testing isn't a sunk cost. It's a compounding return that shapes better code and ultimately accelerates your team.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"We don't have time to test," stated the principal engineer matter-of-factly, with an expression that left no room for debate. It was a proclamation of absolute truth like "the sun always rises in the east" or "you always find the lost thing in the last place you look." A startup fact-of-life, or so it seems.&lt;/p&gt;

&lt;p&gt;This was in response to my candid observation that test coverage was low, existing tests didn't always run locally for mysterious reasons, integration tests were non-existent, and pull requests could merge without passing the few tests that did exist.&lt;/p&gt;

&lt;p&gt;I was shocked into silence. This anti-testing sentiment wasn't new to me, but it still astonished me to hear it said so forthrightly. Like getting insulted on the schoolyard playground by a bully, I was unable to swiftly come up with a clever retort. I kept silent but my brain was screaming "Oh yeah, you, you … you don't have time not to test!" Take that, principal engineer!&lt;/p&gt;

&lt;p&gt;Some time has passed and this statement still bothers me. It seems that software engineers everywhere continue to have an aversion to writing tests and I don't understand why that is. Maybe the "move fast and break things" philosophy has gone too far. Testing is a really good tool. It's as indispensable as having turn-by-turn GPS guidance when navigating in an unfamiliar city.&lt;/p&gt;

&lt;p&gt;In this post, I'll dig into this conundrum a little more. What is really going on here? What is testing, actually? Why should it be a tool in your professional toolkit?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp710kr7xz30fq3mjgsys.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp710kr7xz30fq3mjgsys.jpg" alt="Gorignak, demanding features" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Under Pressure
&lt;/h2&gt;

&lt;p&gt;I get it. Leadership is pressuring you to ship some features so the company can find product-market fit in order to acquire customers and not die. Yes, that can be stressful.&lt;/p&gt;

&lt;p&gt;It reminds me of the humorous &lt;a href="https://www.youtube.com/watch?v=EQG3I5efwWo" rel="noopener noreferrer"&gt;rock monster scene&lt;/a&gt; from one of my favorite movies, Galaxy Quest. In it, a very angry rock monster is chasing the commander of a space ship. To fend off the monster, a fellow crew member helpfully suggests, "form some sort of a rudimentary lathe." This is an absurd suggestion and (at least to me) one of the funniest lines in the movie. What would make this even more absurd would be if another crew member were to yell out that he should make sure the lathe does its lathing properly by testing it out on a mock angry rock monster.&lt;/p&gt;

&lt;p&gt;When the engineer said "we don't have time to test," in response to my no-doubt-aggravating observations, it was essentially equivalent to him shrugging his shoulders and pointing backwards at the looming monster. When under pressure you are certainly going to take some shortcuts in order to live another day.&lt;/p&gt;

&lt;p&gt;I'm not here to fault any engineer. For all I know, they are also bummed about the state of affairs, but don't have the political capital to argue the point with management. You have to pick your battles.&lt;/p&gt;

&lt;p&gt;I agree, yes you have to make tradeoffs, but I believe that abandoning tests and test automation - even at the very earliest stage - is a mistake.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2t6it2bifg8pmeaa22ei.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2t6it2bifg8pmeaa22ei.jpg" alt="Wait. What is a test?" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Test?
&lt;/h2&gt;

&lt;p&gt;As a software engineer, you'll have heard of unit tests, integration tests, system tests, end-to-end tests, smoke tests, performance tests, chaos tests, regression tests, load tests, penetration tests and so on.&lt;/p&gt;

&lt;p&gt;I'm going to intentionally ignore these distinctions. To me, the argument of "what is a unit test" vs "what is an integration test" is unhelpful. It's just bike-shedding [&lt;a href="https://medium.com/r/?url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FLaw_of_triviality" rel="noopener noreferrer"&gt;1&lt;/a&gt;]. The key question is whether a test exists at all, regardless of what dependencies a test might require to run.&lt;/p&gt;

&lt;p&gt;So, what is a test? To me, a test is a chunk of code that directly verifies the &lt;em&gt;most important concerns&lt;/em&gt; of another chunk of code. It can be run on demand, rapidly repeated, replicated elsewhere, and automated.&lt;/p&gt;

&lt;p&gt;A test may be able to operate independently, which is usually the case for unit tests on purely functional bits of code. A test may require some help - like a database, an external service, or mocked dependencies - as is often the case with integration or end-to-end tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Most Important Concerns
&lt;/h3&gt;

&lt;p&gt;What are the most important concerns? At its core, it validates that the code does what it claims to do. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An MD5 hash function calculates the correct MD5 hash of a file.&lt;/li&gt;
&lt;li&gt;A photo EXIF extractor properly fetches details like exposure and aperture from a JPG file.&lt;/li&gt;
&lt;li&gt;A database repository returns exactly the same data that it saves in a database (looking at you, time zones and decimal numbers).&lt;/li&gt;
&lt;li&gt;A third party library sanity check, demonstrating the API and verifying it does the thing it is supposed to do.&lt;/li&gt;
&lt;li&gt;The right error is raised when something fails.&lt;/li&gt;
&lt;li&gt;A log message is emitted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conversely, a test should fail if the code under test ceases to do the thing it is supposed to do. In the real world, you want to catch when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A third party library changes its implementation.&lt;/li&gt;
&lt;li&gt;A database subtly changes the semantics of rollbacks.&lt;/li&gt;
&lt;li&gt;A rogue engineer on your team (you know the one) makes a bone-headed change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I always ask myself, "If this critical assumption changes, will a test break?"&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoke On Demand
&lt;/h3&gt;

&lt;p&gt;A test should be easy to run, whether from the command line or right inside an IDE. I personally use IntelliJ, which has support for languages such as Java, Go, Python and Scala. With a single keyboard shortcut, it magically knows how to run the test under the cursor. Figuring out the command line equivalent of running an individual test is a task well suited for ChatGPT, as it varies widely by language.&lt;/p&gt;

&lt;p&gt;Using an IDE also makes it easy to step through code in a debugger. This is especially crucial when you're learning a new codebase or tracking down a tricky bug. The ability to directly execute the code you're interested in - rather than triggering it indirectly through a webpage or API endpoint - is immensely valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rapidly Repeat
&lt;/h3&gt;

&lt;p&gt;Running a test at the touch of a button significantly boosts your velocity. This is where tests genuinely help you deliver code faster: you reach a working state sooner and gain a companion that both validates your thinking and captures your intent - like executable documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicate Elsewhere
&lt;/h3&gt;

&lt;p&gt;Once your test can be run on demand, it becomes easy to try it out in other environments - like a teammate's laptop or a CI/CD pipeline. This is where your tests start to pay dividends for the team and unlock real automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate
&lt;/h3&gt;

&lt;p&gt;Once you have a collection of tests, the next logical step is to run them all - either on demand (say, after a major refactor) or automatically during key points in the development lifecycle (like when opening a pull request). This safety net helps ensure your code continues to behave as expected, even as the team keeps moving forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Reality
&lt;/h2&gt;

&lt;p&gt;Now, to be fair, most engineers are not skipping testing altogether. They're probably doing some manual checks with Postman, watching logs, or visually confirming debug output. The resistance isn't to testing itself, it's to codifying and automating those tests.&lt;/p&gt;

&lt;p&gt;Why the resistance? The reasons are many, but here are some I've heard (and maybe said myself):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I don't know how to write proper tests in [this new language] and - I'm afraid to admit it.&lt;/li&gt;
&lt;li&gt;I barely got the code working on my laptop - I can't worry about onboarding or CI/CD right now.&lt;/li&gt;
&lt;li&gt;We don't have time to build a framework for database tests.&lt;/li&gt;
&lt;li&gt;We can't write integration tests - our CI doesn't support spinning up dependencies in Docker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on …&lt;/p&gt;

&lt;p&gt;Two things are happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The time required to build testing infrastructure is vastly overestimated.&lt;/li&gt;
&lt;li&gt;The accumulating, compounding time savings are vastly underestimated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time isn't budgeted to build it even though it will quickly pay for itself. Conversely, time not spent on infrastructure is only borrowing against the future when it will be more costly and time consuming to deal with bugs, production incidents and onboarding delays. I think of this as Time Technical Debt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fau6uvvykfx66lfgxg64x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fau6uvvykfx66lfgxg64x.png" alt="Decreasing productivity" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When this misunderstanding takes root, teams often enjoy a short burst of speed, only to slow down over time. They become increasingly less productive over time as they fight bugs and fragile systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05dqa4om9yr5bec6i2q1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05dqa4om9yr5bec6i2q1.png" alt="Increasing productivity" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it doesn't have to be that way. With a small upfront investment - even just a bit of time learning, or scaffolding a few basic tests - teams can start reaping the rewards immediately. The savings scale across individuals, across the team, across months and years.&lt;/p&gt;

&lt;p&gt;Yes, you do have to spend some time to build up your testing infrastructure. But this isn't an all-consuming task. I know this from my time at Retrofit, where I was CTO and employee number two. Two other engineers and I spent the first six weeks building not only the core architecture, but simultaneously putting in place comprehensive testing and CI/CD infrastructure. It paid dividends for years.&lt;/p&gt;

&lt;p&gt;An old proverb states "The best time to plant a tree was 20 years ago. The second best time is now." The same is true for automated testing. The sooner you do it, the sooner you start to accrue the hockey stick benefits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuc6rhkdonhst6rvgqzt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuc6rhkdonhst6rvgqzt.jpg" alt="Your code, before testing" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you need to write a test to validate your code, you inevitably end up changing how you write your code. It's not just about checking behavior - it's about shaping it. Here are a few ways this happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You start returning more detailed information from methods - like the number of records changed or a modified data object with fields filled in (e.g., a generated timestamp).&lt;/li&gt;
&lt;li&gt;Errors have more details, or better yet, become a return value instead of something that is raised or thrown.&lt;/li&gt;
&lt;li&gt;You discover that something you start also needs to be cleanly stopped, so you can verify shutdown logic.&lt;/li&gt;
&lt;li&gt;You also discover that things that start usually need to be initialized in some way, and that init step needs to be validated, so your code starts to have construct → init → start → stop → cleanup phases rather than just start-and-crash behavior.&lt;/li&gt;
&lt;li&gt;You begin using interfaces so you can simulate failure scenarios - like a network outage - and confirm your code handles them gracefully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Asking the question "How am I going to test this?" forces you to think critically and honestly about your code. You ultimately end up with better code, which means less time wasted in the future on bugs and dealing with unexpected behavior.&lt;/p&gt;

&lt;p&gt;Testable code also tends to be more self-contained. It has a clear lifecycle. It's introspectable. It has better logging. And above all, it's modular.&lt;/p&gt;

&lt;p&gt;Modularity is good.&lt;/p&gt;

&lt;p&gt;You're building Lego bricks - reusable, well-formed pieces you can snap together to create great software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcluy6aepdin91l2ich9r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcluy6aepdin91l2ich9r.png" alt="Coffee is for coders" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Always Be Refactoring
&lt;/h2&gt;

&lt;p&gt;Having a suite of tests facilitates another software development superpower: refactoring.&lt;/p&gt;

&lt;p&gt;Your understanding of the requirements is constantly evolving, especially in a startup. An assumption you made yesterday might be invalidated tomorrow, forcing you to rethink a feature or rework some underlying architecture code.&lt;/p&gt;

&lt;p&gt;Channeling the famous Always Be Closing (ABC) scene from &lt;a href="https://www.google.com/search?q=always+be+closing+scene&amp;amp;rlz=1C5CHFA_enUS901US901&amp;amp;oq=always+be+closing+scene&amp;amp;gs_lcrp=EgZjaHJvbWUyCQgAEEUYORiABDIICAEQABgWGB4yCAgCEAAYFhgeMggIAxAAGBYYHjIICAQQABgWGB4yCAgFEAAYFhgeMg0IBhAAGIYDGIAEGIoFMgYIBxBFGDzSAQg0MTAxajBqNKgCALACAQ&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8#fpstate=ive&amp;amp;vld=cid:0c1fc1d3,vid:O6ybfVT9gxA,st:0" rel="noopener noreferrer"&gt;Glengarry Glen Ross&lt;/a&gt;, you should "Always Be refaCtoring!"&lt;/p&gt;

&lt;p&gt;A chunk of code is rarely ever "done-done," and rarely so on the first pass. You'll learn something new about your use case and realize you need to tweak it. A new version of your programming language comes out which forces a change. A faster third-party library offers a 25% performance boost. That handful of helper functions? It's now big enough to deserve its own library.&lt;/p&gt;

&lt;p&gt;Whatever the case, you are bound to revisit a piece of code dozens or hundreds of times over a product's lifetime.&lt;/p&gt;

&lt;p&gt;Having a suite of tests available to vet those refactorings is a gift that keeps giving. It gives you carte blanche to change any existing code with a minimal amount of stress or worry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pay It Forward
&lt;/h2&gt;

&lt;p&gt;Some poor soul has to maintain your code. That person might just be you, months or years later. More likely, it'll be someone diving into it for the first time. Maybe under pressure, during an on-call incident.&lt;/p&gt;

&lt;p&gt;Ideally, we all want to be kind to this future maintainer - just as we hope the engineers before us were kind to us.&lt;/p&gt;

&lt;p&gt;A test that runs and directly invokes the target code is far more valuable than forcing someone to navigate an unfamiliar, sprawling codebase. Even better, that test becomes an example - making it easier to write new tests, reproduce bugs, and validate fixes.&lt;/p&gt;

&lt;p&gt;So, do your future self (and others) a favor: Write some tests. Pay it forward.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoi5gzy18t8gq4ssva2d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoi5gzy18t8gq4ssva2d.jpg" alt="Software engineers getting well deserved rest" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let Me Sleep
&lt;/h2&gt;

&lt;p&gt;I don't want 2 a.m. wake-up calls because some code I wrote went off the rails. Been there, done that - and I'd really rather not do it again.&lt;/p&gt;

&lt;p&gt;Pager duty is one of the least fun parts of being a software engineer. I'm generally okay supporting my own code, because I've taken the time to make sure it behaves well - not just in happy-path scenarios, but when everything goes sideways. What I dread is getting paged because someone else didn't test their code properly.&lt;/p&gt;

&lt;p&gt;Good tests are a kindness - to your future self, your teammates, and whoever ends up on call next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe Coding
&lt;/h2&gt;

&lt;p&gt;The hot new topic in software engineering is, of course, AI - and with it, the rise of "vibe coding," where you let the AI take the wheel and hope for the best.&lt;/p&gt;

&lt;p&gt;There are plenty of issues with that approach - especially around maintainability and security - but this post isn't about that. What I will say is this:&lt;/p&gt;

&lt;p&gt;AI can absolutely help you write tests. It can generate scaffolding, speed up repetitive work, and even suggest edge cases you hadn't considered. That's great.&lt;/p&gt;

&lt;p&gt;But be careful about handing over the keys entirely.&lt;/p&gt;

&lt;p&gt;You still need to think critically about your code - and make sure your tests are validating the most important concerns. That's not something you can vibe your way through.&lt;/p&gt;

&lt;p&gt;Use AI for smaller, focused chunks. Review everything. Don't let it spew out thousands of unchecked lines and call it a day.&lt;/p&gt;

&lt;p&gt;Would you drive across a bridge that was vibe architected?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x9dmx310u98pucbp6jc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x9dmx310u98pucbp6jc.jpg" alt="You are going to invest in testing now, right?" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Testing Is Not an Option
&lt;/h2&gt;

&lt;p&gt;Automated testing isn't some extra layer you add after the fact - it's a habit you build as you write code. It helps you move faster, catch problems earlier, and write cleaner, more modular software. It helps your teammates. It helps future you.&lt;/p&gt;

&lt;p&gt;You don't need a perfect test suite. You just need tests that verify the most important concerns, can be run on demand, and give you the confidence to change things without fear.&lt;/p&gt;

&lt;p&gt;Write tests not because you were told to, but because they make your job easier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Originally published at &lt;a href="https://medium.com/@DougDonohoe/you-dont-have-time-not-to-test-e82bda121d64" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; and discussed on &lt;a href="https://news.ycombinator.com/item?id=43563039" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt;.  Comments here are welcome and encouraged.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>softwaredevelopment</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Multi-Architecture Google Cloud Builds on arm64 VMs</title>
      <dc:creator>Doug Donohoe</dc:creator>
      <pubDate>Wed, 25 Jan 2023 14:37:07 +0000</pubDate>
      <link>https://dev.to/dougdonohoe/multi-architecture-google-cloud-builds-on-arm64-vms-4c7j</link>
      <guid>https://dev.to/dougdonohoe/multi-architecture-google-cloud-builds-on-arm64-vms-4c7j</guid>
      <description>&lt;p&gt;As an engineer, I lean heavily on answers other engineers give at sites like &lt;code&gt;dev.to&lt;/code&gt; or via blog posts. I like to return the favor when I can.&lt;/p&gt;

&lt;p&gt;I just published a &lt;a href="https://medium.com/@DougDonohoe/faster-multi-architecture-docker-builds-on-google-cloud-build-using-arm64-vms-and-buildx-63c7b5ac017b" rel="noopener noreferrer"&gt;Medium article&lt;/a&gt; on a series of lessons I learned about building so-called "multi-architecture" Docker images with &lt;code&gt;docker buildx&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This stems from the prevalence of Apple's new M1 / M2 chips.  I got a new Mac laptop when I started my job in June.  Practically none of our Docker images were available for &lt;code&gt;arm64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My article shows lessons learned getting to multi-architecture images, including using an &lt;code&gt;arm64&lt;/code&gt; VM on Google Cloud Build to speed up the &lt;code&gt;arm64&lt;/code&gt; half of the builds (as shown in this image):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2oguhbosrekcj31qic7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2oguhbosrekcj31qic7.png" alt="Use IAP to connect to arm64 VM from Google Cloud Build" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a &lt;a href="https://github.com/dougdonohoe/multi-arch-docker" rel="noopener noreferrer"&gt;companion git repo&lt;/a&gt; with full source code.&lt;/p&gt;

&lt;p&gt;Hopefully it can be of use to someone else!&lt;/p&gt;

</description>
      <category>help</category>
      <category>api</category>
      <category>cloud</category>
      <category>googlecloud</category>
    </item>
  </channel>
</rss>
