How to smartly change function signatures

Lorenzo Peppoloni
Sep 11, 2018 · 3 min read
Photo by Ilija Boshkov on Unsplash

THIS IS A TRUE STORY.
The events depicted in this blog post
took place in London in 2018.
At the request of the survivors,
the function names have been changed.
Out of respect for the dead code,
the rest has been told exactly
as it occurred.

Background

In my current company, we have several different Slack channels for the different programming languages we use.
Language-specific tips are posted every day in each channel, moreover, anyone can post help requests which are usually addressed by more expert team members.
Recently one of my co-workers asked an interesting question.

He was working with an internal library, where a function had some hard-coded parameters.

The author of the library defined the function following a convention over configuration approach.
If you are not familiar with this design pattern, basically it aims at decreasing the number of decisions that a developer using a certain framework needs to make, without losing the necessary flexibility.

Now, my co-worker wanted to make it possible to pass the hard-coded parameters as arguments.

Changing the function signature would have led to potentially destructive changes since all the function calls in the code-base should have been updated.

Let’s make a concrete example in Go to better understand the situation.

type struct User{
Name string
Role string
}
func NewUser(name string) *User {
return &User{
Name: name,
Role: “bar”,
}
}

In the example code, I wrote a function to create an entity of type User, and for some reason, I decided that it’s better to hardcode the Role field.

Then, at a certain point, I decide that I need to pass also the Role as an argument…

One way to do that is to change the function to

func NewUser(name, role string) *User {
return &User{
Name: name,
Role: role,
}
}

Do you see how all the existing function calls (tests included) need to be changed?

An alternative solution

Photo by Olena Sergienko on Unsplash

An alternative solution is to create the function that you need and make the old one call that function with a default arg.

func NewUserWithRole(name, role string) *User {
return &User{
Name: name,
Role: role,
}
}
func NewUser(name) *User {
return NewUserWithRole(name, “bar”)
}

In this way, you do not have to make any change to the legacy code.

Caveats

  1. This should not be abused.
    Having 3–4 levels of nesting it’s not clean and leads easily to cryptic code which is really hard to follow.
  2. In other languages (like Python) you could’ve just added default args from the get going, something like (I’m not gonna spend time here debating if it should be __init__ or __new__, it’s out of scope)
class User: 
def __init__(self, name, role=”bar”):
self.name = name
self.role = role

Conclusions: In the article we had a look at how we can elegantly change a function signature without actually changing the function itself. This will to a cleaner management of the legacy code, since we do not have to modify all the existing function calls.

If you enjoy the article and you found it useful feel free to 👏 or share.

Cheers

Lorenzo Peppoloni

Written by

Tech enthusiast, life-long learner, with a PhD in Robotics. I write about my day to day experience in Software and Data Engineering.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade