体面编码之异常日志和测试处理


在方法接口的域中抛出异常。这可以避免破坏抽象或应用程序层。例如,DAO不应抛出HTTP异常,也不应传播JDBC异常。可以捕获异常并将其包装在更合适的位置以便于实现此目的。

要么用日志记录异常,要么抛出它们 - 通常不是两者。抛出的异常将在某种程度上被捕获; 如果较低级别的投掷者无法处理异常,则可能不会最好决定是否应该记录该异常,并且在上下文中提供信息。广泛的log-and-throw也会导致重复日志记录,因为异常会使调用层次结构冒泡。规则的一个例外是当更高级别超出我们的控制范围(例如框架)时 - 并且不记录,记录在不合需要的级别,或者不包括足够的细节。

从一个无法完成其名称承诺的方法中抛出异常。该名称表达了期望,如果无法满足,则需要通知调用者。

避免日志记录错误并继续执行。这种做法不是有意义的错误处理,并且可能导致后续错误和损坏。当前上下文中的执行应该停止,或采取一些备用恢复路径。

在异常/日志消息中包含相关和上下文信息。这些信息有助于诊断问题。示例包括有问题的值,状态和标识符。对于自定义异常,可以在构造函数中强制使用某些信息,而不是接受单个字符串消息的常见做法。

考虑catch-wrap-rethrow添加更有用的消息和/或上下文信息。原始抛出者可能没有太多的背景或数据来构建特别有用的消息。此类抛出者的调用者可以捕获此类异常并将其包含在更具信息性的异常中,然后再将其抛弃。

通常,日志记录完全记录异常。仅记录通用消息或仅记录捕获的异常消息,会丢弃可能有用的信息 - 包装的cause-exceptions和堆栈跟踪的消息。在系统边界处(例如,当暴露API时),通常希望省略(或在较低级别登录)客户端错误的细节(例如,请求验证)以避免过多的对数噪声。

Web API
以适当的错误响应代码,以及包含更多详细信息的信息机构。这使客户能够快速识别问题的性质,而无需查阅服务日志。例如,响应可能表示错误的请求以及对错误的解释。

避免在错误响应中泄露(或制作可感知的)敏感信息或实现细节。敏感信息是客户不应该知道的任何信息(即使它没有显示在他们正在使用的UI中),例如他们无法访问的数据的存在,或者现有的限制/限制在他们的帐户上。显示实施细节可以帮助攻击者,无论是应用程序还是组织中的其他人。许多Web框架具有用于集中且一致地处理该问题的特征和/或鼓励模式。

用户界面
向用户显示适当且有用的信息。主体应该以非技术术语来表达问题所在,用户正在做什么的当前状态以及纠正问题的途径。技术细节可能包含在可揭示的区域中,以包含在错误报告中。

如果问题是暂时的,请仅让用户再试一次。在连接问题可能正常后再次尝试。再次尝试使用相同的无效表单输入肯定不会。

日志记录
请记住日志记录的目的。主要是:确定应用程序是否运行顺畅,并诊断问题是否运行不顺畅。记住这些有助于确定我们是否应该记录,如果是,应该包括哪些信息。INFO应用程序正在使用时应该有一些“tickover” 日志记录,表明一切都很好。当事情不好的时候,应该注意WARN并ERROR记录注意力,并详细说明问题所在。

遵循应用程序约定的时间/内容/方式。这可确保应用程序的所有区域始终如一。

避免琐碎,不相关或重复的日志记录。这种记录只是噪声,有损于实际重要记录的“信号”。

读取日志记录输出以确保“流量”。它应该连贯地阅读,作为正在发生的事情的故事。在执行特定的应用程序任务时尝试阅读它,并且正在进行负载测试。

避免紧密耦合的日志记录协作和依赖项。日志消息应该最多能够独立存在。如果修改或删除其他远程日志调用,它们应保持有意义; 也就是说,完整的日志输出不应该是脆弱的。避免提及,设置期望,与远程日志记录合作构建多消息“句子”或“关闭您开始的业务”。

日志内容是写清楚,简洁,明确的消息。遵循这些原则的消息更快更容易理解,并有助于避免误解或混淆。

包括相关和上下文信息。这些信息有助于诊断问题。示例包括键值,状态和标识符。为了便于搜索并允许通过日志查看工具进行解析,请考虑使用诸如的模式key=value | other=value。

使用分隔符区分消息文本中的值。当它们嵌入在消息中时,某些类型的数据很难与日志消息模板本身区分开来。使用分隔符(如引号,大括号或尖括号)可在必要时阐明边界。

使用映射的诊断上下文(MDC)来区分多个线程的日志记录。当多个并发线程的记录输出交错时,几乎不可能理解正在发生的事情。在各个日志语句中包括诸如用户/请求ID之类的上下文信息是繁琐且重复的。相反,配置记录器的模式以在每条线上包含此类信息,就像在时间戳中一样。

日志应该设置适当和一致地使用水平。建立(或获得)指南,并遵循它们。例如,当应用程序平稳运行时,它不应该喷出大量的错误和警告。

客户端错误不是应用程序错误。如果客户端发送无效请求或以其他方式执行非法/错误的操作,那就是错误的客户端 - 而不是处理请求的应用程序。记录应用程序错误等错误会在行为不当或执行不当的客户端调用我们的应用程序时产生噪音。

测试
测试也是代码。本指南中其他主题的项目适用。它们需要进行代码审查,并且必须遵循与主代码相同的自动和手动质量检查和规则。糟糕的测试代码不太可靠,并且可能难以更改或重构主代码。

保持测试代码接近他们正在测试的代码。阅读:文件

每次测试的单一焦点和目的。范围广泛的测试不太清楚,并且更难以确定故障原因。针对仅与测试名称中描述的情况相关的故障,并根据需要进行断言。

平衡价值与成本。测试需要努力编写和维护。考虑一段给定的代码(无论多小),如果你在正确性方面获得的信心以及避免未来的破坏,那么它与测试的耗时和难度相比是值得的。这个项目不是使代码难以测试的反模式的借口。

覆盖范围是一个指南; 它并不意味着充分性和不充分性。覆盖率表示在执行测试时通过代码的路径。它没有说明好的测试用例选择或做出适当的断言。对提供高覆盖率的代码的测试可能需要改进,并且对提供较低覆盖率的代码的测试可能是完全足够的。避免编写低价值测试以提高覆盖率指标。

避免测试代码“它实现什么功能就测试什么功能”的测试。这样的测试通常会检查某些模拟被调用,并且不测试被测单元的任何逻辑(如果单元甚至有任何逻辑)。它们很脆弱,是重构,噪音和低价值的开销。

避免测试模拟库的测试。通常是偶然的,这样的测试不会断言我们的代码,而是例如检查方法存根功能是否有效。

包括边缘情况和不愉快路径的测试。我们需要确信我们的代码在所有情况下都能正常工作,而不仅仅是绝大多数情况下可能出现的“正常”代码。示例包括错误,超时,无效数据,无数据和边界值。

避免测试之间的共享状态。共享状态会破坏测试隔离,通常会导致涉及错误传递或错误失败的混乱,具体取决于哪些测试一起运行以及以何种顺序运行。示例包括共享变量(通常是为了避免重新声明),重用测试实例和重用的模拟。始终重新开始,或在模拟的情况下,重置它们。

避免钩子中的共享设置逻辑。这意味着测试框架在单个测试集合之前运行的代码块。考虑到极端,它涉及嵌套的测试组,每个级别都有一个共享的设置块。它鼓励使用共享状态,允许意外使用共享状态,使设置难以遵循,使得单个测试的定制设置变得困难,并且使得难以修改测试并添加新测试。

实用函数中的抽象通用设置逻辑。这避免了使用钩子引起的所有问题。将共享设置逻辑提取到实用程序功能中,并根据需要直接使用它们。这些函数返回准备在测试中使用的对象,并且通常接受参数(数据或选项)以允许为各个测试定制设置。后者可以很容易地看到每个测试之间的设置差异。

避免依赖当前系统时间和时区。此类测试不会在每次运行时测试相同的内容,并且可能在将来或由不同位置的人员或CI服务器运行时导致问题。将当前时间注入代码中,允许在测试中注入特定的固定时间。

断言
使用严格的断言。这些加强了测试,使其更有可能发现未来的回归。示例包括严格相等,以及具有特定消息的特定类型的错误/异常。

使用最合适的断言。测试框架包括除了相等之外的断言。他们澄清测试代码,并产生更好的失败消息。
确保实际运行异步代码中的断言。使用特定于测试框架的技术来确保它们运行。一些框架提供了同步断言,断言后来的断言实际上已经完成。

其他测试
模拟直接依赖而不是传递依赖。这使测试保持独立并专注于单个单元。

避免将模拟运行时数据重用为模拟测试数据。这些数据是为不同目的而设计的(通常是演示),并且应该保持可修改而不影响测试。

避免等待经过的时钟时间。对于异步代码和计时器等待这种方式的测试运行缓慢; 当你有数百个时,这就成了一个大问题。相反,使用您的测试框架的时间模拟工具,可以立即提前。