防御的プログラミングと契約プログラミング

首先

这篇文章是我博客文章的转载。
https://yoshitaro-yoyo.hatenablog.com/entry/2022/08/07/On_Defensive_and_Contract_Programming

这篇文章是增田先生在推特上介绍的。
如果其他人能读到它并给我各种意见,我会很高兴,所以我也在这里发布了它。

让我们从主要部分开始。

1. 怀疑 vs 相互信任,防御 vs 契约

在稍后描述的学习课程中,我有一个关于防御性编程和合同编程的问题,我试图更深入地挖掘学习课程中解释的内容。

一切都是一个学习故事,但本文重点介绍“类设计风格”的两个选项

  1. 事务脚本方法
  2. 领域模型方法

    出现的将是“防御性编程和合同编程”。

    • 事务脚本方法是“防御性编程”
    • 领域模型方法是“合约编程”

    增田先生表示,为了实现类设计的可变性,应该选择“领域模型法”。

    在这篇文章中,在实现阶段,每个类在哪一层之后呢?应该进行防御性编程还是契约式编程取决于具体情况。我要谈谈它。

    后面是哪一层?这将外部和内部分开。我认为外部对应的是3层架构中的表现层。它是面向Web API的入口和出口,用户表单等请求的接收,输入和输出等的一层,也是一个防御层。内部定义为防御层内部的层。

    我使用以下材料对三层架构进行了粗略的 LT。说明服务器的三层结构和服务器内部的三层结构。供你参考

    ❐ 结论

    • 外部饰面层应该是防御程序
    • 之后的层可以接受合同编程(DDD 中的合同编程,取决于设计和领域)

    防御性编程和合约编程的根本区别在于
    我认为关键是分别基于“怀疑”和“互信”的思想。

    在实现阶段,不是好坏的问题,而是心智模型的不同,我想知道上面的心智模型应该应用到的系统中的层是否不同。此外,我认为还需要考虑健壮性、准确性、性能、长期可维护性等,但我不会在本文中涉及。

    为什么?

    我想用断言(assertion)和异常(exception)的故事来解释。

    2. 增田彻研究组“设计理念与方法”

    以下博客介绍了学习课程的详细信息。

    在本次工作坊中,我们大致谈到了以下三点。

    1. 追求好的设计
    2. 选择设计风格
    3. 获得设计技能

      (1) 以良好的设计为目标

      • ETC(Easy To Change)原则,好的设计比坏的设计更容易改变
      • 设计使更改变得容易和安全,这就是开发人员应该做的

      (2)设计风格的选择(怎么做)

      • 类设计风格

      →采用领域模型方法代替事务脚本方法

      • 餐桌设计风格

      → 构建不可变的数据模型

      • 如何开发

      → 首先,创建一个描述业务逻辑的类

      → 创建一个使用该类的应用程序类

      → 快速构建并定期重构

      ③ 掌握设计技能

      • 启发式大脑数据库

      →增加经验法则(扩展数据库)

      • 超快的大脑搜索

      →提高搜索速度(大脑中的缓存分区)

      • 手头任务和经验规则的高级匹配(吸收上下文差异)

      → 适应上下文差异(提高模式匹配能力)

      3.类设计十字路口

      在增田先生的演讲中,以下两种选择将是类设计风格的分叉。

      1. 交易脚本方法(老趋势)
      2. 领域模型法(当前趋势)

        “如果您正在考虑使更改变得简单和安全,我认为域模型方法是一个非常强大的选择......”

        4.交易脚本方式(老趋势)

        1. 单独的数据和功能类
        2. 核心关注点是输入/输出处理(屏幕/DB/Web API)
        3. 使用原始类型编程
        4. 防御性编程

          虽然结构简单易懂,但主要目的是事务处理。它并不真正关心它的含义或它在域上下文中的用途,它的目的是实现应用程序用例。

          确定画面、DB、Web API 等外部规格并使其发挥作用。
          原始类型是编程语言提供的基本数据类型,与域模型方法中采用的原始类型不同。

          ❐ 防御性编程

          永远不要相信输入数据或作为参数传递的数据。
          接收方彻底检查,不接受非法数据。

          ❐事务脚本方法的问题

          如何处理计算决策逻辑的复杂性?但是,事务脚本方法和领域模型方法有很大区别

          • 计算决策逻辑在各处输入/输出操作中被分割和重复。
          • 业务规则已加密
          • 很难找到计算决策逻辑写在哪里
          • 稍微改变计算决策逻辑变得繁琐和危险

          →修改或更改时的碎片和重复在哪里?你在找它吗?恐怕不能彻底测试。
          目前还不清楚他们应该在多大程度上进行测试。发布后不久就发现了错误。

          5.领域模型法(当前趋势)

          1. 将逻辑和数据封装在一个类中
          2. 主要关注的是计算决策逻辑(业务规则)
          3. 将应用程序处理的值定义为唯一类型
          4. 合同编程

            关键是只有计算决策逻辑与输入/输出分离。
            只指定逻辑计算逻辑和业务规则,作为程序完成,将相关数据封装在一个类中。
            因此,使用相同数据的计算决策逻辑不会在这里和那里分散/重复。

            ❐ 将逻辑和数据封装在一个类中

            数据和操作数据的逻辑被封装在一个类中作为单独的模块。

            用类名(类型名)和方法名表达业务约定

            • 为您的业务中处理的每种类型的数据准备一个类
            • 识别并命名每种数据类型的必要计算决策(类名/方法
              姓名)
            • 合同编程:使用类型断言前置条件(参数类型)和后置条件(返回类型)

            ❐ 主要关注的是计算决策逻辑(业务规则)

            封装逻辑的含义是核心关注点是计算决策逻辑。计算决策逻辑是程序员的角度,从业务的角度看,是一个业务规则。承诺促进业务。

            封装数据和逻辑的方法,计算决策逻辑的核心关注点,是软件复杂性的来源

            ❐ 将应用程序处理的值定义为唯一类型

            金额、数量、期限、客户类别、运输产品类别等被定义为唯一类型。

            ❐ 合约编程

            防御性编程和对立的方法
            作为前提条件,参数类型是严格定义的,前提条件承诺只要对象与参数类型一起传递,就会返回某个值。

            你承诺什么?它返回一个指定类型的对象,或者
            基本上,诸如不返回异常和不返回 null 之类的承诺。

            如果主叫满足前置条件,被叫必须以后置条件为大前提。

            契约编程就是要摆脱防御性编程。

            6. 用类设计分离复杂性

            ❐ 业务规则类设计(领域层)

            • 决定业务活动(业务规则)
            • 查看值类型/除法定义
            • 以声明方式

            其他程序可以通过将条件分支的复杂性(例如 if 语句和 switch 语句)限制为描述域层业务规则的类并以声明方式编写它们来简化。如果领域层通过合约编程来保证这些复杂部分的结果,

            ❐ 业务动作类设计(应用层)

            在上述前提的情况下,实现应用层所谓的用例类功能的类是

            • 进行计算决策(使用域对象执行计算决策)
            • 执行记录/查看
            • 通知/请求执行

            7.这是学习小组的故事

            到目前为止,您应该能够看到防御性编程和契约式编程之间的一些区别。从那时起,

            防御性编程和合约编程

            这将是对差异的深入挖掘。

            8. 什么是按合同设计? - 断言和例外 -

            到目前为止,我们已经在某种程度上讨论了合约编程。

            契约式设计是 Bertrand Meyer 在《面向对象简介》一书中介绍的概念。
            契约式设计考虑并分析函数/方法与使用它们的代码之间的以下契约:

            如果你答应在先决条件满足的情况下给我打电话,我承诺最终实现后置条件作为回报。

            用 PHP7 编写健壮的代码 - 异常处理、断言编程、合同设计 / PHP Conference 2016
            防御的プログラミングと契約プログラミング

            在确定一个函数的规格时,明确和分析执行这个函数需要什么(前置条件)和返回什么样的结果(后置条件)。此外,通过将函数的前置条件和后置条件表示为基于契约的代码中的断言,更容易发现代码中的错误,从而实现高度可靠的软件开发。

            合同设计中的断言和例外都是由开发人员编写和提出的,以防止出现非法条件。

            • 违反先决条件证明调用者处于非法状态
            • 违反后置条件证明供应商存在非法状态

            不良状态基本上表明存在错误。不要抓虫子。它应该立即被丢弃。

            报价来源:在介绍异常之前

            防御的プログラミングと契約プログラミング

            ❐ 断言

            如果调用者满足参数类型和值的条件作为前置条件,结果可以作为后置条件返回给被调用者。

            当您认为“不可能”时使用断言。
            如果有你认为“不可能”的事情,用代码表达出来。不可能性是一个隐含的前提,在代码中明确表示可以让读者更容易理解代码的前提。
            这意味着从调用者传递的值不满足前提条件是“不可能的”。程序员的错误,如果可能的话。前置条件是调用者必须遵守的条件。

            因此,如果不可能输入或无效输入看起来“可能”,则不应使用它。
            不要用于正确的错误处理。

            是早期捕捉PG错误和描述错误的调试工具,是识别编程错误和通讯错误的工具。如果不可能发生,调用者负责,调用者错误的原因是缺乏共同理解,缺乏沟通,缺乏文档。

            在朋友内部的程序、自己编写的其他程序、自己组织内部的程序之间进行交互的情况下,被叫方和主叫方应该能够相互信任。因此,应订立合同。

            调用者必须遵守合同和先决条件。
            如果在年龄列中存在不应给出或接受负数的前提条件/约定,例如“请传递正整数”,则将负条件传递给此状态是编程错误。这是揭露此类事情的声明。

            断言是 Truethy 表达式。换句话说,输入是一个表达式,表示某事必须是或应该是。它的认知负荷相当低,可以阅读以了解调用者必须遵守的先决条件。

            从这个角度来看,断言就像对规范的描述。由于方法的前置条件和调用条件都写在了断言中,即使文档中没有描述方法的前置条件和调用条件,也可以在断言的描述中找到。

            句法

            *需要单独的激活设置。

            assert expression
            assert expression : message
            expression
                boolean値を返す条件式を指定する。ここに指定した条件式の評価がfalseだった場合、
                java.lang.AssertionError 例外がスローされる。
            message
                アサーションの詳細メッセージを指定する。ここに指定したメッセージが 
                java.lang.AssertionError クラスのコンストラクタに引数として渡される。
            

            例子

            public class Test {
              public static void main(String[] args) {
            
                Person p = new Person(-30, "Yoshi Taro");
            
                int age = p.getAge();
                String name = p.getName();
            
                assert name != null : "Nameがnullです)";
                assert age >= 0 : "Ageがマイナスの値です";
            
              }
            }
            

            断言违反异常

            Exception in thread "main" java.lang.AssertionError: Ageがマイナスの値です
                at Test.main(Test.java:8)
            

            从断言的描述中可以看出,开发者根据以下假设创建了以下程序。

            1. “年龄只能大于或等于0。”
            2. “名称不能为空。”

              ❐异常

              在可能未遵守前置条件和后置条件时使用。

              即使调用者满足前提条件,被调用者的响应或请求也可能不满足。

              或者如果你发布一个外部 API,你不知道调用者是否会遵守规则。完全可以预料到会有无效值和空值进来。

              如果在这种状态下使用断言,则关系不再是满足前提条件的契约。因为有合同,后置条件是有保障的。断言也是一种规则,如果不遵守规则,就会发生错误,但它只是返回错误,不负责后续处理。如果这种情况发生在强调健壮性的嵌入式系统和应用程序中,则用户体验可能会非常低。

              与断言不同,异常必须准备好假设它们是可能的。定义异常条件、合适的异常类型和消息,连接调用栈,发送给调用者。

              在虚假处理的情况下抛出异常。与断言相比,认知负荷更高。因此,从抛出异常时的逻辑中破译出什么样的规范比断言更难。

              断言是粗制滥造的,异常是高级错误处理

              9. 防御性编程不做任何假设

              开车可能。
              无论你如何解释前置条件,都可能有同事忽略它们并进入它们。假设可能有,检查并抛出异常。防御性编程是怀疑有人无论如何都会插入字符串的编程。

              大局中发生了什么?

              防御性编程是局部优化。可能没有人遵守规则,但至少他们在捍卫自己写的东西。
              当您查看整个系统中发生的事情时,每个人在每一层都有相似的防御。因为没有人相信任何人。

              10. 相互信任是合约编程的前提

              另一方面,契约式设计旨在全面优化。
              这个想法是决定交换规则,而不是在存在相互保护的前提时进行辩护。

              结果,减少了防御性编程,减少了代码,提高了可维护性,减少了层和实现之间的重复,并且可以在整体系统可维护性和内部质量方面创建理想的状态。

              在接受用户输入的层编写防御性编程,这是最不可信输入的层,之后的层不需要防御性编程。

              你不知道面对外面的地方会发生什么,所以你别无选择,只能保卫它。
              但是,越过那道防线后,我相信我的同事,因为这是他们开发的世界。如果你不相信你遵守规则的假设,并且你进行防御性编程,你最终会得到更多的代码重复、更多的代码和更多的复杂性。契约式设计的概念是用规则来捍卫,相信规则被遵循,并提高整体的可维护性。

              合同设计和表示植根于相互信任

              辩护和例外源于怀疑

              在实施阶段,没有好坏之分,只是心智模型不同,系统中应用上述心智模型的层次也不同。

              我已经接受了理解。

              我是一年级的小鸡,所以如果您有任何意见或建议,如果您能发表评论,我会很高兴。
              欢呼雀跃


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308631560.html

相关文章: