文章目录
9.1 异常行为
9.1.1 禁止抑制或者忽略已检查异常
说明: 编码人员常常会通过一个空的或者无意义的
except
块来抑制捕获的已检查异常。每一个except
块都应该确保程序只会在继续有效的情况下才会继续运行下去。因此,except
块必须要么从异常情况中恢复,要么重新抛出适合当前except
块上下文的另一个异常以允许最邻近的外层try-except
语句块来进行恢复工作。异常会打断应用原本预期的控制流程。例如,try块中位于异常发生点之后的任何表达式和语句都不会被执行。因此,异常必须被妥当处理。许多抑制异常的理由都是不合理的。例如,当对客户端从潜在问题恢复过来不抱期望时,一种好的做法是让异常被广播出来,而不是去捕获和抑制这个异常。
# 【错误示例】: 下面代码假设获取客户端输入的Email地址,在使用之前对Email进行数据格式的合法性校验。
try:
# to_do
pass
except Exception as ex:
import traceback
traceback.print_exc()
# 错误示例中, except 块只是简单地将异常堆栈轨迹打印出来。
# 虽然打印异常的堆栈轨迹对于定位问题是有帮助的,但是最终的程序运行逻辑等同于抑制异常。
# 注意,即使这个错误示例在发生异常时会打印一个堆栈轨迹,但是程序会继续运行,如同异常从未被抛出过。
# 换句话说,除了try块中位于异常发生点之后的表达式和语句不会被执行之外,发生的异常不会影响程序的其他行为。
# 【正确示例1】:
validFlag = False
while not validFlag:
try:
# If requested file does not exist, throws FileNotFoundException
# If requested file exists, sets validFlag to true
validFlag = True
pass
except FileNotFoundException:
import traceback
traceback.print_exc()
# 【正确示例2】:
# 继承Exception Reporter并过滤敏感异常。有时候异常必须对用户隐藏。
# 在这种情况下,一种可行的方式是继承Exception类,并且在重写默认的str方法的基础上,增加一个filter()方法。
class MyExceptionReporter(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return filter(self.msg)
def filter(self):
# Sanitize sensitive data or replace sensitive exceptions with non-sensitive exceptions (whitelist)
# Return non-sensitive exception
pass
例外情况:
- 在资源释放失败不会影响程序后续行为的情况下,释放资源时发生的异常可以被抑制。释放资源的例子包括关闭文件、网络套接字、线程等等。这些资源通常是在
except
或者fianlly
块中被释放,并且在后续的程序运行中都不会再被使用。因此,除非资源被耗尽,否则不会有其他途径使得这些异常会影响程序后续的行为。在充分处理了资源耗尽问题的情况下,只需对异常进行净化和记录日志(以备日后改进)就足够了;在这种情况下没必要做其他额外的错误处理。 - 如果在特定的抽象层次上不可能从异常情况中恢复过来,则在那个层级的代码就不用处理这个异常,而是应该抛出一个合适的异常,让更高层次的代码去捕获处理,并尝试恢复。对于这种情况,最通常的实现方法是省略掉
except
语句块,允许异常被广播出去。
9.1.2 禁止在异常中泄露敏感信息
说明: 敏感数据的范围应该基于应用场景以及产品威胁分析的结果来确定。典型的敏感数据包括口令、银行账号、个人信息、通讯记录、密钥等。如果在传递异常的时候未对其中的敏感信息进行过滤常常会导致信息泄露,而这可能帮助攻击者尝试发起进一步的攻击。攻击者可以通过构造恶意的输入参数来发掘应用的内部结构和机制。不管是异常中的文本消息,还是异常本身的类型都可能泄露敏感信息。例如,对于IOError异常,其中的异常消息会透露文件系统的结构信息,而通过异常本身的类型,可以得知所请求的文件不存在。因此,当异常会被传递到信任边界以外时,必须同时对敏感的异常消息和敏感的异常类型进行过滤。
# 【错误示例】:
import sys
if len(sys.argv) != 2:
print("Invalid file")
else:
try:
open(sys.argv[1], "r")
except MyError as e:
# Log the exception
pass
# 如果传入一个文件名后,程序返回一个异常,则暗示该文件不存在,而如果不抛出异常则说明该文件是存在的。
# 使得系统面临暴力攻击的风险,攻击者可以多次传入不同的文件名进行暴力碰撞来发现有效文件。
# 【正确示例】:
import sys
if len(sys.argv) != 2:
print ("Invalid file")
else