A well written code could read almost like a verbal explanation of what's happening. So, with that in mind, I try to avoid comments by reaching for a good architecture and well named structures (classes, methods, variables, etc).
Sometimes we fail to express ourselves with code. That's when we really need comments (but it should be rare!). In those cases, I try to keep them very objective (small) and close to the code it's about.
Tests are also a very good way to document your code without comments.