Likewise, 1.5 "Bulldozer" aka breaking up a function into subfunctions that aren't "useable everywhere".
I can't claim I'm the best programmer, but I've used this to "simplify" programs and reduce nesting to great success.
Add to that Aspect Oriented Programming in C# by using Attributes to add/remove logging to functions is easier if stuff is broking down into parts.
Subroutines shouldn't simply be about reuse. I think that narrows their purpose too much. There is a place for code reuse. There is also a place for functions that only get called from one location.
Thinking about subroutines as code reuse is wrong. Subroutines are named blocks of code. When you're writing a program, you're building a language (or, I'd say, a series of languages) and subroutines are the basic building blocks for that. You want to split your function into a bunch of smaller ones if it makes the whole thing easy to read; reusability is just a (useful) side effect of a properly designed "language".
Unfortunately, a lot of bugs can be caused when a function that was only ever intended to be used from a single place gets called from another place, when it seems to do the needed work.
Subroutines are not "named blocks of code" any more than they are facilitators of code reuse. Inline comments around a block of code also matches the concept of a named block of code.
The point of subroutines is to gather the code that implements a particular concept, abstraction or algorithm in one place. Sometimes it coincides with code reuse or splitting your function into a bunch of smaller ones.
The best way to tell if you are using subroutines effectively is if you can explain the effect of the subroutine without describing the implementation of it or (when splitting up a function) what it is trying to achieve for the function that calls it. If you can only describe it in relation to the function calling it, you fail. If you achieve code reuse and it's more than a line of code you get an automatic passing grade, it's just not necessarily a good grade.
Great point. A subroutine is essentially assigning a signature (name, args, returns) to a block of code and never having to look into the details of the block of code again. A bad subroutine is a subroutine that hides nothing: all the internals details of the subroutine leak through the signature.
> There is also a place for functions that only get called from one location
Do it inline. If you need it to look separate from the other context try an immediately invoked function or at worst some comment demarcation. No need to spray functionality across a file(s) when you can do it all inline, especially if it is only called from one place. I'll admit I didn't come up with this 100% myself, but having tried both ways, I really like inlining.
Looking at that, and considering A/B/C, I've seen myself using B most often - but I've not been afraid to use any of those.
I'm not a fan of A, because I want to see the "bigger picture" first and then dive into details (minor 1/2/3/...) as needed.
The reason for doing B vs C, in my experience with C#:
* Logging. I can add, via PostSharp ( https://www.postsharp.net/features ) ( or similar extension/addons ) something as simple as `[LogDebug]` to a function and get logging goodness without having to mess with lines inside the code. PostSharp and Log4Net are my goto's.
* Simplification - Partially included above (no need for a lot of logging code with the right tools).
* Mental Gymnastics - its easier to see Step 1, 2, 3 when they are next to each other instead of pages apart.
* Scope - only the parts that get sent in via parameters are worried about, and only a single return is normally needed.
* Nesting - I've used minor functions to remove 3-4-5 levels of nesting before.
Thanks for this link. A lot of these things were things I've grown to believe from working in game development (and listening to programmers better than myself), but I never had seen it written out as competently and thoroughly as this.
If you're looking more at improving readability than performance, I like Lisp's approach to that, with flet and labels, that let you establish a bunch of functions limited to a scope (just like let does for variables) - you can split your function into few named subroutines and then define them inside that function. So you still get readability, and can keep the code that's only called once near the place it will be called from.
You know, unless my reading was wrong, the conclusion to be made is that: Knuth was right ... Inline seems to be an extreme poor man version of literate programming, isn't it?
Especially when you're aware of the difference between pure and impure functions, and when you believe in the importance of testing. I'd rather test a dozen small, mostly pure functions, and have the function that ties them all together be 'obvious' (and still integration tested), than to have one gigantic complex function that I need to try and test, and any related change causes a dozen tests to break.
I would be suspicious of any subroutine whose function couldn't at least be described in words. So I agree that a function might be practically impossible to use in any other context, but it's worrisome to me when I see a function that doesn't do something that can be described in words.
In most cases, after factoring out a function from a bigger function, it's usually a good opportunity to think about what this function really does, and to make it do something conceptually simpler (even if that is less tailored to its sole use case). This way, you can even write meaningful tests for the new function.
I agree, it's very seldom a method should be longer than 20 lines, and optimally around 6 lines. Any method over 50+ lines should require a long hard look if there isn't a way to simplify it. Inlining everything only gets messy.
I would say that if you have a method composed of many methods you might want to encompass that into a separate class. That way you have everything needed to look at that method nicely in one place rather than interwoven with another class.
Depends on the scope of the method though, you want neither a blob nor a poltergeist.
I can't claim I'm the best programmer, but I've used this to "simplify" programs and reduce nesting to great success.
Add to that Aspect Oriented Programming in C# by using Attributes to add/remove logging to functions is easier if stuff is broking down into parts.
Subroutines shouldn't simply be about reuse. I think that narrows their purpose too much. There is a place for code reuse. There is also a place for functions that only get called from one location.