规格说明(Specifications)

1.为什么需要规格说明?

(1)许多程序中的严重错误是由于代码之间接口的行为误解引起的。虽然每个程序员心中都有规格说明,但并非所有程序员都将其写下来。这导致团队中的不同程序员对同一个接口有不同的理解。

(2)编写明确的规格说明有助于确定错误的来源,并避免解决问题时的困惑。

(3)规格说明对方法的使用者有利,因为它们可以省去阅读代码的麻烦。规格说明对方法的实现者也有好处,因为它们赋予实现者改变实现而不需要通知使用者的自由。

 (1)行为等价(Behavioral Equivalence)

考虑以下两个方法:

static int findFirst(int[] arr, int val) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == val) return i;
    }
    return arr.length;
}

static int findLast(int[] arr, int val) {
    for (int i = arr.length - 1; i >= 0; i--) {
        if (arr[i] == val) return i;
    }
    return -1;
}
规格说明的结构

方法的规范包含几个部分:

  • 前置条件(precondition):由关键字requires指示
  • 后置条件(postcondition):由关键字effects指示

前置条件是对客户端的要求,描述方法调用时的状态。

后置条件是对方法实现者的要求,描述方法完成时的状态。

在Java中,我们使用Javadoc注释来编写规范。

例如:

/**
 * 找到数组中指定值的位置。
 * @param arr 要搜索的数组,要求 val 在 arr 中恰好出现一次
 * @param val 要搜索的值
 * @return 数组中值为 val 的索引 i
 */
static int find(int[] arr, int val) {
    // 方法实现
}

(2)Null引用(Null References)

在Java中,对象和数组的引用可以是null,这意味着引用没有指向任何对象。null值会导致运行时错误,因此在6.005课程中,不允许参数和返回值为null

避免null的建议:

  • 使用非null注解:例如@NonNull
  • 在方法规范中明确说明null的使用
static boolean addAll(@NonNull List<T> list1, @NonNull List<T> list2)

(3)变异方法的规范(Specifications for Mutating Methods)

描述会改变对象状态的方法的规范:

import java.util.List;

/**
 * 将 list2 中的所有元素添加到 list1 的末尾。
 * 
 * @param list1 要添加元素的列表
 * @param list2 要从中添加元素的列表
 * @param <T> 列表中元素的类型
 * @return 如果调用该方法后 list1 发生了变化,则返回 true
 * @throws IllegalArgumentException 如果 list1 和 list2 是同一个对象
 * 
 * 前置条件(Requires):
 *  - list1 != list2
 * 
 * 修改效果(Modifies):
 *  - list1
 * 
 * 后置条件(Ensures):
 *  - list1 包含它最初包含的所有元素,顺序不变
 *  - list1 包含 list2 中的所有元素,顺序与 list2 中一致
 *  - 当且仅当 list2 不为空时,方法返回 true
 */
public static <T> boolean addAll(List<T> list1, List<T> list2) {
    // 检查 list1 和 list2 是否是同一个对象
    if (list1 == list2) {
        throw new IllegalArgumentException("两个列表不能是同一个对象。");
    }
    
    // 记录 list1 的原始大小
    int originalSize = list1.size();
    
    // 将 list2 的所有元素添加到 list1 中
    boolean modified = list1.addAll(list2);
    
    // 返回 true 如果 list1 因调用而发生变化
    return modified;
}

2.异常处理(Exceptions)

(1)信令bug的异常(Signaling Bugs with Exceptions)

在Java编程中,常见的异常有ArrayIndexOutOfBoundsException(数组索引超出有效范围时抛出)和NullPointerException(尝试对null对象引用调用方法时抛出)。这些异常通常表示代码中存在错误,Java在抛出异常时显示的信息可以帮助您查找并修复这些错误。

public static void main(String[] args) {
    int[] array = {1, 2, 3};
    System.out.println(array[5]); // 抛出ArrayIndexOutOfBoundsException
}
(2)特殊结果的异常(Exceptions for Special Results)

处理特殊结果的一种常见方法是返回特殊值,例如在查找操作中返回-1null

但使用异常可以让代码更简洁,减少错误。

  • try 块中的代码抛出 NotFoundException 异常时,Java 运行时环境会搜索与该异常类型匹配的 catch 块。
  • catch (NotFoundException nfe) 表示捕获 NotFoundException 类型的异常,并将其赋值给变量 nfe
class BirthdayBook {
    public LocalDate lookup(String name) throws NotFoundException {
        // 假设找不到名字时抛出异常
        throw new NotFoundException();
    }
}

public static void main(String[] args) {
    BirthdayBook birthdays = new BirthdayBook();
    try {
        LocalDate birthdate = birthdays.lookup("Alyssa");
    } catch (NotFoundException nfe) {
        System.out.println("未找到生日信息");
    }
}
 (3)已检查和未检查的异常(Checked and Unchecked Exceptions)
  • 已检查异常用于表示预期的、可以合理恢复的异常情况。编译器强制要求程序员在方法签名中声明这些异常,并且在调用该方法时处理这些异常。典型的已检查异常包括IOExceptionSQLException等。

    示例:文件读取操作 假设我们有一个读取文件内容的方法,该方法在文件不存在或无法读取时抛出IOException

  • import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class CheckedExceptionExample {
        // 声明可能抛出IOException
        public static String readFile(String filePath) throws IOException {
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            StringBuilder content = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
            reader.close();
            return content.toString();
        }
    
        public static void main(String[] args) {
            String filePath = "example.txt";
            try {
                String content = readFile(filePath);
                System.out.println(content);
            } catch (IOException e) {
                System.out.println("捕获到IOException: " + e.getMessage());
            }
        }
    }
    

  • 未检查异常(Unchecked Exception)

    未检查异常用于表示程序中的编程错误或不可恢复的错误。这些异常不需要在方法签名中声明,也不需要在调用时显式处理。典型的未检查异常包括NullPointerExceptionArrayIndexOutOfBoundsException等。

  • 示例:数组访问操作 假设我们有一个访问数组元素的方法,该方法在访问越界时会抛出ArrayIndexOutOfBoundsException

  • getElement方法没有声明它可能抛出ArrayIndexOutOfBoundsException,但调用者可以选择处理这个异常。

  • public class UncheckedExceptionExample {
        public static int getElement(int[] array, int index) {
            return array[index]; // 可能抛出ArrayIndexOutOfBoundsException
        }
    
        public static void main(String[] args) {
            int[] array = {1, 2, 3};
            try {
                int element = getElement(array, 5);
                System.out.println(element);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("捕获到ArrayIndexOutOfBoundsException: " + e.getMessage());
            }
        }
    }
    
(4)异常设计注意事项(Exception Design Considerations)

(1) 不要滥用已检查异常

已检查异常要求调用者处理,有时会导致代码变得繁琐。如果预期异常很少发生,且调用者无法采取合理措施处理,可以考虑使用未检查异常。

public class Queue {
    private LinkedList<Integer> list = new LinkedList<>();

    public void enqueue(int item) {
        list.add(item);
    }

    public int dequeue() {
        if (list.isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        return list.removeFirst();
    }
}

(2) 使用自定义异常

在某些情况下,标准异常类可能无法准确描述问题。此时可以创建自定义异常,以更好地表达特定的错误情况。

public class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

(3.)提供有意义的异常信息

在抛出异常时,应提供有意义的错误消息,以帮助调试和理解问题。错误消息应描述发生了什么错误以及可能的原因。

 

public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public class User {
    private int age;

    public void setAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("年龄必须在0到150之间,输入的年龄为:" + age);
        }
        this.age = age;
    }
}
(5)滥用例外(Misuse of Exceptions)

使用异常来控制正常的程序流是一种不好的做法。这不仅会导致性能问题,还会使代码难以理解和维护。

// 错误示例:滥用异常控制循环
try {
    int[] array = {1, 2, 3};
    int i = 0;
    while (true) {
        System.out.println(array[i++]);
    }
} catch (ArrayIndexOutOfBoundsException e) {
    // 结束循环
}

// 正确示例
int[] array = {1, 2, 3};
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值