But instead what you usually see is minor tweaks to the common functions. Pass in a flag here, tweak the inputs there, add an if statement over yonder... And before you know it, it's all a terrible tangled mess that is full of branches and technical debt. The two use cases have the same functions, but don't even follow the same branches within the functions.
https://softwareengineering.stackexchange.com/questions/3000...
The author is responding to specific examples of respected and influential people advocating a very extreme and dogmatic approach to the issue. As this article does not advocate for large functions, but against going to extremes, it is saying the very things that you say you stand for.
I don't have a good example on hand, but imagine a complicated mathematical expression. Up to a point, writing functions for parts of the expression might well make it easier to understand, but encapsulating each and every operator in a function will not. That, of course, is an absurd idea, but that is the point: it is no more absurd than the idea that anything you might want to see explained in a comment would be better done by creating a function.
It is possible, I believe, to write functions that do less than one thing, and I will be looking for examples - and also for examples where a desire for small functions has been accompanied by the creation of hidden dependencies.
A few things. First, I'm not sure you actually bothered reading the article, because the conclusion very obviously states what you're suggesting here, in that:
"This post’s intention was neither to argue that DRY or small functions are inherently bad (even if the title disingenuously suggested so). Only that they aren’t inherently good either."
As for the lack of examples, I'd imagine most people can extrapolate and draw analogies from their long and storied programming careers of the sort they stake a claim to. If there's something you'd like explained in more detail, let me know and I can try cooking up a contrived example, but it will remain somewhat contrived all the same.
This article isn't for duplication either - as a matter of fact it's against absolutes and blanket generalizations like "Code smells if your functions are longer than 3 lines", which is something I often come across.
I think what you call "mediocre programmers" -- personally though, I'd like to be more charitable and think of this demographic as the average programmer or the vast majority of programmers -- are also the ones most likely to cargo cult a piece of advice that's sold as "programming wisdom". Any "advice" needs to be taken with a grain of salt. The article goes on to state how important this is, as well:
"As with most other things, “the ideal” lies somewhere in between. There is no one-size-fits-all happy medium. The “ideal” also varies depending on a vast number of factors — both programmatic and interpersonal — and the hallmark of good engineering is to be able to identify where in the spectrum this “ideal” lies for any given context, as well as to constantly reevaluate and recalibrate this ideal."
1st: The importance of concrete examples in illustrating your point. Sure, we can all think of and agree on extreme examples of short-functions or long-functions which are bad, but what about more realistic examples. In Clean Code, Robert Martin presents many realistic and reasonable code snippets, featuring long-functions, which he then refactors into a form (ie, short-functions) which he claims is more readable and maintainable. For those specific examples, do you agree or disagree that his changes are an improvement? If you happen to agree, can you present other realistic examples of your own to illustrate your point? Doing so would allow us to have a more grounded discussion, comparing and contrasting two realistic alternatives. It would also allow us to better understand where you draw the line between too short and just-right.
2nd: Yes, too-short-functions and too-long-functions are both bad, but I disagree with your false equivalence between them. In my experience, the latter is a bigger problem than the former. I say this because this is the mistake that most mediocre programmers make. In my intro CS classes, I invariably see most people default towards writing their entire code in a single main function, with lots of code duplication, and this tendency tends to stick even afterwards. At my recent companies, I've even seen senior developers, people with fancy degrees and 200k+ salaries, write code that's horrendously hard to understand and maintain, because it's endlessly duplicated and squashed into a single long function. I don't doubt that there are those who take short-functions to an extreme as well, but in my experience, long-duplicated-functions are a much more prevalent problem, with greater downsides as well. Hence my point that short-functions and DRY works as a much better guideline than the converse.
Granted, my 2nd point above is my subjective opinion, and I agree with you that it's better to aim for the ideal, than to settle for erring on either side. So if you don't want to go down that rabbithole, I understand. However, I do think that presenting concrete realistic examples will go a long way towards enhancing this discussion. Looking forward to your follow-up blog post.
Ever wondered that they might be senior and commanding those salaries because they think and program a certain way? Have you ever discussed this with them? Many senior developers tend to shy away from abstractions in my experience, and they do it for a reason.
- the code in question constantly produced production bugs
- the code in question was extremely hard to debug when said production bugs surfaced
- everyone complained about that code being problematic, including other more senior engineers with 300k+ salaries
- all the above problems went away and everyone was pleased when I broke it up into smaller pieces
The author disagrees. She's saying people tend to use too much abstraction, so the DRY principle (not principal) is actively harmful as it encourages more of a bad thing. The trouble with abstraction is it hides things from view.
To make this concrete consider a codebase with lots of 1-10 line functions spread amongst lots of files, vs one with half the functions in half the files which are 10-20 lines instead. The work has to get done somewhere, and lots of shorter functions of just a few lines and keeping it dry (no repetition) tends to lead to lots of classes/files/modules which don't do much alone calling each other, making it much harder to reason about execution or read the code.
Rather than functions with just a few lines or with hundreds of lines, I imagine she's talking about functions 10-100 lines long on average, with exceptions where reasonable. There is a middle ground here where a function is more readable and crucially doesn't force you to jump around much to find out what work it does.
There's a good quote from Sandi Metz in the article: "duplication is far cheaper than the wrong abstraction"
That doesn't mean the patterns are bad, it doesn't mean small functions are bad, it just means the person who implemented them lacked good judgement. Good judgement is somewhat subjective. Fortunately, we can combat this subjectivity by looking at what the long term $ of a decision (programmer time, onboarding time, resources used...), how a particular decision advances an organization's objectives, etc.
We need functionality, not code. That functionality exists to support (what should be) clearly defined interests and goals. "Right" code is code that serves the functionality, and therefore the goals, best.
Underlying principle of maintainable code is "simply" that it is a concise and flexible expression of knowledge. The "simply" part is in quotation marks because, of course, it takes a good amount of self-awareness and meta-consideration to understand why some ways to express knowledge are better than others. There is also a limit to how "perfect" it can get, because it really depends on the audience too.
This is also why it's OK to let it slide sometimes and move on to more interesting topics - there are diminishing returns on splitting hairs, once broad strokes of good encapsulation and basic readability are applied.
The original formulation of DRY was:
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
Elimination of syntactic repetition is not an application of DRY, although application of DRY will often incidentally eliminate syntactic repetition.
The principles are actually sound, but letting metrics drive the design process is a problem all on its own.
As a maintanance programmer I had that misfortune very often. But I don't believe it's really so relevant to the article.
Having short-functions and DRYness is indeed prone to abuse, but it still works as a general guideline.
It doesn't, it's more of a heuristic and, as such, a way of detecting that you might be doing it wrong, not a sound rule to apply.
The problem is exactly that one: people that use "write short functions" as something that you must do. I've suffered such a fool as a boss and it was really sad. The man believed that two lines function (and the calling spagetti that followed) were a superior system to ten lines functions that accomplished a definite goal.
In general the problem is people that use general rules without understanding them.
https://softwareengineering.stackexchange.com/questions/1334...
The whole point of a guideline is that it's not a rule. It's a heuristic that people can selectively apply, based on the specific context and their level of experience.
People have all kind of heuristics that correlate with short functions, like DRY and single responsibility principle. People do apply those as heuristics. But function length is a number, and it is very hard to keep your conclusions fuzzy when operating over numbers.
I find it really interesting you mention this:
>>"when you find yourself violating them, you're supposed to question why"
This is essentially what I see a lot of programmers (including myself) who've internalized these rules tend to do. However I wonder if we've got it backwards - in that, should we be thinking more in terms of about how we design our abstractions upfront and optimize for allowing ourselves enough wiggle room instead of applying the so called "best practices" right away and only stopping to think if something might be wrong when we explicitly violate some of these "best practices" like DRY or small functions or what have you.
I find a lot of us tend to lose sight of the forest for the trees when we focus on cosmetic things like function length. It's a bottom-up view of the abstractions we've built, and maybe we actually need to think about the top-down design more thoroughly?
This summarizes perfectly the whole OOP vs FP debate.
And while the article doesn't make fully concrete examples, it does propose the outlines of a few; enough to get across a meaningful message (to me). Also; arguments that lean on concrete example are more at risk of attacking strawmen precisely because a concrete example can be flawed in irrelevant ways that obscure the underlying principles. Not that I'm opposed to examples - just that most examples are almost necessarily simplifications, and choosing a sourcecode simplification is not categorically different or better than choosing a pseudocode or diagram simplification.
The people who don't agree with that, ironically also end up making the same mistake when they assert that X is not at all useful, but do it in the reverse way by using their particular scenario to guide their statements.
I've come to believe that the vast majority of peoples opinions on programming are BS. I've come to rely on simple practical things. If you try something, and it works for you, you can safely ignore the people telling you you're doing it wrong.
Now, I find such discussions valuable, but only in the context of concrete examples. Devoid of concrete and realistic examples, the discussion often devolves into attacking strawmen and airy philosophizing. If this article had presented realistic examples of small functions that should have been duplicated and inlined, I think we can then have a much better discussion around it.
That said, I do have to offer a word of warning. It's possible that the author is a good programmer who knows how to incorporate long functions in a way that is still clear and readable. Unfortunately, I've had the misfortune of working with horrendous programmers who write functions that are hundreds of lines long, duplicated all over the place, and are a pain to understand and maintain. Having short-functions and DRYness is indeed prone to abuse, but it still works as a general guideline. Great programmers may be able to ignore these guidelines, but at least it prevents the mediocre ones from shooting themselves (and others) in the foot.