Avoid Public Functions Calling Other Public Functions

64 points by telemachus 16 hours ago on lobsters | 9 comments

andrewrk | 13 hours ago

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.

bputh | 15 hours ago

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.

azimux | 12 hours ago

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.

[OP] telemachus | 15 hours ago

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.)

gavinmorrow | 13 hours ago

That was my thought too, but I assumed that it was just an example and that it shouldn’t be looked into too deeply.

azimux | 12 hours ago

Re: the private methods, I suppose I feel like either could be refactored into the other without much issue. I'm viewing it more as that one problem with extra stuff exposed through bar is that somebody outside could couple to that and now removing it breaks stuff. That is, we could improve the internal code-quality of the implementation as needed, but messing up the public API can be more painful for longer and in some rare cases for eternity.

weberc2 | 11 hours ago

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?

spc476 | 11 hours ago

Data that comes from or goes outside the program itself. This can be a file, a socket, a TTY, environment variables, command line parameters, that type of stuff. When I made my DNS library I explicitly separated packet receiving/sending from parsing/generating (you get a DNS packet, then send it to be decoded; it's not decoded directly from the wire).

enobayram | 2 hours ago

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