Actually even relatively simple programs can be hard to understand deeply and give rise to confusion, because they describe a complex computation; "complex" precisely in the sense that the computation's detailed shape is hard to deduce from the program text. (Langton's Ant being a good example, though without much practical relevance.)
For an interesting real-world example around which I had trouble bending my mind recently see mustache, a Ruby program which does template expansion. The challenge was to get this to handle indentation correctly. It's written well enough, it even has a nice set of unit tests.
But it made me realize that "indentation" was very much a harder concept to define formally than to grasp intuitively, and that it cut across the grain of the program's current design in interesting and non-obvious ways.
OK - that's entirely fair. The map-territory distinction I describe above reinforces a somewhat different set of rationality skills of rationality.
I've developed a sense for when I understand an algorithm to the point of being able to program it, rather than understanding it sufficiently to explain it or expect it to work. Feeling like I know how to code an algorithm is just like feeling sure that a mathematical proof is correct. They have a specific sort of robust clarity that most thought lacks. I hadn't really thought about it, but that must be a learned feeling. I expect it's a valuable sense to have; if programming can teach it, then more power to learning programming.
An art with a history
Deep implications
1 This history is sadly ignored by a majority of practicing programmers, to detrimental effect. Inventions pioneered in Lisp thirty or forty years ago are being rediscovered and touted as "revolutions" every few years in languages such as Java or C# - closures, aspects, metaprogramming...