开发者

Is it best practice to try - catch my entire PHP code, or be as specific as possible?

开发者 https://www.devze.com 2022-12-30 01:21 出处:网络
I do not have many kinds of Exceptions in my project. Right now,(we use MVC) I have the try catch encompassing my entire code:

I do not have many kinds of Exceptions in my project.

Right now,(we use MVC) I have the try catch encompassing my entire code:

try{
   fronController::dispatch($somthing...);
}catch(Exception $E){
  //handle errors
}

I wonder if there is a good rea开发者_开发技巧son to use the try-catch block in as specific as possible way as I can or just keep it general as it is now?


The idea of an exception is so that a function can report failure without having to return special values. In ye old PHP, the only way a function could say it had a problem was by returning some special value like false or -1. This is not pleasant. For example, suppose I am writing a variant of file_get_contents().

The typical return value is a handle - represented by a positive integer. However, there are two basic problems I can encounter: the file you specified was not found, or the file you specified was not readable. To indicate an error I might return a negative number - because handles are positive - that associates to the particular cause of error. Let's say that -1 means the file wasn't there and -2 means the file wasn't readable.

Now we have a problem that -1 and -2 do not inherently mean anything to someone reading the code. To rectify this we introduce the global constants FILE_NOT_FOUND and FILE_NOT_READABLE. Let's see some resultant code.

<?php

define('FILE_NOT_FOUND', -1);
define('FILE_NOT_READABLE', -2);

function my_file_get_contents($file) {
    // blah blah blah
}

$friendListFile = getDefaultFriendListFile();

$result = my_file_get_contents($friendListFile);

if ($result == FILE_NOT_FOUND) {
    deleteFriendListFromMenu();
} elseif ($result == FILE_NOT_READABLE) {
    alertUserAboutPermissionProblem();
} else {
    useFriendList($result);
}

By having different error codes we can act accordingly to what the problem really is. That functionality is well and fine. The issue is purely in how the code reads.

$result is a horrible variable name. Variable names should be descriptive and obvious, like $friendListFile. The real name for $result is $fileContentsOrErrorCode which is not only too long, it examplifies how we are overloading a single variable with two meanings. You never, ever, want to have the same data mean two things. We want a separate $errorCode and $fileContents!

So how do we get around this problem? One not-really-a-solution some PHP libraries have used is to have their my_file_get_contents()-like functions return false if they encounter a problem. To disambiguate what the problem actually was we instead call my_file_get_contents_getError(). This almost works.

define('FILE_OKAY', 0);
define('FILE_NOT_FOUND', -1);
define('FILE_NOT_READABLE', -2);

$my_file_get_contents_error = FILE_OKAY;

function my_file_get_contents_getError() {
    // blah blah blah
}

function my_file_get_contents($file) {
    global $my_file_get_contents_error;
    // blah blah blah
    // whoa, an error? return false and store the error code in
    // $my_file_get_contents_error
    // no error? set $my_file_get_contents_error to FILE_OKAY
}

$friendListFile = getDefaultFriendListFile();

$result = my_file_get_contents($friendListFile);

if (my_file_get_contents_getError() == FILE_NOT_FOUND) {
    deleteFriendListFromMenu();
} elseif (my_file_get_contents_getError() == FILE_NOT_READABLE) {
    alertUserAboutPermissionProblem();
} elseif (my_file_get_contents_getError() == FILE_OKAY) {
    useFriendList($result);
} else {
    die('I have no idea what happened. my_file_get_contents_getError() returns '
        . my_file_get_contents_getError()
    );
}

As a note, yes, we can do a much better job by avoiding a global variable and other such little bits. Consider this the nuts-and-bolts demonstration.

We still cannot call $result anything better than $fileContentsOrFalseIfError. That problem has not been fixed.

I have now rectified one problem that you may have noticed in the earlier example. What if we do not cover all of the error codes? If a programmer decides that there needs to be a -3 code we weren't originally detecting it! We could have checked if $result was a string to make sure it wasn't an error code, but we aren't supposed to really care about types in PHP, right? Now that we can utilize a second return value from my_file_get_contents_getError() it is no problem to include a success code.

There is now a brand new problem that has emerged. Fix one and find three more eh? The new problem is that only the most-recent error code can be kept. This is terribly fragile! If anything else calls my_file_get_contents() before you deal with your error code, their code will overwrite yours!

Gah, now we need to keep a list of functions that are unsafe to call before you deal with the return value from my_file_get_contents_getError(). If you don't do that, you have to keep as an ad-hoc coding convention that you always call my_file_get_contents_getError() immediately after my_file_get_contents() in order to save the error code that belongs to you before it is mysteriously overwritten.

Wait! Why don't we just hand out identifiers to our callers? In order to use my_file_get_contents() you now have to ask create_my_file_get_contents_handle() for some number that will disambiguate you with all other callers. Now you can call my_file_get_contents($myHandle, $myFile) and the error code can be stored in a special location just for you. Now when you call my_file_get_contents_getError($myHandle) you can access that special place, get your error code, and no one has stepped on your toes.

Er, but if there are many callers we don't want to have zillions of useless error codes laying around. We had better ask users to call destroy_my_file_get_contents_handle($myHandle) when they are done so we can free some memory.

I hope this is all feeling very familiar to ye old PHP mantras.

This is all so crazy, just make it simple, please!

What would it mean if the language supported a better mechanism to react to errors? Clearly, trying to create some solution with the existing tools is confusing, obnoxious, and error-prone.

Enter exceptions!

<?php

class FileNotFoundException extends Exception {}
class FileNotReadableException extends Exception {}

function my_file_get_contents($file) {
    if (!is_file($file)) {
        throw new FileNotFoundException($file);
    } elseif (!is_readable($file)) {
        throw new FileNotReadableException($file);
    } else {
        // blah blah blah
    }
}

$friendListFile = getDefaultFriendListFile();

try {
    $fileContents = my_file_get_contents($friendListFile);
    useFriendList($fileContents);
} catch (FileNotFoundException $e) {
    deleteFriendListFromMenu();
} catch (FileNotReadableException $e) {
    alertUserAboutPermissionProblem();
}

All of a sudden our old headaches of special return values and handles and coding conventions have been cured!

We can now truly rename $result to $fileContents. If my_file_get_contents() has a problem, the assignment is aborted altogether and we jump right down to the appropriate catch block. Only if there is no error do we even think about giving $fileContents a value or calling useFriendList().

No longer are we plagued by multiple callers stepping on each other's error codes! Every call to my_file_get_contents() will instantiate its own exceptions, if the error arises.

No memory problems! The garbage collector will happily clean up no-longer-used exception objects without you thinking about it. Using ye old handle system we had to remember to manually destroy the handle, lest have it lurk around forever in memory.

There are many other benefits and traits to exceptions. I strongly recommend looking to other sources to learn about these. Particularly interesting are how they bubble up the execution stack until some caller can catch them. Also interesting is how you can catch an exception, try to fix the problem, and then rethrow the exception if you can not. Do not forget that exceptions are objects! There is loads of flexibility to be gained by that. For exceptions that no one can catch, look into the exception handler.

My intent to answer the question was to demonstrate why we need exceptions. By doing this, I hope it is easy to infer what problems we can solve with them.


generally throw locally, catch globally unless an exception handler is specific to a function in which case handle locally.

 class fooException extends Exception{}

 // DB CLASS

 public function Open(){
    // open DB connection
    ...
    if ($this->Conn->connect_errno) 
      throw new fooException("Could not connect: " . $this->Conn->connect_error);
  }

 // MAIN CLASS

 public final function Main(){
    try{
      // do stuff
    }
    catch(fooException $ex){
       //handle fooExceptions
    }
 }


Remember that exceptions are for exceptional cases. As I understand that, that happens when the error is out of your control. For example, invalid parameters are passed to a public API function, division by zero, situations like 'network connection lost', 'file not found'... this kind of things.

As a general rule, you should catch the exceptions that you know how to handle, like recovering from the error, log the error and propagate it, etc. If you don't know how to handle it, it's better to let it fail. Otherwise your application could be in an error state that you may not want.

So answering your question, it's better to be as specific as possible since every exception should be handled only if you know what to do with it (silently swallowing is a bad idea). If not just let the exception notify the user that something went wrong. Or if you want to, catch the exception to log the error and rethrow it.

There's good discussion here for C++, but the general concepts apply. I found the java tutorials on exceptions also very good.


You should be as specific as possible with catching errors in your code. Catching specific errors appropriately increases code maintainability, makes your code structured and organized.

It is also good practice as a convention, especially if you later work on team-based projects and you're not the only one looking at the code.

Personally throwing everything into one try catch block seems to be a code smell.


If you are using a try block for all your code, you might as well define a default exception handler (see the docs).

Other than that, the size of the try block is up to you, it depends on how fine you want your error handling to be. If you can't recover from any of the exceptions, there's really no reason to be specific, unless you want to log error messages that are specific (but the message of the exception and the stack trace will probably be enough).


If your handling all of your errors with one general catch you will get minimal feedback and options when an error does occour, it may be fine during development but when its on the front line it could cause you no end of problems.

Be specific and cover all of your bases where feedback is needed and recoverablity is possible.


Different errors may require different responses.

You wouldn't jump out of an airplane in response to every possible problem that could arise. Would you?

Well, that's what your app is doing.

There are situations where an exception can be caught and the application could continue to run. More likely, the app may need to respond to the same class of exception differently in different situations. Perhaps in one function an I/O exception isn't detrimental but in another it is.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号