I don't think of myself as a great developer, and sometimes not even as a good one. I have a lot of experience with some solutions, but I'm not a prodigy. I've been programming professional for around 8 years, and have done a lot of things I'm proud of, but I still struggle even with simple things that I previously understood and applied. I have trouble remembering the code I recently wrote, and I struggle to work in systems that require a lot of memorization of context.
I'm not great, but maybe that's good.
Growth
When I got started in programming, I found myself in a seemingly endless world of languages, patterns, tools, and solutions. Lit on fire, I consumed every ounce of information I could as if it were the first thing I'd eaten in a week. An endless feed of puzzles and solutions at my fingertips fueled my junior energy.
Every time I learned a new pattern, I sought out nails for my new ultimate hammer. I'd bang the hammer on everything to see how it did, until I learned what it helps, and what it destroys. As the hammer destroyed, I'd find more nuanced tools as I slowly (very slowly) learned what was useful in which situations.
Bursting with righteous confidence I'd apply the many patterns to greenfield projects, and even urge that terrible code could be made better, if only they were refactored into patterns that I understood. The patterns would shift again and again over time, and code would sometimes be refactored repeatedly as I grew. This is what happens with growth, and it's important to swing the hammers to learn the differences between them.
Inevitably I began making "improvements" to systems that frustrated me, only to find out in the end that I didn't improve them at all... I had only made them different. Doing this kind of refactoring was fine for solo projects, but they often went far against convention. On projects with teams, or in opinionated environments, these kinds of radical shifts didn't make anything better. Instead, they made my code harder to understand and maintain. Not only for other developers, but also for myself.
Deep in the weeds
When deeply embedded in a single project and riding the waves of flow state, there's a lot of contextual information that comes easily. Decisions chain on decisions, blending with purpose, and elegantly moving towards a final solution.
Context is critical here, because the more complicated the problem, typically, the more context that is needed. The larger the scope of the problem, the more abstractions that are needed to create a clean and cohesive solution.
Repetition feels like the enemy of developers. Mixed with scope and complexity, avoiding it leads to developers creating all sorts of abstractions to reduce boilerplate and increase reusability.
The more the problem grows, the more revision and maintenance the growing abstractions require. A large ship is hard to steer, but a complicated one is hard to maintain.
Forgetting
When I'm face down, focused, and riding the waves of productivity, all of my solutions make sense. It becomes easy for me to forget that that the solution that makes sense to me might be really difficult for another developer to understand. Even if the code seems elegantly written from my perspective, it's usually only the temporary context floating around in my memory that makes it so.
Once some poor soul either takes the time to understand my pull request, or simply nitpicks tiny patterns and clicks approve, I start to forget. It may be a day, a week, or even months before I have to return to that work, but by that time, I've usually forgotten. I'm forced to look at my complicated abstractions as if it was written by someone else.
I've met many developers who seem to magically remember every line of code they've ever written, but over time I've come to accept that I'm not one of them. Instead, I've learned to give into it, and try to lean on convention and existing project patterns until it's absolutely clear that a change adds more value than it detracts.
I know that I'll forget, so I try to keep custom abstractions minimized and conventions in a codebase consistent. As a result, I hope that I end up writing code that is easier to maintain for others, as well as myself.
The Reality
I still refactor the living hell out of codebases! Although, I no longer do it to keep work interesting trying to be a purist. Instead I argue for patterns that will be easier to maintain for new developers as well as my forgetful self.
In development, we build on abstractions, and, as a codebase grows, we NEED custom abstractions. I'm not arguing that we should stop progress and only do the things we know. Instead, I'm arguing that abstractions should make a codebase easier to maintain, rather than harder. Custom solutions should hide complexity, not just add more for the sake of programmatic purity or interest.
The more files a developer has to remember to modify across a stack to achieve a basic task, the more context they have to remember, and the more difficult it becomes for them to do work. If, within an existing framework, a developer would normally change 6 files to create a CRUD operation, but, due to custom abstractions, they now have to change 12, then the productivity of the developer will drop. If that same developer has to only change 2 files, instead of 6, but they have to first understand and possibly modify a custom set of undocumented tooling to do so, likely, their productivity will drop.
When writing abstractions, try to use existing libraries first, and start by using them the way they were intended, rather than the way you want them to be. A developer in an ecosystem will either already be familiar with the solution, or they'll be able to rely on existing documentation and tutorials to learn it. If you're writing libraries that mimic common solutions, but, y'know, the way that it "should" have worked, you're creating a maintenance, learning, and context overhead that will likely harm more than it helps.
If you're creating reusable abstractions that are truly useful to your organization, then make sure you're developing it as if it could be used by anyone, and with minimal understanding of the internals. If you're not willing to include the appropriate documentation, tests, and types(if relevant), then you're creating more of a problem than a solution in the long run.
Write comments that describe purpose (especially if it shows up with intellisense). Use variable names that read easily, rather than just terse. Keep abstracted business logic shallow, rather than 20 files deep. Try to write line by line as if you're telling a story, so that it can be read more like a book than a pinball machine.
Finally
Create abstractions that make code more readable, rather than just programmatically nice. Create solutions that handle things for the consuming developer, rather than forcing them to overprepare data for your API. Push for refactoring that enhances the code, rather than just making it different.
Write code as if it's for someone else.
Write code as if you'll forget.
Top comments (0)