Commit frequently, and pushing my work to Github. It allows private repos so not everything needs to be shown. It may not be useful now, it may be throw away, but I might be doing something else one day and realize I've got a snippet or some learnings in that other repo, so I can go reference it. On the other hand if I made a script or utility for myself, no harm in making it public. Even if there's nobody using or even looking at it. Doing this makes me conscientious about my repos and project structure.
I try to cross-reference my code with any relevant tickets/Jira issues/Confluence pages, leaving a bunch of comments like "// Works around backend checkout API off-by-one bug that can't be changed right now... see #BE-1234". And the Jira ticket itself will link back to the commit and mention a specific line if possible. But those things are ephemeral. Licenses changes, middle managers change, companies change.
The code is the only thing I write that might still be read years after I leave. Even if the Jira instance itself is gone, someone might still be able to search for "BE-1234" in their Slack or email history or whatever and maybe figure out the gist of what the original rationale was, or at least know that it was some sort of off-by-one bug that they need to further investigate. If it's not in a comment next to the code, it's not real institutional memory, just some other abandoned document in the graveyard.
I assume every line of code I write will be obsolete and rewritten within 2-3 years anyway (such is web dev life). The code itself doesn't really even matter (it's just a mishmash of JS fashion trends over the years), but the comments are the only things really illuminating the business need that caused the code in the first place, especially the weird idiosyncrasies and exceptions.
Any new programmer to the team can (hopefully) read the comment and figure out what they need to code, but if they just look at the code without any accompanying English description, they will often try to reverse engineer it, get the obvious parts, but totally miss the deliberate exceptions.
Writing specific tests for any non-obvious behavior really helps too, especially when a function or component behaves as expected for 95% of use cases but deliberately changes its behavior for some 5% of edge cases (like needing to support grandfathered data from another pipeline or whatever).
- Reading a new codebase's tests: it gets you familiar with it really fast. Often, it's even better than the documentation because test functions have these long, descriptive names about what they're testing, and show you strange ways to use the code that often are not even mentioned in the docs. Tests also point to where the party's at. People tend to test the important parts.
- For libraries, I write the library's documentation before writing the code, then write stubs that return the same values for the parameters in the docs, then give it to non developers and ask them to follow the documentation. I don't help them. I just give them an environment ready to use, the documentation, and I shut up and observe their every move. Every eye twitch, every facial expression, every keystroke. I don't correct their "mistakes", I simply take notes of their "mistakes" and put them on "bad developer experience", i.e: my bad. I won't risk polluting raw, pure, data with hints or help because it's not a realistic situation: I won't be with whoever will use it in real life. Doing this shines a bright light on all the things I thought were "obvious" or "normal", all the bad design decisions.
If they could follow the documentation and run the code, I know that developers can. I show it to developers and ask them and see how quickly they're going through it: if someone just takes a glance at the docs and starts writing code without looking again at the docs except for values/parameters, I have impedance matching and the interface/API "makes sense", and they're not being slowed down by weird implementation details that make them go back to the documentation. It made sense enough that they've got it loaded in their head with a glance and they're off using it to do whatever else they set out to do. It won't become something they think about.
This is also valid for applications and UX. I put them facing the application, keep my lips clasped, and observe. Down the line, I think about the millions of hours, and millions of dollars on "Customer Success" or "Customer Support" that will be saved. I think about all we can deliver per person if we're not busy helping people figure out things they shouldn't have needed help for in the first place.
- Commenting intent and rationale behind the implementation to make the XY problem obvious: someone reading might detect that my logic is flawed, or an assumption that is, or has become, false. References to other related issues and information about why it was written that way.
- Logging. Logging. Beautiful loggers.
- Error tracking and performance monitoring. Sort by severity and frequency, then solving that.
- Abstracting but stopping at N=3 or 4. For example, say you're on AWS and writing code that dynamically creates Kubernetes clusters on EKS... You can start with N=1, very concrete, writing code for a Kubernetes cluster on EKS, but your code would look eerily similar to create_eks_cluster. Maybe not you, but there will be a bias towards that, and that bias will carry on and will not only be hard to change, but will also dictacte future code because of inertia. In for a variable, in for a module.
However, if you say, let's support doing that on the three or four major cloud providers, you'll find that even if you don't end up supporting them, your variable names, your functions, your classes, your modules, will not be tied to one cloud provider, and your code will be better.
I say a spectrum, because the pitfall is that some people will want to go higher on the abstraction, and they end up spending way too much time trying to write the most generic code that ends up making coffee. Hence limiting to 3 or 4: even if you don't end up supporting them, the code will be better.
- Documentation. I'm shot in the head. "Future me" thanks "past me" often enough to know that, for another person, documentation will be even more useful than it was for me.
- Tests. Writing them, reading them as stated above.
And much more,