原文連接:overreacted.io/goodbye-cle…javascript
本文主要介紹Dan大佬對於"整潔的代碼"理解的心路歷程。
Let clean code guide you. Then let it go.
那是一個深夜。java
個人同事剛剛檢查完他們花了一整個周所完成的代碼。當時咱們正在作一個圖形編輯器,已經實現了經過拖動邊緣的小手柄來調整矩形和橢圓形等形狀的功能。react
代碼運行起來沒問題。程序員
可是代碼裏有不少重複的地方。每一個形狀(例如矩形或橢圓形)都有一組不一樣的手柄,當咱們沿不一樣方向拖動手柄時,它們會以不一樣的方式影響形狀的位置和大小。若是用戶按住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
},
};
複製代碼
這些重複的代碼讓我很是困擾。ide
這些代碼一點都不整潔。函數
大部分的重複代碼存在於類似方向的拖拽上。舉個例子,Oval.resizeLeft()
和Header.resizeLeft()
有類似之處,由於它們都須要處理左側把手的拖拽。學習
還有一些重複代碼存在於同一個形狀的方法中。舉個例子,Oval.resizeLeft()
和其餘的Oval
方法有類似之處。這是由於他們都在處理橢圓形。一樣的道理,在矩形、頁頭和文本框中也存在這個問題,由於它們都是矩形。ui
我想到了一個辦法!
咱們能夠經過這樣的方式,把重複的代碼都抽離出來:
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);
複製代碼
代碼總量減小一半,而且重複代碼所有消失了!好整潔。若是要更改特定方向或形狀的行爲,只須要在一個地方進行修改,不須要像之前那樣把全部方法都更新一遍。
已經很晚了(睡意逐漸讓我分心)。我把重構後的代碼合併到了主分支,而後滿懷着清理完同事的凌亂代碼而產生的自豪感,進入了夢鄉。
... 好像有點不對勁
老闆把我叫出去進行了一次面對面交談,他很禮貌的讓我把昨晚的代碼還原回去。我懵了,由於我堅信上一版的代碼一團糟,而個人很是整潔!
我很勉強的答應了,但卻一直心有不甘。直到不少年後,我才明白了其中的道理。
癡迷於"整潔的代碼"和"消除重複"是咱們許多人都會經歷的一個階段。當咱們對本身的代碼不夠自信時,就很容易將自我價值感和專業自豪感附加到能夠衡量的事物上。好比一系列的嚴格lint規則、命名規範、文件結構、禁止重複等。
剛開始你可能並不清楚如何清理重複代碼,可是隨着實踐增多你會愈來愈駕輕就熟。漸漸地,你能夠判斷出在每一次代碼更改後,代碼的重複度究竟是增多了仍是減小了。結果,年輕的咱們就會以爲,消除重複代碼就是在提高代碼的質量。更糟糕的是,這種想法會跟人們的自我認同感交織在一塊兒:"我就是那種會寫整潔代碼的人"。這種想法就像任何一種自我欺騙同樣,如此的強烈。
一旦咱們知道了何爲抽象,就會按耐不住想要去提高這種能力,以致於每當看到重複代碼就會去憑空提取一些抽象出來。通過幾年的"磨練",咱們終於作到了——目光所及之處,全都是有待抽象的重複代碼。若是有人對咱們說抽象是一種美德,那咱們必定會跟他作同志了。而後一塊兒去批判那些不崇尚"整潔代碼"的人。
如今我明白了,個人"重構"從兩個方面來講是一場災難:
首先,我沒有跟寫下它們的人交談過。我在沒有他們參與的狀況下,獨自重寫了代碼併合並進了主分支。即便這是一項改進(其實並非),這也不是解決問題的好辦法。健壯的工程團隊最重要的是創建起信任。在項目協做開發中,沒有和同事深刻討論就去重寫他們的代碼,對咱們的團隊協做能力是一個巨大的打擊。
其次,沒有什麼是免費的。個人代碼犧牲了應對需求變化的能力,換來了更少的重複代碼,但這其實不是一個好買賣。例如,後來咱們針對不一樣形狀的不一樣手柄,新增了許多特殊的情景和行爲。個人抽象必須用數倍的複雜度去完成這些需求,而對於原來凌亂的版本,這樣的改動跟切蛋糕同樣容易。
我是在說你應該寫"髒亂"的代碼嗎?不,我建議你深刻思考一下何爲"整潔"何爲"髒亂"。你感覺到過"反叛"?"正義"?"美麗"?"優雅"嗎?你怎麼肯定你能把這些精神品質映射到具體的工程成果呢?它們究竟如何影響編寫和修改代碼的方式呢?
我能確定的是我本身對這些事情沒有深思過。我僅僅對代碼的外觀進行了不少思考,但卻沒有考慮過它們是如何從一個團隊中造成的。
編碼是一段旅程。考慮一下你從寫下第一行代碼至今所經歷的事情。我猜,當你第一次抽離出一個函數或者重構了一個類,並讓代碼變得更簡單的時候,你當時必定很開心。若是你對本身的技術感到自豪,就去追求代碼的整潔性。先這樣作一段時間吧。
可是不要停在那裏。不要成爲一個整潔代碼的狂熱者。整潔的代碼歷來不是最終目標。而是一種從正在處理的系統的巨大複雜性中得到某種意義的嘗試。這是一種防護機制,當你不肯定你的更改會對項目產生怎樣的影響時,你可能須要一些指導。
讓整潔的代碼指導你前進。 Then let it go.
DRY(Don't Repeat Yourself)在Wikipedia中是這麼描述的:
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system
這個方法論是如此的重要,它幾乎貫穿咱們Coding的整個過程,稍有追求的程序員都會時刻注意代碼的抽象性,打心底裏拒絕複製/粘貼。
可是若是咱們不假思考的去遵照DRY原則,就可能會遇到本文中的問題:
不肯定代碼的當下,沒想好代碼的未來,在這種狀況下,以錯誤的假設爲前提,強行遵照DRY原則,結果寫出了在未來變得不可維護的代碼抽象
所以,除了理解透DRY原則,咱們還須要更多方法論來指導咱們編程,AHA原則就是其中一個,來看一下吧:Avoid Hasty Abstractions