Hacker News new | ask | show | jobs
by gspencley 1148 days ago
Small functions are not about readable code. It's about single responsibility.

Single responsibility is an engineering principle that is not limited to software development. It is the reason that we don't combine the breaking functionality in our car with the am/fm radio. We keep moving parts isolated because the fewer "things" that something does, the less likely it is to break. The less complicated it is.

But there are other side effects to creating small, single-purpose functions (or anything for that matter). It is not always obvious when you will have an opportunity to reuse something. And duplication is not always apparent. When you take single-responsibility as far as you can go, you not only isolate all of your moving parts but you maximize the opportunities for reuse.

And it goes even further than that. Your large functions likely have a few dependencies, at least. Those dependencies will make your functions more difficult to write tests for. And your test cases will be more complicated if you have larger functions because they are doing more than one thing that needs to be captured.

Readability is more about expressing the intent of code. You can do that in "long functions." You can express the intent of code while making it extremely compilcated. Hell, just add lots of verbose comments and your code will be more "readable."

Since you seem to be one of those people who has a stick up you about design patterns (your last comment about AstractFactorySingletonViewModel ... who hurt you?) I will offer you this piece of food for thought: the purpose of "best practices" and design patterns is to SIMPLIFY code. If you ever see a misapplication of them in the wild* then what you are witnessing is not "over" engineering ... it is POOR engineering. Consider that before throwing the baby out with the bathwater.

* I rarely do, so I often wonder to myself if this is a made up problem by lazy devs who don't want to actually study theory. But I do hear that this occurs from time to so I'll take you at your word that there are people out there that don't know how and when to actually apply design patterns properly. The problem is the misapplication, not the patterns themselves - which are just common solutions to recurring problems. Do you not think DRY is a good idea?

4 comments

Ha! Thanks for sharing “enterprise FizzBuzz”; I’d not seen that before. I was digging through the code before I had a PTSD-related flashback from debugging some OO at my last company…

My friend and I were working together, when one of our seniors came over and asked us for help figuring out how to modify an algorithm that someone new on the project had rewritten. The algorithm was 20-30 well-commented lines of code, until the rewriter decided that it lacked flexibility. It had been rewritten to use a Singleton Factory to instantiate an implementation of an interface that itself delegated every snippet that could possibly be conceived of as a function to one of several different classes (Policy, Actor, Dispatcher, etc.). Every function was three lines or less, and all were entirely illegible taken in part or in whole.

I’m glad that team member got to show us how many times he read about design patterns and how much he knew about OOP, but it took four engineers (we later roped in another) to venture 15 layers down the call stack of virtual functions to figure out where a simple constant came from. Quality code indeed.

Any rule, principle or pattern taken to its extreme is bad.

What you gain by making every function responsible for exactly one thing you lose through more abstraction and worse local context.

It's always a balance.

> Any rule, principle or pattern taken to its extreme is bad.

That's a non-sequitur. The word "extreme" describes matter of degree. If something is "good" then it does not follow, logically, that "extremely good" becomes "bad" just because it was "taken to the extreme." As you yourself said, context matters.

Then to say "it's always a balance", a balance between what? Good and bad? Simple and complex? What are you talking about?

We are speaking in the abstract, necessarily, when we speak of "small, single purpose functions" because we are taking the context out of it. Can we agree that the simplest solution should always win? If so, why?

After 25 years of developing software professionally, I have seldom seen someone apply design patterns, so-called "best practices" and engineering principles to a point where they shot themselves in the foot. It happens, we are all human after all, but in my experience it is not a huge problem.

What is a huge problem, is that time after time we inherit projects written by very decent hardworking developers who can get something that works out the door quickly but don't know how to write it in such a way that it is easy to change over time. We see massive files, massive functions, lots of cyclomatic complexity, terse variable names, duplication, tight coupling, near zero modularity and very poor separation of concerns. This is the norm, not the exception.

And yet lately, I have seen a trend that is a knee-jerk reaction against decades of work done to address this problem and it should be concerning. If 75% of the projects I worked on were tech-debt free, and we could identify actual examples of software where people wrote it so abstractly that it was incomprehensible then I would be on the same side. But what I see in the wild is not examples of people going "patterns happy" or taking things to a point where it starts to become complex rather than simple. What I see are developers who have never even heard of design patterns, or they learned one and then misapplied it everywhere because suddenly every tool looks like a nail to them.

Therefore my position is that we should be preaching single-responsibility and small functions more, not less.

Just like more and more average people does not equal a genius, more and more "good" usually doesn't equal "extremely good".

> Then to say "it's always a balance", a balance between what? Good and bad? Simple and complex? What are you talking about?

A balance of the extent that you follow the rule/principle/pattern. There is a reason we have both WET and DRY.

> Therefore my position is that we should be preaching single-responsibility and small functions more, not less

Fine, but you're preaching to the wrong people here.

This is a very level-headed defense of design patterns. I'm kinda like the parent commenter. I have a stick up my ass about these kinds of things. I think they're misused quite often. But it's good to see a reminder that they do have their place.

Even DRY, I've seen DRY cause test suites become hard to work with. Rules of thumb sometimes get pumped really hard and I think it causes people to over-use them. The rule becomes the goal rather than something more material like UX or DX. This code is DRY, DRY is good, therefore this code is good - I hear arguments like that quite often. But your take feels very grounded.

> We keep moving parts isolated because the fewer "things" that something does, the less likely it is to break. The less complicated it is.

It doesn't really work if I'm breaking up a function body into a bunch of smaller functions that will never be called from anywhere except the refactored function. I've just moved the complexity from a large function body into a bunch of smaller functions and the calls to them - but it's still the exact same logic, with the same complexity (but perhaps harder to read, due to all the jumping back and forth).