How to write a good function?
Tags: python, codenewbie, codequality, javascript
Published: 2021-05-30 11:02:12

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. 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1622096113415/WUj9cCcH_.png)

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