夜深了。
同僚們把這周寫的代碼送出了。我們在開發一個圖形編輯器畫布,已經實作了形狀調整功能,即通過拖拽形狀邊緣的搖桿來調整形狀(比如矩形和橢圓形)。
代碼可以正常運作。
但重複代碼有點多。每一種形狀(比如矩形和橢圓形)有不同的搖桿,往不同方向拖拽搖桿對形狀的位置和大小影響也不一樣。如果使用者同時按住 Shift 鍵,在改變大小的同時要保持比例不變。這裡涉及了很多數學運算。
代碼看起來像這樣:
let Rectangle = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
};
let Oval = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeTop(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottom(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
};
let Header = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
}
let TextBlock = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 10 repetitive lines of math
},
};
那些重複的代碼真讓我心煩。
這樣的代碼不夠
clean
。
大部分重複是因為朝相同方向調整形狀的代碼都差不多,比如
Oval.resizeLeft()
和
Header.resizeLeft()
就很類似。
其他重複是因為同一種形狀的方法之間很相像,比如
Oval.resizeLeft()
和
Oval
其他的方法就很類似。另外,
Rectangle
、
Header
和
TextBlock
之間也有重複的地方,因為文本框也是矩形。
基于上面的分析,重構的思路就清晰了。
我們可以将代碼分組,然後把重複代碼移除掉。就像下面這樣:
let Directions = {
top(...) {
// 5 unique lines of math
},
left(...) {
// 5 unique lines of math
},
bottom(...) {
// 5 unique lines of math
},
right(...) {
// 5 unique lines of math
},
};
let Shapes = {
Oval(...) {
// 5 unique lines of math
},
Rectangle(...) {
// 5 unique lines of math
},
}
然後,把它們的行為組合起來。
let {top, bottom, left, right} = Directions;
function createHandle(directions) {
// 20 lines of code
}
let fourCorners = [
createHandle([top, left]),
createHandle([top, right]),
createHandle([bottom, left]),
createHandle([bottom, right]),
];
let fourSides = [
createHandle([top]),
createHandle([left]),
createHandle([right]),
createHandle([bottom]),
];
let twoSides = [
createHandle([left]),
createHandle([right]),
];
function createBox(shape, handles) {
// 20 lines of code
}
let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);
代碼量減少了一半,重複代碼完全消失了!一下子
clean
了。如果要修改某個
形狀
或
方向
的行為,隻需要在一個地方做出改動,不需要修改所有的方法。夜已深,我把改好的代碼送出到 master 分支,然後上床睡覺。因為幫同僚把雜亂的代碼清理幹淨了,我心裡很自豪。
第二天
事情并沒有像我期待的那樣發生。老闆找我談話,他們希望我把代碼復原回去。我感到很驚訝,畢竟原先的代碼簡直就是一團亂麻,而我改得很clean啊!我很不情願地答應了,但幾年之後,我才意識到他們其實是對的。
必經之路
癡迷于“clean Code”和删除重複代碼是我們很多人都會經曆的一個階段。當我們對自己的代碼不是很自信時,就很容易将自我價值感和職業自豪感與一些可以被衡量的東西聯系在一起,比如嚴格的 lint 規則、命名規範、檔案結構、沒有重複。我們沒辦法自動去除重複代碼,但可以自己動手做。每次修改代碼之後,我們可以很容易地知道重複代碼是少了還是多了。是以,去除重複代碼感就像是在改進代碼品質。更糟糕的是,它擾亂了人們的認同感,讓他們覺得“我是那種編寫CLean Code的人”,但這其實無異于自我欺騙。一旦學會了抽象,我們就很容易對這種能力産生很高的期望,每當看到有重複代碼就會想要對它們進行抽象。在寫了幾年代碼之後,我們發現重複代碼到處都是,而抽象成了我們獲得的一項超級能力。如果有人告訴我們說抽象是一種美德,那我們肯定會深信不疑,并且會因為别人不崇尚“CLean Code”而對他們品頭論足。現在,我知道之前的代碼重構就是一個災難,原因如下。
- 首先,我沒有事先和寫代碼的人溝通。我直接修改了他們的代碼并送出,沒有和他們讨論。即使這是一種改進(但我現在不這麼認為了),但我這樣的行事方式并不值得稱道。一個健康的工程團隊應該以信任為基礎,不經過讨論就修改他人的代碼會對團隊協作造成沉重的打擊。
- 其次,天下沒有免費的午餐。我以犧牲靈活性為代價,以此來減少重複代碼,這算不上是一個好的權衡。例如,後來我們要求不同形狀的不同搖桿具備一些特殊的行為,被我重構過的代碼需要修改多次才能滿足需求,而原先“雜亂”的代碼卻可以很容易實作這些需求。
那麼,我的意思是我們應該盡量寫“Dirty”代碼嗎?當然不是。我隻是建議大家在考慮什麼是“Clean”或“Dirty”代碼時進行深度思考。你當時有什麼樣的感覺?厭惡?正義?美麗?優雅?你可以肯定這些品質會帶來實質性的工程成果嗎?它們又是如何影響代碼的編寫和修改方式的?我确實沒有深入思考過這些事情。我隻考慮到代碼本身,但從來沒有想過代碼與團隊之間的演化關系。編碼就像是一段旅程,想想你從寫第一行代碼到現在走了多遠。當第一次通過提取函數或重構類讓複雜的代碼變簡單,我覺得那是一種樂趣。如果你對自己的“傑作”感到自豪,那麼就很容易掉入追求
clean Code
的旋渦。但不要就此止步!不要隻做一個執着于clean Code的重構狂。寫出clean Code并不是我們的終極目标,我們隻是嘗試通過這種方式找到處理複雜系統的方法。當你不确定代碼改動會對代碼庫造成怎樣的影響,在未知的海洋中需要燈塔的指引,那麼這不失為一種防禦機制。寫出
clean code
可以作為一種方向,但後面還有很長的路需要去探索。
作者:Dan Abramov | 譯者:無明 | 策劃:小智