On almost every codebase I've been brought into, the simple guideline of "do less within your methods/functions" would lead to a higher engineering velocity for the organization. Yes, putting no thought behind the larger structure (e.g. how your public interfaces into/out of components are defined) will lead to too much jumping around, but if the functions are small and not leaking state then this is relatively easily remedied by someone with a systems bent. Small methods also convey to the reader the intent of the code by tying meaning to it through naming (if naming is thought out :)).
Large methods, on the other hand, build rigidity into the system and decreases its changeability. In this case, changes to the system necessitate reading and understanding the full implementation before modification. I will concede that I'm using "large methods" as a not-necessarily-true placeholder for "methods that do multiple things"
> refactoring your code for unit tests is making life easier for the computer, not humans
A machine does not care if you have unit tests. Unit tests are expressly for the human (team!) task of creating software that is maintainable and changeable.
"thousands of little classes everywhere and no one knows how to find the places where things really happen."
One of my first tasks was to make a minor change and after an hour or so I still had no idea where the code was that needed changing. I called my manager over and he spent about 40 minutes trying to find it. He did, eventually. The change I made then took about 10 minutes to write and test.
That's 110 minutes of intern time + 40 minutes of manager time for a 10 minute fix.
It's like that, but even smaller pieces. No more than one or two lines of less than 80 characters each (including indentation).
I write code that way (hundreds of tiny methods/functions) because I find it easier to read and modify, and therefore maintain and I've largely done this from a junior dev's point of view where maintainance was most of the job I was doing day in, day out. If you find it hard to follow that sort of program flow maybe it's a sign that there's no rule and different people find different types of coding easier to maintain.
In which case we're all a bit screwed as we inevitably eventually will have to handle the code of someone who's had the opposite view than ours.
On the plus side, that means we don't have to sweat it so badly with the "right" way to write code and just accept that code eventually becomes one big mess with a gigantic, horrible 50-case switch statement in the middle.
No! No no. No it doesn't. Not unless someone makes it that way. Code doesn't have to become awful. It becomes awful when successive generations of maintainers choose quick easy partial-fixes over a bit of refactoring to fix the problem properly.
Code wants to be short, simple, clear, and easy to understand. People make it horrible by not caring (or by not seeing patterns).
I often find that writing programs with testability in mind leads to more self-contained code, with less dependencies and cleaner interfaces. All this tends to make the code easier to reason about. I'd be curious to see an example of code that's good for unit testing but bad for humans.
If you're writing functional code, very short functions are in fact considered best practice. This is because (obviously!) the basic unit of composition in functional languages is the function, and not the class. So you want to be able to compose those functions as much as possible.
In fact, I've also read and been told by old school programmers (I'm 40, so I'm talking 50-60 year-old C hackers) that even non-functional languages like C and Go should limit the length of functions as much as possible.
Experience backs this up, since some of the worst code I've ever worked had functions 2-3 screen lengths long, often with unrelated logic there within. Invariably, this was either C or VisualBasic. Say what you want about Java, and it is verbose, but at least it limits programmers to 1 public class per file, and Java programmers do tend to break their class logic into smaller methods.
So for imperative languages, the advice is frequently: don't limit functions to a line or two (although that's entirely appropriate and even recommended for Haskell and functional JavaScript), but preferably not more than a dozen or two lines.
And if you name your functions well, you usually shouldn't be needing to jump all over the screen to lookup a function. The name should tell you exactly what it does. If you're using statically typed languages with a decent IDE, the type signature should provide any additional needed info.
Google's C++ style guide: "Prefer small and focused functions."
Haskell.org's style guide: "Most haskell functions should be at most a few lines, only case expression over large data types (that should be avoided, too) may need corresponding space."
felixge's nodejs style guide (one of the more popular ones for NodeJS): "Keep your functions short. A good function fits on a slide that the people in the last row of a big room can comfortably read. So don't count on them having perfect vision and limit yourself to ~15 lines of code per function." (my experience with functional JS is that the best code bases strive to keep functions around 5-6 lines in most cases; check out React, which for any criticisms is very well-written).
I've heard this described as Lasagna Layered code or Lasagna code. (Of course Copy Pasta is in our lexicon too, mmmmm)
Took quite some effort to disentangle that. As with most things no extreme is good, neither Blobs nor Poltergeists :)
I just tore apart one of those kinds of monstrosities last week, where the person who originally wrote it, went so far as to do C-style name prefixing on the methods, i.e. Foo_DoThing(), Bar_DoOtherThing(), Foo_DoOtherThing(), but left it all in one honking monster class instead of splitting it out.
Haha, wow, that's another way to go I guess. It never ceases to amaze me how inexperienced programmers can brute force problems with the limited tools they possess :)
That is, wherever the IDE would overwrite any edits you made. I'm currently refactoring a 3 partial class monster usercontrol: 238 lines, 2370 lines, and 4526 lines. Fun.
Maybe the tradeoff is actually between:
A. short term productivity / speed of development -- use longer functions without factoring out non-repeating code excessively
B. long term maintainability -- short and easily testable functions are definitely better
So use approach A. to code an MVP and approach B. for a decade-lasting enterprise application on whose development tens of people will rotate?
Oh, and... please, if you language allows you, use closures / functions inside function when you factor out a small bit of code that does not repeat, NOT private methods or non-exported functions! This increases code locality it's obvious that one line helper is used only once. And of course, give the mini-functions meaningful names, otherwise there is no point in doing this in the first place - you only factor out to increase readability. And prevent the mini-helpers from using variables from outer scope too much (if your mini-helper uses 5 outer-scope variables, then DON't factor it into a mini-helper).
(Advice above is extracted from a talk inspired by smth J. Carmack wrote, but can't remember exactly what...)
I'm always super afraid of growing mounds of logic, and my refactoring usually abstracts a lot of that logic out.
If you use a good IDE like IDEA/Jetbrains, you can ease the pain of those "one-line functions" with "click to navigate" function/variable names
So in theory a well unit-tested spaghetti-code would make you more relaxed because it would assert that spaghetti A interacts with spaghetti B in the C way.
I don't follow. In my experience well-unit-tested and spaghetti code are mutually exclusive as the spaghetti code does not have separation of concerns.
A note on my opinion on abstractions to clarify: I do not think that abstraction is inherently bad, just that starting with the idea that every component has to be generic, each class must have an abstract interface and that code should never ever see into another classes implementation tends to force developers to make so much mental yoga that they end up tying the components together without realising it.
Or the code for the OpenSSL handshake that interleaves preprocessing directives? https://github.com/openssl/openssl/blob/master/test/handshak...
> only called from one place
I'd argue the modern equivalent is the overuse of design pattern abstraction where every function has one or two lines in it. The symptom is the same: following the control flow of your program requires a maddening amount of jumping around in your editor, making it difficult to reason about.
There almost seems to be a cognitive dissonance with some people, where the terms "GOTOs considered harmful" and "spaghetti code" are thrown around but they see nothing wrong with hundreds of tiny functions that are only called from one place. It annoys me that the original point of those terms, that code is for humans, is lost.
With regards to the article's example, refactoring your code for unit tests is making life easier for the computer, not humans. And maybe that's why Bill is annoyed, being human and all.
And don't quote essays you didn't read.
edit: I removed a line but here was the gist of it: Unit tests are great but many languages don't support them well, especially compiled ones. We need to strive for a language that let's you isolate a module for testing without the type system or linker throwing a fit.