> My main solution is to use good function and variable names.
The problem with this is that it isn't actually solution. A name can't contain much that's helpful to know about a thing, which should be obvious given how much prose and narrative everyone produces and consumes. The program itself can only tell you what. Names can sometimes hint at intent, but they they're not very good at it (given the incentive for terseness). Comments are good for explaining the "whys" for a thing as well as intent. Those are important things to know when debugging a problem or trying to enhance something.
Comments shouldn't be thought of as the one thing you need to look at to understand some code. You really have to look at a whole constellation of things (comments, implementation, commit history, tribal knowledge) and synthesize them. When you take that approach, even out of date comments can be more useful than no comments.
> I understand what you mean by "function and variable names are comments". We do use them to describe what the code does. Still, they are actually code :)
They're not any more code than comments are. Run an obfuscator and change all names to random strings and everything will still work fine, just like if you ran a program to strip out comments.