0. 课程目标
概念
6.005课程的主要目标是学习如何编写具有以下属性的软件:
安全性
-
代码无Bug,防御性编程:
- 使用输入验证:验证所有输入的数据,防止注入攻击和非法输入。
- 错误处理:使用try-catch块捕获异常并处理错误,防止程序崩溃。
- 边界检查:检查数组和集合的边界,避免越界错误。
-
静态检查、动态检查:
- 静态检查工具:使用Lint工具(如ESLint, Pylint)和静态代码分析工具(如SonarQube)进行代码检查,发现潜在的错误和代码异味。
- 单元测试:编写单元测试(如JUnit, pytest),确保代码在各种情况下都能正确运行。
- 动态分析工具:使用动态分析工具(如Valgrind)检测运行时错误和内存泄漏。
易读性
-
代码易于理解和修改:
- 命名规范:使用有意义的变量名、函数名和类名,遵循命名约定(如camelCase, snake_case)。
- 代码风格一致性:使用代码格式化工具(如Prettier, Black)保持代码风格一致。
- 注释:在复杂或关键的代码块添加注释,解释代码逻辑和意图。
-
文档化假设、使用final关键字:
- 文档化假设:在代码中使用注释或文档工具(如Javadoc, Sphinx)说明代码的假设和前提条件。
- 使用final关键字:在Java中,对不希望被修改的变量、方法和类使用final关键字,增加代码的可读性和稳定性。
可修改性
-
代码易于更新和扩展:
- 模块化设计:将代码拆分为独立的模块或组件,减少耦合,增加代码的可复用性和可维护性。
- 接口和抽象类:使用接口和抽象类定义代码的契约,方便以后替换或扩展实现。
-
文档化假设、静态检查:
- 文档化假设:在代码中记录假设和设计决策,帮助后续的开发人员理解和维护代码。
- 静态检查工具:使用静态分析工具检查代码的结构和依赖,确保代码符合设计规范,容易扩展。
1. 数组和集合
数组是定长的类型序列,而列表是变长的类型序列。
// 使用列表
List<Integer> list = new ArrayList<Integer>();
list.add(n);
// 使用数组
int[] a = new int[100];
a[i] = n;
2. 迭代
使用for循环遍历数组或列表中的元素。
// 找到冰雹序列中的最大值
int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
3. 方法
在Java中,所有语句必须在方法中,每个方法必须在类中。
public class Hailstone {
public static List<Integer> hailstoneSequence(int n) {
List<Integer> list = new ArrayList<Integer>();
while (n != 1) {
list.add(n);
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
list.add(n);
return list;
}
}
4. 不变性与变化
不变性是指对象一旦创建,其状态就不能改变。final关键字用于创建不可变引用。
辩析:
- 在Java中,
final
可以用来修饰对象引用,表示该引用不可更改,但对象的内容仍然可以更改。 - 在C++中,
const
可以修饰指针,表示指针本身不可变,或指向的对象不可变,或者两者都不可变。
public static List<Integer> hailstoneSequence(final int n) {
final List<Integer> list = new ArrayList<Integer>();
// 生成序列的代码
}
5.文档化假设
记录代码中的假设,例如变量类型、方法参数要求等,有助于提高代码的可读性和可靠性。
/**
* 计算冰雹序列。
* @param n 序列起始数。假设n > 0。
* @return 以n开头并以1结尾的冰雹序列。
*/
public static List<Integer> hailstoneSequence(int n) {
// 方法实现
}
6.黑客派与工程派
黑客派编写大量代码而不测试,工程派逐步编写并测试代码,记录假设并进行防御性编程。
class BankAccount {
private double balance;
// 记录假设:initialBalance不能为负数
public BankAccount(double initialBalance) {
if (initialBalance < 0) {
throw new IllegalArgumentException("Initial balance cannot be negative");
}
this.balance = initialBalance;
}
// 防御性编程:deposit金额必须为正数
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
balance += amount;
}
// 防御性编程:withdraw金额必须为正数,且不能超过当前余额
public void withdraw(double amount) {
if (amount <= 0 || amount > balance) {
throw new IllegalArgumentException("Invalid withdrawal amount");
}
balance -= amount;
}
public double getBalance() {
return balance;
}
}
public class BankAccountTest {
public static void main(String[] args) {
// 测试初始余额
BankAccount account = new BankAccount(1000);
assert account.getBalance() == 1000;
// 测试存款
account.deposit(500);
assert account.getBalance() == 1500;
// 测试取款
account.withdraw(200);
assert account.getBalance() == 1300;
// 测试无效存款
try {
account.deposit(-100);
} catch (IllegalArgumentException e) {
assert e.getMessage().equals("Deposit amount must be positive");
}
// 测试无效取款
try {
account.withdraw(2000);
} catch (IllegalArgumentException e) {
assert e.getMessage().equals("Invalid withdrawal amount");
}
}
}
- 记录假设:在构造函数中记录并验证初始余额不能为负数。
- 防御性编程:在存款和取款方法中进行输入验证。
- 逐步测试:在主方法中逐步测试每个功能点,包括正常情况和异常情况。