当前位置: 首页 > news >正文

mit6.031 2023spring 软件构造 笔记 Specification

为什么需要规范

当程序失败时,很难确定错误在哪里。 代码中的精确规范可以让您将责任分担给代码片段,而不是人。

有助于使模块更易于理解,就像黑匣子外部的标签一样。

拥有规范可以让你了解模块的作用,而无需阅读代码。

在这里插入图片描述

规范的结构 Specification structure

  • 函数签名,给出名称、参数类型和返回类型
  • require 子句,描述对参数的其他限制
  • effects 子句,描述函数的返回值、异常和其他效果

TypeScript中的规范

一些语言(尤其是 Eiffel)将前置条件和后置条件作为语言的基本组成部分,作为运行时系统(甚至编译器)可以自动检查的表达式,以强制执行客户端和实现者之间的契约。
TypeScript 并没有走得这么远,但它的静态类型声明实际上是函数的前置条件和后置条件的一部分,这是由编译器自动检查和强制执行的部分。 契约的其余部分——我们不能写成类型的部分——必须在函数之前的注释中进行描述,并且通常依赖于人类来检查和保证它。

// 函数签名:明确规定了参数 a 和 b 必须是数字,返回值也必须是数字。
// TypeScript 编译器会确保:
// 1. 调用者传入的必须是两个 number 类型的数据。
// 2. 函数实现者必须返回一个 number 类型的数据。
// 如果违反这些,代码在编译阶段就会报错,无法正常运行。
function average(a: number, b: number): number {return (a + b) / 2;
}// 合法的调用(编译器通过)
let result: number = average(5, 10); // result 为 7.5// 非法的调用(编译器报错,无法运行)
// average("5", "10"); // 错误:类型“string”的参数不能赋给类型“number”的参数。

一个常见的约定,最初是为 Java 设计的,但现在也被 ts 和 js 使用,是在函数之前放置一个文档注释,其中:

  • 参数由@param子句描述
  • 结果由 @returns 子句描述

What a specification may talk about

函数的规范可以谈论函数的参数和返回值,但永远不应该谈论函数的局部变量或函数类的私有字段。 您应该考虑该实现对规范的读者不可见。
就客户端而言,它位于防火墙后面。

避免空值

空值既麻烦又不安全,以至于好的编程会试图避免它们。
当 ts 启用了严格的 null 检查时,静态类型检查器保证这一点:

let word: string = null;  // compile error when strict null checking is on
let words: Array<string> = [ null ]; // compile error when strict null checking is on

谷歌在其核心Java库Guava中针对null值的使用进行了讨论,该项目说明:

  1. null是”万恶之源“
  • “粗心使用 null 会导致各种惊人的bug”: 这是因为 null 本质上是一个“空值”或“无值”的表示。如果你试图在一个期望有实际对象的地方使用 null(例如,调用 null.toString()),程序会立即抛出 NullPointerException (NPE),导致崩溃。
  • “95%的集合本不应包含任何null值”: 这是一个非常重要的观察。在绝大多数业务逻辑中,我们往集合(如 List, Map, Set)里放的都是有意义的实际对象。允许 null 被放入集合,相当于在数据结构中埋下了一颗“地雷”,未来任何遍历或使用这个集合的代码都可能意外踩到它而崩溃。
  • “快速失败(Fail Fast*)比静默接受更有帮助”
    • “静默接受”: 指代码不做检查,允许 null 被存入集合或传递给方法。这很危险,因为错误不会在源头被发现,而是会传播到后续代码中,在某个不可预知的地方爆发,使得调试变得极其困难。
    • “快速失败”: 指立即报错。如果一个方法或集合明确规定不允许 null,那么一旦传入 null,它就应该立刻抛出异常(如 NullPointerExceptionIllegalArgumentException)。
    • 为什么“快速失败”更好? 因为它让错误在发生的地点就立刻暴露出来,而不是隐藏起来直到后续某个不确定的时刻。这极大地简化了调试过程,开发者能马上定位到是哪个调用者传入了非法的 null 值。
  1. null 令人不快的歧义性”: 这是 null 的另一个致命缺点。当你看到一个方法返回 null 时,你无法确切知道这个 null 到底想表达什么语义

    经典例子:Map.get(key)

    • 这个调用返回 null 可能有两种完全不同的含义:
      1. 键(key)不存在于映射(Map)中。 (这是一种“失败”或“未找到”的信号)
      2. 键(key)存在,但它对应的值(value)本身就是 null (这是一种“成功找到,但找到的值是 null”的信号)
    • 作为API的调用者,你根本无法区分这两种情况!你必须额外调用 map.containsKey(key) 来确认,这很麻烦,而且容易忘记。

    “Null可以意味着失败、成功,几乎任何事”: 在不同的API设计里,null 被用来代表各种意思:“未初始化”、“错误”、“空结果”、“未找到”等等。没有统一的规范,导致代码可读性急剧下降。

    “使用 null 以外的其他东西可以使你的意图清晰”: 这是Guava(以及现代Java、其他语言)推荐的解决方案。使用明确、无歧义的表达方式来替代 null

    • Guava/Java 8+ 的解决方案:Optional<T>
      • 如果一个方法可能没有返回值,它应该声明为返回 Optional<String>,而不是 String
      • Optional.of("Hello") 表示肯定有一个值(“Hello”)。
      • Optional.absent() (Guava) 或 Optional.empty() (Java 8) 表示肯定没有值
      • 这样,调用者看到返回类型就知道可能需要处理空值的情况,并且 null 不再被用作“无值”的信号,歧义性就消失了。
http://www.dtcms.com/a/363412.html

相关文章:

  • 【XR硬件系列】Apple Vision Pro 完全解读:苹果为我们定义了怎样的 “空间计算” 未来?
  • springboot项目使用websocket功能,使用了nginx反向代理后连接失败问题解决
  • 集采与反腐双重压力下,医药销售的破局之道:从资源依赖到价值重构
  • DASK shuffle任务图分析
  • 阅读Linux 4.0内核RMAP机制的代码,画出父子进程之间VMA、AVC、anon_vma和page等数据结构之间的关系图。
  • 解密llama.cpp CUDA后端:512 token大模型批处理的异步流水线架构
  • 【llama.cpp】qwen2_vl_surgery.py详解
  • 应用层:HTTP/HTTPS协议
  • 2025年- H109-Lc1493. 删掉一个元素以后全为 1 的最长子数组(双指针)--Java版
  • 软件测试小结(1)
  • 【完整源码+数据集+部署教程】粘土石实例分割系统源码和数据集:改进yolo11-LVMB
  • MVP架构深层剖析-从六大设计原则的实现角度到用依赖注入深度解耦
  • 安全芯片助力游戏设备防抄板
  • 「Windows自动化之王:PowerShell极简美学」​
  • 微信小程序 navigateTo 栈超过多层后会失效
  • 【开题答辩全过程】以 基于微信小程序的体育场馆预约管理系统为例,包含答辩的问题和答案
  • 【Git】一文详解Git Rebase和Merge区别 看完必会
  • 整体认识K8s之PriorityClass优先级调度/HPA自动扩缩容机制
  • golang 依赖管理
  • 网络技术名词 CDN NAT GA DNS
  • 深度学习篇---Pytorch常用优化器
  • 力扣72:编辑距离
  • 用 PyTorch 实现食品图像分类:从数据预处理到模型训练与预测
  • mayfly-go:web 版 linux、数据库等管理平台
  • 码农必备!本地调试神器act,GitHub Actions最佳拍档
  • C++ 条件变量,互斥锁
  • vue飞自在酒店管理系统(代码+数据库+LW)
  • 第十七讲:编译链接与函数栈帧
  • Python图像处理模块介绍
  • Linux 文本处理四剑客:cut, sort, uniq, tr