我什么时候编写自己的异常类? [英] when do I write my own exception class?

查看:82
本文介绍了我什么时候编写自己的异常类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

自从我走进OOP的浑水,并且在有必要编写自己的异常类扩展时,已经编写了几个分布式库,我一直对此感到疑惑.

I have been wondering since I stepped into the murky waters of OOP and have written a couple or so of distributed libraries when it is necessary to write my own extension of the exception class.

到目前为止,我仅使用内置的异常类,它似乎对我很有用.是否有必要,如果可以,我可以编写一个异常子类.

So far I simply use the built in exception class and it seems to serve me well. Is it necessary, and if so when is it ok, for me to write an exception subclass.

推荐答案

当需要区分不同类型的错误时,应使用自己的Exception类型扩展Exception类.抛出Exception只是表示出了什么问题.您不知道出了什么问题.你应该放弃一切吗?这是预期的错误吗?相反,抛出UserIsNotAllowedToDoThisException意味着更具体.重要性在于区分哪些代码可以处理哪种类型的错误:

You should extend the Exception class with your own Exception types when you need to differentiate between different types of errors. Throwing an Exception just means something went wrong. You have no idea what went wrong though. Should you abort everything? Is this an expected error? Throwing a UserIsNotAllowedToDoThisException instead means something much more specific. The importance is to differentiate what code can handle what kind of error:

try {
    new Foo($bar);
} catch (UserIsNotAllowedToDoThisException $e) {
    echo "Sorry, you're not allowed to do this.";
}

此代码处理不允许出现某些情况的简单情况.如果Foo会引发其他异常,例如TheDatabaseJustCrashedAndIsBurningException,则您不想在这里了解此信息,而是希望使用全局错误处理程序来处理它.通过区分出出了什么问题,它可以让您适当地处理问题.

This code handles the simple case when something is not allowed. If Foo would throw some other exception, like TheDatabaseJustCrashedAndIsBurningException, you don't want to know about this here, you want some global error handler to handle it. By differentiating what went wrong, it allows you to handle problems appropriately.

好的,这是一个更完整的示例:

OK, here a little more complete example:

首先,如果使用正确的OOP,则需要 异常以使对象构造失败.在无法使对象构造失败的情况下,您将忽略OOP的大部分内容:类型安全性,因此也保证了数据完整性.参见例如:

First, if you use proper OOP, you need Exceptions to fail object constructions. Without being able to fail object constructions, you're ignoring a large part of OOP: type safety and therefore data integrity. See for example:

class User {

    private $name = null;
    private $db = null;

    public function __construct($name, PDO $db) {
        if (strlen($name) < 3) {
            throw new InvalidArgumentException('Username too short');
        }
        $this->name = $name;
        $this->db = $db;
        $this->db->save($this->name);  // very fictional DB call, mind you
    }

}

在此示例中,我们看到了很多东西:

In this example, we see a lot of things:

  • 我的User对象必须具有名称.未能将$name参数传递给构造函数将使PHP在整个程序中失败.
    • 用户名需要至少3个字符.如果不是,则无法构造该对象(因为会引发异常).
    • My User objects have to have a name. Failing to pass a $name argument to the constructor will make PHP fail the whole program.
      • The username needs to be at least 3 characters long. If it is not, the object cannot be constructed (because an Exception is thrown).
      • 不通过$db参数将使PHP在整个程序中失败.
      • 无法通过有效的PDO实例将使PHP导致整个程序失败.
        • 我不能只传递任何东西作为第二个参数,它必须是有效的PDO对象.
        • 这表示如果PDO实例的构建成功,则表明我具有有效的数据库连接.此后,我无需担心或检查我的数据库连接的有效性.这就是我构造一个User对象的原因.如果构建成功,则我有一个有效的用户(有效的意思是他的名字至少3个字符长).我不需要再次检查.曾经.我只需要为User对象键入提示,PHP就会处理其余的事情.
        • Failing to pass the $db argument will make PHP fail the whole program.
        • Failing to pass a valid PDO instance will make PHP fail the whole program.
          • I can't pass just anything as the second argument, it needs to be a valid PDO object.
          • This means if the construction of a PDO instance succeeded, I have a valid database connection. I do not need to worry about or check the validity of my database connection henceforth. That's the same reason I'm constructing a User object; if the construction succeeds, I have a valid user (valid meaning his name is at least 3 characters long). I do not need to check this again. Ever. I only need to type hint for User objects, PHP takes care of the rest.

          因此,您将看到OOP + Exceptions为您提供的功能.如果您具有某个类型的对象的实例,则可以100%保证其数据有效.这是在任何中途复杂的应用程序中传递数据数组的巨大步骤.

          So, you see the power that OOP + Exceptions gives you. If you have an instance of an object of a certain type, you can be 100% assured that its data is valid. That's a huge step up from passing data arrays around in any halfway complex application.

          现在,上面的__construct可能由于两个问题而失败:用户名太短,或者由于某种原因数据库不起作用. PDO对象是有效的,因此在构建对象时连接正在工作,但与此同时它可能已断开.在这种情况下,对$db->save的调用将抛出其自己的PDOException或其子类型.

          Now, the above __construct may fail due to two problems: The username being too short, or the database is for whatever reason not working. The PDO object is valid, so the connection was working at the time the object was constructed, but maybe it's gone down in the meantime. In that case, the call to $db->save will throw its own PDOException or a subtype thereof.

          try {
              $user = new User($_POST['username'], $db);
          } catch (InvalidArgumentException $e) {
              echo $e->getMessage();
          }
          

          因此,我将使用上面的代码构造一个User对象.我不事先检查用户名是否至少3个字符长,因为这会违反DRY原则.相反,我只是让构造函数担心它.如果构建失败并显示InvalidArgumentException,我知道用户名不正确,因此我将通知用户.

          So I'd use the above code to construct a User object. I do not check beforehand whether the username is at least 3 characters long, because this would violate the DRY principle. Instead, I'll just let the constructor worry about it. If the construction fails with an InvalidArgumentException, I know the username was incorrect, so I'll let the user know about that.

          但是,如果数据库关闭了怎么办?然后,我将无法继续在当前应用中执行任何操作.在那种情况下,我想完全停止我的应用程序,显示一个HTTP 500 Internal Server Error页面.这是一种方法:

          What if the database is down though? Then I cannot continue to do anything in my current app. In that case I want to halt my application completely, displaying an HTTP 500 Internal Server Error page. Here's one way to do it:

          try {
              $user = new User($_POST['username'], $db);
          } catch (InvalidArgumentException $e) {
              echo $e->getMessage();
          } catch (PDOException $e) {
              abortEverythingAndShowError500();
          }
          

          但这是的方式.数据库可能会随时在应用程序中的任何地方发生故障.我不是要在将数据库连接传递给任何对象时的每一步都执行此检查.我要做的是让异常冒泡.实际上,它已经泡沫化了. new User不会引发该异常,它是在对$db->save的嵌套函数调用中引发的.异常至少已经传播了两层.所以我就让它走得更远,因为我已经设置了全局错误处理程序来处理PDOExceptions (它记录了错误并显示了一个不错的错误页面).我不想在这里担心这个特殊的错误.因此,它来了:

          But this is a bad way. The database may fail at any time anywhere in the application. I do not want to do this check at every point I'm passing a database connection to anything. What I'll do instead is I let the exception bubble up. In fact, it has already bubbled up. The exception was not thrown by new User, it was thrown in a nested function call to $db->save. The Exception has already traveled up at least two layers. So I'll just let it travel up even further, because I have set up my global error handler to deal with PDOExceptions (it's logging the error and displays a nice error page). I do not want to worry about this particular error here. So, here it comes:

          使用不同类型的异常可以使我在代码中的某些点忽略某些类型的错误,并让我的代码的其他部分来处理它们.如果我有某种类型的对象,则可以无需质疑,检查或担心其有效性.如果无效,那么我一开始就不会有它的实例.并且,如果它失败有效(例如突然失败的数据库连接),则对象可以自行发出错误发生的信号.我需要做的只是在正确的地方catch异常(可能非常高),我不需要需要检查程序中每个点是否成功.结果是更少,更健壮,结构更好的代码.在这个非常简单的示例中,我仅使用通用的InvalidArgumentException.在带有一些接受许多参数的对象的更复杂的代码中,您可能希望区分不同类型的无效参数.因此,您将创建自己的Exception子类.

          Using different types of Exceptions allows me to ignore certain types of errors at certain points in my code and let other parts of my code handle them. If I have an object of a certain type, I do not ever have to question or check or worry about its validity. If it wasn't valid, I wouldn't have an instance of it in the first place. And, if it ever fails to be valid (like a suddenly failing database connection), the object can signal by itself that an error occurred. All I need to do is catch the Exception at the right point (which can be very high up), I do not need to check whether something succeeded or not at every single point in my program. The upshot is less, more robust, better structured code. In this very simple example, I'm only using a generic InvalidArgumentException. In somewhat more complex code with objects that accept many arguments, you'd probably want to differentiate between different types of invalid arguments. Hence you'd make your own Exception subclasses.

          尝试仅使用一种类型的Exception来复制此代码.尝试仅使用函数调用和return false复制此文件.您需要更多的代码才能做到,每次 您都需要进行检查.编写自定义异常和自定义对象需要更多的代码,并且预示着明显的复杂性,但是从长远来看,它可以节省以后的大量代码,并使事情变得更简单.因为不应的任何内容(例如用户名太短的用户)都可以确保引起某种错误.您无需每次都检查.相反,您只需要担心要包含的错误层,而不用担心是否会找到它.

          Try to replicate this by using only one type of Exception. Try to replicate this using only function calls and return false. You need a lot more code to do so every time you need to make that check. Writing custom exceptions and custom objects is a little more code and apparent complexity upfront, but it saves you a ton of code later and makes things much simpler in the long run. Because anything that shouldn't be (like a user with a too short username) is guaranteed to cause an error of some sort. You don't need to check every time. On the contrary, you only need to worry about at which layer you want to contain the error, not whether you'll find it at all.

          写自己的异常"实际上是不费吹灰之力的:

          And it's really no effort to "write your own Exceptions":

          class UserStoppedLovingUsException extends Exception { }
          

          在那里,您已经创建了自己的Exception子类.现在,您可以在代码中的适当位置进行throwcatch了.您不需要做更多的事情.实际上,您现在已经正式声明了应用程序中可能出错的事物的类型.难道不是击败了许多非正式文档和if s和else s和return false s?

          There, you have created your own Exception subclass. You can now throw and catch it at the appropriate points in your code. You don't need to do any more than that. In fact, you have now a formal declaration of the types of things that may go wrong in your app. Doesn't that beat a lot of informal documentation and ifs and elses and return falses?

          这篇关于我什么时候编写自己的异常类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆