別名錯誤

2016 年 11 月 14 日

當同一個記憶體位置透過多個參考存取時,就會發生別名。這通常是好事,但它經常以意想不到的方式發生,導致令人困惑的錯誤。

以下是這個錯誤的一個簡單範例。

Date retirementDate = new Date(Date.parse("Tue 1 Nov 2016"));

// this means we need a retirement party
Date partyDate = retirementDate;

// but that date is a Tuesday, let's party on the weekend
partyDate.setDate(5);

assertEquals(new Date(Date.parse("Sat 5 Nov 2016")), retirementDate);
// oops, now I have to work three more days :-(

這裡發生的事情是,當我們進行指定時,partyDate 變數會指定一個參考給退休資料所參考的同一個物件。如果我之後改變那個物件的內部(使用 setDate),那麼兩個變數都會更新,因為它們參考的是同一個東西。

雖然在那個範例中別名是個問題,但在其他情況下,這正是我所預期的。

Person me = new Person("Martin");
me.setPhoneNumber("1234");
Person articleAuthor = me;
me.setPhoneNumber("999");
assertEquals("999", articleAuthor.getPhoneNumber());

通常會想要這樣分享記錄,然後如果它改變了,所有參考都會改變。這就是為什麼思考參考物件很有用的原因,我們刻意分享它們 [1],以及我們不想要這種共享更新行為的值物件。避免值物件共享更新的好方法是讓值物件不可變。

當然,函式語言偏好一切不可變。因此,如果我們想要變更能被共享,我們需要將其視為例外,而不是規則。不可變性是一個方便的屬性,它讓建立各種錯誤變得更困難。但是,當事情確實需要改變時,不可變性可能會增加複雜性,所以這絕不是免費的早餐。

致謝

Graham Brooks 和 James Birnie 在我們內部郵件清單上的評論促使我寫這篇文章。

延伸閱讀

混用錯誤一詞已經存在一段時間了。它出現在 Eric Raymond 的 術語檔案 中,在 C 語言的背景下,原始記憶體存取使得它更加令人不快。

備註

1: Evans 分類 有實體的概念,我認為這是一種常見的參考物件形式。