您的位置:首页 > 编程语言

程序员成长指南之提升代码“可靠性”

2021-04-22 11:44 525 查看



码农修行

如何提高代码的可靠性

产品质量和可靠性息息相关。有的软件经常会崩溃、有的功能时好时坏、有的系统隔一段时间就得做一次重启。这都是可靠性不高的表现。软件的可靠性和每一行代码都息息相关。任何一位软件设计师都有可能把软件搞崩溃,因为稍有疏忽就可能出现非法指针、内存错误等问题,而这类问题都是致命性的。因此每一位软件设计师都要对可靠性心存敬畏之心,对每一行代码的质量负责。系统的可靠性除了代码层面的可靠性外,还包含其他方面的内容,比如子系统间的核查、自愈、容错、容灾等方面。影响系统可靠性的因素非常多,抛开系统自身的因素,就外部因素而言又包含各种场景。比如网络延时、丢包、中断、非预期的输入、流量激增、系统断电、硬件故障等。不过这些因素已经超出了本书所讨论的范畴……本文仅就编程阶段一些与可靠性相关的细节给读者一些提示,希望读者能从基础的地方筑牢系统可靠的防线。



码农修行

提高代码可靠性的重要法则:避免过度防御


非法指针是C/C++程序中最令人头痛的问题之一。你也许会有类似的习惯,在函数入口即对指针的合法性进行检查。代码如下。

即使在调用该函数前已经做过一次检查。代码如下。

这两处检查都属于过度防御,完全没有必要。首先,参数的合法性应该由调用者保证。可以参考一些参数为指针的API,比如strncpy,当对其强制传入一个空指针时,程序并不会悄无声息,而是会抛出异常。

因为这样的参数已经违反了接口的规约,函数内部不知道该如何处理,只能抛出异常让程序崩溃。此外,就指针而言,哪些属于非法指针,哪些属于合法指针其实很难界定。对于上面的例子,假设调用者强制传入一个“1”,其实程序也会崩溃。

空指针只是非法指针中的一种而已,对于其他情况更是防不胜防,关键要划清责任范围。
过度防御还会造成大量冗余代码,因为很多类似这样的分支是永远不会执行的。

更令人沮丧的是,过度防御可能会增加某些问题定位的难度,因为没有在出现问题的“第一现场”暴露问题。假设一个交易系统中定义了一个带有过度防御功能的StrNCpy函数。代码如下。

在交易成功后需要保存订单,SaveOrder中使用了StrNCpy进行字符串复制,组装了一个Order数据结构,并调用DB_SaveOrder存入数据库。代码如下。

假设由于某个bug(漏洞)导致上层传入的“公司名称”companyName为一个空指针NULL。由于StrNCpy的过度防御,该函数“悄无声息”地执行完毕,让SaveOrder“误以为”数据复制成功,并将信息写入数据库。当消费者想要根据这张订单开具发票时,结果发票开具失败。

通过分析发现数据库中该订单的“公司名称”字段没有内容,这是导致发票开具失败的直接原因,因为购方企业名称是发票开具流程中的必填参数。

这个字段为什么没有内容?是一开始就没有?还是后来被误删除了?这需要翻阅大量的日志,才能分析定位该问题。但如果改用符合接口规约的API函数,那么在保存订单时程序就会抛出异常,及时暴露问题,像上述那样不完整的订单信息就不会写到数据库中。以下代码中将StrNCpy替换为strncpy。

那么正确的保护方法应该是什么?答案是:在可能出错的地方进行保护,做“正当防卫”。比如上面的例子中,假设入参name是通过new动态申请的。

new申请动态内存可能会失败,因此需要在申请返回后进行保护。
当然Java语言情况稍好一些,new不会返回null,因此不需要类似的保护。但有一种情况需要注意,如果一个对象是通过某个方法获取到的,就需要关注该方法是否可能失败,如果是就需要在返回的地方加以保护。


以上内容节选自:《码农修行,编写优雅代码的32条法则》林文 著



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: