DEV Community

Cover image for Code Smell 132 - Exception Try Too Broad
Maxi Contieri
Maxi Contieri

Posted on • Updated on • Originally published at maximilianocontieri.com

Code Smell 132 - Exception Try Too Broad

Exceptions are handy. But should be as narrow as possible

TL;DR: Be as specific as possible when handling errors.

Problems

  • Fail fast principle violation

  • Missing errors

  • False negatives

Solutions

  1. Narrow the exception handler as much as possible

Sample Code

Wrong

import calendar, datetime
try: 
    birthYear= input('Birth year:')
    birthMonth= input('Birth month:')
    birthDay= input('Birth day:')
    #we don't expect the above to fail
    print(datetime.date(int(birthYear), int(birthMonth), int(birthDay)))
except ValueError as e:
    if str(e) == 'month must be in 1..12': 
        print('Month ' + str(birthMonth) + ' is out of range. The month must be a number in 1...12')
    elif str(e) == 'year {0} is out of range'.format(birthYear): 
        print('Year ' + str(birthMonth) + ' is out of range. The year must be a number in ' + str(datetime.MINYEAR) + '...' + str(datetime.MAXYEAR))
    elif str(e) == 'day is out of range for month': 
        print('Day ' + str(birthDay) + ' is out of range. The day must be a number in 1...' + str(calendar.monthrange(birthYear, birthMonth)))

Enter fullscreen mode Exit fullscreen mode

Right

import calendar, datetime

birthYear= input('Birth year:')
birthMonth= input('Birth month:')
birthDay= input('Birth day:')
# try scope should be narrow
try: 
    print(datetime.date(int(birthYear), int(birthMonth), int(birthDay)))
except ValueError as e:
    if str(e) == 'month must be in 1..12': 
        print('Month ' + str(birthMonth) + ' is out of range. The month must be a number in 1...12')
    elif str(e) == 'year {0} is out of range'.format(birthYear): 
        print('Year ' + str(birthMonth) + ' is out of range. The year must be a number in ' + str(datetime.MINYEAR) + '...' + str(datetime.MAXYEAR))
    elif str(e) == 'day is out of range for month': 
        print('Day ' + str(birthDay) + ' is out of range. The day must be a number in 1...' + str(calendar.monthrange(birthYear, birthMonth)))
Enter fullscreen mode Exit fullscreen mode

Detection

[X] Manual

If we have a good enough test suite, we can perform mutation testing to narrow the exception scope as much as possible.

Tags

  • Exceptions

Conclusion

We must make exceptions as surgical as possible.

Relations

Credits

Photon from Jakob Braun on Unsplash


The primary duty of an exception handler is to get the error out of the lap of the programmer and into the surprised face of the user.

Verity Stob


This article is part of the CodeSmell Series.

Latest comments (5)

Collapse
 
xtofl profile image
xtofl

I agree to the idea, but the example violates the 'fail fast' principle.

It should not let you input a month when the year is already unparseable.

Indeed, the very try block contains too much code, which results in an awkward effort to reconstruct which of the three statements went wrong.

This could be greatly improved by try/except for each input separately.

Collapse
 
codenameone profile image
Shai Almog

I'm not sure if I would agree with that as a general rule. The whole idea of exception handling is to propagate exceptions sometimes even way up the stack. For many cases I don't even catch exceptions and let them propagate into the generic handling code.

Collapse
 
mcsee profile image
Maxi Contieri

Yes.
This is for the case you want to catch it.
If you just want to propagate this code smell does not apply.

Collapse
 
otumianempire profile image
Michael Otu
birthYear= int(input('Birth year:'))
birthMonth= int(input('Birth month:'))
birthDay= int(input('Birth day:'))
Enter fullscreen mode Exit fullscreen mode

Wouldn't any of these also raise an error if the user enters a non-integer character?

Again in

print(datetime.date(int(birthYear), int(birthMonth), int(birthDay)))
Enter fullscreen mode Exit fullscreen mode

the data above are been cast to int.

So I think you meant,

birthYear= input('Birth year:')
birthMonth= input('Birth month:')
birthDay= input('Birth day:')


try: 
    print(datetime.date(int(birthYear), int(birthMonth), int(birthDay)))
except ValueError as e:
   ....
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mcsee profile image
Maxi Contieri

Yes. You are right.
I am not very fluent in Python.
I try to use as many languages as possible to show code smells are language independent.
Thank you very much for the correction