Here are my 3 things to look out for while writing a good function. The code examples are given in python but the concept applies across all the programming languages. If you have any other ideas/strategies please mention them in the comments.
Don't Trust The Inputs
Look at the code below and think about what can go wrong.
def process_date(str_date):
"""
This takes date in format dd/mm/yyyy and returns
a dict of format {'y':2000,'m':10,'d':19 } as output
"""
d,m,y = str_date.split('/')
value = {'y':y,'m':m,'d':d}
return value
At first glance, the code seems to work fine. If you run the function
process_date(str_date = 10/20/2000)
.
Then the output of the function will be:
{'y' : 2000,'m' : 20, 'd' : 10}
The function returns an output so everything seems to be working right? But there is a logical error. Months can never be greater than 12. The same goes for days and years. Apart from that what if the user passed negative values-1/-1/-1
? Also, what if the user provided an empty string?
Here, we made a mistake of trusting the inputs. We should never trust the inputs. Be skeptical about the parameters and think hard about the edge cases. Make sure to sanitize your input before you perform any computation on them.
Fail Loudly and Clearly
What if you change the format of the date? From 10/20/2000
to 10-20-2000
. This would completely break your code.
So, when the input is not what we want them to be, we want to notify the user about this issue. And if we have to fail, choose to fail loudly and clearly. The above error message is quite not clear compared to the one below, which clearly mentions that the issue is because of the input format of date supplied
regex_date_format = "^\d{1,2}/\d{1,2}/\d{4}$"
if not re.match(regex_date_format, date_str):
raise ValueError(
f"str_date: {str_date} should be of the format dd/mm/yyyy "
)
Also, Most of the time, we tend to simply return None
if we get any error.
if error:
return None
if error:
return None
some code
return result
This especially causes issue while debugging the program. In above dummy program, if we get a None
as output, then which error in particular gave us the None
value?
So, being loud and clear about the errors that occurs in program helps other developers and users of the function to understand what it is that is causing the issue/error.
Be consistent with return type
One thing I like about statically typed language is their consistency. If you are using a function then you'll know if that will return an array
or a dict
or an string
etc. This allows us to process the output in some consistent manner and also avoids confusion and bugs in the program.
def return_something( input ):
if error:
return 'Error connecting to the server'
code
return {"data":"Hello world"}
Suppose someone uses this function in the following way:
response = return_something(inp)
data = response['data']
Here, the code breaks if there is any error while connecting to the server. The right way of checking it would be
response = return_something(inp)
if isinstance(response, str):
handle_error
data = response['data']
The other way to write the same function would be:
def return_something( input ):
if error:
return {"error": 'Error connecting to the server' , "data": "" }
code
return {"data":"Hello world", "error": "" }
In both cases we get the same fields, this helps us be consistent with the way we process the data further in out code, and should not worry if we get a string or an array etc.
response = return_something(inp)
data = response['data']
error = response['error']
if error:
handle_error
if data:
do_something
This increases the code readability also. In the first example, one could wonder, why is it that we are handling the error if the instance is a string. Whereas in the second implementation it's clear that any response contains two fields the data
and error
and if we get any error we can do some error handing.
Please provide your feedback
Top comments (0)