防御和进攻编程

news2024/12/24 2:06:24

防御性编程是许多程序员都听说过的一个术语,对于某些程序,防御性编程是必不可少的。对于其他程序,它可能偶尔使用一下。除此之外,还有攻击性编程。

在本文中,我们将首先研究“正常编程”。我们首先研究它,是因为有些人误以为它是"防御性"编程。然而,无论你是否进行"防御性"编程,"正常编程"都是你应该做的事情。

然后,我们将研究防御性编程,接着研究进攻性编程。

正常编程

正常编程意味着在代码中进行所有必要的检查。它还意味着始终处理某些类型的错误。

代码中必要的检查

有些代码需要很多条件。你可能觉得你对条件的数量“过于谨慎”。

一个例子就是检查NULL或者None(导致发生价值数十亿美元的错误?)。空值和空值检查非常棘手,许多代码库都需要if判断到处都有这些语句。

另一个例子是验证用户输入。你需要进行多次检查以确保用户输入有效。你的程序需要非常严格地处理它。否则,你将面临安全漏洞。

但这不是防御性编程。

相反,忘记一个空值检查之类的事情就是一个错误。它们不是你“以防万一”而做的不必要的检查,它们是必要的检查。值NULL有时会出现,这是正常的。如果你忘记了一个,你就有了一个错误。毫无疑问。

必要的错误处理

错误处理在程序中非常重要,你总是需要考虑你的程序应该如何响应错误。

这也取决于错误的类型。

一般来说,大多数程序都会处理超出其控制范围的“预期错误”。例如:

  • 由于网络连接断开,无法发送网络请求。
  • 由于用户删除了文件而无法找到它。

如果程序因为这些错误而崩溃,用户体验将非常糟糕。而且,处理这些问题相对容易。

因此,即使没有进行防御性编程,大多数程序也会处理这些问题。因此,这再次被视为“正常编程”,而不是防御性编程。

另一种错误是 bug。在大多数程序中,这些错误被视为“不可恢复”。大多数程序的经验法则是在这些错误上崩溃,而不是处理它们。

防御性编程

在我看来,防御性编程与容错有关。这意味着要尽一切努力确保你的程序继续工作。

防御性编程用例示例

正如Adrian Georgescu 在其关于 NASA 编码标准的文章中所写,防御性编程的一个例子是用于太空探索任务的代码。

代码开发完成后就会被送往太空。一旦出错,数十亿美元的工作成果就会付诸东流。

对于这种代码,你需要采取极端措施。无论如何,代码必须正确运行,不能崩溃。

这与普通程序非常不同。对于普通程序,错误通常不是什么大问题。即使程序有错误,它仍然可以使用。在最坏的情况下,可以通过致电客户服务手动解决问题。如果程序无法使用,您可以使其崩溃并重新启动。如果它是一个后端程序,则可能有多个服务器在运行它。如果它是一个客户端,用户可以自己重新启动程序。在非常糟糕的情况下,您可以更新服务器代码。您甚至可以手动转到物理服务器并重新启动它。

但对于某些关键软件,你不能这样做。软件必须始终正常运行。

问题是人并不完美。我们会产生错误。更不用说还会发生程序无法控制的其他错误(例如操作系统错误)。这意味着程序可能会失败。

但是,有些软件不提供这个选项。

因此,您需要竭尽全力防止失败。

如何进行防御性编程

防御性编程主要意味着尽一切可能确保你的程序正常运行并将继续正常运行。这可以包括:

  • 拥有非常好的软件开发实践。
  • 在代码中进行多次检查,以再次三番地确认一切是否正常工作。
  • 可选地,具有错误恢复机制。这样,如果出现问题,程序也许可以恢复。

良好的软件开发实践

第一步是尽可能使代码没有错误并且易于使用。

这意味着你需要如下东西:

  • 非常严格的质量保证
  • 非常彻底的测试
  • 非常彻底的运行时监控
  • 非常严格的编码和开发标准。事实上,你可能会完全禁止某些模式或语言特性,例如递归。
  • 良好的总体软件质量
  • 易于理解的源代码
  • 行为可预测的逻辑

这些要点对于所有软件都很重要,毕竟,如果你的源代码没有经过充分测试或不易理解,它可能会存在错误,这违背了防御性编程的初衷。

额外检查

采用防御性编程的代码往往有许多额外的检查。这些检查是为了发现错误。如果代码完全没有错误,就不需要这些检查。不是为了发现错误的检查属于“正常编程”,而不是“防御性编程”。

代码中存在条件,用于检查某些内容(例如程序中的某些状态)是否有效。如果检查失败,则表明存在错误。

在那时候:

  • 如果程序正在开发中,您可以让其崩溃(发生异常)并修复错误。这与在开发过程中在攻击性编程中使用断言的原理相同。
  • 如果程序正在生产中,您可以运行错误恢复(如果您已经实施了它)以便程序可以继续工作。

常见的情况是让程序崩溃并修复错误,例如在python代码的try-except中处理异常情况。在开发过程中,你希望测试和额外检查的组合能够捕获所有错误。然后,当程序投入生产时,它应该能够按预期运行。

这些检查的另一个好处是它们可以尽早发现错误。你检查的状态越多,你就能越早发现错误。这使得调试更容易。这也意味着你可以更早地开始错误恢复。

最后,您可以实现一些错误恢复。然后,如果检查失败,您可以运行错误恢复代码。

您可以根据需要进行任意数量的检查。您必须根据风险分析决定要检查的内容。一些重要的检查可能是涉及重要计算和数据的结果。

检查函数参数

您可以检查函数调用时是否使用了有效的参数。参数应具有正确的类型和范围。

检查数据计算结果

另一个例子是检查涉及数据的结果。

通常,您只会在第一次收到某些数据时对其进行检查。例如,如果用户提交了一些数据,您会对其进行检查以确保其有效。

然后,您将处理这些数据。您可能会对其进行格式化或以某种方式对其进行转换。您将进行测试以确保这些过程正常运行。

理论上,您不需要检查最终结果。初始数据是有效的。您处理它的代码运行正常。因此,最终结果应该是正确的。

但是,如果您正在进行防御性编程,您可能也会检查最终结果。

从意外错误中恢复

到目前为止,提到的步骤均试图减少程序中的错误数量。但是,错误可能仍然存在。因此,您可能需要实施错误恢复机制。

这可能需要深思熟虑。它甚至可能需要成为功能规划的一部分。如果程序在恢复过程中需要响应用户,情况就会如此。面向用户的行为最好是和产品经理合作确定,而不仅仅是由程序员确定。

此外,错误恢复可能是代码的很大一部分。作为一个虚构的例子,考虑一个接受产品订单网络请求的后端。服务器在处理订单时可能会出错。为了处理这种情况,您可以执行以下操作:

  • 让初始服务器记录订单信息以免丢失。
  • 为故障服务器提供一些恢复机制。例如,其他进程可能会重新启动它。或者,服务器可以尝试在内部修复其自身状态。
  • 可以将订单交给不同的服务器,或者错误服务器可以在修复之后尝试再次处理它。

以下是一些可能的恢复机制的示例。如果代码中的某些内容失败:

  • 也许您可以尝试在程序中手动修复或重置状态。
  • 也许您可以尝试再次运行该操作。如果问题是竞争条件,下次可能会成功。
  • 如果是子程序出错,也许你可以重新启动它。如果问题是子程序中的无效状态,那么重新启动它可能会起作用。
  • 也许你可以在服务器上托管一个备份程序。如果客户端产生错误的结果,那么也许它可以调用服务器来执行计算。
  • 也许你可以使用功能比主程序少的备份程序。如果主程序出错,也许可以运行仅提供基本操作的备份程序。

当然,如果程序的关键部分有错误,那么你可能在运行时无法做任何事情。唯一的解决方案可能是修复代码,然后再次发布。

您还需要进行风险分析。您需要考虑以下事项:

  • 哪些代码可能存在错误?
  • 它发生错误的可能性有多大?
  • 这个错误会产生什么影响?
  • 防止错误发生或实施错误恢复机制的成本是多少?

防御性编程的缺点

防御性编程有明显的缺点。例如:

  • 它需要更多的代码。至少,与没有防御性编程的类似程序相比,您将拥有更多的检查。
  • 性能可能会变差。这是因为执行额外的检查需要时间。
  • 额外检查会增加代码量,如果这些额外代码的架构设计较差,将难以阅读和维护。
  • 错误恢复机制可能需要较长时间来规划和实施。

何时使用防御性编程

是否使用防御性编程取决于您的程序。

如前所述,某些程序需要最大程度的可用性、可靠性和安全性。这些类型的程序可能需要大量防御性编程。

对于大多数其他程序,您不需要防御性编程。“正常编程”就足够了。尽管如此,您可以自由地在代码的某些关键区域使用一些防御性编程技术。由您来决定。

无论你做什么,记住要务实。使用风险分析。考虑:

  • 哪些代码可能存在错误?
  • 它发生错误的可能性有多大?
  • 这个错误会产生什么影响?
  • 防止错误发生或实施错误恢复机制的成本是多少?

然后,在必要的情况下使用适量的防御性编程。如果没有必要,尽量避免过度使用防御性编程。

攻击性编程

攻击性编程的目标是尽早发现错误并崩溃,前提是尽早崩溃是有帮助的,或者说避免造成更加严重的损失。

这意味着您可以立即收到错误通知。此外,崩溃时的堆栈跟踪更接近问题根源。这有助于调试。

如何进行攻击性编程

要进行攻击性编程,您需要:

  • 进行正常编程,这是基本的
  • 不要从错误中恢复(避免防御性编程)
  • 以错误明显且易于查找的方式编写代码,这便于维护
  • 出现错误时立即让程序崩溃

就像普通编程一样,你仍然需要条件来处理非错误的事情。例如,你需要条件来进行NULL检查。

同样,您可能应该处理"不是错误的错误"。例如,当用户提供无效数据时,或者当您无法在文件系统中找到文件时。大多数情况下,在这些情况下崩溃是不合理的。换句话说,您可能应该遵循“正常编程”的方式来处理这些问题。

此外,你还应该以一种容易发现错误的方式编写代码。以下是一些技巧。

避免回退代码和默认值

默认状态、默认参​​数和后备代码等可能会隐藏错误。

例如,您可能调用一个带有错误参数的函数。您可能不小心将null参数改成了字符串,例如,您使用python代码将''.format(None)改为了'None'。这是一个错误。但是,由于默认参数,参数无论如何都是字符串。这个错误不会被捕获,因此程序可能会做错事。

类似的事情也适用于“后备代码”。一个例子是继承和子类化。您可能忘记在子类中实现一个方法。然后,您调用该方法并执行父类的方法。这是非预期的行为,是一个错误。

为了防止这种情况,请避免使用默认状态、默认值和后备实现等。

避免检查因错误而崩溃的代码

有时,有缺陷的代码会自行崩溃。您无需做任何额外的事情。让代码保持原样,让它崩溃。

例如,考虑下面的代码。array永远不应该是null。 如果是null,那就是一个错误。

如果你对其进行了防御性检查,代码就不会崩溃:

def getValue(array) {
  if array and len(array) > 0 { 
    return array[0];
  }
}

但如果没有防御性检查,代码就会崩溃。

def getValue(array) {
    return array[0];
}

如果您希望代码尽早崩溃,在这种情况下,请保持原样,不进行防御性检查。

使用条件或断言来检查错误

与上面的观点相反,有些错误不会导致程序崩溃。

例如,你的程序中可能存在一些不正确的状态。你的程序可能不会因此崩溃。

再举一个例子,一些在正常情况下不应该执行的代码可能会执行。

在这些情况下,你可以使用手动检查。然后,如果你发现错误,你可以手动让程序崩溃。

例如:

def execute(arg) {
  switch(arg) {
    case 'a':
      // do something
      break;
    case 'b':
      // do something
      break;
    default:
      // 这行代码不应该被执行,所以如果程序执行了这行代码,就引发异常
      raise Exception('Default case should never execute.');
  }
}

更传统上,这些类型的“错误检查”使用断言而不是条件。

断言是查找错误的工具。如果它们失败,则表示存在错误。条件是控制流工具。如果条件“失败”,则并不意味着存在错误。这意味着应该执行不同的代码块。

因此,您可以使用断言来代替条件语句。有关如何执行此操作的详细信息,请参阅您所用编程语言的文档。

在某些编程语言中,断言会导致程序崩溃。然而,在其他编程语言中,它们不会使程序崩溃。它们可能只会将错误消息打印到控制台或其他东西上。两者都可用。然而,攻击性编程建议在可能的情况下硬崩溃。

此外,某些编程语言允许您在生产中关闭断言以获得更好的性能。

攻击性编程的弊端

与防御性编程类似,进攻性编程也有缺点。

一个缺点是必须避免某些类型的代码,例如默认参数。默认参数有有效的用例。它们提供“合理的默认值”。它们可以使一些代码更易于使用。

另一个缺点是程序崩溃。这可能是您不期望在应用程序中发生的事情。

另一个缺点是性能。在整个代码中大量使用断言语句会显著降低性能。

因此,许多编程语言在断言失败时不会崩溃。此外,它们还可以选择从生产代码中删除断言。使用此选项,您将失去生产中攻击性编程的好处。您只能在开发过程中获得好处。

何时使用攻击性编程

攻击性编程能帮你尽早发现 bug。这是一个重大特性。

因此,在开发过程中使用它是个不错的选择。通常,你会在这里或那里放置断言语句来确保某些事情是正确的。

至于实际,则视情况而定。考虑两种编程方式的利弊,然后做出决定。

务实

当选择处理错误的方法时,你需要务实。

对于大多数程序来说,“正常编程”是您需要做的最低限度的事情。

对于某些程序,你可能会使用防御性编程。特别是对于需要高水平的程序

  • 可用性
  • 安全
  • 可靠性

但也要了解缺点。主要缺点是性能相对较差和开发时间较长。

进攻性编程有助于您发现错误。这在开发过程中(甚至生产过程中)非常有用。

您可以根据需要混合搭配这些方法。您甚至可以在代码的不同区域使用不同的方法。由您决定。

最后说明

这就是本文的全部内容。希望您觉得它有用。

如果遗漏了任何要点,或者您不同意任何内容,或者有任何评论或反馈,请在下面发表评论。

谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1945531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

android(安卓)最简单明了解释版本控制之MinSdkVersion、CompileSdkVersion、TargetSdkVersion

1、先明白几个概念 (1)平台版本(Android SDK版本号) 平台版本也就是我们平时说的安卓8、安卓9、安卓10 (2)API级别(API Level) Android 平台提供的框架 API 被称作“API 级别” …

Mongodb的通配符索引

学习mongodb,体会mongodb的每一个使用细节,欢迎阅读威赞的文章。这是威赞发布的第95篇mongodb技术文章,欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题,欢迎在文章下面点个赞,或者关…

Blender 4.2 安装GIS插件步骤

Blender 4 更新以后插件安装变得复杂,插件界面的安装按钮不显示,界面布局改变,怎么安装插件: 1. 在线安装: “编辑”(Edit)>进入偏好设置(Preferences setting)>…

文件粉碎销毁 硬盘粉碎销毁 废弃的文件如何销毁

废弃的文件可以采用多种方法进行销毁,具体取决于文件的敏感性和数量。以下是一些常见的废弃文件销毁方法: 1. 机械粉碎:这是一种常见的方法,尤其适用于含有敏感信息的文件。可以使用碎纸机将文件切碎,对于小批量的资料…

Python新手如何制作植物大战僵尸?这篇文章教会你!

引言 《植物大战僵尸》是一款非常受欢迎的塔防游戏,玩家需要种植各种植物来抵御僵尸的进攻。在这篇文章中,我们将使用Python编写一个简化版的植物大战僵尸游戏,以展示如何使用Python创建游戏。 游戏规则 玩家将种植不同类型的植物来防御僵尸…

微软“蓝屏”事件:对全球IT基础设施韧性与安全性的深刻反思

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

【ai】windows ollama安装qwen

可以直接github下载,或者官方网站下载:参考大神的介绍当前最新0.28安装windows的预览版本,出现一个服务? 直接跑会TLS超时? 配置代理 export https_proxy=http://192.168.50.65:7890 export http_proxy

JavaWeb笔记_Session

Session概述 Session是一种在服务端记录用户会话信息的技术 Session的创建和获取 /*** HttpServletRequest对象中的方法:* public HttpSession getSession()* 如果当前服务端没有session,那就在服务端新建一个session对象* 如果在服务端有这个session,那么就直…

【C++】类和对象之继承

目录 继承的概念和定义 继承的概念 继承的定义 继承的定义格式 继承关系和访问限定符 继承基类成员访问方式的变化 访问权限实例 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 继承的…

别再只知道埋头苦学python了!!学了python后月入1w不在话下,不准你还不知道!!!

在Python接单的过程中,掌握一些技巧、注意相关事项以及选择合适的接单平台是非常重要的 一、Python接单要注意哪些 报酬问题:在接单前,务必明确客户所说的报酬是税前还是税后,以避免后期产生纠纷。时间管理:不要与客户…

nacos get changed dataId error, code: 403

nacos get changed dataId error, code: 403问题解决 问题出现原因:解决办法:需要在运行项目的配置添加权限账号和密码,重启服务 问题出现原因: 由于nacosserver开启了权限验证,项目启动时出现异常 nacos.core.auth.caching.ena…

Java基础06:变量,常量,作用域(狂神说Java)

一.变量 有了static,即类变量,就可以不用new了可以直接调用,类变量之后再细讲 二.常量 三.变量的命名规范

权限(linux)

权限就是文件权限(linux万物皆文件) 本文主要涉及文件/文件夹权限 涉及指令: shell: kernal : linux内核 shell : 外壳 shell可以方便交互与操作 bash是一个具体的shell su su 切换用户 su -root 变…

AppInventor导入导出项目以及打包apk安装包

AppInventor导入导出项目以及打包apk安装包 1.概述 当项目开发好了之后,如果想将项目分享给其他人,或者导入其他人开发的项目怎么办那。 如果给其他人安装你的项目,如何安装那? 2.自带导出和导入功能 导出项目,在P…

【网络】tcp_socket

tcp_socket 一、tcp_server与udp_server一样的部分二、listen接口(监听)三、accept接收套接字1、为什么还要多一个套接字(明明已经有了个socket套接字文件了,为什么要多一个accept套接字文件?)2、底层拿到新…

基于VMware(虚拟机) 创建 Ubunton 24.04

目录 1. 设置网络 1. 在安装ubuntu时设置网络 2.在配置文件中修改 2.设置 root 密码 3. 防火墙设置 1 安装防火墙 2 开启和关闭防火墙 3 开放端口和服务规则 4 关闭端口和删除服务规则 5 查看防火墙状态 4. 换源 1. 在创建的时切换源 2.修改源配置 1、Ubuntu24.04 …

MBR60200PT-ASEMI无人机专用MBR60200PT

编辑:ll MBR60200PT-ASEMI无人机专用MBR60200PT 型号:MBR60200PT 品牌:ASEMI 封装:TO-247 批号:最新 恢复时间:35ns 最大平均正向电流(IF):60A 最大循环峰值反向…

学习华为IPD流程黑话2.0

目录 1、内容简介 2、概念六:管道管理 3、概念七:业务计划 4、概念八:IPMT 的投资活动 5、概念九:BETA、ESS、ESP 作者简介 1、内容简介 学习任何新事物都是从概念开始的。 以我个人最近遇到的一个事为例: 前…

TCP三次握手和四次挥手的理解

三次握手 第一次握手: 客户端发出 请求报文其中SYN应1,选择一个序列号x 第二次握手: 服务端接收到之后回复 确认报文,其中SYN应1,ACK1,确认号是x1,同时为自己初始化序列号y 第三次握手&…

uboot的mmc partconf命令

文章目录 命令格式参数解释具体命令解释总结 mmc partconf 是一个用于配置 MMC (MultiMediaCard) 分区的 U-Boot 命令。具体来说,这个命令允许你设置或读取 MMC 卡的分区配置参数。让我们详细解释一下 mmc partconf 0 0 1 0 命令的含义。 命令格式 mmc partconf &…