首先
这篇文章是我博客文章的转载。
https://yoshitaro-yoyo.hatenablog.com/entry/2022/08/07/On_Defensive_and_Contract_Programming
这篇文章是增田先生在推特上介绍的。
如果其他人能读到它并给我各种意见,我会很高兴,所以我也在这里发布了它。
让我们从主要部分开始。
1. 怀疑 vs 相互信任,防御 vs 契约
在稍后描述的学习课程中,我有一个关于防御性编程和合同编程的问题,我试图更深入地挖掘学习课程中解释的内容。
一切都是一个学习故事,但本文重点介绍“类设计风格”的两个选项
- 事务脚本方法
- 领域模型方法
出现的将是“防御性编程和合同编程”。
- 事务脚本方法是“防御性编程”
- 领域模型方法是“合约编程”
增田先生表示,为了实现类设计的可变性,应该选择“领域模型法”。
在这篇文章中,在实现阶段,每个类在哪一层之后呢?应该进行防御性编程还是契约式编程取决于具体情况。我要谈谈它。
后面是哪一层?这将外部和内部分开。我认为外部对应的是3层架构中的表现层。它是面向Web API的入口和出口,用户表单等请求的接收,输入和输出等的一层,也是一个防御层。内部定义为防御层内部的层。
我使用以下材料对三层架构进行了粗略的 LT。说明服务器的三层结构和服务器内部的三层结构。供你参考
❐ 结论
- 外部饰面层应该是防御程序
- 之后的层可以接受合同编程(DDD 中的合同编程,取决于设计和领域)
防御性编程和合约编程的根本区别在于
我认为关键是分别基于“怀疑”和“互信”的思想。在实现阶段,不是好坏的问题,而是心智模型的不同,我想知道上面的心智模型应该应用到的系统中的层是否不同。此外,我认为还需要考虑健壮性、准确性、性能、长期可维护性等,但我不会在本文中涉及。
为什么?
我想用断言(assertion)和异常(exception)的故事来解释。
2. 增田彻研究组“设计理念与方法”
以下博客介绍了学习课程的详细信息。
在本次工作坊中,我们大致谈到了以下三点。
- 追求好的设计
- 选择设计风格
- 获得设计技能
(1) 以良好的设计为目标
- ETC(Easy To Change)原则,好的设计比坏的设计更容易改变
- 设计使更改变得容易和安全,这就是开发人员应该做的
(2)设计风格的选择(怎么做)
- 类设计风格
→采用领域模型方法代替事务脚本方法
- 餐桌设计风格
→ 构建不可变的数据模型
- 如何开发
→ 首先,创建一个描述业务逻辑的类
→ 创建一个使用该类的应用程序类
→ 快速构建并定期重构
③ 掌握设计技能
- 启发式大脑数据库
→增加经验法则(扩展数据库)
- 超快的大脑搜索
→提高搜索速度(大脑中的缓存分区)
- 手头任务和经验规则的高级匹配(吸收上下文差异)
→ 适应上下文差异(提高模式匹配能力)
3.类设计十字路口
在增田先生的演讲中,以下两种选择将是类设计风格的分叉。
- 交易脚本方法(老趋势)
- 领域模型法(当前趋势)
“如果您正在考虑使更改变得简单和安全,我认为域模型方法是一个非常强大的选择......”
4.交易脚本方式(老趋势)
- 单独的数据和功能类
- 核心关注点是输入/输出处理(屏幕/DB/Web API)
- 使用原始类型编程
- 防御性编程
虽然结构简单易懂,但主要目的是事务处理。它并不真正关心它的含义或它在域上下文中的用途,它的目的是实现应用程序用例。
确定画面、DB、Web API 等外部规格并使其发挥作用。
原始类型是编程语言提供的基本数据类型,与域模型方法中采用的原始类型不同。❐ 防御性编程
永远不要相信输入数据或作为参数传递的数据。
接收方彻底检查,不接受非法数据。❐事务脚本方法的问题
如何处理计算决策逻辑的复杂性?但是,事务脚本方法和领域模型方法有很大区别
- 计算决策逻辑在各处输入/输出操作中被分割和重复。
- 业务规则已加密
- 很难找到计算决策逻辑写在哪里
- 稍微改变计算决策逻辑变得繁琐和危险
→修改或更改时的碎片和重复在哪里?你在找它吗?恐怕不能彻底测试。
目前还不清楚他们应该在多大程度上进行测试。发布后不久就发现了错误。5.领域模型法(当前趋势)
- 将逻辑和数据封装在一个类中
- 主要关注的是计算决策逻辑(业务规则)
- 将应用程序处理的值定义为唯一类型
- 合同编程
关键是只有计算决策逻辑与输入/输出分离。
只指定逻辑计算逻辑和业务规则,作为程序完成,将相关数据封装在一个类中。
因此,使用相同数据的计算决策逻辑不会在这里和那里分散/重复。❐ 将逻辑和数据封装在一个类中
数据和操作数据的逻辑被封装在一个类中作为单独的模块。
用类名(类型名)和方法名表达业务约定
- 为您的业务中处理的每种类型的数据准备一个类
- 识别并命名每种数据类型的必要计算决策(类名/方法
姓名) - 合同编程:使用类型断言前置条件(参数类型)和后置条件(返回类型)
❐ 主要关注的是计算决策逻辑(业务规则)
封装逻辑的含义是核心关注点是计算决策逻辑。计算决策逻辑是程序员的角度,从业务的角度看,是一个业务规则。承诺促进业务。
封装数据和逻辑的方法,计算决策逻辑的核心关注点,是软件复杂性的来源
❐ 将应用程序处理的值定义为唯一类型
金额、数量、期限、客户类别、运输产品类别等被定义为唯一类型。
❐ 合约编程
防御性编程和对立的方法
作为前提条件,参数类型是严格定义的,前提条件承诺只要对象与参数类型一起传递,就会返回某个值。你承诺什么?它返回一个指定类型的对象,或者
基本上,诸如不返回异常和不返回 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)从断言的描述中可以看出,开发者根据以下假设创建了以下程序。
- “年龄只能大于或等于0。”
- “名称不能为空。”
❐异常
在可能未遵守前置条件和后置条件时使用。
即使调用者满足前提条件,被调用者的响应或请求也可能不满足。
或者如果你发布一个外部 API,你不知道调用者是否会遵守规则。完全可以预料到会有无效值和空值进来。
如果在这种状态下使用断言,则关系不再是满足前提条件的契约。因为有合同,后置条件是有保障的。断言也是一种规则,如果不遵守规则,就会发生错误,但它只是返回错误,不负责后续处理。如果这种情况发生在强调健壮性的嵌入式系统和应用程序中,则用户体验可能会非常低。
与断言不同,异常必须准备好假设它们是可能的。定义异常条件、合适的异常类型和消息,连接调用栈,发送给调用者。
在虚假处理的情况下抛出异常。与断言相比,认知负荷更高。因此,从抛出异常时的逻辑中破译出什么样的规范比断言更难。
断言是粗制滥造的,异常是高级错误处理
9. 防御性编程不做任何假设
开车可能。
无论你如何解释前置条件,都可能有同事忽略它们并进入它们。假设可能有,检查并抛出异常。防御性编程是怀疑有人无论如何都会插入字符串的编程。大局中发生了什么?
防御性编程是局部优化。可能没有人遵守规则,但至少他们在捍卫自己写的东西。
当您查看整个系统中发生的事情时,每个人在每一层都有相似的防御。因为没有人相信任何人。10. 相互信任是合约编程的前提
另一方面,契约式设计旨在全面优化。
这个想法是决定交换规则,而不是在存在相互保护的前提时进行辩护。结果,减少了防御性编程,减少了代码,提高了可维护性,减少了层和实现之间的重复,并且可以在整体系统可维护性和内部质量方面创建理想的状态。
在接受用户输入的层编写防御性编程,这是最不可信输入的层,之后的层不需要防御性编程。
你不知道面对外面的地方会发生什么,所以你别无选择,只能保卫它。
但是,越过那道防线后,我相信我的同事,因为这是他们开发的世界。如果你不相信你遵守规则的假设,并且你进行防御性编程,你最终会得到更多的代码重复、更多的代码和更多的复杂性。契约式设计的概念是用规则来捍卫,相信规则被遵循,并提高整体的可维护性。合同设计和表示植根于相互信任
辩护和例外源于怀疑
在实施阶段,没有好坏之分,只是心智模型不同,系统中应用上述心智模型的层次也不同。
我已经接受了理解。
我是一年级的小鸡,所以如果您有任何意见或建议,如果您能发表评论,我会很高兴。
欢呼雀跃
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308631560.html