When I split big methods (or procedures, or functions — this question is not specific to OOP, but since I work in OOP languages 99% of the time, it's the terminology that I'm most comfortable with) into a lot of small ones, I often find myself displeased with the results. It becomes harder to reason about these small methods than when they were just blocks of code in the big one, because when I extract them, I lose a lot of underlying assumptions that come from the context of the caller.
Later, when I look at this code and see individual methods, I don't immediately know where are they called from, and think about them as ordinary private methods that can be called from anywhere in the file. For example, imagine an initialisation method (constructor or otherwise) split into a series of small ones: in the context of method itself, you clearly know that object's state is still invalid, but in an ordinary private method you probably go from assumption that object is already initialised and is in a valid state.
The only solution I've seen for this is the where
clause in Haskell, which allows you to define small functions that are used only in the "parent" function. Basically, it looks like this:
len x y = sqrt $ (sq x) + (sq y)
where sq a = a * a
But other languages I use don't have anything like this — the closest thing is defining a lambda in a local scope, which is probably even more confusing.
So, my question is — do you encounter this, and do you even see this is a problem? If you do, how do you typically solve it, particularly in "mainstream" OOP languages, like Java/C#/C++?
Edit about duplicates: As others noticed, there are already questions discussing splitting methods and small questions that are one-liners. I read them, and they don't discuss the issue of underlying assumptions that can be derived from caller's context (in example above, object being initialised). That's the point of my question, and that's why my question is different.
Update: If you followed this question and discussion underneath, you might enjoy this article by John Carmack on the matter, in particular:
Besides awareness of the actual code being executed, inlining functions also has the benefit of not making it possible to call the function from other places. That sounds ridiculous, but there is a point to it. As a codebase grows over years of use, there will be lots of opportunities to take a shortcut and just call a function that does only the work you think needs to be done. There might be a FullUpdate() function that calls PartialUpdateA(), and PartialUpdateB(), but in some particular case you may realize (or think) that you only need to do PartialUpdateB(), and you are being efficient by avoiding the other work. Lots and lots of bugs stem from this. Most bugs are a result of the execution state not being exactly what you think it is.