最佳做法是尝试 - 捕获我的整个PHP代码,还是尽可能具体?这一切都太疯狂了,请把它简单一点!

2022-08-30 23:50:34

我的项目中没有很多种异常。
现在,(我们使用MVC)我有一个包含我的整个代码的尝试捕获:

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

我想知道是否有充分的理由以尽可能具体的方式使用 try-catch 块,或者只是让它像现在这样通用?


答案 1

异常的概念是使函数可以报告故障,而不必返回特殊值。在旧的PHP中,函数可以说它有问题的唯一方法是返回一些特殊值,如或。这并不令人愉快。例如,假设我正在编写file_get_contents()的变体。false-1

典型的返回值是一个句柄 - 由正整数表示。但是,我可能会遇到两个基本问题:找不到您指定的文件,或者您指定的文件不可读。为了指示错误,我可能会返回一个负数 - 因为句柄是正数 - 与特定的错误原因相关联。假设 -1 表示文件不存在,-2 表示文件不可读。

现在我们有一个问题,-1和-2对阅读代码的人来说本质上没有任何意义。为了纠正这一点,我们引入了全局常量FILE_NOT_FOUND和FILE_NOT_READABLE。让我们看一些结果代码。

<?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);
}

通过具有不同的错误代码,我们可以根据问题的实际情况采取相应的行动。该功能很好。问题纯粹在于代码的读取方式。

$result是一个可怕的变量名称。变量名称应具有描述性且显而易见,如 .的真正名称是,它不仅太长,而且检查了我们如何重载具有两种含义的单个变量。你永远不会,永远,想要拥有相同的数据意味着两件事。我们想要一个单独的和!$friendListFile$result$fileContentsOrErrorCode$errorCode$fileContents

那么,我们如何解决这个问题呢?一些PHP库使用的一个不是真正的解决方案是,如果遇到问题,请返回它们的类函数。为了消除歧义,实际上问题是什么,我们称之为。这几乎有效。my_file_get_contents()falsemy_file_get_contents_getError()

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()
    );
}

请注意,是的,我们可以通过避免全局变量和其他此类小位来做得更好。将此视为螺母和螺栓演示。

我们仍然不能称任何比.该问题尚未解决。$result$fileContentsOrFalseIfError

我现在已经纠正了一个问题,您可能已经在前面的示例中注意到了这个问题。如果我们没有涵盖所有错误代码怎么办?如果程序员决定需要一个-3代码,我们最初并没有检测到它!我们可以检查是否是一个字符串,以确保它不是错误代码,但我们不应该真正关心PHP中的类型,对吧?现在我们可以利用它的第二个返回值,包含成功代码是没有问题的。$resultmy_file_get_contents_getError()

现在出现了一个全新的问题。修复一个,再找三个呀?新问题是只能保留最新的错误代码。这是非常脆弱!如果在您处理错误代码之前有任何其他调用,他们的代码将覆盖您的代码!my_file_get_contents()

Gah,现在我们需要在处理来自my_file_get_contents_getError()的返回值之前保留一个不安全调用的函数列表。如果不这样做,则必须保留为临时编码约定,以便始终立即调用该约定,以便在神秘覆盖之前保存属于您的错误代码。my_file_get_contents_getError()my_file_get_contents()

等!我们为什么不直接向呼叫者分发标识符?为了使用,您现在必须要求一些号码,以消除您与所有其他呼叫者的歧义。现在,您可以调用错误代码,并将其存储在专门为您使用的特殊位置。现在,当您打电话时,您可以访问那个特殊的地方,获取错误代码,并且没有人踩到您的脚趾。my_file_get_contents()create_my_file_get_contents_handle()my_file_get_contents($myHandle, $myFile)my_file_get_contents_getError($myHandle)

呃,但是如果有很多调用者,我们不希望有无数无用的错误代码。我们最好要求用户在完成后拨打电话,这样我们就可以释放一些内存。destroy_my_file_get_contents_handle($myHandle)

我希望这一切都让你们这些旧的PHP咒语感到非常熟悉。

这一切都太疯狂了,请把它简单一点!

如果语言支持更好的机制来应对错误,这意味着什么?显然,尝试使用现有工具创建一些解决方案是令人困惑的,令人讨厌的,并且容易出错。

输入例外!

<?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();
}

突然之间,我们关于特殊返回值、句柄和编码约定的旧烦恼已经得到解决!

现在,我们可以真正重命名为 .如果有问题,分配将完全中止,我们直接跳到相应的捕获块。只有当没有错误时,我们才会考虑给出一个值或调用 。$result$fileContentsmy_file_get_contents()$fileContentsuseFriendList()

我们不再受到多个呼叫者踩到彼此错误代码的困扰!如果出现错误,对my_file_get_contents() 的每次调用都将实例化其自己的异常。

没有内存问题!垃圾回收器将很乐意清理不再使用的异常对象,而无需您考虑它。使用旧的手柄系统,我们必须记住手动销毁手柄,以免它永远潜伏在内存中。

例外还有许多其他好处和特征。我强烈建议您寻找其他来源来了解这些。特别有趣的是它们如何冒泡执行堆栈,直到某些调用方能够捕获它们。同样有趣的是,如何捕获异常,尝试解决问题,然后如果不能,则重新抛出异常。不要忘记,例外是对象!这样做可以获得很多灵活性。对于没有人可以捕获的异常,请查看异常处理程序。

我回答这个问题的目的是为了证明为什么我们需要例外。通过这样做,我希望很容易推断出我们可以用它们解决哪些问题。


答案 2

通常在本地抛出,全局捕获,除非异常处理程序特定于函数,在这种情况下,在本地处理。

 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
    }
 }

推荐