Size is irrelevant. You should be thinking in terms of levels of abstraction and points of potential change. At that point, there is no minimum size of a function. The function should capture an idea. For example,
def normalize(s):
return s.strip()
In this case, normalize
is essentially just an alias for strip
. However, strip
operates at the level of string manipulation, while normalize
conveys the notion that there are non-normalized and normalized representations. Conceptually, normalize
is just an operation on an abstract data type that just happens to be being represented by a string. It's also quite possible that what is necessary to "normalize" a string in the relevant sense might change. How often a function is used in this context is irrelevant. If I make a Stack abstract data type, I don't inline the definition of a pop
operation, just because it's used only once.
On the other hand, oftentimes functions functions capture commonly reoccurring patterns for convenience. These are functions that are defined in terms of functions at one level of abstraction and produce a function at the same level of abstraction. You should still endeavor to have these functions capture a specific idea, and that may suggest replacing the reoccurring pattern with a smaller/simpler pattern of functions rather than just a single function. That said, a commonly occurring pattern usually indicates that there is some idea that can be captured. For these types of functions, how often they occur does matter. It doesn't make much sense to make a convenience function for something that rarely occurs. It also isn't a good trade in time and cognitive load to make someone look up and remember the definition of an operation that is rarely used when you could just inline the definition.
Your question gives too little context to decisively provide an answer. If save_data
is part of a barrier of abstraction from how the data is stored, then you absolutely should have it regardless of how short it is. However, in that case, there should (probably) be corresponding operations that are also part of that abstraction barrier that read the data in. It should be possible to, for example, switch from JSON to XML just by modifying the functions of the abstraction barrier and none of the consuming functions. If this is not the case, then you either need to raise the level of abstraction elsewhere, or save_data
isn't serving an abstraction purpose, and may well be hiding relevant details and thus shouldn't exist.
If you don't expect save_data
to be used elsewhere and it isn't serving as part of an abstraction barrier, then it probably shouldn't exist. By pulling code out into a function, you are stating that the details aren't that important to the consumer. However, if those details are important, they shouldn't be hidden. For example, if save_data
is used inside a serialize_to_json
method, hiding the fact that save_data
does actually write out JSON doesn't make sense. And if you decide, serialize_to_json
should be serialize_to_xml
, you shouldn't need to go to some other function to change that detail. So, in this case, I would agree with your colleague that the function's definition should be inlined and the function shouldn't exist.