golang-nuts上有人提了一个问题[1],询问怎么样把减少两个类似数据结构和算法的重复代码。简而言之,有两个struct:QuickFindSet和QuickUnionSet,它们各有Count, IsConnected, Find, Union等方法。他发现这两个struct的一些函数的实现是一样的,因此他希望能消除这些重复代码。
// quick-find
type 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-union
type 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. )
最后,我的结语是:
凡事都有个度,怎么样避免过度重构呢?也许我们可以从重构的动机和结果来考虑一下,而不只是为了重构而重构。