[PYTHON] The End of Catastrophic Programming # 04 "Mistaken Exception Catch Range"

An ordinary programmer understands intuitively and reflexively, I would like to write an article about a rudimentary and simple story. Because of the rudimentary nature, a heavy programmer like me I do it well (even if I thought I understood it).

If there are any mistakes or inappropriate content, please comment We will consider corrections and adjustments to the content.

1. Exception handling

(Although there are pros and cons), Many languages have exception handling as a language specification.

This time, Imagine an exception to the try --catch method. Java, C #, C ++, Python, etc. should have similar exception mechanisms. (In Python, try --except may be transmitted.)

The range of where to catch exceptions is very important. If you misunderstand this, you may not be able to do it.

2. Compare the two codes

The code below is full of other things to dig into, For the time being, I would like you to pay attention only to the range of try --catch. (I want to make a simple example, so Is it more efficient to use threads, or is it okay to null? Forget about that this time. )

The code is C #, but even if you have experience with other languages, I think you can imagine it somehow.

Consider a method that notifies multiple users of an urgent message. If you pass a list of users and a message string, It's like notifying all the users on the list with a message. We are catching exceptions because notifications to each user can raise exceptions.

/// <summary>
///Notify multiple users of urgent messages.
/// </summary>
/// <param name="users">User list of destinations.</param>
/// <param name="message">The message to send.</param>
void NotifyUrgentMessage(User[] users, string message)
{
  try
  {
    //List users.
    foreach (var user in users)
    {
      //Notify each user of a message.
      user.NotifyMessage(message);
    }

  }
  catch(NotificationException ne)
  {
    //Output to log because notification failed
    Log.ErrorFormat("Notification failed: ErrorCode = {0}", ne.ErrorCode, ne);
  }

}

The other code is almost the same. I implemented the same functionality in the same way, but try --The range of try for catch is different. Other than that, there is almost no difference (actually, the log output contents are slightly different due to the structural difference).

/// <summary>
///Notify multiple users of urgent messages.
/// </summary>
/// <param name="users">User list of destinations.</param>
/// <param name="message">The message to send.</param>
void NotifyUrgentMessage(User[] users, string message)
{
  //List users.
  foreach (var user in users)
  {

    try
    {
      //Notify each user of a message.
      user.NotifyMessage(message);
    }
    catch(NotificationException ne)
    {
      //Output to log because notification failed
      Log.ErrorFormat("Notification failed: ErrorCode = {0}, UserGUID = {1}", ne.ErrorCode, user.Guid, ne);
    }

  }

}

2. Consider the difference between Implementation 1 and Implementation 2

In situations where the NotificationException exception does not occur, there is no operational difference between the two. (Strictly speaking, there are differences in operating performance.)

In situations where a NotificationException exception occurs, the behavior is the same and It may behave completely differently. In essence, it behaves completely differently.

ʻSuppose the Userlist contains 10 Users. If aNotificationException` occurs at the 3rd place in the list (assuming it starts from 0th) In "Implementation 1", you will exit from the for statement (foreach statement). Notification processing after the 4th in the list is not executed. You can expect to be notified to three people.

On the other hand, in "Implementation 2", it cannot be removed from the for statement (foreach statement), so The fourth and subsequent notification processes in the list will be executed. If the NotificationException was only the third, You can expect to be notified to 9 people.

NotificationException, If it occurs at the end of the list, 9th (assuming it starts from 0th), It can be expected that 9 people have been notified of both "Implementation 1" and "Implementation 2". However, where in the list the NotificationException occurs is I don't usually know.

The point depends on the number in the list where the exception occurs. The number of users who receive notifications will also change. Above all, due to a user's notification error (for example, the effect of a misconfiguration) It also affects notifications to other users.

If the NotificationException is an exception that can occur independently for each ʻUser`, "Implementation 2" seems to be better.

3. The end

Not limited to this example, in exception handling Bad things can happen if you don't implement the range you want to try properly.

Especially, try --catch, Structurally, whether it should be inside or outside for, foreach, while, etc. It may be necessary to consider it thoroughly.

In addition, even if the repeating statement is not included, when the result of process A is used in process B, etc. The scope of the exception try is not appropriate I went to process B even though I couldn't get the result in process A. There are some cases where uncaught exceptions are thrown to unexpected destinations.


ResultA result = null;

try
{
  result = doA();
}
catch(AException ae)
{
   Log.Error(ae);
}

doB(result.Text)

(Although this example is too simple) If doA () can throw ʻAException, If an exception occurs You should get a NullReferenceException (NullPointerException) in the result.Text part of the doB ()` call.

4. Countermeasures

Basically, you need to think carefully when implementing exception handling.

The range of try for exception handling is

I think that is important.

There should be a reasonable range for almost all exception handling.

In the previous example of NullReferenceException (NullPointerException), I think there is a way to check for null, but In this case, if the result of process A affects process B, I think the following is cleaner. (How to deal with exceptions is not the focus of the discussion this time.)


try
{
  var result = doA();

  doB(result.Text)
}
catch(AException ae)
{
   Log.Error(ae);
}
catch(BException be)
{
   Log.Error(be);
}

If the range is slightly different, when an exception occurs, It may behave differently as a whole, Careful consideration must be given to how it behaves when various exceptions occur. Also, as much as possible, it would be nice to be able to test the behavior when an exception occurs.

Recommended Posts

The End of Catastrophic Programming # 04 "Mistaken Exception Catch Range"
The popularity of programming languages
Python Basic Course (at the end of 15)
The end of catastrophic programming # 02 "Dice 5 and 6 are hard to come out ... I feel like random numbers are considered"
I read the implementation of range (Objects / rangeobject.c)
Specifying the range of ruby and python arrays
Transition animation of the most popular programming languages (#programming languages #popular)
Send Gmail at the end of the process [Python]
Remove specific strings at the end of python