健壮性和正确性:
健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度
正确性:程序按照spec加以执行的能力
面向健壮性的编程:
-处理未期望的行为和错误终止
-即使终止执行,也要准确/无歧义的向用户展示全面的错误信息
-错误信息有助于进行debug
-返回给用户的错误提示信息要详细、准确、无歧义
内部错误与异常:
内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束
1.用户输入错误
2.设备错误
3.物理限制
异常:你自己程序导致的问题,可以捕获、可以处理
异常处理
Java中Exception可以被分为两个部分,蓝色的运行时异常和绿色的其他异常。
运行时异常:由程序员在代码里处理不当造成,在源代码中引入了故障,而如果在代码中提前进行验证,这些故障就可以避免。动态类型检查的时候会发现这种异常,而一旦出现,代码就必然有错误,可以通过调试解决。
其他异常:由外部原因造成,程序员无法完全控制的外在问题所导致的,即使在代码中提前加以验证,也无法完全避免失效发生。
Checked异常、Unchecked异常:
Unchecked异常:客户端无法再继续处理的异常,代表代码中的错误
可以不处理,编译没问题,但执行时出现就导致程序失败,代表程序中的潜在bug,类似于编程语言中的dynamic type checking
Checked异常:客户端能够进行处理的异常,而对开发者来说错误可预料但不可预防,它的出现已经脱离了程序能够掌控的范围。
必须捕获并指定错误处理器handler,否则编译无法通过,类似于编程语言中的 static type checking
处理checked异常时,可以使用如下关键词:
– try &– catch 测试并捕获异常
– finally处理异常时释放资源:当异常抛出时,方法中正常执行的代码被终止,如果异常发生前曾申请过某些资源,则清理异常发生后的这些资源。
– throws抛出异常(声明程序可能会发生此异常)
– throw 抛出异常(实际抛出)
流程则基本如下:
– Declaring exceptions (throws) 声明“本方法可能会发生XX异常”
– Throwing an exception (throw) 抛出XX异常
– Catching an exception (try, catch, finally) 捕获并处理XX异常
String readData(Scanner in) throws EOFException // 声明:本函数可能发生该异常
{
. . .
while (. . .)
{
if (!in.hasNext()) // EOF encountered
{
if (n < len)
throw new EOFException(); // 异常在这里发生了
}
. . .
}
return s;
}
LSP&异常
如果子类型中override了父类型中的方法,那么子类型中方法抛出的异常不能比父类型抛出的异常类型更宽泛——异常不能逆变
子类型方法可以抛出更具体的异常,也可以不抛出任何异常——异常可以协变
如果父类型的方法未抛出异常,那么子类型的方法也不能抛出异常
断言与防御式编程
防御式编程的基本思想
最好的防御就是不要引入bug,如果无法避免,则尝试着将bug限制在最小的范围内或限定在一个方法内部,不扩散
Fail fast:尽快失败,就容易发现、越早修复
断言:
在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。
断言即是对代码中程序员所做假设的文档化,也不会影响运行时性能(在实际使用时,assertion都会被disabled)
语法:assert condition : message;
所构造message在发生错误时显示给用户,便于快速发现错误所在
断言与异常的作用区别:
断言用于提高“正确性”,作用于程序的内部,处理“绝不应该发生”的情况
异常用于提高“健壮性”,作用于程序的外部,处理“能预料会发生”的情况
assert使用场所:
内部不变量:判断某个局部变量应该满足的条件,assert x > 0
表示不变量:checkRep()
控制流不变量:例如,若不想让程序走向switch-case的某个分支,则可以用断言直接在分支上assert false;
方法的前置条件:判断传入参数是否满足前置条件
方法的后置条件:判断结果时候满足后置条件
由于Assert运行时非常影响效率,一般将其应用移除出代码。
防御性编程:
防御性编程是一种防御性设计,旨在确保软件在不可预见的情况下保持安全性
对来自外部的数据源要仔细检查,例如:文件、网络数据、用户输入等,以从非法输入中保护自己
对每个函数的输入 参数合法性要做仔细检查,并决定如何处理非法输入
Barricade 设置路障:一种损失控制策略
类的public方法 接收到的外部数据都应被认为是dirty的,需要处理干净再传递到 private方法——隔离舱
“隔离舱”外部的函数应使用异常处理,“隔离舱”内的函数应使用 断言。