什么是防御性编程

相关文档:https://xie.infoq.cn/article/9bbd384642d0f6ca171a91231

防御型编程是一种细致、谨慎的编程方法。为了开发可靠软件,我们要设计系统中的每个组件,使其尽可能保护自己。我们通过对代码的设想进行检查,防止我们的代码以将会展现错误行为的方式被调用。

防御型编程使我们可以尽早发现较小的问题,而不是等到发展成大灾难时才被发现。

以往的开发过程: 编写 => 测试 => 发现问题 => 编写 => 测试 => 发现问题 => ……

以往的开发过程

防御性编程开发过程: 编写 => 测试 => 成功

防御性编程开发过程

防御式编程帮助我们从一开始就编写正确的代码,不再需要经历“ 编写 => 测试 => 发现问题 => 编写 => 测试 => 发现问题 => ……”的循环。

优点和缺点

优点:

  1. 节省大量调试时间
  2. 避免了大量安全性问题
  3. 编写可以正常运行、只是速度有些慢的代码,要远远好过大多数时间都正常运行、但有时会崩溃的代码

缺点:

  1. 降低了代码效率,即使一个很小的代码也需要一些额外的时间执行。对于一个函数来说也许不要紧,但有多个函数组成的系统,问题就变得严重了
  2. 每种防御性的做法都需要一些额外的工作

防御性编程不能排除所有错误,但可以减少问题带来的麻烦,并易于修改。

防御性编程是一种防卫方式,而不是一种补救方式。

防御式编程技巧

相关文档:https://blog.csdn.net/everpenny/article/details/6316698

  1. 使用好的编码风格和合理的设计

    采用良好的编码风格来防范大多数编码错误,如:使用有意义的变量名。可以使编码更加清晰明了,减少缺陷出现的可能性。

  2. 不要仓促地编写代码

    每敲一行代码都要三思而后行,要先想好可能会出现什么错误?是否考虑了所有可能出现的逻辑分支?有条不紊的编程虽然看上去很平凡,但确是减少缺陷的好办法。

  3. 不要相信任何外部输入的参数

    不要相信任何输入的值和结果,因为使用者可能是任何人,可能没有阅读过相关手册,他们会输入任何意想不到的参数。所以需要对所有输入的参数进行合法性的检查,发现问题立即终止函数,返回预设的错误值。

  4. 不要忽略任何一个错误

    代码产生任何警告信息都应立即修正。出现警告总是有原因的,即使你认为某个警告无关紧要,也不要置之不理。

  5. 不要假定异常不会发生

    异常不是错误,错误时可预期的,也是经常会发生的,我们有对应的错误与处理方案,但异常确是少见的、意料之外的。

    虽然异常发生是“小众事件”,但我们不能假定异常不会发生。所以在编写代码时,需要考虑代码是否要设置异常捕捉和恢复。

  6. 编码的目标是清晰,不是简洁

    不要让代码过于复杂。编写的代码一定要逻辑清晰,可读性强。多对数据进行判断,当多个数据进行相似判断时可封装模块进行判断。

  7. 使用安全的数据结构

    代码中的一部分安全隐患是由于不正确的使用固定大小的数据结构造成的。

  8. 检查所有返回值

    但函数返回一个值一定是有理由的,但同时也要检查返回值,如果返回值是一段错误代码,就必须辨别这个代码并处理所有的错误。

    例如需要返回的数据需要 id, name, age 三个数据都不能为空才会存入 data 中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const arr = [{
    id: 1,
    name: '张三',
    age: 20
    }, {
    id: 2,
    name: '张四',
    age: 20
    }, {
    id: 3,
    name: '',
    age: 20
    }]

    当其中一个数据为空时就会造成问题,我们可以在数据存入 data 之前进行判断,如果参数有一个为空就不存入:

    1
    2
    3
    4
    5
    const data = []
    arr.forEach(item => {
    if (!item.id && !item.name && !item.age) return
    data.push(item)
    })
  9. 审慎的处理内存

    对于在执行期间获取的任何资源,必须彻底释放。例如:创建页面后添加的定时器,在销毁页面是也需要销毁定时器。

  10. 在使用变量前初始化变量

    变量声明但不关注初始值可能会引发后续的问题。例如下面代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <script>
    export default {
    data() {
    return {
    list: []
    }
    },
    methods: {
    getList() {
    const arr = ['张三', '李四', '王五']
    arr.forEach(item => {
    list.push({ name: item })
    })
    }
    }
    }
    </script>

    因为没有在调用方法四对数据进行初始化,再次调用 getList 方法时就会出现这种情况:

    1
    2
    3
    4
    5
    6
    7
    8
    list: [
    {name: '张三'},
    {name: '李四'},
    {name: '王五'},
    {name: '张三'},
    {name: '李四'},
    {name: '王五'}
    ]

    所以在使用变量前要及时初始化。

  11. 平时编码时还需要注意到一些细节:

    1. 检查数值的上下限:确保每次运算数值变量都不会溢出,数据类型的使用要谨慎
    2. 注意强制转换是否合理
    3. 声明变量时,声明位置与使用位置尽量接近,防止干扰代码其他部分

总结

想要做好防御式编程,我们往往需要花3倍以上的时间去处理(数据流的处理,思考代码严谨性、思考信息提示、思考代码走向……)。
如果你想快速开发出高质量模块,不仅需要经验的积累,培养防御型编程的思维必不可少。

优秀的程序应该做到:

  • 关心代码是否健壮
  • 确保每个设想都显示地体现在防御性代码中
  • 希望代码对无用信息的输入有正确的行为
  • 在编程的时候认真思考自己所编写的代码
  • 编写可以保护自己不受其他人的愚蠢伤害的代码。