原創(chuàng)聲明:該文章是個人在項目中親歷后的經(jīng)驗總結(jié)和分享,如有搬運需求請注明出處。 這是“深入淺出系列”文章的第一篇,主要記錄和分享程序設(shè)計的一些思想和方法論,如果讀者覺得所有受用,還請“一鍵三連”,這是對我最大的鼓勵。
一、老生常談,到底啥是可讀性
一句話:見名知其義。有人說好的代碼必然有清晰完整的注釋,我不否認;也有人說代碼即注釋,是代碼簡潔之道的最高境界,我也不否認。但我都不完全接受,如果照搬前者,有人會在每個方法、每個循環(huán)、每個判斷都添加大量注釋,對于一個表達不嚴謹?shù)腸oder來說,代碼與漢字可能詞不達意;而且,一旦代碼邏輯發(fā)生變化,注釋改不改?對于后者,英語水平可能也就是個半吊子,動詞名詞不區(qū)分,真能做到代碼即注釋的有多少人?
二、罵歸罵,總歸要硬著頭皮干
先來舉個簡單例子:
public StepExitEnum doExecute(StepContext stepContext) throws Exception { String targetFilePath = this.getOriginFilePath(stepContext.getJobContext());//獲取目標(biāo)路徑 File targetDir = new File(targetFilePath); if (!targetDir.exists()) { targetDir.mkdirs();//如果不存在目錄則創(chuàng)建 } String encryptedFilePath = this.getEncryptedFilePath(stepContext.getJobContext());//獲取加密文件路徑 String fileName = this.getFileName(stepContext);//獲取文件名 File[] encryptedFiles = new File(encryptedFilePath).listFiles(this.buildFilenameFilter(fileName));//過濾文件 FileEncryptor dencryptor = this.buildFileEncryptor(stepContext);//創(chuàng)建FileEncryptor Stream.of(encryptedFiles) .forEach(encryptFile -> { File targetFile = new File(targetFilePath, encryptFile.getName()); dencryptor.invoke(encryptFile, targetFile);//解密文件 }); return StepExitEnum.CONTINUING; }
這種代碼很常見,耐著性子其實也容易看懂:創(chuàng)建目錄->讀取加密文件->解密文件,就當(dāng)前來說其實滿足了業(yè)務(wù)需求也就可以了,但不夠優(yōu)雅,從長期來講,這會產(chǎn)生bad smell,首先,“如果不存在目錄則創(chuàng)建”、“獲取文件名”這類注釋有何意義?有可能這是coder當(dāng)時的方案思路,但這里真的需要嗎?它確確實實影響我的注意力了,但我沒有獲取到任何有價值信息;其次,若想要理解doExecute這個方法的目的,必須通讀代碼,而我只是想知道它做了什么事;最后,這個方法如果某一行出問題了,那么影響范圍是整個業(yè)務(wù)流程。
如果后期需要改動,大部分人可能會增加條件判斷,或是在后面繼續(xù)追加代碼實現(xiàn),最后會導(dǎo)致越來越難以閱讀,這其實也就是“能運行就不要動它”這個梗的根源了,因為沒人能讀明白它到底做了什么,但又不得不改,同時可能伴隨著“口吐芬芳”。
三、意識先行,從一行做起
那么到底該如何做呢?下面是我的一個例子:
public StepExitEnum doExecute(StepContext stepContext) throws Exception { initTempFilePath(stepContext); File[] encryptedFiles = findEncryptedFiles(stepContext); dencryptFiles(encryptedFiles, stepContext); return StepExitEnum.CONTINUING; }
先不論具體實現(xiàn)細節(jié),是不是一眼看過之后就了解doExecute做了什么事?這個方法的確沒有任何注釋,是否影響閱讀?其實我做的只是把先前的代碼重新歸類,分別放到了三個方法中,核心實現(xiàn)還是原本的代碼,沒有改動,現(xiàn)在閱讀起來是不是順暢了許多?
通讀代碼后我發(fā)現(xiàn)其實只做了三件事:創(chuàng)建目錄、讀取加密文件、解密文件,這是最核心的三個步驟,把它抽象出來,獨立為方法,既表達了邏輯功能,也清晰閱讀,還可以縮小影響范圍,今后哪里有問題改哪里,不需要再通讀代碼了。
四、回到主題,再說可讀性
(1)抽象,合理的業(yè)務(wù)邏輯抽象
“一個方法只應(yīng)該做一件事”,想必很多人聽過類似的表述,聽起來簡單做起來難,怎么定義“只做一件事”?這件事的邊界是什么?這就依賴coder對業(yè)務(wù)邏輯、對功能實現(xiàn)的深入理解和合理抽象,這才能清晰的區(qū)分出各個功能的邊界,或者說是如何定義這件“事”。
沒有基于業(yè)務(wù)的合理抽象,硬生生地寫了幾個方法,你會發(fā)現(xiàn)這幾個方法“藕斷絲連”,一個方法的參數(shù)變化總會影響到另一個方法,很難將一個方法單拎出來應(yīng)用在其他場景,一處改,處處改,這時候就要考慮,方法抽象的是否合理?
合理的抽象,從功能角色、職責(zé)劃分上就很清晰,有了這個基礎(chǔ),才能清晰的編寫業(yè)務(wù)邏輯代碼,而不是堆砌各種條件判斷和循環(huán),同時帶著兩條斜杠和注釋,這是可讀性的基礎(chǔ)。
(2)各司其職,職責(zé)單一
一個方法只做一件事,擴展到一個類也如此,職責(zé)單一,歸根結(jié)底還得基于合理的抽象,所以,它其實是抽象的一種具體體現(xiàn),二者總是相輔相成。
(3)命名規(guī)范
這也是老生常談了,但真正做到的coder其實不多,類名、方法、變量的命名規(guī)則其實很有講究,但這不是本文的主題,不多贅述,類名用名詞,方法名用動詞,因為類表述的是做什么事,而方法名表述的是如何做,規(guī)范的命名和正確的詞法,這是編碼的基礎(chǔ)功底,這會有助于他人閱讀代碼,當(dāng)然也是為什么我們讀spring源碼會感覺順暢,而讀同事寫的業(yè)務(wù)代碼卻很蹩腳的原因,我們太過于強調(diào)spring的IOC了,卻忽略了最基礎(chǔ)的東西。
(4)關(guān)鍵注釋
注釋不能少,但也不應(yīng)該每個方法、每個判斷、每個循環(huán)到處都是//和/*,畢竟代碼是主體不是注釋,而且這樣還會帶來隱性的工作量問題:代碼修改,注釋也必須修改。所以好的注釋不是多,是關(guān)鍵。例如java.util.HashMap類的注釋上會告訴你線程安全問題:
Note that this implementation is not synchronized.
這是很關(guān)鍵的信息,所以注釋要給出關(guān)鍵性的、使用上注意的事項,不在于多。
代碼可讀性其實是一個比較寬泛的問題,也是一個老生常談的問題,隨著編碼經(jīng)驗積累,在不同職業(yè)階段,我們對可讀性都會有不同的理解和認識,本文從我自己的角度和經(jīng)驗,討論了一些比較淺的理解,如何寫出易讀、易懂的優(yōu)秀代碼,可能是我們coder永遠追尋的目標(biāo)之一,即使它沒有終點。
?
最后別忘了“一鍵三連”!
審核編輯 黃宇
-
代碼
+關(guān)注
關(guān)注
30文章
4695瀏覽量
68080
發(fā)布評論請先 登錄
相關(guān)推薦
評論