Hacker News new | past | comments | ask | show | jobs | submit login

This is my least favorite programming advice. Splitting functions for no reason other than "it's too long" is a bad practice. https://news.ycombinator.com/item?id=25263488



Most of the illegible code I've ever written was because I kept splitting functions up, thinking I was following good practice, but was really just making emotional/aesthetic decisions.

When you go back to edit your code, it's like calling a 1-800 number and getting re-routed to 15 different departments to finally find the person you need to talk to.


I really agree with this.

I see this all the time with "business rules" problems.

If you have a situation where you can't make levelled abstractions, you've got some thorny interconnections in your logic (and those may be fundamental!!!). The way I handle this is with a "gauntlet pattern." You still can split your logic up into parts, but you just do it by "rejecting" certain logical chunks at a time within a function and comment above each state of the gauntlet.

It looks something like this:

// marketing told me we should never do this ever under any circumstances

if (!a) {

  return CASE_1;
}

// if the user enrolled like this, we should check if there's this bad property about that user

if (!b) {

  return CASE_2;
}

// oh, you crazy people in finance

if (c > 4 && d < 0) {

  return CASE_3;
}

return CASE_4;

The key thing is not to get hung up on duplication or having exact control flow like how the business thinks about it. You want 1) to return the largest percentage or simplest cases out first, 2) keep everything flat as possible without layers of branching, and 3) be able to associate tests to each one of those early-reject lines.

The nice thing about this is the reduction of cognitive load. By the time you get to the 3rd case or whatever, your mind can already know certain things are true. It makes reasoning about rules much easier in my experience.


I think the trick is to split it the eight way. If you do it the wrong way, you end up with functions becoming layer upon layer of indirection that feels like a rabbit hole you need to dive further and further into to understand what is actually going on. But if instead you keep the core control flow in the original function, but move sensibly named chunks of into into helper functions - that way the original function ends up reading like pseudo-code, and you don't really need to actually look at any of the helper functions to understand what is going on.


The "right" way to split, as advocated in the article I linked, is not based on naming but based on state. Have all your split out functions be pure. Retain all the state mutation in one place where you can keep an eye on it.


Use an editor with folding and this is just unnecessary indirection and jumping around to read a linear series of steps that happen one after another. If a function is only called in one place, it should rarely be a function. It may help a small amount when viewing a stack trace, so you can tell at a glance without looking up the line and jumping to the section, if that is a big enough advantage, can make it a lambda and leave it all in place as long as the debugger/stack trace will list the lambda assignment name.


This is the first time I've heard "functions should be no bigger than your head" and I actually really like it. Rather than prescribing some arbitrary function length, it highlights that functions are meant to be understood and the appropriate length should come from that.


I can't really agree to this. Through without questions there are always things you should not split out.

Splitting a functions which consists of multiple logical steps is most times a good idea because it makes testing much simple and tends to show you where you accidentally had subtle cross-cutting concerns or unintended cross-talk between sub-domains (domains in sense of modelling, not http).

What is important is to properly name functions (and if you can't you probably shouldn't write that function).

Also it's important to learn how to live with abstraction, i.e. to reason about code wrt. a specific problem without needing to jump into the implementations about every function /method it calls.

Through the later point is much easier with a reasonable use-full type system. And with this I mean useful for abstraction without making abstraction to hard and without allowing to many unexpected things. Languages like rust or scala have such a type system (as long as you don't abuse it) but e.g. C++ fails this even through it has a powerful type system.

At least this are is opinion.

And without question if you can't cleanly split something out, then don't split it out. If you can split it out but not name it reasonable either your understanding it lacking or it should never have been split out.


That advice should be understood in the same way as "sentences should be no longer than a few dozen words" or "paragraphs should be no longer than a few lines". Of course adding a line break at an arbitrary point in a long paragraph doesn't make it better. But a solid wall of text is a red flag.


I thought I hated small functions too, til I realized I just hated scrolling. The moment it's off my screen, it's out of my head.

Assuming you just need helper functions for f(), compare

  int f(int x)
  {
    return g(h(x));
  }
  
  int g(int x)
  {
    return ...
  }
  
  int h(int x)
  {
    return ...
  }
to

  f = g . h
    where g = ...
          h = ...
Turns out my language choice was the problem, not over-abstraction. If you can fit it all on one screen, then have at it tbh.


The logical conclusion of this line of thinking is APL or K. Some people swear by it. I admit terseness is appealing for solo coding, and I loathe the bloat of Java etc, but when taken to the extreme terseness starts being a problem for collaboration.


I think you're right about those langs and collaboration; all the procedures are right there in front of you, but you gotta keep track of the data shape in your head. Just as much work as scrolling page-spanning functions imo.

That's what makes point-free Haskell such a sweet spot for me: terse, symbollic control flow and nice combinators, but types to guide you. I try to use it as a better J.


ive developed a defacto rule of thumb / intuition that a function size should basically be as big as it can be without being inconvenient to test. which means that its pretty small most of the time




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: