All Software Is Complex
Once we get beyond the typical "toy" teaching example that's less than 100 lines long, understanding the entire application takes time and effort. Individually, each line of code is nearly always simple and easy to understand. Yes, sometimes you have to Read That Fine Manual (RTFM), as was the case with the collect! idiom in Part 1 of this series, "Ruby for the Nuby."
The complexity in an application lies more in the interactions between the effects of each line of code rather than in the individual lines themselves, which are more or less easy to comprehend. The complexity arises because the data on which one line of code operates may have been changed by any number of lines of code that preceded the one you're looking at.
Object-oriented programming languages such as Ruby reduce this complexity somewhat by encapsulating all data inside objects, so the volume of code that can directly affect any data is reduced. Whenever the data within an object is changed, you know that it had to have been changed by one of the object's methods. This drastically decreases the complexity of the task facing us as developers when we want to understand what some code is doing.
The complexity is further reduced by the fact that with objects, most programmers try to give the methods intention-revealing names, while keeping the methods themselves relatively short. This means that it's relatively safe to infer from the name of a method what is happening. Ruby takes this even further by allowing trailing question marks (?) and exclamation points (!) in method names. Query method names conventionally end in ? (Array.empty?) and methods that can drastically affect the object conventionally end in ! (Array.sort!).
One hidden source of complexity in understanding software is that even with encapsulated data, objects can still be modified by simply invoking one of their methods. To get around this type of complexity, most developers adopt the convention that an object should only send messages to closely related objects, and frown on any object that's globally accessible (and hence could be accessed from anywhere). Ruby supports this convention by explicitly prefixing all globally accessible variables with $, making it immediately obvious when this style rule is being broken.