Lately, I've noticed a pattern emerging that I think John is referring to in the second part. The situation is that often a large function will be composed of many smaller, clearly separable steps that involve temporary, intermediate results. These are clear candidates to be broken out into smaller functions. But, a conflict arises from the fact that they would each only be invoked at exactly one location. So, moving the tiny bits of code away from their only invocation point has mixed results on the readability of the larger function. It becomes more readable because it is composed of only short, descriptive function names, but less readable because deeper understanding of the intermediate steps requires disjointly bouncing around the code looking for the internals of the smaller functions.
The compromise I have often found is to reformat the intermediate steps in the form of control blocks that resemble a function definitions. The pseudocode below is not a great example because, to keep it brief, the control flow is so simple that it could have been just a chain of method calls on anonymous return values.
AwesomenessT largerFunction(Foo1 foo1, Foo2 foo2)
{
// state the purpose of step1
ResultT1 result1; // inline ResultT1 step1(Foo1 foo)
{
Bar bar = barFromFoo1(foo);
Baz baz = bar.makeBaz();
result1 = baz.awesome(); // return baz.awesome();
} // bar and baz no longer require consideration
// state the purpose of step2
ResultT2 result2; // inline ResultT2 step2(Foo2 foo)
{
Bar bar = barFromFoo2(foo); // second bar's lifetime does not overlap with the 1st
result2 = bar.awesome(); // return bar.awesome();
}
return result1.howAwesome(result2);
}
I make a point to call out out that the temp objects are scope-blocked to the minimum necessary lifetimes primarily because doing so reduces the amount of mental register space required for my brain to understand the larger function. When I see that the first bar and baz go out of existence just a few lines after they come into existence, I know I can discard them from short term memory when parsing the rest of the function. I don't get confused by the second bar. And, I don't have to check the correctness of the whole function with regards to each intermediate value.