怎么优雅地展开错误处理(clean code阅读笔记之六)

程序出了难题怎么做


注:正文中的引用是平昔引用作者来说,两条横线中间的段子的是自家自己的观点,其余几乎都得以算是笔记了。


错误处理是卓殊必要的,不过假设对错误处理使用不当则会让代码变得越发重合,让阅读者看不明清码的逻辑,更要紧的是,那也会让程序变得非常脆弱。本文旅长列出一些行使错误处理的技艺,辅助您写出既简单又健康的代码。

使用Exception而不是再次回到码

重返码是一个历史遗留难点,在从前的没有Exception的言语(比如c语言)中,它是卓有成效且必备的,可是在有Exception的语言中动用再次来到码是一直不其他好处的。

对于利用再次来到码的函数,调用者在收获调用结果(那里是再次回到码)之后要及时去验证重临码,这对于代码的可读性和协会的客体都是大幅度的挑衅,使用「极度处理」能让事情逻辑和错误处理在代码结构上分别,代码的结构和逻辑会更清楚。

首先把try-catch-finally块写出来

当碰着需求做更加处理的时候,首先把try-catch-finally块写出来,那能扶助你写出更好的可怜处理代码。

永久使用unchecked万分


这又是一个十分有争议性的话题,到底是运用checked非常,依然unchecked非常。

_checked异常(通常是Exception的子类)是Java中的一个概念,它是指如果一个艺术抛出此卓殊,那么它就务须被显式地破获或者传递,平时是在措施上添加讲明。

unchecked非凡指的是可能天天抛出的不得了(经常是RuntimeExceptionError,和她们的子类)。

推荐阅读:Unchecked Exceptions — The
Controversy
,在那篇Oracle的法定指南中讲到:借使您要从某个非凡中平复,那么就接纳checked非常,否则使用unchecked非常,意思就是unchecked的那几个表示系统出难点了,那么必须停下来,去检查到底什么难题,然后解决。_


小编的看法是:若是在某些特殊的景况下必须求捕获分外并作出处理,那么只好选用checked十分。可是在平常的费用进度中应当幸免拔取checked非常。

checked非常的行使确实带来了一些功利,可是也同时也导致了部分标题,最紧要的是它违反了「开闭」原则。比如如果一个方法抛出某个checked十分,而以此那一个的处理(平常是catch)是在3个点子调用层级之上,那么当您改改最尾部这些法子的抛出万分时,所有在这几个调用链中的方法签名都急需被修改。那明明违反了「对修改关闭」的尺度,也背离了面向对象中「封装内部贯彻」的特性。

提供格外的上下文

在那些中自然要提供可供这些那多少个的捕获者(经常是在catch中)用来分辨这几个特其余上下文音讯,使之可以透过log或任何措施最后突显出来。

绳趋尺步调用者的需要定义卓越类

当大家定义一个丰富类时,可以按照那几个相当类所属的模块,抑或是以此越发类的系列来定义,不过当我们在先后中定义一个不行类时,更加多的则应该是在设想「那几个可怜会在什么意况下被捕获」。代码6-1中显示了常见的依照类型定义的老大类,

//代码6-1
ACMEPort port = new ACMEPort(12);
try {
    port.open();
}  catch (DeviceResponseException e) {
    reportPortError(e);logger.log("Device response exception", e);
}  catch (ATM1212UnlockedException e) {
    reportPortError(e);logger.log("Unlock exception", e);
}  catch (GMXError e) {
    reportPortError(e);logger.log("Device    response exception");
}  finally {...
}

代码6-2中显示了运用一个装进类定义的一个新的可怜类,不仅代码看起来越发清楚简单,而且还会带来越来越多其余的益处。

//代码6-2
LocalPort port = new LocalPort(12);
try {
    port.open();
}  catch (PortDeviceFailure e) {
    reportError(e);
    logger.log(e.getMessage(), e);
}  finally {
    ...
}

public class LocalPort {
    private ACMEPort innerPort;
    public LocalPort(int portNumber) {
        innerPort = new ACMEPort(portNumber);
    }
    public void open() {
        try {
            innerPort.open();
        }  catch (DeviceResponseException e) {
            throw new PortDeviceFailure(e);
        }  catch (ATM1212UnlockedException e) {
            throw new PortDeviceFailure(e);
        }  catch (GMXError e) {
            throw new PortDeviceFailure(e);
        }
    }
    ...
}

像代码6-2中那么对于第三方的类库中的API进行封装会带来很多益处。实际上,封装第三方API能够算是一种极品实践。

当你在你的品种中对第三方类库举办了温馨的包装之后,约等于解耦了你的档次和那些第三方类库,你可以任意的将以此类库更换掉,更紧要的是,你可以不需求自然根据这些类库的规划来拔取它,比如代码6-2中所示的,本来类库中定义了八个重合

概念常规流程

尽管我们得以写出很好的错误处理代码,它们外形优雅、结构清晰,但是有时大家并不是想要「终止代码的逻辑流程」而抛出极度,这种情景就无须选拔非常类来处理,应该利用更健康的流水线来拍卖,比如代码6-3中那一个处理。

//代码6-3
try {
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
    m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
    m_total += getMealPerDiem();
}

代码6-3中的错误处理只是为着处理一种特殊现象(可能是数据库中找不到此次吃饭的费用意况),而不是要中断总结而抛出相当,那么它可以被重构为代码6-4中的样子。

//代码6-4
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();

//这里引入一个新的特殊情况的类
public class PerDiemMealExpenses implements MealExpenses {
    public int getTotal() {
        // return the per diem default
    }
}

这种编程情势被称呼极度景况方式,它应用一个新的类来拍卖那种奇特意况,那么就不须要抛出格外类,因为在分外的动静下也会回去一个MealExpenses对象,那么万事工艺流程看起来就和寻常的业务流程一样了。

永不回来Null

Null是惨酷了,不要在代码中回到Null

万一你的代码中有重回Null的状态,那么在代码的任何地方都可能抛出NullPointerException而导致系统崩溃退出,你的代码中也说不定会存在一大堆的判断「一个目的是不是为Null」的代码,那也许是富有开发者都不情愿看到的意况,能够行使代码6-4中所使用的非凡景况方式来幸免再次回到Null,永远重回一个有值的对象。

并非传递Null

在函数调用的参数中传递Null是比再次来到Null更邪恶的政工。所以在您协调的代码中,永远不要传递Null

相关文章