再見(jiàn),整潔代碼
轉(zhuǎn)自:https://overreacted.io/zh-hans/goodbye-clean-code/
那是一個(gè)深夜。
我的同事們剛剛提交了過(guò)去整整一周所寫的代碼。我們正在開(kāi)發(fā)一個(gè)基于 Canvas 的圖形編輯器,他們負(fù)責(zé)實(shí)現(xiàn)各種“形狀”(比如:矩形、橢圓形)的縮放功能,縮放行為由拖拽“形狀”邊緣上的操作柄來(lái)實(shí)現(xiàn)。
代碼運(yùn)行正常。
但是代碼有些重復(fù)。每個(gè)“形狀”(如:矩形、橢圓形)各自擁有若干操作柄,從不同方向拖拽操作柄都會(huì)對(duì)“形狀”的位置和尺寸產(chǎn)生相應(yīng)的影響。如果用戶按住“Shift”鍵,我們還需要保持住“形狀”的寬高比不變。這里涉及到一些數(shù)學(xué)計(jì)算。
代碼看起來(lái)是這樣的:
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
??},
};
這種重復(fù)的數(shù)學(xué)計(jì)算邏輯,對(duì)我來(lái)說(shuō),真的難受。
這不是“整潔”的代碼。
多數(shù)的重復(fù)都在于“方向”上的相似,舉個(gè)例子,Oval.resizeLeft() 和 Header.resizeLeft() 就很類似。這是因?yàn)椋鼈兌际窃谔幚硗献ё髠?cè)操作柄的問(wèn)題。
另一個(gè)原因在于“形狀”所擁有的方法上的相似,例如,Oval.resizeLeft() 和 Oval 上的其它方法是類似的。這是因?yàn)椋鼈兌际窃谔幚怼皺E圓形”上的問(wèn)題。更進(jìn)一步,Rectangle, Header 以及 TextBlock 都有相似的地方,因?yàn)槲谋究蚨际恰熬匦巍薄?/p>
我有個(gè)辦法。
我們可以通過(guò)對(duì)代碼邏輯進(jìn)行分組,來(lái)實(shí)現(xiàn)“去除全部重復(fù)”的目的:
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
??},
}
然后,像這樣來(lái)組合它們:
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);
代碼量減少到 1 / 2,并且,重復(fù)代碼不復(fù)存在!這真是“整潔”啊。如果我們希望改變具體某個(gè)“方向”上的或“形狀”上的行為,只需要修改一處,而非多處。
已經(jīng)很晚了(我很疲憊了),我提交我重構(gòu)后的代碼,然后就躺下了,并對(duì)自己“幫助同事整潔代碼”而感到心滿意足。
第二天上午
… 事情并非我想的那樣。
我的上級(jí)單獨(dú)找到了我,委婉地要求我撤銷昨天的那些修改。我感到很不可思議,舊代碼那么差勁,而我的是多么“整潔”啊!
我那時(shí)只好謹(jǐn)遵照守;但是后來(lái),歷經(jīng)數(shù)年,我才明白他們的要求是對(duì)的。
那是一個(gè)職業(yè)階段
對(duì)“整潔代碼”的沉迷、好于“消除重復(fù)代碼”,是我們大多數(shù)開(kāi)發(fā)者都會(huì)經(jīng)歷的階段。當(dāng)我們對(duì)自己的代碼沒(méi)有信心,自我價(jià)值感和專業(yè)榮譽(yù)感往往會(huì)使我們與一些可測(cè)量的東西靠攏,一組嚴(yán)格的 lint 規(guī)則、一套命名規(guī)范、一種文件結(jié)構(gòu)、無(wú)重復(fù)性指標(biāo),等等。
你不可能讓“重復(fù)代碼”自動(dòng)被消除,但是確實(shí)可以借助“實(shí)踐經(jīng)驗(yàn)”使它更可行。你往往可以看到,每次修改后的代碼量是變得更多或更少。結(jié)果就是,消除重復(fù)代碼看起來(lái)提高了代碼的某些客觀的可測(cè)量指標(biāo)。然而,糟糕的是,這擾亂了人們的自我認(rèn)知,他們會(huì)對(duì)自己說(shuō):“我是那種會(huì)寫出整潔代碼的人”,而這其實(shí)跟其它的自我欺騙行為沒(méi)什么兩樣。
一旦學(xué)會(huì)了如何“抽象”,我們似乎就會(huì)更進(jìn)一步,每次看到重復(fù)的代碼,我們甚至能夠從“無(wú)”中發(fā)現(xiàn)可“抽象”的東西。編碼數(shù)年以后,我們已經(jīng)厲害得隨處都能看到重復(fù)的代碼,我們同時(shí)獲得了一項(xiàng)新的超能力:抽象。進(jìn)而,如果有人告訴我們“抽象是好的”,我們便對(duì)此更加篤信。于是我們開(kāi)始去批判那些不崇尚”整潔代碼”的人。
我現(xiàn)在明白,我的那次“重構(gòu)行為”確實(shí)是一場(chǎng)災(zāi)難(譯者:夸張了吧~),從兩個(gè)方面來(lái)說(shuō):
首先,我沒(méi)有告訴代碼的作者。我在沒(méi)有他們參入的情況下,重寫并提交了代碼。即便,那確是一次對(duì)代碼的“改善”(我現(xiàn)在不以為然了),它也十分可怕。一個(gè)健康的工程團(tuán)隊(duì)需要不斷地“建立彼此的信任”。不經(jīng)討論而擅自重寫同事的代碼,是對(duì)高效協(xié)作能力的徹底否定。 其次,一切都需要權(quán)衡。為了減少重復(fù),我的代碼難于應(yīng)付需求變更,而這并不值得。例如,我們后邊需要為不同“形狀”上的各個(gè)操作柄添加特殊的行為邏輯。為了應(yīng)付這些,我的“抽象”無(wú)疑使修改困難了數(shù)倍,反觀那原本“骯臟的”代碼,它則能夠輕松處理。
我的意思是你應(yīng)該寫“臟”的代碼嗎?不,我建議你深入思考一下“整潔”與“骯臟”分別意指何物。你有對(duì)于批判、正確、美和優(yōu)雅的直覺(jué)嗎?你如何能確定地說(shuō)你的這次工程成果達(dá)到了那些質(zhì)量指標(biāo)?它們?nèi)绾未_切地影響到代碼的產(chǎn)生與維護(hù)?
我當(dāng)時(shí)確然對(duì)于這些問(wèn)題沒(méi)有進(jìn)行過(guò)深入思考;我當(dāng)時(shí)考慮的是代碼“看起來(lái)”如何,而不是代碼該如何隨著不斷流動(dòng)的團(tuán)隊(duì)而演進(jìn)。
寫代碼是一場(chǎng)旅行。回想一下,你編寫第一行代碼時(shí)的情形,到現(xiàn)在你所處的狀態(tài)。我相信,當(dāng)突然發(fā)現(xiàn)“抽取出一個(gè)函數(shù)”或“重構(gòu)一個(gè)類的定義”是如何使問(wèn)題由復(fù)雜變得簡(jiǎn)單的時(shí)候,你必定是開(kāi)心的。一旦對(duì)自己的作品產(chǎn)生了自豪感,你就會(huì)熱切地去追求代碼的整潔。對(duì)此,你可以保持一段時(shí)間。
但是不要止乎此,不要成為一個(gè)整潔代碼的狂熱分子。整潔代碼并不是目的,它只是讓我們從所面對(duì)系統(tǒng)的異常復(fù)雜性中解脫出來(lái)的方法。在你不是很明確一個(gè)改動(dòng)會(huì)如何影響到整個(gè)代碼庫(kù)時(shí),這信念可以作為一種安全防護(hù)機(jī)制。但是在未知的海洋里,你需要一個(gè)指南針。
那就讓整潔代碼指引你吧,然后忘了它。
點(diǎn)贊和在看就是最大的支持??
