Readability of heavy function call nesting? - coding-style

I've often seen it argued that heavily nested function calls should not be used because they are unreadable. However, using temporary variables instead creates a lot of unnecessary verbosity and forces the reader to mentally link each temporary variable to what it represents. When looking at the way Lisp code is typically formatted, it occurred to me that nested function calls can actually be made quite readable if you format them to reflect the nesting. For example:
// Totally unreadable:
auto linesIter = filter!"a.length > 0"(map!strip(File(filename).byLine())))
// Perfectly readable. The only difference is formatting.
auto linesIter = filter!"a.length > 0"(
map!strip(
File(filename).byLine()
)
);
// Readable, but unnecessarily verbose:
auto rawLines = File(filename).byLine();
auto stripped = map!strip(rawLines);
auto filtered = filter!"a.length > 0"(stripped);
Writing something like the first example in nested function form is, IMHO, equivalent to doing the following in more procedural-style code:
for(i = 0; i < 10; i++) { for(j = 0; j < 10; j++) { if(x < 2) { z++; } else { y++; }}}
In both cases the real problem is poor formatting, not excessive nesting. How would you rate the readability/comprehensibility of the well-formatted nested function version vs. the temp variable version? Do you think heavy function call nesting is bad style even if it's formatted for maximum readability? If so, why?

I see nothing wrong with breaking large nested function calls in the way you have using formatting. It's readable, which was the intention.
With temporary variables, I try to avoid them. And the only reason for this is quite simply, I am utterly incapable of giving them good names. I try when they are absolutely necessary, and end up wasting 5 - 10 minutes thinking 'what would be the good name for blah blah that does cor blimey'? And almost always end up giving up and using buffer if it's an array, or temp if it's a scalar, etc.
That said, with your nested structure, the functions represent verbs, and not the products of the verb's action, which is what you'd get with temporary variables. With temporary variables, you do get a chance to give that product a name. But if it's a thing that is better described by the verb and direct object themselves (verb(directObject)) then that's the route you should take.

You say "using temporary variables instead creates a lot of unnecessary verbosity and forces the reader to mentally link each temporary variable to what it represents" -- but IMO that's just another way of saying that you've broken up the thing into steps that the reader can understand one at a time -- in other words, you've made it more readable.
I'm quite happy to add an extra variable to break a long line up into seperate steps (your example 3), but the key thing for me is whether you can break the thing up cleanly into genuine steps. One good indicator is whether you can find a good variable name; if you can't, maybe it's not a genuine seperate step that needs splitting out.
There's nothing much wrong with your example 2, but much longer than that and I would certainly break it up. You'll thank yourself when it comes to debugging...

IMHO, objects and names should be carefully chosen so that things are readable when you do the function-style coding of slamming everything onto one line like in your first example. If it is totally unreadable, somebody(s) chose bad names. Your goal is that if something looks off, often times it means there is a bug.
In the real world when you have to deal with those badly-chosen names, there isn't anything wrong with creating temporary constants (or even routines) with proper names to clean things up a bit. It beats a comment, because it compiles and is easier to modify.

I personally think that if there is no performance penalties readability is more important. Although i do not know anything about the Lisp syntax, looks like your examples makes the same number of calls? If thats the case, do it readable way. But if you are calling a heavy function (like opening a file and reading again and again) more times in a loop, you should avoid it. I hope this answer is useful.

I prefer not to use too many variables b/c they are often the source of bugs. If I can express something as a single expression I prefer it that way. I would probably go with your second example.

Related

The best way to handle options in a performance-based application

The Problem
I'm writing an application where performance is very important. (Specifically: A raytracer)
I want to have an option for something called "Adaptive Supersampling" in my program. It's fairly simple to implement but I want to have an option to turn it on or off.
Unfortunately I only see two options, which are:
Put an if/else around two separate procedures (which are fairly similar) like so:
void renderLoop() {
if(adaptive) {
doAdaptiveLoop();
} else {
doNormalLoop();
}
}
Put specialized if/else statement scattered around the code.
void renderLoop() {
if(adaptive) something();
else somethingElse();
for(int i = 0; i < LOOP_1; i++) {
if(adaptive) something1();
else somethingElse1();
for(int j = 0; j < LOOP_2, j++) {
if(adaptive) something2();
else somethingElse2();
}
}
//... So on and so forth
}
Both of these methods are fairly terrible. Method 1 repeats a lot of code, and Method 2 is both messy and inefficient. Both of them make it very difficult to add on many features. If I want to add more features, I have to add more if/else statements at a factorial rate with Method 1, and in a very confusing way in Method 2.
The Question
I've run across this problem many times in performance dependent applications, but I've never really been able to solve it.
The questions is: How do I add toggleable features to my program which is centered around many loops, whose performance will decrease with many boolean tests, and whose code will become messy with those tests?
I also run into this problem on a regular basis, and I usually have to choose between options 1 and 2. If the number of options keeps growing, I usually fall back on the swiss-army-knife method: code generation.
In other words, I write program A to write functions B1, B2, etc. Program A takes the options as arguments and generates the appropriate function Bi as a text file, and then the final program includes all the Bi.
It's not pretty, but pretty is overrated.
The advantage is, the common code among all the Bi is only stated in one place, so if you make a change to it, you only have to do it in one place, so you have fewer opportunities to get it wrong.
I think what you are searching for are function pointers or the analogous.
Let me elaborate, you specifically asked for performance, and I agree that chasing pointers might get in your way - BUT since you now seem to have a large number of branches depending on settings somewhere inside you application, you might as well wrap that code up in some function pointers. That does not mean you should call a function through a pointer when just adding two vectors.
On a side note; this is also relevant when parallelizing code; the work-items need not be too large as to mitigate the parallelization efforts but need be large enough to be processed efficiently.
Maybe use a global function pointer variable to hold the loop function your options tell you to use.

Thoughts on variable/function naming conventions

I've been coding on and off my whole life. I've mostly coded Perl, but also some Java, PHP, C, C++. I've even took a stab at Emacs Lisp, and I've done the occasional shell script. However, I've never actually engaged the subject to gain any kind of expertise – other things have had higher priorities for me. I don't consider myself to be really proficient in any language but Perl, and also now Haskell which I'm taking a course in right now.
Lately, I've been thinking about my style of coding. Not the style of the actual code; as a CS student I only do projects for fun or for school, which enables me to write what in my opinion is beautiful code almost always. One question in particular has been troubling me. It's a rather curious thing, but still something I would like to hear other opinions about.
Here's the thing: I find myself taking a fair amount of time to name my functions and variables to the most easily understood names I can possibly think of. Sometimes this task can be very tedious, even when not taking into account the difficulty of finding a variable name that conveys the meaning of a piece of code. For example, right now I'm making a function that looks like this.
This is Haskell code but the meaning should be quite clear. (It's exact meaning isn't that important so if you want to, just skip the code and read on.)
-- return the row with least number of Nothing values
bestRow :: [[Maybe Int]] -> Int -> Maybe (Int,Int)
bestRow [] _ = Nothing
bestRow (row:rows) thisIndex
| nextRow == Nothing && thisFilled > 8 = Nothing
| nextRow == Nothing = Just (thisIndex,thisFilled)
| thisFilled >= nextFilled = Just (thisIndex,thisFilled)
| thisFilled < nextFilled = nextRow
where thisFilled = length $ filter (/= Nothing) row
nextRow = bestRow rows (thisIndex + 1)
(nextIndex,nextFilled) = fromMaybe (-1,-1) nextRow
I couldn't decide on the variable names. The function did it's task well but it wasn't as clear as it could be. Once I settled on a solution, I spent 15 minutes on naming and renaming the variables. Should I go with curIndex, nextIndex || index, nextIndex || ind, indN etc? 15 minutes after I decided I was done, I realized this function wasn't needed after all: I found a much better solution to my problem. BOOM I had lost a lot of time simply cleaning code to no use to anyone, least of all to me. Or at least it felt that way.
This is something which has happened to me more than once, and is quite frustrating, mostly because it makes me feel dumb. What are your thoughts on this subject? Is this something you've experienced, something wrong with my way of doing things or simply something unavoidable?
Are there "perfect" variable names, or is it "ok" if they at least doesn't obfuscate your code?
Thanks,
Stefan Kangas
Read the book Clean Code by Robert C. Martin
One thing he goes into detail on is naming. Variable and function names should be as exact as possible. If you require a comment to describe it, the function is either doing too much, or the name can be clearer.
The goal, is to have code that reads like a story. Changing variable and function names and breaking them down into several functions or variables if necessary to achieve this goal.
Great book, worth every penny
Good names for variables and functions make understandable code. Good names should describe the intent and use of a variable or function. Bad names confuse other coders when it comes time to make changes.
Clarity is good in code.
I think its psychological, I always used to think my variables etc was unreadable to anyone but me even though I always placed comments in my code.
Whenever I had to go through code by someone else I was quite surprised at how I could easily read their code without having to read every single comment, the exact same thing happens when they read my code so clearly I'm doing a good job ;)
I think as long as your comments are meaningful and actually describe what a method does, what a variable is being used for you should be fine.
One tip is to always make sure your comments are up to date. I've seen code too many times where the comments for a method haven't changed, but the method code has.....it can be very confusing!

Should we create objects if we need them only once in our code?

This is a coding style questions:-
Here is the case
Dim obj1 as new ClassA
' Some lines of code which does not uses obj1
Something.Pass(obj1) ' Only line we are using obj1
Or should we directly initiaize the object when passing it as an argument?
Something.new(new ClassA())
If you're only using the object in that method call, it's probably better to just pass in "new ClassA()" directly into the call. This way, you won't have an extra variable lying around, that someone might mistakenly try to use in the future.
However, for readability, and debugging it's often useful to create the temporary object and pass it in. This way, you can inspect the variable in the debugger before it gets passed into the method.
Your question asks "should we create objects"; both your examples create an object.
There is not logically any difference at all between the two examples. Giving a name to an object allows it to be referred to in more than one place. If you aren't doing that, it's much clearer to not give it a name, so someone maintaining the code can instantly see that the object is only passed to one other method.
Generally speaking, I would say no, there's nothing wrong with what you're doing, but it does sound like there may be some blurring of responsibilities between your calling function, the called function, and the temporary object. Perhaps a bit of refactoring is in order.
I personally prefer things to be consistent, and to make my life easier (what I consider making my life easier may not be what you consider making your life easier... so do with this advice what you will).
If you have something like this:
o = new Foo();
i = 7
bar(o, i, new Car());
then you have an inconsistency (two parameters are variables, the other is created on the fly). To be consistent you would either:
always pass things as variables
always pass things created on the fly
only one of those will work (the first one!).
There are also practical aspects to it as well: making a variable makes debugging easier.
Here are some examples:
while(there are still lines in the file)
{
foo(nextLine());
}
If you want to display the next line for debugging you now need to change it to:
while(there are still lines in the file)
{
line = nextLine();
display(line);
foo(line);
}
It would be easier (and safer) to have made the variable up front. It is safer because you are less likely to accidentally call nextLine() twice (by forgetting to take it out of the foo call).
You can also view the value of "line" in a debugger without having to go into the "foo" method.
Another one that can happen is this:
foo(b.c.d()); // in Java you get a NullPointerException on this line...
was "b" or "c" the thing that was null? No idea.
Bar b;
Car c;
int d;
b = ...;
c = b.c; // NullPointException here - you know b was null
d = c.d(); // NullPointException here - you know c was null
foo(d); // can view d in the debugger without having to go into foo.
Some debuggers will let you highlight "d()" and see what it outputs, but that is dangerous if "d()" has side effects as the debugger will wind up calling "d()" each time you get the value via the debugger).
The way I code for this does make it more verbose (like this answer :-) but it also makes my life easier if things are not working as expected - I spend far less time wondering what went wrong and I am also able to fix bugs much faster than before I adopted this way of doing things.
To me the most important thing when programming is to be consistent. If you are consistent then the code is much easier to get through because you are not constantly having to figure out what is going on, and your eyes get drawn to any "oddities" in the code.

What is the most obfuscated code you've had to fix?

Most programmers will have had the experience of debugging/fixing someone else's code. Sometimes that "someone else's code" is so obfuscated it's bad enough trying to understand what it's doing.
What's the worst (most obfuscated) code you've had to debug/fix?
If you didn't throw it away and recode it from scratch, well why didn't you?
PHP OSCommerce is enough to say, it is obfuscated code...
a Java class
only static methods that manipulates DOM
8000 LOCs
long chain of methods that return null on "error": a.b().c().d().e()
very long methods (400/500 LOC each)
nested if, while, like:
if (...) {
for (...) {
if (...) {
if (...) {
while (...) {
if (...) {
cut-and-paste oriented programming
no exceptions, all exceptions are catched and "handled" using printStackTrace()
no unit tests
no documentation
I was tempted to throw away and recode... but, after 3 days of hard debugging,
I've added the magic if :-)
Spaghetti code PHP CMS system.
by default, programmers think someone else's code is obfuscated.
The worse I probably had to do was interpreting what variables i1, i2 j, k, t were in a simple method and they were not counters in 'for' loops.
In all other circumstances I guess the problem area was difficult which made the code look difficult.
I found this line in our codebase today and thought it was a nice example of sneaky obfuscation:
if (MULTICLICK_ENABLED.equals(propService.getProperty(PropertyNames.MULTICLICK_ENABLED))) {} else {
return false;
}
Just making sure I read the whole line. NO SKIMREADING.
When working on a GWT project, I would reach parts of GWT-compiled obfuscated JS code which wasn't mine.
Now good luck debugging real obfuscated code.
I can't remember the full code, but a single part of it remains burned into my memory as something I spend hours trying to understand:
do{
$tmp = shift unless shift;
$tmp;
}while($tmp);
I couldn't understand it at first, it looks so useless, then I printed out #_ for a list of arguments, a series of alternating boolean and function names, the code was used in conjunction with a library detection module that changed behaviour if a function was broken, but the code was so badly documented and made of things like that which made no sense without a complete understanding of the full code I gave up and rewrote the whole thing.
UPDATE from DVK:
And, lest someone claims this was because Perl is unreadable as opposed to coder being a golf master instead of good software developer, here's the same code in a slightly less obfuscated form (the really correct code wouldn't even HAVE alternating sub names and booleans in the first place :)
# This subroutine take a list of alternating true/false flags
# and subroutine names; and executes the named subroutines for which flag is true.
# I am also weird, otherwise I'd have simply have passed list of subroutines to execute :)
my #flags_and_sub_names_list = #_;
while ( #flags_and_sub_names_list ) {
my $flag = shift #flags_and_sub_names_list;
my $subName = shift #flags_and_sub_names_list;
next unless $flag && $subName;
&{ $subName }; # Call the named subroutine
}
I've had a case of a 300lines function performing input sanitization which missed a certain corner case. It was parsing certain situations manually using IndexOf and Substring plus a lot of inlined variables and constants (looks like the original coder didn't know anything about good practices), and no comment was provided. Throwing it away wasn't feasible due to time constraints and the fact that I didn't have the specification required so rewriting it would've meant understanding the original, but after understanding it fixing it was just quicker. I also added lots of comments, so whoever shall come after me won't feel the same pain taking a look at it...
The Perl statement:
select((select(s),$|=1)[0])
which, at the suggestion of the original author (Randal Schwartz himself, who said he disliked it but nothing else was available at the time), was replaced with something a little more understandable:
IO::Handle->autoflush
Beyond that one-liner, some of the Java JDBC libraries from IBM are obfuscated and all variables and functions are either combinations of the letter 'l' and '1' or single/double characters - very hard to track anything down until you get them all renamed. Needed to do this to track down why they worked fine in IBM's JRE but not Sun's.
If you're talking about HLL codes, once I was updating project written by a chinese and all comments were chinese (stored in ansii) and it was a horror to understand some code fragments, if you're talking about low level code there were MANY of them (obfuscated, mutated, vm-ed...).
I once had to reverse engineer a Java 1.1 framework that:
Extended event-driven SAX parser classes for every class, even those that didn't parse XML (the overridden methods were simply invoked ad hoc by other code)
Custom runtime exceptions were thrown in lieu of method invocations wherever possible. As a result, most of the business logic landed in a nested series of catch blocks.
If I had to guess, it was probably someone's "smart" idea that method invocations were expensive in Java 1.1, so throwing exceptions for non-exceptional flow control was somehow considered an optimization.
Went through about three bottles of eye drops.
I once found a time bomb that had been intentionally obfuscated.
When I had finally decoded what it was doing I mentioned it to the manager who said they knew about the time bomb but had left it in place because it was so ineffective and was interwoven with other code.
The time bomb was (presumably) supposed to go off after a certain date.
Instead, it had a bug in it so it only activated if someone was working after lunchtime on Dec 31st.
It had taken three years for that circumstance to occur since the guy who wrote the time bomb left the company.

Is it possible to write good and understandable code without any comments?

Can any one suggest what is the best way to write good code that is understandable without a single line of comments?
I once had a professor when I was in college tell me that any good code should never need any comments.
Her approach was a combination of very precise logic split out into small functions with very descriptive method/property/variable names. The majority of what she presented was, in fact, extremely readable with no comments. I try to do the same with everything I write...
Read Code Complete, 2nd Edition cover to cover. Perhaps twice.
To give some specifics:
Making code readable
Eliminating code repetition
Doing design/architecture before you write code
I like to 'humanise' code, so instead of:
if (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200){
doSomething();
}
I'll do this:
bool starIsBright;
starIsBright = (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200);
if(starIsBright){
doSomething();
}
In some cases - yes, but in many cases no. The Yes part is already answered by others - keep it simple, write it nicely, give it readable names, etc. The No part goes to when the problem you solve in code is not a code problem at all but rather domain specific problem or business logic problem. I've got no problem reading lousy code even if it doesn't have comments. It's annoying, but doable. But it's practically impossible to read some code without understanding why is it like this and what is it trying to solve. So things like :
if (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200){
doSomething();
}
look nice, but could be quite meaningless in the context of what the program is actually doing. I'd rather have it like this:
// we do this according to the requirement #xxxx blah-blah..
if (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200){
doSomething();
}
Well written code might eliminate the need for comments to explain what you're doing, but you'll still want comments to explain the why.
If you really want to then you would need to be very detailed in your variable names and methods names.
But in my opinion, there is no good way to do this. Comments serve a serious purpose in coding, even if you are the only one coding you still sometimes need to be reminded what part of the code you're looking at.
Yes, you can write code that doesn't need comments to describe what it does, but that may not be enough.
Just because a function is very clear in explaining what it does, does not, by itself, tell you why it is doing what it does.
As in everything, moderation is a good idea. Write code that is explanatory, and write comments that explain why it is there or what assumptions are being made.
I think that the concept of Fluent Interfaces is really a good example of this.
var bob = DB.GetCustomers().FromCountry("USA").WithName("Bob")
Clean Code by Robert C. Martin contains everything you need to write clean, understandable code.
Use descriptive variable names and descriptive method names. Use whitespace.
Make your code read like normal conversation.
Contrast the use of Matchers in Junit:
assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));
with the traditional style of assertEquals:
assertEquals(3, x);
When I look at the assertEquals statement, it is not clear which parameter is "expected" and which is "actual".
When I look at assertThat(x, is(3)) I can read that in English as "Assert that x is 3" which is very clear to me.
Another key to writing self-documenting code is to wrap any bit of logic that is not clear in a method call with a clear name.
if( (x < 3 || x > 17) && (y < 8 || y > 15) )
becomes
if( xAndYAreValid( x, y ) ) // or similar...
I'm not sure writing code that is so expressive that you don't need comments is necessarily a great goal. Seems to me like another form of overoptimization. If I were on your team, I'd be pleased to see clear, concise code with just enough comments.
In most cases, yes, you can write code that is clear enough that comments become unnecessary noise.
The biggest problem with comments is there is no way to check their accuracy. I tend to agree with Uncle Bob Martin in chapter 4 of his book, Clean Code:
The proper use of comments is to compensate for our failure to express ourself in
code. Note that I used the word failure. I meant it. Comments are always failures. We must
have them because we cannot always figure out how to express ourselves without them,
but their use is not a cause for celebration.
So when you find yourself in a position where you need to write a comment, think it
through and see whether there isn’t some way to turn the tables and express yourself in
code. Every time you express yourself in code, you should pat yourself on the back. Every
time you write a comment, you should grimace and feel the failure of your ability of
expression.
Most comments are either needless redundancy, outright fallacy or a crutch used to explain poorly written code. I say most because there are certain scenarios where the lack of expressiveness lies with the language rather than the programmer.
For instance the copyright and license information typically found at the beginning of a source file. As far as I'm aware no known construct exists for this in any of the popular languages. Since a simple one or two line comment suffices, its unlikely that such a construct will be added.
The original need for most comments has been replaced over time by better technology or practices. Using a change journal or commenting out code has been supplanted with source control systems. Explanatory comments in long functions can be mitigated by simply writing shorter functions. etc.
You usually can turn your comment into a function name something like:
if (starColourIsGreaterThanThreshold(){
doSomething();
}
....
private boolean starColourIsGreaterThanThreshold() {
return starColour.red > THRESHOLD &&
starColour.blue > THRESHOLD &&
starColour.green > THRESHOLD
}
I think comments should express the why, perhaps the what, but as much as possible the code should define the how (the behavior).
Someone should be able to read the code and understand what it does (the how) from the code. What may not be obvious is why you would want such behavior and what this behavior contributes to the overall requirements.
The need to comment should give you pause, though. Maybe how you are doing it is too complicated and the need to write a comment shows that.
There is a third alternative to documenting code - logging. A method that is well peppered with logging statements can do a lot to explain the why, can touch on the what and may give you a more useful artifact than well named methods and variables regarding the behavior.
If you want to code entirely without comments and still have your code be followable, then you'll have to write a larger number of shorter methods. Methods will have to have descriptive names. Variables will also have to have descriptive names. One common method of doing this is to give variables the name of nouns and to give methods the names of verbal phrases. For example:
account.updateBalance();
child.givePacifier();
int count = question.getAnswerCount();
Use enums liberally. With an enum, you can replace most booleans and integral constants. For example:
public void dumpStackPretty(boolean allThreads) {
....
}
public void someMethod() {
dumpStackPretty(true);
}
vs
public enum WhichThreads { All, NonDaemon, None; }
public void dumpStackPretty(WhichThreads whichThreads) {
....
}
public void someMethod() {
dumpStackPretty(WhichThreads.All);
}
Descriptive names is your obvious first bet.
Secondly make sure each method does one thing and only one thing. If you have a public method that needs to do many things, split it up into several private methods and call those from the public method, in a way that makes the logic obvious.
Some time ago I had to create a method that calculated the correlation of two time series.
To calculate the correlation you also need the mean and standard deviation. So I had two private methods (well actually in this case they were public as they could be used for other purposes (but assuming they couldn't then they would be private)) for calculating A) the mean, B) the standard deviation.
This sort of splitting up of function into the smallest part that makes sense is probably the most important thing to make a code readable.
How do you decide where to break up methods. My way is, if the name is obvious e.g. getAddressFromPage it is the right size. If you have several contenders you are probably trying to do too much, if you can't think of a name that makes sense you method may not "do" enough - although the latter is much less likely.
I don't really think comments are a good idea in most cases. Comments don't get checked by the compiler so they so often are misleading or wrong as the code changes over time. Instead, I prefer self documenting, concise methods that don't need comments. It can be done, and I have been doing it this way for years.
Writing code without comments takes practice and discipline, but I find that the discipline pays off as the code evolves.
It may not be comments, but, to help someone better understand what it going on you may need some diagrams explaining how the program should work, as, if a person knows the big picture then it is easier to understand code.
But, if you are doing something complex then you may need some comments, for example, in a very math intensive program.
The other place I find comments useful and important, is to ensure that someone doesn't replace code with something that looks like it should work, but won't. In that case I leave the bad code in, and comment it out, with an explanation as to why it shouldn't be used.
So, it is possible to write code without comments, but only if you are limited in what types of applications you are writing, unless you can explain why a decision was made, somewhere, and not call it a comment.
For example, a random generator can be written many ways. If you pick a particular implementation it may be necessary to explain why you picked that particular generator, as the period may be sufficiently long for current requirements, but later the requirements may change and your generator may not be sufficient.
I believe it's possible, if you consider the fact that not everybody likes the same style. So in order to minimize comments, knowing your "readers" is the most important thing.
In "information systems" kind-of software, try using declarative sentence, try to approximate the code line to a line in english, and avoid "mathematical programming" (with the i,j and k for index, and the one-liners-to-do-a-lot) at all costs.
I think code can be self-documenting to a large degree, and I think it's crucial, but reading even well-written code can be like looking at cells of the human body with a microscope. It sometimes takes comments to really explain the big picture of how pieces of the system fit together, especially if it solves a really complex and difficult problem.
Think about special data structures. If all that computer scientists had ever published about data structures were well-written code, few would really understand the relative benefit of one data structure over another -- because Big-O runtime of any given operation is sometimes just not obvious from reading the code. That's where the math and amortized analysis presented in articles come in.

Resources