《代码大全2》第8章 防御式编程

2023-11-10

目录

前言

8.1 保护程序免遭非法输入数据的破坏

8.1.1 三种方式处理“垃圾进”

8.2.2 思考:程序输出时也应该增加防御

8.2.3 保留”证据“

8.2 断言

8.2.1 建立自己的断言机制

 8.2.2 使用断言的指导建议

8.3 错误处理技术

8.3.1 健壮性和正确性

8.3.2 高层次对错误处理方式的影响

8.4 异常

8.5 隔离程序,使之包容由错误造成的损害

8.5.1 隔离和断言的关系

核对表:防御式编程

一般事宜

异常

安全事宜


《Code_Complete_2》持续更新中......_@来杯咖啡的博客-CSDN博客这本书有意设计成使你既可以从头到尾阅读,也可以按主题阅读。1. 如果你想从头到尾阅读,那么你可以直接从第2章“用隐喻来更充分地理解软件开发”开始钻研。2. 如果你想学习特定的编程技巧,那么你可以从第6章“可以工作的类”开始,然后根据交叉引用的提示去寻找你感兴趣的主题。3. 如果你不确定哪种阅读方式更适合你,那么你可以从第3章3.2节“辦明你所从事的软件的类型”开始。.....................https://blog.csdn.net/qq_43783527/article/details/126275083

前言

        防御式编程来自防御式驾驶。在防御式驾驶中要建立这样一种思维,那就是你永远也不能确定另一位司机将要做什么。这样才能确保在其他人做出危险动作时你也不会受到伤害。你要承担起保护自己的责任,哪怕是其他司机犯的错误。防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。更一般地说,其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序。

        本章就是要讲述如何面对严酷的非法数据的世界、在遇到 “绝不会发生”的事件以及其他程序员犯下的错误时保护你自己。

个人结论

        我是服务的提供者(方法是public的,别人可能会调用我的接口、组件、方法),那么调用者就需要遵守我的规则:该传的参数必须要传。

        只要是public的接口,那么参数校验是少不了的。web服务是一个领域、ser服务也是一个领域、repo服务又是一个领域,都得做校验。

8.1 保护程序免遭非法输入数据的破坏

8.1.1 三种方式处理“垃圾进”

        “垃圾进,垃圾出”的程序显然不符合程序安全的标准。通常有三种方法来处理进来垃圾的情况。

        1、检查所有来源于外部的数据的值 。当从文件、用户、网络或其他外部接口中获取数据时,应检查所获得的数据值,以确保它在允许的范围内。

  1.  对于数值,要确保它在可接受的取值范围内;
  2. 对于字符串,要确保其不超长。如果字符串代表的是某个特定范围内的数据(如金融交易 ID 或其他类似数据),那么要确认其取值合乎用途,否则就应该拒绝接受。
  3. 如果你在开发需要确保安全的应用程序,还要格外注意那些狡猾的可能是攻击你的系统的数据,包括企图令缓冲区溢出的数据、注入的 SQL 命令、注入的 HTML 或 XML 代码、整数溢出以及传递给系统调用的数据,等等。
  4. 补充:对于外部数据,不仅仅是简单的对输入参数进行非空校验,更应该结合业务场景增加校验。比如对于输入的文件,你需要检查这个文件是否完整;对于修改某条记录,你可以先去根据ID查询该记录是否存在,如果不存在返回一个提示。

       2、 检查子程序所有输入参数的值 。检查子程序输入参数的值,事实上和检查来源于外部的数值一样,只不过数据是来自于其他子程序而非外部接口。第 8.5节“隔离程序,使之包容由错误造成的损害”阐述了一种实用方法可用于确定哪些子程序需要检查其输入数据。

        3、决定如何处理错误的输入数据 。一旦检测到非法的参数,你该如何处理它呢?根据情况的不同,你可以从十几种不同的方案中选择其一,在本章后面第 8.3节“错误处理技术〞中会详细描述这些技术。

8.2.2 思考:程序输出时也应该增加防御

        当提到‘输入’时,我们是处于「服务提供者」的角色,也就是别人来调用我的程序;当提到‘输出’时,我们是处于「服务调用者」的角色,也就是我们来调用别人的程序。

        处于服务调用者角色的时候,我们也应该对获取到的数据进行校验。比较常见的是:对获取的数据进行非空检查。

8.2.3 保留”证据“

        当服务被调用的时候,可以优先进行日志输出,以保留服务被调用时的‘痕迹’。

8.2 断言

断言可以用于在代码中说明各种假定,澄清各种不希望的情形。可以用断言检查如下这类假定:

  • 输入参数或输出参数的取值处于预期的范围内
  • 子程序开始(或者结束)执行时文件或流是处于打开(或关闭)的状态;
  • 子程序开始(或者结束)执行时,文件或流的读写位置处于开头(或结尾)处;
  • 文件或流已用只读、只写或可读可写方式打开;
  • 仅用于输入的变量的值没有被子程序所修改;
  • 指针非空
  • 枚举值是否符合预期;
  • 传入子程序的数组或其他容器至少能容纳X个数据元素
  • 表已初始化,存储着真实的数值
  • 子程序开始(或结束)执行时,某个容器是空的(或满的);
  • 一个经过高度优化的复杂子程序的运算结果和相对缓慢但代码清晰的子程序的运算结果相一致。

当然,这里列出的只是一些基本假定,你在子程序中还可以包括更多可以用断言来说明的假定

8.2.1 建立自己的断言机制

        C++、Java等语言都支持断言。如果你想在原有的基础上,构建自己的断言机制,可以对原有的断言进行封装:

 8.2.2 使用断言的指导建议

        下面是关于使用断言的一些指导性建议。

        1、用错误处理代码(try catch)来处理预期会发生的状况,用断言(assert)来处理绝不应该发生的状况

        2、避免把需要执行的代码放到断言中。

        3、用断言来注解并验证前条件和后条件。 前条件(preconditions)和后条件(postconditions)是一种名为“契约式设计 (design by contract )” 的程序设计和开发方法的一部分(Meyer 1997)。前条件是调用方代码(服务的提供者)对其所调用的代码要承担的义务。后条件是子程序或类在执行结束后要确保为真的属性,后置条件是子程序或类(服务的调用者)对调用方代码所承担的责任

8.3 错误处理技术

        断言可以用于处理代码中不应发生的错误。那么又该如何处理那些预料中可能要发生的错误呢?根据所处情形的不同,你可以:

  • 返回中立值(neutral value)
  • 换用下一个正确数据
  • 返回与前次相同的值
  • 换用最接近的有效值
  • 在日志文件中记录警告信息
  • 返回一个错误码
  • 调用错误处理子程序或对象
  • 显示出错信息或者关闭程序
  • ......
  • ——或把这些技术结合起来使用。

8.3.1 健壮性和正确性

        处理错误最恰当的方式要根据出现错误的软件的类别而定。错误处理方式有时更侧重于正确性,而有时则更侧重于健壮性

        开发人员倾向于非形式地使用这两个术语,但严格来说,这两个术语在程度上是截然相反的。

  1. 正确性〈correctness)意味着永不返回不准确的结果,哪怕不返回结果也比返回不准确的结果好。人身安全攸关的软件往往更倾向于正确性而非健壮性。不返回结果也比返回错误的结果要好。
  2. 然而,健壮性(robustness)则意味着要不断尝试采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确的结果

8.3.2 高层次对错误处理方式的影响

        既然有这么多的选择, 你就必须注意,应该在整个程序里采用一致的方式处理非法的参数。对错误进行处理的方式会直接关系到软件能否满足在正确性、健壮性和其他非功能性指标方面的要求。确定一种通用的处理错误参数的方法,是架构层次(或称高层次)的设计决策,需要在那里的某个层次上解决

        一旦确定了某种方法, 就要确保始终如一地贯彻这一方法。如果你决定让高层的代码来处理错误,而低层的代码只需简单地报告错误,那么就要确保高层的代码真的处理了错误!有些语言允许你忽略“函数返回的是错误码”这一事实—在 C++中,你无须对函数的返回值做任何处理——但千万不要忽略错误信息!检查函数的返回值。即使你认定某个函数绝对不会出错,也无论如何要去检查一下。防御式编程全部的重点就在于防御那些你末曾预料到的错误。

8.4 异常

        异常是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。对出错的前因后果不甚了解的代码,可以把对控制权转交给系统中其他能更好地解释错误并采取措施的部分。

        异常和继承有一点是相同的,即:审慎明智地使用时,它们都可以降低复杂度;而草率粗心地使用时,只会让代码变得几乎无法理解。下面给出的一些建议可以让你在使用异常时扬长避短,并避免与之相关的一些难题:

  • 用异常通知程序的其他部分,发生了不可忽略的错误 。异常机制的优越之处在于它能提供一种无法被忽略的错误通知机制 (Meyers 1996)。其他的错误处理机制有可能会导致错误在不知不觉中向外扩散,而异常则消除了这种可能性。
  • 不能用异常来推卸责任 。如果某种的错误情况可以在局部处理,那就应该在局部处理掉它。
  • 避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获。当从构造函数和析构函数里抛出异常时,处理异常的规则马上就会变得非常复杂。
  • 在异常消息中加入关于导致异常发生的全部信息  每个异常都是发生在代码抛出异常时所遇到的特殊情况下。这一信息对于读取异常消息的人们来说是很有价值的,因此要确保该消息中含有为理解异常抛出原因所需的信息。
  • 了解所用函数库可能抛出的异常  如果你所用的编程语言不要求子程序或类定义它可能抛出的异常,那你一定要了解所用的函数库都会抛出哪些异常。末能捕获由函数库抛出的异常将会导致程序崩溃,就如同未能捕获由自己代码抛出的异常一样。如果函数库没有说明它可能抛出哪些异常,可以通过编写一些原型代码来演练该函数库,找出可能发生的异常。
  • 考虑创建一个集中的异常报告机制  。有种方法可以确保异常处理的一致性,即创建一个集中的异常报告机制。这个集中报告机制能够为一些与异常有关的信息提供一个集中的存储,如所发生的异常种类、每个异常该被如何处理以及如何格式化异常消息等。
  • 把项目中对异常的使用标准化。 为了保持异常处理尽可能便于管理,你可以用以下几种途径把对异常的使用标准化。
    • 如果你在用一种像 C++一样的语言,其中允许抛出多种多样的对象、数据及指针的话,那么就应该为到底可以抛出哪些种类的异常建立一个标准。为了与其他语言相兼容,可以考虑只抛出从 std::exception 基类派生出的对象。
    • 考虑创建项目的特定异常类,它可用做项目中所有可能抛出的异常的基类。这样就能把记录日志、报告错误等操作集中起来并标准化。
    • 规定在何种场合允许代码使用 throw-catch 语句在局部对错误进行处理
    • 规定在何种场合允许代码抛出不在局部进行处理的异常。
    • 确定是否要使用集中的异常报告机制。
    • 规定是否允许在构造函数和析构函数中使用异常。

        有些程序员用异常来处理错误,只是因为他所用的编程语言提供了这种特殊的错误处理机制。你心里应该自始至终考虑各种各样的错误处理机制在局部处理错误、使用错误码来传递错误、在日志文件中记录调试信息、关闭系统或其他的一些方式等

        仅仅因为编程语言提供了异常处理机制而使用异常,是典型的“为用而用〞;这也是典型的“在一种语言上编程〞而非“深入一种语言去编程”的例子。(有关这两者的区别,请参阅第4.3节“你在技术浪潮中的位置”和第34.4节“以所用语言编程,但思路不受其约束”)

8.5 隔离程序,使之包容由错误造成的损害

        以防御式编程为目的而进行隔离的一种方法,是把某些接口选定为“安全”区域的边界。对穿越安全区域边界的数据进行合法性校验,并当数据非法时做出敏锐反映。

        让软件的某些部分处理“不干净的”数据,而让另一些部分处理“干净的”数据,即可让大部分代码无须再担负检查错误数据的职责

         也同样可以在类的层次采用这种方法。类的公用方法可以假设数据是不安全的,它们要负责检查数据并进行清理。一旦类的公用方法接受了数据,那么类的私用方法就可以假定数据都是安全的了

        在输入数据时将其转换为恰当的类型 。 输入的数据通常都是字符串或数字的形式。这些数据有时要被映射为“是”或“否”这样的布尔类型,有时要被映射为像 Color_Red、 Color_Green 和 Color_Blue 这样的枚举类型。在程序中长时间传递类型不明的数据,会增加程序的复杂度和崩溃的可能性——比如说有人在需要输入颜色枚举值的地方输入了“是’〞。因此,应该在输入数据后立即将其转换到恰当的类型。

8.5.1 隔离和断言的关系

        隔栏的使用使断言和错误处理有了清晰的区分隔栏外部的程序应使用错误处理技术,在那里对数据做的任何假定都是不安全的。而隔栏内部的程序里就应使用断言技术,因为传进来的数据应该已在通过隔栏时被清理过了。如果隔栏内部的某个子程序检测到了错误的数据,那么这应该是程序里的错误而不是数据里的错误。隔栏的使用还展示了“在架构层次上规定应该如何处理错误”的价值。规定隔栏内外的代码是一个架构层次上的决策。

核对表:防御式编程

一般事宜

  • 子程序是否保护自己免遭有害输入数据的破坏?
  • 你用断言来说明编程假定吗?其中包括了前条件和后条件吗?
  • 断言是否只是用来说明从不应该发生的情况?
  • 你是否在架构或高层设计中规定了一组特定的错误处理技术?
  • 你是否在架构或高层设计中规定了是让错误处理更倾向于健壮性还是正确性?
  • 你是否建立了隔栏来遏制错误可能造成的破坏?是否减少了其他需要关注错误处理的代码的数量?
  • 代码中用到辅助调试的代码了吗?
  • 如果需要启用或禁用添加的辅助助手的话,是否无须大动干戈?
  • 在防御式编程时引入的代码量是否适宜一既不过多,也不过少?
  • 你在开发阶段是否采用了进攻式编程来使错误难以被忽视?

异常

  • 你在项目中定义了一套标准化的异常处理方案吗?
  • 是否考虑过异常之外的其他替代方案?
  • 如果可能的话,是否在局部处理了错误而不是把它当成一个异常抛到外部?
  • 代码中是否避免了在构造函数和析构函数中抛出异常?
  • 所有的异常是否都与抛出它们的子程序处于同一抽象层次上?
  • 每个异常是否都包含了关于异常发生的所有背景信息?
  • 代码中是否没有使用空的 catch 语句?(或者如果使用空的 catch 语句确实很合适,那么明确说明了吗?)

安全事宜

  • 检查有害输入数据的代码是否也检查了故意的缓冲区溢出、SQL注入、HTML 注入、整数溢出以及其他恶意输入数据?
  • 是否检查了所有的错误返回码?
  • 是否捕获了所有的异常?
  • 出错消息中是否避免出现有助于攻击者攻入系统所需的信息?

项目中使用

1、如果a方法被十几二十几处被调用。

  • 那么建议在所有调用a的地方进行异常捕获,且异常msg进行统一描述; 原因:一旦系统出现异常,在日志系统中可以通过对异常msg进行搜索,然后快速捕获处到底是十几二十几处那几处调用失败。
  • 然后再根据实际业务情况,对这个捕获了的异常进行抛出或者吞掉

2、凡是从对象获取到的属性,然后再去调用该属性的某个方法,都需要对这个属性进行空校验:

如:message.getShielded() == null ? 0 : (message.getShielded() ? 1 : 0)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《代码大全2》第8章 防御式编程 的相关文章

  • 如何在 Ubuntu 22.04 上设置私有 Docker 注册表

    作者选择了免费开源基金接受捐赠作为为捐款而写程序 介绍 Docker 注册表是一个管理 Docker 容器镜像存储和交付的应用程序 注册表集中容器映像并减少开发人员的构建时间 Docker 镜像通过虚拟化保证相同的运行时环境 但构建镜像可能
  • 如何在 Ubuntu 16.04 上将 Nginx Web 根移动到新位置

    介绍 在 Ubuntu 上 默认情况下 Nginx Web 服务器将其文档存储在 var www html 它通常与操作系统的其余部分一起位于根文件系统上 但有时 将文档根移动到另一个位置会很有帮助 例如单独安装的文件系统 例如 如果您从同
  • 如何使用BackupPC在Ubuntu 12.04 VPS上创建备份服务器

    Status 已弃用 本文介绍不再受支持的 Ubuntu 版本 如果您当前运行的服务器运行 Ubuntu 12 04 我们强烈建议您升级或迁移到受支持的 Ubuntu 版本 升级到Ubuntu 14 04 从 Ubuntu 14 04 升级
  • 如何设置 Nginx 负载均衡

    关于负载均衡 负载平衡是一种有用的机制 可以在多个功能强大的虚拟专用服务器之间分配传入流量 通过将处理机制分配给多台计算机 可以为应用程序提供冗余 确保容错和提高稳定性 用于负载平衡的循环算法将访问者发送到一组 IP 中的一个 在最基本的层
  • 如何在 Java 中使用运算符

    作者选择了免费开源基金接受捐赠作为为捐款而写程序 介绍 An operator是一个或多个符号的组合 比如著名的算术运算符减号 并加上 或更高级的instanceof 当您对值或变量应用运算符时 您会得到运算结果 此类操作是编程的基础 因为

随机推荐

  • 如何在 Ubuntu 14.04 服务器上安装 Node.js

    介绍 Node js 是一个用于服务器端编程的 Javascript 平台 允许用户快速构建网络应用程序 通过在前端和后端都利用 Javascript 开发可以更加一致并在同一系统内进行设计 在本指南中 我们将向您展示如何在 Ubuntu
  • mysql如何显示ddl_Mysql DDL语句之视图

    Mysql 视图是一个虚拟表 内容由 select 查询语句定义 同真实的表数据一致 但是视图并不在数据库中以存储的数据值形式存在 试图引用自定义查询表的字段 并且在引用试图时动态生成 对其所引用的基础表来说 Mysql 视图的作用类似于筛
  • 【计算机视觉

    文章目录 一 ModaNet 二 SKU110K 三 SceneNet 四 VT5000 五 Washington RGB D 六 Argoverse HD 七 CADC Canadian Adverse Driving Condition
  • 小技巧——宝塔面板重启、重置命令

    1 Centos 安装脚本 yum install y wget wget O install sh http download bt cn install install sh sh install sh 2 Ubuntu Deepin
  • Qt示例5:用Qt画一个漂亮预警仪表

    以下是用Qt实现漂亮预警仪表的步骤和代码 创建一个Qt项目 并添加一个主窗口 在主窗口中添加QGraphicsView控件 用于绘制预警仪表 创建一个QGraphicsScene对象 并将其设置为QGraphicsView的场景 QGrap
  • emmx用xmind打开_XMind思维导图:专注扩展延伸和梳理,让你事半功倍!

    更多精彩软件 请关注我们 今日新闻 现如今 思维导图被普遍运用在各行各业 充当着重要的角色 但你会发现这些导图绝大多数是通过电脑软件绘制的 随着移动互联网的高速发展 实际情况告诉我们需要一款手机版的思维导图软件 便于我们在手机上就能自由整理
  • mmdetection踩坑1~docker内RuntimeError: DataLoader worker (pid 1727) is killed by signal: Bus errer

    今天在docker内使用mmdetection做训练时 workers per gpu参数设置为0时 可以正常训练 但修改配置文件中workers per gpu 2参数后 开始训练 程序报错 网上查资料显示 是因为docker的共享内存不
  • 初始化列表

    在构造函数后面 属性 值 参数 属性 值 参数 define CRT SECURE ND WARNINGS include
  • 1.4最流行的NoSQL——Redis

    本文比较重要的概念 NoSQL 及它的优点 Redis 及它的优点 NoSQL Not Only SQL NoSQL 在互联网中作用很大 可以在很大程度上提高互联网系统的性能 具备一定持久层的功能 也可以作为一种缓存工具 注释 Redis缓
  • 重学JavaScript 第二天

    数据类型 js数据类型整体分为两大类 1 基本数据类型 2 引用数据类型 1 数据类型 数字类型 number JavaScript 中的正数 负数 小数等 统一称为 数字类型 注意 JS 是弱数据类型 变量到底属于那种类型 只有赋值之后
  • 【排序算法】快速排序(C语言)

    排序算法 快速排序 目录 一 快速排序的单趟排序 1 霍尔法 2 挖坑法 3 前后指针 二 快速排序 1 排序步骤 2 排序完整步骤图 3 快速排序代码 3 1 递归实现 3 2 非递归实现 三 选择基准数key 1 为什么要选择基准数ke
  • 互联网精准广告定向技术: 一切你该了解的知识总结与整理

    互动中国编者前言 本文来自于读者的投稿 牛国柱博客 原文作者为资深互联网广告行业从业者 经过一段长时间的积累 从基础知识 技术应用以及在线广告作弊手段整理和反作弊方法三大角度 共计15篇系列专文 对于精准广告定向技术的介绍进行了一个全面且切
  • MATLAB2016添加工具箱toolbox方法,有截图

    我这里添加的是Image Graphs 下载路径 需要注册账号 https ww2 mathworks cn matlabcentral fileexchange 53614 image graphs requestedDomain zh
  • Linux-Bridge(LBR)网络虚拟化实战

    1 Linux 网络虚拟化概述 1 网络通信模型 Linux 系统的网络通信模型 即信息是如何从程序中发出 通过网络传输 再被另一个程序接收到的 整体上看 Linux 系统的通信过程无论按理论上的 OSI 七层模型 还是以实际上的 TCP
  • Java 三大特性学习笔记(基础)

    目录 约定俗成的运算符 铺垫 1 逻辑运算中的 和 和 一个符号 和两个符号 的区别是 2 位 bit 运算中的 和 第一个特性 封装性 封装修饰符介绍 以下封装等级由低写到高 1 public 公开等级 相当于没有封装 2 protect
  • 南方科技大学计算机系师资,于仕琪 - 教师个人主页 - 南方科技大学

    Rijun Liao Shiqi Yu Weizhi An Yongzhen Huang A model based gait recognition method with body pose and human prior knowle
  • 【Java编程】SSH:Struts、Spring、Hibernate

    SSH Struts Spring Hibernate SSH 框架指的是 Struts Spring 和 Hibernate 三者的集成 集成 SSH 框架的系统从职责上分为表示层 业务逻辑层 数据持久层和域模块层 Struts 属于 M
  • ChatGPT数据分析及作图插件推荐-Code Interpreter

    今天打开chatGPT时发现一个重磅更新 code interpreter插件可以使用了 去查看openai官网 发现从2023 7 6号 前天 开始 code interpreter插件已经面向所有chatGPT plus用户开放了 为什
  • 千辛万苦拿到阿里内推却挂了,重拾起鼓三面字节跳动成功收到字节offer(社招四面面经总结)

    前言 先说说结果吧 阿里挂了 和很多小伙伴一样 我也是挂在阿里的HR手里了 阿里的HR是真的恐怖 希望将来要面阿里的小伙伴们 千万要当心HR面 不过 就像我们一个老师说的 有时候真的要看缘分 可能我和阿里 真的是缘分未到吧 不过周五晚上 收
  • 《代码大全2》第8章 防御式编程

    目录 前言 8 1 保护程序免遭非法输入数据的破坏 8 1 1 三种方式处理 垃圾进 8 2 2 思考 程序输出时也应该增加防御 8 2 3 保留 证据 8 2 断言 8 2 1 建立自己的断言机制 8 2 2 使用断言的指导建议 8 3