Dan is the founder of Tellspin, an on-call scheduler in Slack for DevOps and developers (https://tellspin.app). Helping workspaces reduce their contact footprint, resolve incidents faster, and regain deep focus.
Code smell is a way to describe code that hasn’t aged well and has the potential for a lot of issues.
It usually is the source of a lot of hot fixes or workarounds keeping it functional. My most common reflex is to rewrite it. However, if I’m not careful, I’ll waste an entire day and not improve anything.
After a decade of programming, here are my 7 steps to reduce code smell gradually.
Step 0: Admit there is a problem
I start to recognize my code is smelly when I start saying things like “that time only took an hour.”
I’m usually doing something simple, like adding another field to a form or another schedule for a customer. I quickly add in code because it feels like the easiest thing to do and ship the feature. There are so many other things on my plate, I don’t have time for this, I’ll say to myself.
By the 5th or 6th hour I’ve hacked the same spot, I realize, had I rewritten it sooner, I would have actually saved time.
Step 1: Identify spots to clean
Smelly code is so disorganized.
Is it really smelly or do I just not understand it? It’s very tempting to always default to a rewrite. If I write all the code, I’ll understand it. But who is to say the next person who looks at it will?
Similar to profiling code to identify the slowest spot, I work to identify the place that smells the most. Are there sections of the code that new devs are always struggling with? Are there frequent small changes that require touching lots of different files or methods?
Creating a list of smelly code helps identify which sections of code need the most attention.
Step 2: Pick the worst spot
Smelly code is like dirty dishes.
With a stack of dishes, I’ll plug my nose until I dispose of the rotting food that’s causing the stink. It was easy to blame the whole pile, but for the most part, all of the other dishes are fairly clean. They don’t need immediate attention. The rotting smell came from something I forgot to clean off when I was in a hurry.
When there is a piece of code that’s really rotten, it’s often hidden somewhere in the pile. Maybe an abstraction went too far, spreading a hundred lines of code across dozens of files.
I keep in mind that I need to fix the worst smell; most of the other code is good enough and doesn’t need my immediate attention.
Step 3: Resist the urge to do everything
Smelly code is never-ending.
Perhaps the hardest part of improving a code base is scoping it to one thing. It’s so liberating to finally get a chance to clean up, that I can easily take it too far. I’ll think, “While I’m at it, I might as well clean up this… oh! and that other thing needs fixing too.”
Resist! Do not do everything.
If I try to tackle everything, I’m not going to finish. Even more likely, it’s not going to pass code review. It’s better to do one piece at a time - ya know, like eating an elephant.
Step 4: Make sure it’s better
Smelly code has edge cases.
Inevitably, in the process of rewriting, I discover why the code was written that way in the first place. I might even stumble across a can of worms. At that point, I realize my not-so-dimwitted co-worker wasn’t as dumb as I thought (or even more likely, I discover I was the one who wrote the code originally 🤦♂️).
After learning all the edge cases, I’ll be tempted to walk away.
Step 5: Don’t immediately give up
Smelly code is messy to work with.
I’m frustrated imagining how far away the current code is from a better solution. I’ve got the code in my head, I know the edge cases, and I’ve got the context. It’s important not to give up as the solution may be right around the corner.
I keep thinking about it while I go for a walk. Maybe even take a break. Solutions often come to me while I’m on walks or in the shower.
Step 6: Use the co-worker bobblehead
Smelly code needs attention.
I steal my co-worker’s bobblehead and explain aloud what I’m doing. In the process, I figure out what I've missed or overlooked.
If a bobble head isn’t available, I resort to using my actual co-workers. (I’m checking my assumptions by walking them through what I’m thinking step by step.)
Step 7: Publish or throw in the towel
Smelly code can improve.
At the end of my steps I have a complete solution or I’m banging my head on the keyboard. If it’s the first, I push the change and take a breath of fresh air. If it’s the second, I commit it to a branch and plan to revisit another day. Sometimes we can’t have nice things.
Rinse and repeat
The depth I go into each step changes based on complexity or how critical the code is. Sometimes I can run through each of the steps in a few minutes, other times it’s spread out over a few weeks. It really depends on what I’m working on.
Running through these steps helps me gradually improve my code. There’s nothing better than finally getting a fix for some smelly code merged and into production. Sometimes we can have nice things.
Dan Willoughby is the founder of Tellspin, an on-call scheduler in Slack for DevOps and developers (https://tellspin.app). Helping workspaces reduce their contact footprint, resolve incidents faster, and regain deep focus.
Starved for top-level software engineering content? Need some good tips on how to manage your team? This article is inspired by Dev Interrupted - the go-to podcast for engineering leaders.
Dev Interrupted features expert guests from around the world to explore strategy and day-to-day topics ranging from dev team metrics to accelerating delivery. With new guests every week from Google to small startups, the Dev Interrupted Podcast is a fresh look at the world of software engineering and engineering management.
Listen and subscribe on your streaming service of choice today.
To call Google a titan of the tech industry would be an understatement. Their name has become synonymous with the internet itself. The very act of retrieving information from the internet - the core functionality of the internet and its most basic purpose - is known simply as “Googling” something. On their road to becoming the web’s biggest search engine and a moniker for the internet itself, Google also pioneered much of what it takes to grow a company at scale.
On the Dev Interrupted Podcast Google Senior Engineer’s Hyrum Wright and Titus Winters, shared their lessons learned from programming at Google with LinearB Co-Founder and COO, Dan Lines. Both engineers have a deep understanding of the principles behind software development: Hyrum is semi-famous as the "Hyrum" of Hyrum's Law; while Titus is responsible for 250 million lines of code that over 12,000 developers work on.
But what lessons can we take from their interview - and their book Software Engineering at Google: Lessons Learned from Programming Over Time? How can we apply those lessons to our own projects? I’ve pulled out the core takeaways from their interview and condensed them so that any developer or company, be they responsible for 2,000 lines of code or 2,000,000, can learn something from Google’s roadmap.
Why listen to Google
In spite of their enormous success and scale, Google doesn’t pretend to have all the answers; this lack of presumption is exactly the thinking that has made them the titan they are. Previous success is no guarantee of future success and no one understands this better than Google.
One thing that Google is very good at is not accepting how everyone else does it as the one true way.” - Titus Winters, on the Dev Interrupted podcast at 20:35
It’s easy to assume that events and conclusions are foregone, or that one event naturally follows another. Yet this is rarely the case. Most of the time people are working towards a specific outcome, and it’s not until later that the outcome is apparent contextually. This is true in life and software development.
Google has spent the past couple decades approaching everything they do as trial and error, learning what does and does not work, and trying to institutionalize the things that do work. This is not a straight path.
This mindset is obvious in Hyrum and Titus’ interview. Titus uses the analogy of Lewis and Clark to explain how the software development process at Google has unfolded.
“They say Lewis and Clark explored the Louisiana Purchase, by which we mean, they took one path out and one path back, which is not exactly mapping, but in a similar way, we're trying to give an exploration/trip-report/map.” - Titus Winters, on the Dev Interrupted podcast at 6:17
He’s admitting that Google doesn’t have all of the answers; that Google’s path isn’t the only path to success; that other paths may be superior to Google’s; and that their path may not work for every business. But, with decades of experience, Google has learned a thing or two along the way, and maybe, just maybe, we can all learn something from the path they have trailblazed.
After all, there aren’t many companies in the world that have hundreds of millions of lines of code.
The 3 Pillars of Software Engineering at Google
“Anyone can change a line of code or change 10 lines of code. But how about changing 10,000 lines of code or 100,000 lines of code in a reasonable time? - Hyrum Wright, on the Dev Interrupted Podcast at 17:15
With so much code to manage, Google has made maintaining their code a strategic goal. Code must be fresh and able to sustain changes to the code base for business or technical reasons. To best allow for this, they have identified 3 concepts that they believe are core to software engineering.
All of the hardest stuff that software engineers are going to have to deal with like skew version, backwards compatibility, issues with data storage, dependency management, upgrades and many more, are all problems created by time. Once dev teams realize this, it will change their perspective on how best to write code.
For instance, if you are going to get rid of your code within one or two years, then you probably don’t need to worry too much about making changes or upgrades to it in the future. But if you are going to write code that will remain in use after five or ten years, then you may want to approach it differently.
If dev teams want their code base to last, they need to think about constructing code so that it can sustain changes within an organization’s lifespan. This fundamental realization allows time to peacefully coexist with the second pillar.
How your system scales is a relationship with time. Scaling isn’t a new problem and Google has been at the forefront of pushing scale their entire existence. For instance, email existed before Gmail and search engines existed before Google, but Google’s brilliance was their ability to scale these technologies better than their competitors. It’s the root of their success.
To beat their competitors they adopted a mindset of scaling as a process - a continual evolution.
As a company grows, all of its operations expand and that continued expansion requires more resources, which begets even more resources still. This means growth cannot occur superlinearly because if it does, eventually a company will consume all of its resources maintaining the status quo.
The key takeaway is to make sure your codebase and software both scale sublinearly, that way if your codebase doubles or triples you won’t need six times as many engineers just to maintain your systems. (Sublinear scaling refers to team growth that occurs more slowly than the number of supported services of a company. Superlinear growth is the opposite - with team growth outpacing the number of supported services.
3. Trade-offs and Costs
After taking into consideration the best practices around time and scale, what is left is good decision making. Just as Hyrum and Titus note in their book, “in software engineering, as in life, good choices lead to good outcomes.”
However, no organization has perfect data on which to base every decision, and therefore must strive to make the best decisions they can with the most data possible. People need insights into what an organization finds impactful.
For instance, if an engineer spends a week on a project, it should probably be a project the organization considers a priority. Because if it is not, then no matter how perfect the code, it probably wasn’t the best use of the engineer’s time. Brain power should be devoted to the most difficult problems, not where a semicolon should be placed. The cost of incorrectly evaluating trade-offs is failing the 1st two pillars.
While Hyrum and Titus may not be Lewis and Clark reborn, they have a valuable story to tell about trial and error in the Information Age.
How a company scales is likely to define how it differentiates itself from its competitors and whether or not it will be successful. A company that can scale sublinearly will thrive, all others will stagnate as victims of their own success.
Minding the principles of these modern-day explorations into the wilderness of code will help any organization keep an eye on the most valuable resource: time. But remember just as Lewis and Clark found one path forward, they didn’t find the only path forward.
We can all learn something from Google, but never forget the path forward is your own.
Join the Dev Interrupted Community
With over 2500 members, the Dev Interrupted Discord Community is the best place for Engineering Leaders to engage in daily conversation. No sales people allowed. Join the community >>