Here's a question I like to ask myself when thinking about whether to add a comment into a section of code: What can I convey that would help the next person understand the overall intent of the code better, so that they can update, fix, or extend it faster and more reliably?
Sometimes the correct answer to this question is that there is nothing much you can add at that point in the code, because you have already selected names and conventions that make the intent about as obvious as it can be. That means you've written solid self-documenting code, and that inserting a comment there would likely detract more than it would help. (Note that redundant comments can actually damage code reliability over time by slowing falling out of sync with the real code over time and thus making it harder to decipher the real intent.
However, in almost any program and in any programming language, you are going to encounter points where certain critical concepts and decisions made by the original programmer -- by you -- will no longer be apparent in the code. This is pretty much unavoidable because a good programmer always programs for the future -- that is, not just to make the program work once, but to make all of its many future fixes and versions and extensions and modifications and ports and who knows what to also work correctly. That latter set of goals is a lot harder, and requires a lot more thinking to do well. It is also very hard to express well in most computer languages, which are more focused on functionality -- that is, on saying what does this version of the program need to do, right now, in order to make it satisfactory.
Here's a simple example of what I mean. In most languages, a quick in-line search of a small data structure will have enough complexity that someone looking at it for the first time likely will not recognized immediately what it is. That's an opportunity for a good comment, because you can add something about the intent of your code that a later reader will likely appreciate immediately as helpful for deciphering the details.
Conversely, in languages such as the logic-based language Prolog, expressing the search of a small list can be so incredibly trivial and succinct that any comment you might add would just be noise. So, good commenting is necessarily context dependent. That includes factors such as the strengths of the language you are using and the overall program context.
The bottom line is this: Think to the future. Ask yourself what is important and obvious to you about how the program should be understood and modified in the future.
For those parts of your code that are truly self documenting, comments just add noise and increase the coherency problem for future versions. So don't add them there.
But for those parts of your code where you made a critical decision from several options, or where the code itself is complex enough that its purpose is obscure, please, add your special knowledge in the form of a comment. A good comment in such a case is one that lets some future programmer know what must be kept the same -- that is the concept of an invariant assertion, incidentally -- and what is OK to change.
 This goes beyond the issue of comments, but it's worth bringing up: If you find that you have a very crisp idea of how your code could change in the future, you should likely think beyond just making a comment and embed those parameters within the code itself, since that will almost always be a more reliable way to ensure the reliability of future versions of your code than trying to use comments to steer some unknown future person in the right direction. At the same time you also want to avoid overgeneralizing, since humans are notoriously bad at predicting the future, and that includes the future of program changes. So, try to define and capture reasonable and well-proven dimensions of future at all levels of program design, but don't let it get you distracted into an exercise in excessive generalization that is unlikely to pay of in the long term.