Avoid Public Functions Calling Other Public Functions

64 points by telemachus


andrewrk

A concrete thing related to this I just ran into yesterday, is that when reporting certain kinds of errors, I collect a stack trace for later rendering to indicate which public function call was responsible for the creation of a particular artifact.

You can make the first stack frame be the callsite of the public function by capturing the return address there, and then passing that to all the private functions. If you called another public function, it would break this pattern because the call stack would contain 2 of the public functions. Not a big deal but it falls neatly into the pattern described by the article.

azimux

The problem I see there is not that foo calls bar specifically, but rather that bar's public interface has been damaged as an implementation detail of foo as opposed to a tiny refactor. So I feel like the better more general rule would be something like "do not damage the public interface unless absolutely necessary." Though, if using the avoid public->public rule works as a mechanism to help where there might be ambiguity or a lack of discipline then, sure, could be a good best practice.

bputh

I've been in the shoes of a developer adding optional function after optional function to make things "easier" for users. Some of the worst APIs grow this way. "Everything is in one function call" except that you've effectively reinvented having multiple functions by way of parameters.

Also, "sanctified ink" is definitely a phrase I will be using in the future.

telemachus

I see how having both foo and bar call a private helper improves the public API. But I immediately wondered why not have different private helpers, frobnicate and transmogrify. (To put this another way: helper(.frobnicate) and helper(.transmogrify) struck me as swapping the original flag boolean for a flag enum.) I guess it all depends on how much common code frobnicate and transmogrify would have, though perhaps even then, the two private helpers could both call another private helper for the overlap. (I'm probably focusing too much on the details of the example, which is not fully fleshed out.)

weberc2

Which is, imo, a natural, but horrible consequence of misguided approach to testing. You shouldn’t be mocking your methods, you should mock your IO

Is mocking a MyCustomResourceStore “mocking your IO”, or does “mock your IO” refer to some lower layer of the I/O stack? Where does I/O begin?

enobayram

Or you could do whatever makes the most sense at a given moment and then when the circumstances change, refactor your code so it keeps making the most sense under the new circumstances

ohrv

It depends: for example, in Rust's Vec there are a lot of pub methods that call other pub methods for a bunch of different reasons (safety, reuse, allocator API, ..).