How to write code someone else can change.

•

•

Write for the Person Who Edits It Angry

I once spent three days trying to untangle a “clever” abstraction in a logistics codebase, only to realize the guy who wrote it had moved on to a different industry two years prior. He hadn’t just left behind technical debt; he’d left behind a puzzle box that required a specific, undocumented mental model to solve. We spend so much time obsessing over design patterns and the latest architectural trends, but we rarely talk about the actual goal: how to write code someone else can change without needing a private investigator to find the original author. Most of the “clean code” advice out there feels like it was written for a textbook, not for a tired engineer trying to push a hotfix on a Tuesday afternoon.

I’m not here to sell you on a specific framework or a new way to map your dependencies. Instead, I want to share the practical, unvarnished lessons I’ve picked up from a decade of seeing things break in production. I’ll focus on the boring but essential habits—the ones that actually make a codebase resilient to the inevitable turnover of a dev team. We’re going to talk about making your intentions visible, managing the trade-offs of abstraction, and ensuring that the next person who touches your work doesn’t hate you for it.

Table of Contents

Using Naming Conventions for Developers to Prevent Confusion

Using Naming Conventions for Developers to Prevent Confusion

I used to think that if I added enough comments, I could get away with naming a variable `data` or `temp_list`. I was wrong. Comments often lie because they rot as the code evolves, but the code itself is the truth. I’ve spent far too many hours during a late-night incident trying to figure out what `process_item()` actually does, only to realize it’s performing a database write, a cache invalidation, and an external API call all at once.

When I talk about writing self-documenting code, I’m not talking about being flowery or verbose. I’m talking about being precise. If a function calculates a tax rate based on a user’s location, call it `calculate_vat_rate_by_region()`. It’s longer, sure, but it removes the mental overhead for the person reading it. This is one of those basic naming conventions for developers that people dismiss as pedantic until they are staring at a 500-line pull request and realize they have no idea what the state of the system is.

The goal isn’t to follow a textbook; it’s to reduce the cognitive load for the next person. When you name things with their intent in mind, you aren’t just writing code—you’re leaving a map.

How Modular Programming Techniques Save Your Sanity

How Modular Programming Techniques Save Your Sanity.

I used to think modularity was just something professors talked about to make their lectures sound important. Then I worked at that logistics startup—the one that folded—and I saw what happens when a single service becomes a “god object” that handles everything from shipping labels to tax calculations. When that one service went down, the entire platform died because everything was tightly coupled. I learned then that modular programming techniques aren’t about being academically pure; they are about containment. If you can isolate a failure to a single, small module, you might actually get to finish your coffee before you have to start the rollback.

The goal isn’t to build a perfect system, but to make sure that when the next person (which is often me, six months later) needs to change how the payment gateway works, they don’t accidentally break the email notification service. By applying basic clean code principles and keeping your logic decoupled, you are effectively reducing technical debt before it even has a chance to accrue interest. I try to write my modules like they are Lego bricks: they should have clear inputs, predictable outputs, and they shouldn’t care what the rest of the castle looks like. It makes the inevitable code review much less of a headache when the scope of a change is actually visible.

Five things I've learned from breaking things in production

  • Write the “why,” not the “how.” I don’t need a comment telling me that `i++` increments a variable; I’ve been coding long enough to know that. I need the comment that explains why we’re using a linear search here instead of a hash map, even though it looks suboptimal. Usually, it’s because the dataset is guaranteed to be under ten elements, and the overhead of a map would actually be slower. If you don’t document the constraint, the next person will “optimize” it right into a performance regression.
  • Treat your public API like a contract, not a suggestion. When I was at the logistics startup, we changed a field from an integer to a string in a shared library without thinking. It took three days to realize we’d broken the downstream billing service. If you’re writing a function that others will call, once it’s merged, it’s basically carved in stone. If you have to change it, you’re not just changing code; you’re breaking a promise.
  • Minimize the “surprise factor” in your side effects. There is nothing more terrifying than calling a function named `calculateTotal()` only to find out it also triggers an email notification and updates a database row. If a function does something that isn’t reflected in its name, you’re setting a trap for the person who has to debug it at 2 AM. Keep the logic predictable.
  • Favor composition over deep inheritance hierarchies. I once spent an entire afternoon tracing a bug through five layers of abstract classes in an old insurance system. It was like a Russian nesting doll of technical debt. When you keep things flat and composed, you can actually see the data moving. It makes the code easier to reason about, and more importantly, easier to rip out and replace when the requirements inevitably change.
  • Write tests that actually describe the behavior, not just the implementation. If your test suite breaks every time you rename a private variable, your tests are too tightly coupled to the code. You want tests that act as a safety net, confirming that the business logic still works even if you decide to refactor the internal plumbing. If the tests don’t give you the confidence to refactor, they’re just extra work you have to maintain.

The stuff I actually carry with me

Name things for the person who has to fix them, not for the person who wrote them. If a variable name requires a comment to explain its purpose, you’ve already lost the battle.

Keep your modules small enough that you can hold the entire logic in your head at once. If you have to jump through five different files just to understand one function, you’re building a labyrinth, not a system.

Document the “why,” not the “how.” The code tells me what is happening; I need your notes to tell me why you chose this specific, weird workaround instead of the obvious one.

The Long View

The Long View of writing maintainable code.

At the end of the day, writing code that someone else can change isn’t about following a specific style guide or mastering the latest design pattern. It’s about the practicalities we’ve discussed: using naming conventions that actually describe the intent of the logic, and keeping things modular so a single change doesn’t trigger a house of cards collapse across the entire system. I’ve spent too many hours untangling “clever” code that was supposed to be efficient but ended up being completely unreadable. If you focus on making your modules predictable and your variables obvious, you aren’t just writing better software; you are reducing the cognitive load required to maintain it when things inevitably go sideways.

I used to think that being a “senior” engineer meant knowing the most obscure language features. Now, I realize it mostly means having the discipline to write code for the human who has to touch it six months from now—and realizing that person is often you, slightly more tired and significantly more stressed. We don’t write code for the compiler; the compiler is easy. We write it for our teammates and our future selves. If you can leave a codebase that doesn’t require a manual to navigate, you’ve already won the most important battle in engineering. Write for clarity, not for ego.

Frequently Asked Questions

How do I find the balance between descriptive names and making the code so verbose that it becomes unreadable?

I used to name everything `UserAuthenticationServiceWithDatabaseConnection` because I thought I was being helpful. It wasn’t. It was just noise.

At what point does breaking a module down further become "over-engineering" rather than helpful abstraction?

I hit the “over-engineering” wall when I start naming things based on their theoretical role rather than what they actually do. If I’m creating an `AbstractTransactionProcessorFactory` to handle a simple database write, I’ve gone too far. A good rule of thumb: if you can’t explain the module’s purpose without using the word “generic” or “provider,” you’re likely building a labyrinth. Abstract when the logic is complex; stay concrete when the logic is simple.

If I'm working in a legacy codebase that already ignores these principles, how do I start introducing better patterns without breaking everything?

Don’t try to rewrite the whole thing. You’ll just end up in a merge conflict hellscape that no one has the energy to fix. Instead, I follow the Boy Scout rule: leave the code slightly cleaner than you found it. If you’re touching a messy function to add a feature, refactor just that small piece. Wrap the old logic in a new, clean interface. It’s slow, but it’s how you actually win without breaking production.

How much documentation is actually "enough" before I'm just wasting time that should be spent on the logic itself?

I used to think documentation was a separate task, something I’d do “later.” That was a lie. Documentation is “enough” when the code explains the what and the how, but the comments explain the why. If I have to spend ten minutes tracing a logic branch to understand why a specific edge case was handled that way, I’ve failed. Document the trade-offs and the “weird” parts; let the clean code handle the rest.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.