format ch7.md

This commit is contained in:
gdut-yy
2020-01-12 16:30:44 +08:00
parent 9b09aa5d76
commit e0b4350fe6

View File

@@ -17,6 +17,7 @@ In this chapter Ill outline a number of techniques and considerations that yo
> 在本章中,我将概要列出编写既整洁又强固的代码——雅致地处理错误代码的一些技巧和思路。 > 在本章中,我将概要列出编写既整洁又强固的代码——雅致地处理错误代码的一些技巧和思路。
## 7.1 USE EXCEPTIONS RATHER THAN RETURN CODES 使用异常而非返回码 ## 7.1 USE EXCEPTIONS RATHER THAN RETURN CODES 使用异常而非返回码
Back in the distant past there were many languages that didnt have exceptions. In those languages the techniques for handling and reporting errors were limited. You either set an error flag or returned an error code that the caller could check. The code in Listing 7-1 illustrates these approaches. Back in the distant past there were many languages that didnt have exceptions. In those languages the techniques for handling and reporting errors were limited. You either set an error flag or returned an error code that the caller could check. The code in Listing 7-1 illustrates these approaches.
> 在很久以前,许多语言都不支持异常。这些语言处理和汇报错误的手段都有限。你要么设置一个错误标识,要么返回给调用者检查的错误码。代码清单 7-1 中的代码展示了这些手段。 > 在很久以前,许多语言都不支持异常。这些语言处理和汇报错误的手段都有限。你要么设置一个错误标识,要么返回给调用者检查的错误码。代码清单 7-1 中的代码展示了这些手段。
@@ -24,6 +25,7 @@ Back in the distant past there were many languages that didnt have exceptions
Listing 7-1 DeviceController.java Listing 7-1 DeviceController.java
> 代码清单 7-1 DeviceController.java > 代码清单 7-1 DeviceController.java
```java ```java
public class DeviceController { public class DeviceController {
@@ -48,6 +50,7 @@ public class DeviceController {
} }
``` ```
The problem with these approaches is that they clutter the caller. The caller must check for errors immediately after the call. Unfortunately, its easy to forget. For this reason it is better to throw an exception when you encounter an error. The calling code is cleaner. Its logic is not obscured by error handling. The problem with these approaches is that they clutter the caller. The caller must check for errors immediately after the call. Unfortunately, its easy to forget. For this reason it is better to throw an exception when you encounter an error. The calling code is cleaner. Its logic is not obscured by error handling.
> 这类手段的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻检查错误。不幸的是,这个步骤很容易被遗忘。所以,遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。 > 这类手段的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻检查错误。不幸的是,这个步骤很容易被遗忘。所以,遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。
@@ -90,12 +93,13 @@ private void tryToShutDown() throws DeviceShutDownError {
} }
``` ```
Notice how much cleaner it is. This isnt just a matter of aesthetics. The code is better because two concerns that were tangled, the algorithm for device shutdown and error handling, are now separated. You can look at each of those concerns and understand them independently.
Notice how much cleaner it is. This isnt just a matter of aesthetics. The code is better because two concerns that were tangled, the algorithm for device shutdown and error handling, are now separated. You can look at each of those concerns and understand them independently.
> 注意这段代码整洁了很多。这不仅关乎美观。这段代码更好,因为之前纠结的两个元素设备关闭算法和错误处理现在被隔离了。你可以查看其中任一元素,分别理解它。 > 注意这段代码整洁了很多。这不仅关乎美观。这段代码更好,因为之前纠结的两个元素设备关闭算法和错误处理现在被隔离了。你可以查看其中任一元素,分别理解它。
## 7.2 WRITE YOUR TRY-CATCH-FINALLY STATEMENT FIRST 先写 Try-Catch-Finally 语句 ## 7.2 WRITE YOUR TRY-CATCH-FINALLY STATEMENT FIRST 先写 Try-Catch-Finally 语句
One of the most interesting things about exceptions is that they define a scope within your program. When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch. One of the most interesting things about exceptions is that they define a scope within your program. When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch.
> 异常的妙处之一是,它们在程序中定义了一个范围。执行 try-catchfinally 语句中 try 部分的代码时,你是在表明可随时取消执行,并在 catch 语句中接续。 > 异常的妙处之一是,它们在程序中定义了一个范围。执行 try-catchfinally 语句中 try 部分的代码时,你是在表明可随时取消执行,并在 catch 语句中接续。
@@ -111,21 +115,25 @@ Lets look at an example. We need to write some code that accesses a file and
We start with a unit test that shows that well get an exception when the file doesnt exist: We start with a unit test that shows that well get an exception when the file doesnt exist:
> 先写一个单元测试,其中显示当文件不存在时将得到一个异常: > 先写一个单元测试,其中显示当文件不存在时将得到一个异常:
```java ```java
@Test(expected = StorageException.class) @Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() { public void retrieveSectionShouldThrowOnInvalidFileName() {
sectionStore.retrieveSection(invalid - file); sectionStore.retrieveSection(invalid - file);
} }
``` ```
The test drives us to create this stub: The test drives us to create this stub:
> 该测试驱动我们创建以下占位代码: > 该测试驱动我们创建以下占位代码:
```java ```java
public List<RecordedGrip> retrieveSection(String sectionName) { public List<RecordedGrip> retrieveSection(String sectionName) {
// dummy return until we have a real implementation // dummy return until we have a real implementation
return new ArrayList<RecordedGrip>(); return new ArrayList<RecordedGrip>();
} }
``` ```
Our test fails because it doesnt throw an exception. Next, we change our implementation so that it attempts to access an invalid file. This operation throws an exception: Our test fails because it doesnt throw an exception. Next, we change our implementation so that it attempts to access an invalid file. This operation throws an exception:
> 测试失败了,因为以上代码并未抛出异常。下一步,修改实现代码,尝试访问非法文件。该操作抛出一个异常: > 测试失败了,因为以上代码并未抛出异常。下一步,修改实现代码,尝试访问非法文件。该操作抛出一个异常:
@@ -140,6 +148,7 @@ public List<RecordedGrip> retrieveSection(String sectionName) {
return new ArrayList<RecordedGrip>(); return new ArrayList<RecordedGrip>();
} }
``` ```
Our test passes now because weve caught the exception. At this point, we can refactor. We can narrow the type of the exception we catch to match the type that is actually thrown from the FileInputStream constructor: FileNotFoundException: Our test passes now because weve caught the exception. At this point, we can refactor. We can narrow the type of the exception we catch to match the type that is actually thrown from the FileInputStream constructor: FileNotFoundException:
> 这次测试通过了,因为我们捕获了异常。此时,我们可以重构了。我们可以缩小异常类型的范围,使之符合 FileInputStream 构造器真正抛出的异常,即 FileNotFoundException > 这次测试通过了,因为我们捕获了异常。此时,我们可以重构了。我们可以缩小异常类型的范围,使之符合 FileInputStream 构造器真正抛出的异常,即 FileNotFoundException
@@ -155,21 +164,21 @@ public List<RecordedGrip> retrieveSection(String sectionName) {
return new ArrayList<RecordedGrip>(); return new ArrayList<RecordedGrip>();
} }
``` ```
Now that weve defined the scope with a try-catch structure, we can use TDD to build up the rest of the logic that we need. That logic will be added between the creation of the FileInputStream and the close, and can pretend that nothing goes wrong. Now that weve defined the scope with a try-catch structure, we can use TDD to build up the rest of the logic that we need. That logic will be added between the creation of the FileInputStream and the close, and can pretend that nothing goes wrong.
> 如此一来,我们就用 try-catch 结构定义了一个范围可以继续用测试驱动TDD方法构建剩余的代码逻辑。这些代码逻辑将在 FileInputStream 和 close 之间添加,装作一切正常的样子。 > 如此一来,我们就用 try-catch 结构定义了一个范围可以继续用测试驱动TDD方法构建剩余的代码逻辑。这些代码逻辑将在 FileInputStream 和 close 之间添加,装作一切正常的样子。
Try to write tests that force exceptions, and then add behavior to your handler to satisfy your tests. This will cause you to build the transaction scope of the try block first and will help you maintain the transaction nature of that scope. Try to write tests that force exceptions, and then add behavior to your handler to satisfy your tests. This will cause you to build the transaction scope of the try block first and will help you maintain the transaction nature of that scope.
> 尝试编写强行抛出异常的测试,再往处理器中添加行为,使之满足测试要求。结果就是你要先构造 try 代码块的事务范围,而且也会帮助你维护好该范围的事务特征。 > 尝试编写强行抛出异常的测试,再往处理器中添加行为,使之满足测试要求。结果就是你要先构造 try 代码块的事务范围,而且也会帮助你维护好该范围的事务特征。
## 7.3 USE UNCHECKED EXCEPTIONS 使用不可控异常 ## 7.3 USE UNCHECKED EXCEPTIONS 使用不可控异常
The debate is over. For years Java programmers have debated over the benefits and liabilities of checked exceptions. When checked exceptions were introduced in the first version of Java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. Moreover, these exceptions were part of the type of the method. Your code literally wouldnt compile if the signature didnt match what your code could do. The debate is over. For years Java programmers have debated over the benefits and liabilities of checked exceptions. When checked exceptions were introduced in the first version of Java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. Moreover, these exceptions were part of the type of the method. Your code literally wouldnt compile if the signature didnt match what your code could do.
> 辩论业已结束。多年来Java 程序员们一直在争论可控异常checked exception的利与弊。Java 的第一个版本中引入可控异常时,看似一个极好的点子。每个方法的签名都列出它可能传递给调用者的异常。而且,这些异常就是方法类型的一部分。如果签名与代码实际所做之事不符,代码在字面上就无法编译。 > 辩论业已结束。多年来Java 程序员们一直在争论可控异常checked exception的利与弊。Java 的第一个版本中引入可控异常时,看似一个极好的点子。每个方法的签名都列出它可能传递给调用者的异常。而且,这些异常就是方法类型的一部分。如果签名与代码实际所做之事不符,代码在字面上就无法编译。
At the time, we thought that checked exceptions were a great idea; and yes, they can yield some benefit. However, it is clear now that they arent necessary for the production of robust software. C# doesnt have checked exceptions, and despite valiant attempts, C++ doesnt either. Neither do Python or Ruby. Yet it is possible to write robust software in all of these languages. Because that is the case, we have to decide—really—whether checked exceptions are worth their price. At the time, we thought that checked exceptions were a great idea; and yes, they can yield some benefit. However, it is clear now that they arent necessary for the production of robust software. C# doesnt have checked exceptions, and despite valiant attempts, C++ doesnt either. Neither do Python or Ruby. Yet it is possible to write robust software in all of these languages. Because that is the case, we have to decide—really—whether checked exceptions are worth their price.
> 那时我们认为可控异常是个绝妙的主意而且它也有所裨益。然而现在已经很清楚对于强固软件的生产它并非必需。C#不支持可控异常。尽管做过勇敢的尝试C++最后也不支持可控异常。Python 和 Ruby 同样如此。不过,用这些语言也有可能写出强固的软件。我们得决定——的确如此——可控异常是否值回票价。 > 那时我们认为可控异常是个绝妙的主意而且它也有所裨益。然而现在已经很清楚对于强固软件的生产它并非必需。C#不支持可控异常。尽管做过勇敢的尝试C++最后也不支持可控异常。Python 和 Ruby 同样如此。不过,用这些语言也有可能写出强固的软件。我们得决定——的确如此——可控异常是否值回票价。
@@ -189,6 +198,7 @@ Checked exceptions can sometimes be useful if you are writing a critical library
> 如果你在编写一套关键代码库,则可控异常有时也会有用:你必须捕获异常。但对于一般的应用开发,其依赖成本要高于收益。 > 如果你在编写一套关键代码库,则可控异常有时也会有用:你必须捕获异常。但对于一般的应用开发,其依赖成本要高于收益。
## 7.4 PROVIDE CONTEXT WITH EXCEPTIONS 给出异常发生的环境说明 ## 7.4 PROVIDE CONTEXT WITH EXCEPTIONS 给出异常发生的环境说明
Each exception that you throw should provide enough context to determine the source and location of an error. In Java, you can get a stack trace from any exception; however, a stack trace cant tell you the intent of the operation that failed. Each exception that you throw should provide enough context to determine the source and location of an error. In Java, you can get a stack trace from any exception; however, a stack trace cant tell you the intent of the operation that failed.
> 你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。在 Java 中你可以从任何异常里得到堆栈踪迹stack trace然而堆栈踪迹却无法告诉你该失败操作的初衷。 > 你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。在 Java 中你可以从任何异常里得到堆栈踪迹stack trace然而堆栈踪迹却无法告诉你该失败操作的初衷。
@@ -198,6 +208,7 @@ Create informative error messages and pass them along with your exceptions. Ment
> 应创建信息充分的错误消息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给 catch 块,并记录下来。 > 应创建信息充分的错误消息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给 catch 块,并记录下来。
## 7.5 DEFINE EXCEPTION CLASSES IN TERMS OF A CALLERS NEEDS 依调用者需要定义异常类 ## 7.5 DEFINE EXCEPTION CLASSES IN TERMS OF A CALLERS NEEDS 依调用者需要定义异常类
There are many ways to classify errors. We can classify them by their source: Did they come from one component or another? Or their type: Are they device failures, network failures, or programming errors? However, when we define exception classes in an application, our most important concern should be how they are caught. There are many ways to classify errors. We can classify them by their source: Did they come from one component or another? Or their type: Are they device failures, network failures, or programming errors? However, when we define exception classes in an application, our most important concern should be how they are caught.
> 对错误分类有很多方式。可以依其来源分类:是来自组件还是其他地方?或依其类型分类:是设备错误、网络错误还是编程错误?不过,当我们在应用程序中定义异常类时,最重要的考虑应该是它们如何被捕获。 > 对错误分类有很多方式。可以依其来源分类:是来自组件还是其他地方?或依其类型分类:是设备错误、网络错误还是编程错误?不过,当我们在应用程序中定义异常类时,最重要的考虑应该是它们如何被捕获。
@@ -224,6 +235,7 @@ try {
} }
``` ```
That statement contains a lot of duplication, and we shouldnt be surprised. In most exception handling situations, the work that we do is relatively standard regardless of the actual cause. We have to record an error and make sure that we can proceed. That statement contains a lot of duplication, and we shouldnt be surprised. In most exception handling situations, the work that we do is relatively standard regardless of the actual cause. We have to record an error and make sure that we can proceed.
> 语句包含了一大堆重复代码,这并不出奇。在大多数异常处理中,不管真实原因如何,我们总是做相对标准的处理。我们得记录错误,确保能继续工作。 > 语句包含了一大堆重复代码,这并不出奇。在大多数异常处理中,不管真实原因如何,我们总是做相对标准的处理。我们得记录错误,确保能继续工作。
@@ -231,6 +243,7 @@ That statement contains a lot of duplication, and we shouldnt be surprised. I
In this case, because we know that the work that we are doing is roughly the same regardless of the exception, we can simplify our code considerably by wrapping the API that we are calling and making sure that it returns a common exception type: In this case, because we know that the work that we are doing is roughly the same regardless of the exception, we can simplify our code considerably by wrapping the API that we are calling and making sure that it returns a common exception type:
> 在本例中,既然知道我们所做的事不外如此,就可以通过打包调用 API、确保它返回通用异常类型从而简化代码。 > 在本例中,既然知道我们所做的事不外如此,就可以通过打包调用 API、确保它返回通用异常类型从而简化代码。
```java ```java
LocalPort port = new LocalPort(12); LocalPort port = new LocalPort(12);
try { try {
@@ -242,6 +255,7 @@ try {
} }
``` ```
Our LocalPort class is just a simple wrapper that catches and translates exceptions thrown by the ACMEPort class: Our LocalPort class is just a simple wrapper that catches and translates exceptions thrown by the ACMEPort class:
> 在本例中,既然知道我们所做的事不外如此,就可以通过打包调用 API、确保它返回通用异常类型从而简化代码。 > 在本例中,既然知道我们所做的事不外如此,就可以通过打包调用 API、确保它返回通用异常类型从而简化代码。
@@ -268,6 +282,7 @@ public class LocalPort {
} }
``` ```
Wrappers like the one we defined for ACMEPort can be very useful. In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty. Wrapping also makes it easier to mock out third-party calls when you are testing your own code. Wrappers like the one we defined for ACMEPort can be very useful. In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty. Wrapping also makes it easier to mock out third-party calls when you are testing your own code.
> 类似我们为 ACMEPort 定义的这种打包类非常有用。实际上,将第三方 API 打包是个良好的实践手段。当你打包一个第三方 API你就降低了对它的依赖未来你可以不太痛苦地改用其他代码库。在你测试自己的代码时打包也有助于模拟第三方调用。 > 类似我们为 ACMEPort 定义的这种打包类非常有用。实际上,将第三方 API 打包是个良好的实践手段。当你打包一个第三方 API你就降低了对它的依赖未来你可以不太痛苦地改用其他代码库。在你测试自己的代码时打包也有助于模拟第三方调用。
@@ -281,6 +296,7 @@ Often a single exception class is fine for a particular area of code. The inform
> 对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。 > 对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。
## 7.6 DEFINE THE NORMAL FLOW 定义常规流程 ## 7.6 DEFINE THE NORMAL FLOW 定义常规流程
If you follow the advice in the preceding sections, youll end up with a good amount of separation between your business logic and your error handling. The bulk of your code will start to look like a clean unadorned algorithm. However, the process of doing this pushes error detection to the edges of your program. You wrap external APIs so that you can throw your own exceptions, and you define a handler above your code so that you can deal with any aborted computation. Most of the time this is a great approach, but there are some times when you may not want to abort. If you follow the advice in the preceding sections, youll end up with a good amount of separation between your business logic and your error handling. The bulk of your code will start to look like a clean unadorned algorithm. However, the process of doing this pushes error detection to the edges of your program. You wrap external APIs so that you can throw your own exceptions, and you define a handler above your code so that you can deal with any aborted computation. Most of the time this is a great approach, but there are some times when you may not want to abort.
> 如果你遵循前文提及的建议,在业务逻辑和错误处理代码之间就会有良好的区隔。大量代码会开始变得像是整洁而简朴的算法。然而,这样做却把错误检测推到了程序的边缘地带。你打包了外部 API 以抛出自己的异常,你在代码的顶端定义了一个处理器来应付任何失败了的运算。在大多数时候,这种手段很棒,不过有时你也许不愿这么做。 > 如果你遵循前文提及的建议,在业务逻辑和错误处理代码之间就会有良好的区隔。大量代码会开始变得像是整洁而简朴的算法。然而,这样做却把错误检测推到了程序的边缘地带。你打包了外部 API 以抛出自己的异常,你在代码的顶端定义了一个处理器来应付任何失败了的运算。在大多数时候,这种手段很棒,不过有时你也许不愿这么做。
@@ -299,6 +315,7 @@ try {
m_total += getMealPerDiem(); m_total += getMealPerDiem();
} }
``` ```
In this business, if meals are expensed, they become part of the total. If they arent, the employee gets a meal per diem amount for that day. The exception clutters the logic. Wouldnt it be better if we didnt have to deal with the special case? If we didnt, our code would look much simpler. It would look like this: In this business, if meals are expensed, they become part of the total. If they arent, the employee gets a meal per diem amount for that day. The exception clutters the logic. Wouldnt it be better if we didnt have to deal with the special case? If we didnt, our code would look much simpler. It would look like this:
> 业务逻辑是,如果消耗了餐食,则计入总额中。如果没有消耗,则员工得到当日餐食补贴。异常打断了业务逻辑。如果不去处理特殊情况会不会好一些?那样的话代码看起来会更简洁。就像这样: > 业务逻辑是,如果消耗了餐食,则计入总额中。如果没有消耗,则员工得到当日餐食补贴。异常打断了业务逻辑。如果不去处理特殊情况会不会好一些?那样的话代码看起来会更简洁。就像这样:
@@ -307,6 +324,7 @@ In this business, if meals are expensed, they become part of the total. If they
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID()); MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal(); m_total += expenses.getTotal();
``` ```
Can we make the code that simple? It turns out that we can. We can change the ExpenseReportDAO so that it always returns a MealExpense object. If there are no meal expenses, it returns a MealExpense object that returns the per diem as its total: Can we make the code that simple? It turns out that we can. We can change the ExpenseReportDAO so that it always returns a MealExpense object. If there are no meal expenses, it returns a MealExpense object that returns the per diem as its total:
> 其总是返回 MealExpense 对象。如果没有餐食消耗,就返回一个返回餐食补贴的 MealExpense 对象。 > 其总是返回 MealExpense 对象。如果没有餐食消耗,就返回一个返回餐食补贴的 MealExpense 对象。
@@ -318,18 +336,16 @@ public class PerDiemMealExpenses implements MealExpenses {
} }
} }
``` ```
This is called the SPECIAL CASE PATTERN [Fowler]. You create a class or configure an object so that it handles a special case for you. When you do, the client code doesnt have to deal with exceptional behavior. That behavior is encapsulated in the special case object. This is called the SPECIAL CASE PATTERN [Fowler]. You create a class or configure an object so that it handles a special case for you. When you do, the client code doesnt have to deal with exceptional behavior. That behavior is encapsulated in the special case object.
> 这种手法叫做特例模式SPECIAL CASE PATTERN[Fowler])。 > 这种手法叫做特例模式SPECIAL CASE PATTERN[Fowler])。创建一个类或配置一个对象,用来处理特例。你来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。
创建一个类或配置一个对象,用来处理特例。你来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。
## 7.7 DONT RETURN NULL 别返回 null 值 ## 7.7 DONT RETURN NULL 别返回 null 值
I think that any discussion about error handling should include mention of the things we do that invite errors. The first on the list is returning null. I cant begin to count the number of applications Ive seen in which nearly every other line was a check for null. Here is some example code: I think that any discussion about error handling should include mention of the things we do that invite errors. The first on the list is returning null. I cant begin to count the number of applications Ive seen in which nearly every other line was a check for null. Here is some example code:
> 我认为,要讨论错误处理,就一定要提及那些容易引发错误的做 > 我认为,要讨论错误处理,就一定要提及那些容易引发错误的做法。第一项就是返回 null 值。我不想去计算曾经见过多少几乎每行代码都在检查 null 值的应用程序。下面就是个例子:
法。第一项就是返回null值。我不想去计算曾经见过多少几乎每行代码都在检查null值的应用程序。下面就是个例子
```java ```java
public void registerItem(Item item) { public void registerItem(Item item) {
@@ -344,17 +360,14 @@ public void registerItem(Item item) {
} }
} }
``` ```
If you work in a code base with code like this, it might not look all that bad to you, but it is bad! When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control. If you work in a code base with code like this, it might not look all that bad to you, but it is bad! When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control.
> 这种代码看似不坏,其实糟透了!返回 null 值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查 null 值,应用程序就会失控。 > 这种代码看似不坏,其实糟透了!返回 null 值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查 null 值,应用程序就会失控。
Did you notice the fact that there wasnt a null check in the second line of that nested if statement? What would have happened at runtime if persistentStore were null? We would have had a NullPointerException at runtime, and either someone is catching NullPointerException at the top level or they are not. Either way its bad. What exactly should you do in response to a NullPointerException thrown from the depths of your application? Did you notice the fact that there wasnt a null check in the second line of that nested if statement? What would have happened at runtime if persistentStore were null? We would have had a NullPointerException at runtime, and either someone is catching NullPointerException at the top level or they are not. Either way its bad. What exactly should you do in response to a NullPointerException thrown from the depths of your application?
> 你有没有注意到,嵌套 if 语句的第二行没有检查 null 值?如果在运行时 persistentStore 为 null 会发生什么事?我们会在运行时得到一个 NullPointerException 异常,也许有人在代码顶端捕获这个异常,也可能没有捕获。两种情况都很糟糕。对于从应用程序深处抛出的 NullPointerException 异常,你到底该作何反应呢?
> 你有没有注意到,嵌套 if 语句的第二行没有检查 null 值?如果在运行时 persistentStore为null会发生什么事我们会在运行时得到一个
NullPointerException异常也许有人在代码顶端捕获这个异常也可能没有捕获。两种情况都很糟糕。对于从应用程序深处抛出的
NullPointerException异常你到底该作何反应呢
Its easy to say that the problem with the code above is that it is missing a null check, but in actuality, the problem is that it has too many. If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead. If you are calling a null-returning method from a third-party API, consider wrapping that method with a method that either throws an exception or returns a special case object. Its easy to say that the problem with the code above is that it is missing a null check, but in actuality, the problem is that it has too many. If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead. If you are calling a null-returning method from a third-party API, consider wrapping that method with a method that either throws an exception or returns a special case object.
@@ -363,6 +376,7 @@ Its easy to say that the problem with the code above is that it is missing a
In many cases, special case objects are an easy remedy. Imagine that you have code like this: In many cases, special case objects are an easy remedy. Imagine that you have code like this:
> 在许多情况下,特例对象都是爽口良药。设想有这么一段代码: > 在许多情况下,特例对象都是爽口良药。设想有这么一段代码:
```java ```java
List<Employee> employees = getEmployees(); List<Employee> employees = getEmployees();
if (employees != null) { if (employees != null) {
@@ -371,15 +385,18 @@ if (employees != null) {
} }
} }
``` ```
Right now, getEmployees can return null, but does it have to? If we change getEmployee so that it returns an empty list, we can clean up the code: Right now, getEmployees can return null, but does it have to? If we change getEmployee so that it returns an empty list, we can clean up the code:
> 现在getExployees 可能返回 null但是否一定要这么做呢如果修改 getEmployee返回空列表就能使代码整洁起来 > 现在getExployees 可能返回 null但是否一定要这么做呢如果修改 getEmployee返回空列表就能使代码整洁起来
```java ```java
List<Employee> employees = getEmployees(); List<Employee> employees = getEmployees();
for(Employee e : employees) { for(Employee e : employees) {
totalPay += e.getPay(); totalPay += e.getPay();
} }
``` ```
Fortunately, Java has Collections.emptyList(), and it returns a predefined immutable list that we can use for this purpose: Fortunately, Java has Collections.emptyList(), and it returns a predefined immutable list that we can use for this purpose:
> 所幸 Java 有 Collections.emptyList( )方法,该方法返回一个预定义不可变列表,可用于这种目的: > 所幸 Java 有 Collections.emptyList( )方法,该方法返回一个预定义不可变列表,可用于这种目的:
@@ -390,20 +407,21 @@ public List<Employee> getEmployees() {
return Collections.emptyList(); return Collections.emptyList();
} }
``` ```
If you code this way, you will minimize the chance of NullPointerExceptions and your code will be cleaner. If you code this way, you will minimize the chance of NullPointerExceptions and your code will be cleaner.
> 这样编码,就能尽量避免 NullPointerException 的出现,代码也就更整洁了。 > 这样编码,就能尽量避免 NullPointerException 的出现,代码也就更整洁了。
## 7.8 DONT PASS NULL 别传递 null 值 ## 7.8 DONT PASS NULL 别传递 null 值
Returning null from methods is bad, but passing null into methods is worse. Unless you are working with an API which expects you to pass null, you should avoid passing null in your code whenever possible. Returning null from methods is bad, but passing null into methods is worse. Unless you are working with an API which expects you to pass null, you should avoid passing null in your code whenever possible.
> 在方法中返回 null 值是糟糕的做法,但将 null 值传递给其他方法就更糟糕了。除非 API 要求你向它传递 null 值,否则就要尽可能避免传递 null 值。 > 在方法中返回 null 值是糟糕的做法,但将 null 值传递给其他方法就更糟糕了。除非 API 要求你向它传递 null 值,否则就要尽可能避免传递 null 值。
Lets look at an example to see why. Here is a simple method which calculates a metric for two points: Lets look at an example to see why. Here is a simple method which calculates a metric for two points:
> 举例说明原因。用下面这个简单的方法计算两点的投射: > 举例说明原因。用下面这个简单的方法计算两点的投射:
```java ```java
public class MetricsCalculator public class MetricsCalculator
{ {
@@ -413,6 +431,7 @@ public class MetricsCalculator
} }
``` ```
What happens when someone passes null as an argument? What happens when someone passes null as an argument?
> 如果有人传入 null 值会怎样? > 如果有人传入 null 值会怎样?
@@ -420,6 +439,7 @@ What happens when someone passes null as an argument?
```java ```java
calculator.xProjection(null, new Point(12, 13)); calculator.xProjection(null, new Point(12, 13));
``` ```
Well get a NullPointerException, of course. Well get a NullPointerException, of course.
> 当然,我们会得到一个 NullPointerException 异常。 > 当然,我们会得到一个 NullPointerException 异常。
@@ -440,11 +460,11 @@ public double xProjection(Point p1, Point p2) {
} }
} }
``` ```
Is this better? It might be a little better than a null pointer exception, but remember, we have to define a handler for InvalidArgumentException. What should the handler do? Is there any good course of action? Is this better? It might be a little better than a null pointer exception, but remember, we have to define a handler for InvalidArgumentException. What should the handler do? Is there any good course of action?
> 这样做好些吗?可能比 null 指针异常好一些,但要记住,我们还得为 InvalidArgumentException 异常定义处理器。这个处理器该做什么?还有更好的做法吗? > 这样做好些吗?可能比 null 指针异常好一些,但要记住,我们还得为 InvalidArgumentException 异常定义处理器。这个处理器该做什么?还有更好的做法吗?
There is another alternative. We could use a set of assertions: There is another alternative. We could use a set of assertions:
> 还有替代方案。可以使用一组断言 > 还有替代方案。可以使用一组断言
@@ -459,6 +479,7 @@ public class MetricsCalculator
} }
} }
``` ```
Its good documentation, but it doesnt solve the problem. If someone passes null, well still have a runtime error. Its good documentation, but it doesnt solve the problem. If someone passes null, well still have a runtime error.
> 看上去很美,但仍未解决问题。如果有人传入 null 值,还是会得到运行时错误。 > 看上去很美,但仍未解决问题。如果有人传入 null 值,还是会得到运行时错误。
@@ -468,6 +489,7 @@ In most programming languages there is no good way to deal with a null that is p
> 在大多数编程语言中,没有良好的方法能对付由调用者意外传入的 null 值。事已如此,恰当的做法就是禁止传入 null 值。这样,你在编码的时候,就会时时记住参数列表中的 null 值意味着出问题了,从而大量避免这种无心之失。 > 在大多数编程语言中,没有良好的方法能对付由调用者意外传入的 null 值。事已如此,恰当的做法就是禁止传入 null 值。这样,你在编码的时候,就会时时记住参数列表中的 null 值意味着出问题了,从而大量避免这种无心之失。
## 7.9 CONCLUSION 小结 ## 7.9 CONCLUSION 小结
Clean code is readable, but it must also be robust. These are not conflicting goals. We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic. To the degree that we are able to do that, we can reason about it independently, and we can make great strides in the maintainability of our code. Clean code is readable, but it must also be robust. These are not conflicting goals. We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic. To the degree that we are able to do that, we can reason about it independently, and we can make great strides in the maintainability of our code.
> 整洁代码是可读的,但也要强固。可读与强固并不冲突。如果将错误处理隔离看待,独立于主要逻辑之外,就能写出强固而整洁的代码。做到这一步,我们就能单独处理它,也极大地提升了代码的可维护性。 > 整洁代码是可读的,但也要强固。可读与强固并不冲突。如果将错误处理隔离看待,独立于主要逻辑之外,就能写出强固而整洁的代码。做到这一步,我们就能单独处理它,也极大地提升了代码的可维护性。