Recommended reading: Clean Code by Robert C. Martin.
In brief, you should
- use meaningful variable/method/class names,
- keep your functions/methods short,
- have each class and method do only one thing,
- have the code in each method be on the same level of abstraction.
Don't fear of extracting even moderately complex expressions from if
statements; which one is clearer to read, this
if (i >= 0 && (v.size() < u || d == e)) ...
or
if (foundNewLocalMaximum()) ...
(Don't try to find any meaning in the first code snippet, I just made it up :-)
Comments in clean code are almost never needed. The only exceptions I can think of is if you are using some obscure language feature (e.g. C++ template metaprogramming) or algorithm, and you give a reference to the source of the method/algorithm and its implementation details in a comment.
The main reason why any other kind of comments is not very useful in the long run is that code changes, and comments tend to not be updated alongside the changes in the corresponding code. So after a while the comment is not simply useless, but it is misleading: it tells you something (implementation notes, reasoning about design choices, bug fixes etc.) which refers to a version of the code which is long gone, and you have no idea whether it is relevant anymore for the current version of the code.
Another reason why I think that "why I chose this solution" is most often not worth documenting in the code, is that the brief version of such a comment would almost always be like either "because I think this is the best way", or a reference to e.g. "The C++ Programming Language, ch. 5.2.1", and the long version would be a three-page essay. I think that an experienced programmer most often sees and understands why the code is written like this without much explanation, and a beginner may not understand even the explanation itself - it's not worth trying to cover everyone.
Last but not least, IMO unit tests are almost always a better way of documentation than code comments: your unit tests do document your understanding, assumptions and reasoning about the code quite efficiently, moreover you are automatically reminded to keep them in sync with the code whenever you break them (well, provided you actually run them with your build...).