這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
golang-nuts上有人提了一個問題[1],詢問怎麼樣把減少兩個類似資料結構和演算法的重複代碼。簡而言之,有兩個struct:QuickFindSet和QuickUnionSet,它們各有Count, IsConnected, Find, Union等方法。他發現這兩個struct的一些函數的實現是一樣的,因此他希望能消除這些重複代碼。
// quick-findtype QuickFindSet struct { numOfComponents uint items []uint } func NewSet(n uint) QuickFindSet { set := QuickFindSet{ numOfComponents: n, items: make([]uint, n) } for i, _ := range set.items { set.items[i] = uint(i) } return set } func (set *QuickFindSet) Count() uint { return set.numOfComponents } func (set *QuickFindSet) IsConnected (p, q uint) bool { return set.Find(p) == set.Find(q) } func (set *QuickFindSet) Find(p uint) uint { return set.items[p] } func (set *QuickFindSet) Union(p, q uint) { rootP := set.Find(p) rootQ := set.Find(q) if rootP == rootQ { return } for i, _ := range set.items { if set.items[i] == rootP { set.items[i] = rootQ } } set.numOfComponents-- } // weighted quick-uniontype QuickUnionSet struct { numOfComponents uint items []uint sizes []uint } func NewSet(n uint) QuickUnionSet { set := QuickUnionSet{ numOfComponents: n, items: make([]uint, n), sizes: make([]uint, n) } for i, _ := range set.items { set.items[i] = uint(i) set.sizes[i] = uint(1) } return set } func (set *QuickUnionSet) Count() uint { return set.numOfComponents } func (set *QuickUnionSet) IsConnected (p, q uint) bool { return set.Find(p) == set.Find(q) } func (set *QuickUnionSet) Find(p uint) uint { for p != set.items[p] { p = set.items[p] } return p } func (set *QuickUnionSet) Union(p, q uint) { rootP := set.Find(p) rootQ := set.Find(q) if rootP == rootQ { return } if set.sizes[rootP] < set.sizes[rootQ] { set.items[rootP] = rootQ set.sizes[rootQ] += set.sizes[rootP] } else { set.items[rootQ] = rootP set.sizes[rootP] += set.sizes[rootQ] } set.numOfComponents-- }
可以看到,QuickFindSet和QuickUnionSet的Count和IsConnected函數的實現是一樣的:
func (set *QuickFindSet) Count() uint { return set.numOfComponents}func (set *QuickFindSet) IsConnected (p, q uint) bool { return set.Find(p) == set.Find(q)}func (set *QuickUnionSet) Count() uint { return set.numOfComponents}func (set *QuickUnionSet) IsConnected (p, q uint) bool { return set.Find(p) == set.Find(q)}
作者說他編程的時候總是想把重複的代碼消除掉,在其他的語言裡可以用class或者宏來達到這個目的,他想知道在go語言裡怎麼樣做比較好。
事實上這裡還有一個問題:這裡需要重構來消除重複代碼嗎?或者說,什麼樣的情況下需要重構代碼?
我認為,以下條件同時滿足時才需要重構代碼:
代碼非常的混亂以至於很難閱讀
已有需求或者有潛在的需求需要修改代碼
而對比這裡的代碼:
這裡重複的代碼非常少,只有幾行代碼。代碼結構也比較清晰。
作為一個和商務邏輯關聯不大的基礎資料結構,變化的需求很小。
因此我的結論是:這裡的代碼不需要重構,不需要去消除那寥寥幾行的重複代碼。
也有人從另外一個角度提出意見:
減少這些演算法的重複代碼也意味著它們之間耦合更加緊密,如果以後需要修改其中的一個演算法,會影響到其他的演算法實現;而一定的重複代碼可以保持它們之間的獨立性。
Russ Cox也給出了類似的意見:
確實在其他語言裡可以使用帶有虛擬方法的抽象類別來消除這樣很小的代碼重複,但它也會把這兩個實際上只有很少共同點的演算法實現捆綁在一起。保留獨立代碼的好處遠遠超過減少一兩行重複代碼的便利。(It is true that (in other languages) one could use 'abstract classes with virtual methods' to eliminate this minor duplication, but it would also tie together two implementations that really have very little in common. The benefits of keeping separate things separate far outweighs the minor convenience of avoiding a duplicated line or two. )
最後,我的結語是:
凡事都有個度,怎麼樣避免過度重構呢?也許我們可以從重構的動機和結果來考慮一下,而不只是為了重構而重構。