別名錯誤
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 分類 有實體的概念,我認為這是一種常見的參考物件形式。