Defaults are useful when you are providing a library function for other teams to use. If you’re inside a more private code base and doing work on the implementation of your team’s service then it is wise to avoid default arguments.
The problem is they provide a point after which it seems acceptable to add a flood of more default arguments. This is particularly the case for junior developers who lack confidence to refactor instead of patch. Default arguments go hand in hand with conditional logic and cause functions to bloat into do-everything multi-page monsters without any focus and no tractable flow of logic.
Forgive the contrived example, but what was once this:
def greet(name):
print(f”Hello {name}”)
ends up becoming this, all because no one would bite the bullet and pick this apart into individual functions: def greet(
name,
language=None,
io=None,
is_ci=False,
and_return=False,
):
greeting = “Hello”
if language:
greeting = translate(greeting)
message = f”{greeting} {name}”
fn = print
flush = False
if is_ci:
fn = log
flush = True
fn(
greeting,
flush=flush,
io=io if io else stdout,
)
if and_return:
return greeting
The slow rot of more and more defaults makes the function longer and longer. Moreover, each time someone adds a new option it gets harder to justify why they shouldn’t do it when the previous person was allowed.