Photo by Elliot Wilkinson on Unsplash
I am a developer of average skill. No more, no less. I know this because, over the last 20 years, I’ve worked with plenty of exceptional developers and have seen what great looks like. I am not that. Despite having no special talent and a suspect university computer science transcript, over the last year, I’ve found myself to be more productive than most teams I’ve worked with in my career. Of course, this is primarily because I don’t have the red tape of a large organization destroying my soul, but I suspect it’s more than that. I decided to reflect on this in case I later on enter a period of low productivity, at which point I’ll at least have this list to remind myself of what worked.
These are in no particular order with the numbering being incidental. They may not work for you. They work for me.
I don’t have to ask permission to do what’s right, making decisions fast. I differentiate between reversible and irreversible decisions - most decisions are reversible and the cost to reverse them is not high. In general, defer all decisions to the latest responsible moment.
I don’t have to go through several intermediaries to get basic questions answered - the answer to any question is usually one conversation away. If somehow it becomes two conversations away, I strive to reduce it back to one as I don’t like gates.
I take to heart the Kent Beck quote, “Make the change easy, then make the easy change”. I think of it as “do the refactoring that makes the change easy”. It makes for cleaner code and avoids large refactorings.
I experiment with different things knowing full well that some of the code I write will never make it to production, but know that the experience will yield a higher-quality result.
I don’t spend time mulling over visual design decisions. I show my software to a professional designer once in a while and get them to critique it.
I have admin access to everything and never need to wait for permission to be granted to some tool that I need to do my job.
I validate my assumptions by talking directly to the customer, not someone who claims to represent the customer.
I can program in multiple stacks and don’t need to wait for someone to do “their part” for the feature to work.
I write down requirements myself after talking to people, not have them handed to me in an issue. So the flow is Person → My Head → Paper. Not Person → Paper → My Head. I find having the requirements “travel” through your body helps.
I don’t use Jira and instead use GitHub issues which keeps all my issues closer to my code. I don’t spend time writing user stories, I just write down enough to get me started to think about the problem - this usually takes 1-2 minutes.
I think of the production logs (and Sentry) as a major input to my backlog, which has helped steadily improve the stability of the system. I find myself spending less and less time on production issues, freeing me up for more valuable work.
I don’t write tests for everything, especially stuff where the framework is doing the heavy lifting. I assume the framework is well-tested.
I practice Test Driven Development to help me think through what a good API should look like.
I have a healthy relationship with every team I have to integrate my code with, even though we sometimes disagree on things. Having good relationships with people reduces anxiety and fear of asking questions.
I try to write cleanly so that my perspective is easily understood by the other party. SCQA and Pyramid Principle help me a lot, especially as I find myself using tools like Slack and Discord to communicate more and more.
I don’t put pressure on myself by committing to arbitrary dates. When someone asks for a date, I ask them what the cost of delay is. They never have a good answer.
When I’m debating between two options and can’t make up my mind, I just pick one and don’t beat myself up if I have to reverse and change my mind later on. I find the cost of indecision to be often higher than the cost of a poor decision.
When I’m stuck on a problem I talk it out loud with my wife (thanks for listening Sana).
I don’t confine myself to the engineering box, I’ll do what’s needed (I don’t have a choice). I might suck at it but I’ll try to confirm whether I suck at it. I’m usually competent enough to get by, although never exceptional.
I apply new learnings to older solutions, i.e., I iterate even when the job is “done” because there is no such thing as done. Much like a writer, I’m critical of older code I’ve written so even when the feature is done and stable, I still go back to update things.
I rarely use branches (rare exceptions being some major upgrades) and whenever I end up using them, I usually regret it.
I don’t think any improvement is too small. An “extract method” refactor. A better name for a variable. Moving the file to a more logical location. I’m obsessed with doing these and the value adds up quickly.
I automate database migrations and don’t have a gatekeeper managing data stores. I love Ecto and could not imagine having to coordinate database changes ever again.
I avoid end-to-end tests (e.g., Cypress, Playwright) as they aren’t worth the maintenance cost and don’t give me any valuable feedback on whether my software is working.
I don’t have a test environment. I flirted with one, it wasted a lot of my time just to keep things in sync. I prefer to test in production by creating test accounts. Performance and penetration testing can become problematic, but I’d rather deal with it locally.
I use spoofing to diagnose and reproduce most customer issues.
I have good levels of observability thanks to some great tooling - thank you Posthog and Better Stack.
I keep a pulse on the channels (e.g., Discord) of major libraries that I’m using (e.g., NextJS), especially the #help channels because people invariable ask questions that apply to me. I find that most people want to help and a problem well-stated is one that’s half-solved.
I think of “future-proofing” as creating changeable designs, not creating sophisticated designs. Closely related to YAGNI except that I try to evaluate the cost of a possible future change before deciding I don’t need it. It’s not always easy but the thought experiment is worth it.
I use MermaidJS and Notion to create architecture diagrams (mostly sequence) to help me think through and document things so that I know why I made a particular decision at a certain time.
I use a Git-friendly API Client (Bruno) that allows me to check in my API test calls so that I remember how I had gotten the API to work way back when I first wrote it.
I use Prettier so that all my code across all repos looks consistent. When using Elixir, I love mix format. Both can be configured as pre-commit hooks.
When I’m stuck on a problem, I step away. It sounds cliche, but stepping away gives you a fresh perspective on things. As I often work alone, stepping away is my second opinion.
I try to follow Kent Beck’s four design rules , especially “reveals intention”. I’ve tried to go away from
getCheckoutFields(userId)
togetActiveCheckoutFieldsForAccountByUserId(userId)
. There is no cost to verbosity and it saves me time whenI come back later to look at this code.I refer to the agile principles at least once a week - they are timeless and grounding. The Agile Industrial Complex may have corrupted Agile, but agile lives on.
AI has been useful - I’ve been using it to improve code and am trying out Amazon CodeWhisperer. So far it’s been a net positive and I think it’ll only get better.