HIT-SC-Chapter Five
HIT-SC-Chapter Five
Designing Specification
1 Functions & methods in programming languages
- Method
public static void threeLines(){
STATEMENTS;
}
public static void main(String[] arguments){
System.out.println("Line 1");
}
-
Parameters
- 参数类型的匹配在静态类型检查阶段完成
-
Return Values
- 返回类型的匹配,也在静态类型检查阶段完成
-
Variable Scope
-
-
Building Blocks:
- 方法可以单独开发、测试和重用
- 方法的用户不需要知道它是如何工作的——这被称为“抽象” s
2 Specification: Programming for communication
(1) Documenting in programming
-
Java API:
- 类层次结构和实现的接口列表。
- 直接子类,对于接口,实现类。
- 类的描述
- 构造函数的总结
- 方法摘要列出了我们可以调用的所有方法
- 每个方法和构造函数的详细描述
- 我们可以看到返回类型、方法名和参数。我们也看到了例外。目前,这通常意味着该方法可能会遇到错误。
- 完整描述
- 方法参数的描述。
- 返回值的描述
-
记录假设
- 变量的数据类型定义
- Java实际上会在编译时检查这个假设,并确保程序中没有违反这个假设的地方。
- final关键字定义了设计决策:“不可改变”
- Java也会进行静态检查
- 变量定义时即意味着程序员的假定,这既是一种抉择
- 代码本身就蕴含着你的设计决策,但是远远不够
- 变量的数据类型定义
-
为了交流而编程
- 因为编程中充满了它们,如果我们不把它们写下来,我们就不会记住它们,而其他需要阅读或修改我们程序的人也不会知道它们。他们得自己猜。
- 代码种的“设计决策”:给编译器读
- 注释形式的“设计决策”:给自己和别人读
(2) Specification and Contract (of a method)
-
Spec or called Contract
-
the 关键
-
没规约,没法写程序;即使写出来,也不知道对错;
-
acts as a contract 契约:
- 实现者负责满足契约,而使用该方法的客户端可以依赖契约。
- 程序和客户端达成的一致
- 说明方法和调用者的职责
-
Why
- s
- ……
- 包含
- Method signature (type specifications) 输入/输出的数据类型
- Functionality and correctness expectations 功能和正确性
- Performance expectations 性能
- What the method does, not how it does it 只讲“能做什么”,不讲 “怎么实现”
- 接口(API),而不是实现
(3) Behavioral equivalence
- 为了确定行为等价性,问题是我们是否可以用一种实现替代另一种实现
- The notion of equivalence is in the eye of the client (站在客户端视角看行为等价性).
- 这两个函数符合这个规约,故它们等价。
- 单纯的看实现代码,并不足以判定不同的implmentation是否是“行为等价的”
- 需要根据代码的spec(开发者与client之间形成的contract)判定行为等价性
- 在编写代码之前,需要弄清楚spec如何协商形成、如何撰写
(4) Specification structure: pre-condition and post-condition
- spec’s consistions:
- Precondition , indicated by the keyword requires
- Postcondition , indicated by the keyword effects
- Exceptional behavior: what it does if precondition violated 意外行为
- 前置条件:对客户端的约束,在使用方法时必须满足的条件
- 后置条件:对开发者的约束,方法结束时必须满足的条件
- 契约:如果前置条件满足了,后置条件必须满足。
- 前置条件不满足,则方法可以做任何事情
- 当我们的前提条件被违反时,客户端就有漏洞。我们可以通过快速失败使错误更容易被发现和修复,尽管我们没有义务这样做。
(5) spec in Java
-
静态类型声明是一种规约,可据此进行静态类型检查static checking。
-
方法前的注释也是一种规约,但需人工判定其是否满足
-
参数由@param子句描述,结果由@return和@throws子句描述。
-
Put the preconditions into @param where possible, and postconditions into @return and @throws.
-
-
-
每一个参数,一个
@param
语句- @param 参数名 解释参数的语句
-
有返回值
- @return 解释返回值的语句
- 无变量名
-
这个练习表明,在规范中,可能会在@param和@return子句之外的地方找到部分前置条件和后置条件,所以仔细阅读是很重要的。
(6) What a specification may talk about
- 只讨论方法的参数值和返回值,不讨论方法的类的局部变量或私有域
- 您应该考虑实现对规范的读者是不可见的
- 在Java中,您的规范的读者通常无法获得该方法的源代码,因为Javadoc工具从您的代码中提取规范注释并将它们呈现为HTML。
(7) Spec for mutating methods
-
除非后置条件里声明过,否则方法内部不应该改变输入参数
-
应尽量遵循此规则,尽量不设计mutating的spec,否则就容易引发bugs。
-
程序员之间应达成的默契:除非spec必须如此,否则不应修改输入参数
-
除非另有说明,否则不允许变异
-
尽量避免使用mutable的对象
-
可变对象使得合约变得复杂
- 别名情况
-
可变对象使简单的契约变得复杂
- 可变的全局变量
- 客户是否有义务不修改取回的物品?
- 实现者是否有义务不保留它返回的对象?
-
开发者可以在其方法内部得到这个引用并改变值
3 Designing specifications
(1) Classifying specifications
-
规约的确定性:
- 一组输入
- 一个输入
-
规约的陈述性:
-
规约的强度:
-
比较规约
-
规约的强度:
-
前提条件更弱
-
后置条件更强
-
则更强
-
那么满足更强的规约的实现也可以用来满足更弱的规约的实现,并且在程序中替换是安全的
-
对他人宽容,对自己严格
-
-
-
-
-
-
会有不可比的情况出现
-
-
当规范被加强时,
-
越强的规约,意味着implementor的自由度和责任越重,而client的责任越轻。
- Deterministic vs. underdetermined specs
- deterministic:当呈现一个满足前提条件的状态时,结果是完全确定的。
- 只能有一个返回值和一个最终状态。
- 不存在任何有效输入对应多个有效输出。
- 确定的规约:给定一个满足precondition的输入,其输出是唯一的、明确的。
- Under-deterministic: 同一个输入,可能有多个输出
- 同一个输入,多次执行时得到的输出可能不同
- 在spec中提供了一个由实现者在实现时做出的选择。
- 一个不确定的spec通常由一个完全确定的实现来实现。
- deterministic:当呈现一个满足前提条件的状态时,结果是完全确定的。
- Declarative vs. operational specs
- 操作式规约:
- 例如伪代码
- 规范给出了该方法执行的一系列步骤;伪代码描述是可操作的。
- 声明式规约:
- 没有内部实现的描述
- 只有“初-终”状态
- 对于客户和代码维护者来说,这是最清晰的。
- 声明性规范更可取。
- 它们通常更短,更容易理解,最重要的是,不会无意中暴露客户机可能依赖的实现细节。
- Why operational spec. exists?
- 程序员使用规范为维护人员解释实现。
- 必要时,在实现体中使用注释,而不是在规范注释中。
- 操作式规约:
(2) Diagramming specifications
-
这个空间中的每个点代表一个方法实现。
-
一个规约在所有可能实现的空间中定义了一个区域。
-
某个具体实现,若满足规约,则落在其范围内;否则,在其之外。
- 程序员可以在规约的范围内自由选择实现方式
- 客户端无需了解具体使用了哪个实现
- 他们必须尊重规范,但也有自由改变他们使用实现的方式,而不必担心它会突然中断。
-
规约更强,区域更小
- 较弱的spec定义较大的区域。
- 更强的后置条件意味着实现的自由度更低了,在图中的面积更小
- 更弱的前置条件意味着实现时要处理更多的可能输入,实现的自由度低了,面积更小
-
(3) Designing good specifications
-
Quality:
- 是简洁的
- 清晰
- 结构良好的
-
guidelines:
-
The specification should be coherent (内聚的)
- 描述的功能一个单一、简单、容易理解
- 若做了多件事,就要分离成多个方法
-
The results of a call should be informative
- 不能让客户端产生理解的歧义
-
The specification should be strong enough
- 在指定特殊情况时,我们必须格外小心,以确保它们不会破坏原本有用的方法
- 太弱的spec,client不放心、不敢用 (因为没有给出足够的承诺)。开发者应尽可能考虑各种特殊情况,在post-condition给出处理措施。
-
The specification should also be weak enough
-
太强的spec,在很多特殊情况下难以达到,给开发者增加了实现的难度(client当然非常高兴)。
-
相反,规范应该说明一些更弱的内容:它试图打开一个文件,如果成功,该文件具有某些属性。
-
The specification should use abstract types
- 用抽象类型编写规范给了客户端和实现者更多的自由。
- 在Java中,这通常意味着使用接口类型,如Map或Reader,而不是特定的实现类型,如HashMap或FileReader。
-
先决条件和后置条件?
- 是否使用先决条件,如果是,方法代码是否应该在继续之前尝试确保先决条件已经满足?
- 不写Precondition,就要在代码内部check;若代价太大,在规约里加入precondition,
把责任交给client。 - 客户端不喜欢太强的 precondition,不满足precondition的输入会导致失败。
- 惯用做法是:
- 不限定太强的precondition,而是在postcondition中抛出异常:输入不合法
- 尽可能在错误的根源处fail,避免其大规模扩散
- 衡量代价的标准:检查参数合法性的代价
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理