MIT 6.031 Software Construction 学习笔记:(三) Mutability & Immutability

谷梁博易
2023-12-01

这节主要是讲 可变对象给编程带来的危害,所谓不可变对象,就是整个生命周期中不可变的对象(废话), e.g. : String

具体来说参见 Basic Java when we discussed snapshot diagrams

Risks of mutation

risk1:passing mutable values

看以下两段代码:

/** @return the sum of the numbers in the list */
public static int sum(List<Integer> list) {
    int sum = 0;
    for (int x : list)
        sum += x;
    return sum;
}

/** @return the sum of the absolute values of the numbers in the list */
public static int sumAbsolute(List<Integer> list) {
    // let's reuse sum(), because DRY, so first we take absolute values
    for (int i = 0; i < list.size(); ++i)
        list.set(i, Math.abs(list.get(i)));
    return sum(list);
}

// meanwhile, somewhere else in the code...
public static void main(String[] args) {
    // ...
    List<Integer> myData = Arrays.asList(-5, -3, -2);
    System.out.println(sumAbsolute(myData));
    System.out.println(sum(myData));
}
  • 可变性带来了一个潜藏的bug(passing mutable objects around is a latent bug)
  • 可读性非常不好,作为读者,见到main方法的第一印象应该是sumAbs()是求绝对值,而sum是求和,而这导致了一个非常深的bug
  • 破坏了 fail-fast 原则, 往往很久才能定位真正的bug

Safe from bugs? In this example, it’s easy to blame the implementer of sum­Absolute() for going beyond what its spec allowed. But really, passing mutable objects around is a latent bug. It’s just waiting for some programmer to inadvertently mutate that list, often with very good intentions like reuse or performance, but resulting in a bug that may be very hard to track down.

Easy to understand? When reading main(), what would you assume about sum() and sum­Absolute()? Is it clearly visible to the reader that myData gets changed by one of them?

那么怎么解决呢?

至少可以做到两点,

  • 尽量在不可变的对象前加 final修饰
  • 会改变参数的时候,在规范(specfication)中说明

risk2: returning mutable values

先看下面一段代码:

/** @return the first day of spring this year */
public static Date startOfSpring() {
    if (groundhogAnswer == null) groundhogAnswer = askGroundhog();
    return groundhogAnswer;
}
private static Date groundhogAnswer = null;

这段代码在求春天的第一天的时候加了一个 cache, 并且return 了一个可变对象。

如果有如下的client 做以下调用:

// somewhere else in the code...
public static void partyPlanning() {
    // let's have a party one month after spring starts!
    Date partyDate = startOfSpring();
    partyDate.setMonth(partyDate.getMonth() + 1);
    // ... uh-oh. what just happened?
}

那么很显然以后再次调用 startOfSpring原来的静态私有变量 groundhogAnswer就被抛出了。

至少有两种解决方案可解决这个问题:

  1. 不用mutabledate而是用imutable 的替代物: package java.time: LocalDateTime, Instant,
  2. defensive copying pattern 也就是说返回的时候返回 groudAns的副本
    return new Date(groundhogAnswer.getTime());

不过第二种方法虽然解决的问题,可是在大多数情况下我们都只需要一个共享的 groudAns因此会带来一些性能上的问题。

Aliasing is what makes mutable types risky

重点说一些这个可变对象多出引用的问题,如果可变对象仅在一个局部被一个变量引用,那么用可变对象肯定是没有什么危害的,可是大多数情况下,可变对象被多出引用,这样就导致了可变对象引入的bug

Useful immutable types

Summary

The key design principle here is immutability: using immutable objects and unreassignable variables as much as possible. Let’s review how immutability helps with the main goals of this course:

  • Safe from bugs. Immutable objects aren’t susceptible to bugs caused by aliasing. Unreassignable variables always point to the same object.

  • Easy to understand. Because an immutable object or unreassignable variable always means the same thing, it’s simpler for a reader of the code to reason about — they don’t have to trace through all the code to find all the places where the object or variable might be changed, because it can’t be changed.

  • Ready for change. If an object or a variable can’t be changed at runtime, then code that depends on that object or variable won’t have to be revised when the program changes.

关于mutable对象带来的更多危害,可见下面的原文:

reference

Reading 8: Mutability & Immutability

 类似资料: