Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

And this is worse advice:

> Functions should do one thing only. ... In addition to be easier to comprehend, smaller functions are easier to test in isolation, and now you’ve isolated the orthogonal code into its own function, its name may be all the documentation required.

Using single-caller functions as a substitute for comments makes the workings of a specific operation much harder to follow, as you have to jump around the source to understand its effects.

A long function is easier to understand than an exploded one.

Also tests should target specific operations (aka functional tests), not every single function in the program.

EDIT: Every function you add becomes part of your internal API. Any API, exported or not, should comprise a cohesive collection.



> A long function is easier to understand than an exploded one.

This is a pretty controversial position, and quite situational in my opinion. I absolutely agree that having to hop all over the source to understand something is frustrating, but that doesn't mean that very long functions are the right solution. Some combination of reasonably named helper methods and a function flow that makes the logic easy to parse should be the goal; either end of the spectrum is a problem.



The summary is great:

> If a function is only called from a single place, consider inlining it.

> If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that.

> If there are multiple versions of a function, consider making a single function with more, possibly defaulted, parameters.

> If the work is close to purely functional, with few references to global state, try to make it completely functional.

> Try to use const on both parameters and functions when the function really must be used in multiple places.

> Minimize control flow complexity and "area under ifs", favoring consistent execution paths and times over "optimally" avoiding unnecessary work.


I took issue with this because it's a conventional wisdom, and does a fair bit of damage.

Single-caller functions attract other callers over time, gain backwards-incompatible features, and result in regressions.


This is one reason I like nested functions. They’re not available to the surrounding scope so they don’t succumb to these weaknesses, while also allowing you to organize your very long function internally by task. I use ‘em in Python all the time.

It’s a bummer that more languages don’t support them, though you can get there with lambdas too, sometimes at the cost of more syntax.


Go has closure functions; great feature.

One of the (few) things I like about Javascript is the ability to define a closure anywhere in the containing function, so it appears in the order of operations:

  function f() {
    setTimeout(fDing, 2000);
    g();
    function fDing() {
      ...
    }
  }


Disclaimer: I have no Golang programming experience.

I am curious with this approach though...you're nesting behavior and/or logic, doesn't this further obscure the meaning of the code and contribute more to the need to jump around the source in order to figure it out?


Go has anonymous functions too.

The Go Programming Language book (Kernighan and Donovan) has some examples of them.


Now write a unit test for

    function fDing() {
      ...
    }
Honestly with modern JS I am not sure this feature is that great. Looks more like a code smell these days imo.

Edit: Formatting


Now write a unit test for the inline block of code within the longer function which would have become the nested function???

You write the test (where any is needed) for the outer function.

Edit: the start of the discussion was about using nested functions to decompose what otherwise would have been “unpartitioned” long functions. Such blocks of code nested within a long function would not be possible to unit test, either.

Unit tests, rather than integration tests, are usually bogus, anyway, though.


You write unit tests for units of code. A function with nested functions inside of it is a single unit of code; that's essentially what those functions being nested, and hence not directly invokable from the outside, indicates.


Make private functions that are only visible to the current module. Then write your unit tests directly in that same module, next to the functions, so they can access them even when the rest of the world cannot. Of course, this requires sensible language support.


Hierarchy vs list. I don’t want a list (of subroutines). I want a tree of self contained routines. Only your containing routine uses you. Which “private“ routines use which? (I know the IDE will tell me about this routine, and that routine, but I don’t want to have to ask)

Your employer doesn’t want you testing getters, anyway, but rather features. Unit test fanatics need to stop.

I guess unit tests were useful for C++, when it was constantly crashing everything :-(

C++ and its legacy need to ride off into the sunset, already.


A bit of our “heritage” in programming destroyed by the C family of programming languages

Pascal, like Algol, had nested subroutines for decomposing longer operations without leaking the details.

Nested functions is one of the things I like about JavaScript as well.


C has static functions for decomposinglo ger operations without leaking the details.

For better or for worse, taking advantage of that forces you to keep source files fairly short and cohesive in functionality.


You are still leaking the details of the function that is the sole caller of those other functions. It's not leaking across translation unit boundary, but it's not the only one that matters.


ML derived languages, Julia, D and C# support them.


GNU C supports nested functions as an extension. I use them for the exact reasons you mention.


That extension results in executable stack memory, which is going to be a non-starter for many developers.


I believe that that only happens if you pass a nested function as a function pointer. Then gcc emits a trampoline.

If you just call it from the enclosing function it's fine.


Yeah, agreed. I do this in Rust a lot too, and I picked it up while writing a lot of Python.


C# has inner named functions.


An excellent balance is to try to make a function only do one level of abstraction at a time. It's a bit of a flexible guideline, but basically you shouldn't call `isUserActive`, do some complex arithemtic, read extract data from a complex data structure, and call a templating engine in one function, since those a results all different levels.

As long as what are doing is approximately the same type of thing, it is fine to do a lot of things without breaking readability.


I believe you've misunderstood this. A function can be very long and do only one thing or very short and do many things.

The function:

  func ManyThing(i int) int {
   fmt.Println(i)
   return i+1
  }
does two things, and it's two lines long. The function tcp_send_message_locked (https://github.com/torvalds/linux/blob/master/net/ipv4/tcp.c...) does one thing at it's 261 lines long.

Shorter code is _indicative of_ orthogonality (what he asks you to break on)__but_ it is not the same thing.


My critique is of single-caller functions as a documentation device, not shorter functions.


But he doesn't advocate that... anywhere. He advocates factoring out orthogonal code to get shorter functions.


Inclined to agree, expounded upon here (“Classes should be deep.”): http://alex-ii.github.io/notes/2018/10/07/philosophy_of_soft...


> Functions should do one thing only

Well, that's extremely good, and standard, advice.

https://en.wikipedia.org/wiki/Single_responsibility_principl...


Longer functions are much more prone to causing errors, and errors that are harder to find. It is honestly much better to have functions that do one thing and one thing only. Might not always be possible, but it is always the best way to code.


It seems that belief is unfounded. See the link posted by @xtian.


I think “doing one thing” and “getting called one time” are getting confused.

GetData()

FormatData()

UploadData()

Those could do one thing but could be called at several points within a larger program, which i think you are fine with. Or they could only be called once in which case i think you are saying it might make sense to just inline them.

If I understand you correctly, then I agree.


I don't necessarily agree with "one thing only". At least, for things that start simple and grow as needed, I split things into functions either to not have to copy and paste the same code, or for readability/structure purpose. But not out of principle and always, until I can't divide any further. I can still split things out into functions later, should I need it, but doing it "just in case" and then not even having a use for it doesn't save me time and just adds overhead.

Though it also depends on whether I'm doing something familiar or something new, if I'm doing something new I might split things up more to help me conceptualize the problem. But when I'm just making a quick CLI tool, I might put it all in main first and only split it up as needed.




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

Search: