Java項目工程代碼深度刨析總結(jié)
一、背景
最近我們團隊有幸接了兩個0到1的項目,一期項目很緊急,團隊成員也是加班加點,從開始編碼到完成僅用了一星期多一點點,期間還不斷反復(fù)斟酌代碼如何抽象代碼,如何寫得更優(yōu)雅,一遍又一遍的調(diào)整,我也是一次又次的閱讀每個團隊成員的代碼,雖然還有些不如意,但整體來說還算是滿意,參與項目的成員經(jīng)過不斷琢磨,對一些功能不斷抽像,團隊進(jìn)步也是非常明顯,以下舉了幾個樣例。
那么這次我為什么對工程代碼抓得更嚴(yán),主要是之前交接了不少其它團隊的工程,由于當(dāng)時設(shè)計不夠好,維護(hù)起來非常痛苦,也正是因為這些工程,我閱讀了非常多的代碼,對自己也有很大的啟發(fā)和感想,因此希望我自己的團隊能盡可能寫好代碼,減少維護(hù)上的一些痛苦。另外就是我們寫的代碼除了給機器執(zhí)行外,更多的時候是給人讀的,這個讀代碼的可能是后來的維護(hù)人員,所以呢也順便總結(jié)一下。
二、衡量代碼好環(huán)的原則
2.1 評判代碼指標(biāo)
實際上,咱們平時嘴中常說的“好”和“爛”,是對代碼質(zhì)量的一種描述。“好”籠統(tǒng)地表示代碼質(zhì)量高,“爛”籠統(tǒng)地表示代碼質(zhì)量低。對于代碼質(zhì)量的描述,除了“好”“爛”這樣比較簡單粗暴的描述方式之外,我們也經(jīng)常會聽到很多其他的描述方式。這些描述方法語義更豐富、更專業(yè)、更細(xì)化。我搜集整理了一下,羅列在了下面,一般有幾下幾標(biāo)準(zhǔn),分別是可讀性、可維護(hù)性、可擴展性、可復(fù)用性 、靈活性、可測試性等等
可讀性 readability
軟件設(shè)計大師 Martin Fowler 曾經(jīng)說過:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”翻譯成中文就是:“任何傻瓜都會編寫計算機能理解的代碼。好的程序員能夠編寫人能夠理解的代碼。”Google 內(nèi)部甚至專門有個認(rèn)證就叫作 Readability。只有拿到這個認(rèn)證的工程師,才有資格在 code review 的時候,批準(zhǔn)別人提交代碼??梢姶a的可讀性有多重要,畢竟,代碼被閱讀的次數(shù)遠(yuǎn)遠(yuǎn)超過被編寫和執(zhí)行的次數(shù)。
我個人認(rèn)為,代碼的可讀性應(yīng)該是評價代碼質(zhì)量最重要的指標(biāo)之一。我們在編寫代碼的時候,時刻要考慮到代碼是否易讀、易理解。除此之外,代碼的可讀性在非常大程度上會影響代碼的可維護(hù)性。畢竟,不管是修改 bug,還是修改添加功能代碼,我們首先要做的事情就是讀懂代碼。代碼讀不大懂,就很有可能因為考慮不周全,而引入新的 bug。
既然可讀性如此重要,那我們又該如何評價一段代碼的可讀性呢?我們需要看代碼是否符合編碼規(guī)范、命名是否達(dá)意、注釋是否詳盡、函數(shù)是否長短合適、模塊劃分是否清晰、是否符合高內(nèi)聚低耦合等等。你應(yīng)該也能感覺到,從正面上,我們很難給出一個覆蓋所有評價指標(biāo)的列表。這也是我們無法量化可讀性的原因。
實際上,code review 是一個很好的測驗代碼可讀性的手段。如果你的同事可以輕松地讀懂你寫的代碼,那說明你的代碼可讀性很好;如果同事在讀你的代碼時,有很多疑問,那就說明你的代碼可讀性有待提高了
- 可維護(hù)性 maintainability
一般指的是在不破壞原代碼設(shè)計的前提下,快速修改bug或增加代碼,不會帶來新bug,表明該代碼的維護(hù)性比較好。落實到編碼開發(fā),所謂的“維護(hù)”無外乎就是修改 bug、修改老的代碼、添加新的代碼之類的工作。所謂“代碼易維護(hù)”就是指,在不破壞原有代碼設(shè)計、不引入新的 bug 的情況下,能夠快速地修改或者添加代碼。所謂“代碼不易維護(hù)”就是指,修改或者添加代碼需要冒著極大的引入新 bug 的風(fēng)險,并且需要花費很長的時間才能完成。
- 可擴展性 extensibility
代碼面對未來新需求的變化能力,一般來說,開發(fā)新需求的時候,不修改原代碼或很少修改,即可達(dá)到需求開發(fā)的能力,通常會預(yù)留一些功能擴展點。
- 可復(fù)用性 reusability
盡量避免重復(fù)造輪子,即能夠沉淀出一些通用的代碼邏輯,保持與上層業(yè)務(wù)代碼的解耦
- 靈活性 flexibility
這個詞比較寬泛。通常與可維護(hù)性、可擴展性以及可復(fù)用性類似
- 可測試性
主要反映在寫單測的時候。從兩個方面體現(xiàn):
1.單元測試是否容易編寫;
2.寫單元測試的時候,不能依賴環(huán)境,遠(yuǎn)程調(diào)用其他服務(wù)的借口,盡可能進(jìn)行mock數(shù)據(jù),保持服務(wù)之間的解耦。雖然要團隊每人都按這個規(guī)范走很難,但我們團隊有一個強制要求,就是每個功能函數(shù)不能超過50行代碼,而且要求代碼越短越好。
這幾個維度是評判代碼維度比較重要的幾個指標(biāo)。
2.2 指導(dǎo)理論
高內(nèi)聚低耦合幾乎是每個程序員員都會掛在嘴邊的,但這個詞太過于寬泛,太過于正確,所以聰明的編程人員們提出了若干面向?qū)ο笤O(shè)計原則來衡量代碼的優(yōu)劣:
- 開閉原則 OCP (The Open-Close Principle)
- 單一職責(zé)原則 SRP (Single Responsibility Principle)
- 依賴倒置原則 DIP (Dependence Inversion Principle)
- 最少知識原則 LKP (Least Knowledge Principle)) / 迪米特法則 (Law Of Demeter)
- 里氏替換原則 LSP (Liskov Substitution Principle)
- 接口隔離原則 ISP (Interface Segregation Principle)
- 組合/聚合復(fù)用原則 CARP (Composite/Aggregate Reuse Principle)
這些理論想必大家都很熟悉了,是我們編寫代碼時的指導(dǎo)方針,按照這些原則開發(fā)的代碼具有高內(nèi)聚低耦合的特性,換句話說,我們可以用這些原則來衡量代碼的優(yōu)劣。
三、代碼實現(xiàn)技巧
我相信每個工程師都想寫出高質(zhì)量的代碼,不想一直寫沒有成長、被人吐槽的爛代碼。那如何才能寫出高質(zhì)量的代碼呢?針對什么是高質(zhì)量的代碼,我們剛剛講到了七個最常用、最重要的評價指標(biāo)。所以,問如何寫出高質(zhì)量的代碼,也就等同于在問,如何寫出易維護(hù)、易讀、易擴展、靈活、簡潔、可復(fù)用、可測試的代碼,但要寫好代碼,也不是一蹴而就,需要非常多的實踐與積累,下面簡舉例說明:
3.1 抽像能力
抽象思維是我們工程師最重要的思維能力,因為軟件技術(shù)本質(zhì)上就是一門抽象的藝術(shù)。我們工程師每天都要動用抽象思維,對問題域進(jìn)行分析、歸納、綜合、判斷、推理,從而抽象出各種概念,挖掘概念和概念之間的關(guān)系,然后通過編程語言實現(xiàn)業(yè)務(wù)功能,所以,我們大部分的時間并不是在寫代碼,而是在梳理需求,理清概念,對需求有一個全局的認(rèn)知。而抽像能力讓我及團隊切身感受到,它給我們在編碼和設(shè)計上帶來的質(zhì)的變化。
案例一:異步Excel導(dǎo)出
其實導(dǎo)出Excel功能在我們工程里隨處可見,特別是咱們的運營希望一次性導(dǎo)出越多數(shù)據(jù)越好,為了不給我們系統(tǒng)帶來太大壓力,對于大數(shù)據(jù)量的導(dǎo)出一般異步進(jìn)行,針對于這樣一個簡單的功能,那么應(yīng)該如何抽像呢?
普通的寫法:
public String exportXXX(參數(shù)) throws Exception { //業(yè)務(wù)實現(xiàn) } public String exportXXX2(參數(shù)) throws Exception { //業(yè)務(wù)實現(xiàn) }
抽像寫法:
我們其實可以把每個異步導(dǎo)出看作是一個異步任務(wù),而每個任務(wù)可導(dǎo)出的內(nèi)容是不一樣的,因此完全可以把導(dǎo)出抽像一個方法,由每個具體實現(xiàn)類去實現(xiàn)導(dǎo)出不同的內(nèi)容,具體如下:
// export excel public interface IExcelExportTask { String export(BizCommonExportTask exportTask) throws Exception; } //樣例實現(xiàn)類 XXXXExportTask implements IExcelExportTask { String export(BizCommonExportTask exportTask) throws Exception{ public String export(BizCommonExportTask exportTask) throws Exception { //組織數(shù)據(jù)篩選條件 TestReq queryReq = GsonUtils.toObject(exportTask.getInputParams(),TestReq.class); String fileName = String.format("%s%s%s", exportTask.getUploadFileName(),System.currentTimeMillis(),".xlsx"); String downUrl = excelService.uploadExcel(fileName, null, new Fetcher<PreOccupyModel>(PreOccupyModel.class) { //循環(huán)獲取數(shù)據(jù) @Override public List<TestModel> fetch(int pageNo, int pageSize) throws OspException{ TestQueryResp resp = testFethchLogic.fetchRecord(queryReq); return pageNo > resp.getPageNum() ? Collections.emptyList() :toExcelModel(resp); } }); return downUrl; } } public class XXXXExportTask1 implements IExcelExportTask { @Override public String export(BizCommonExportTask exportTask) throws OspException { TestQuery query = GsonUtils.toObject(exportTask.getInputParams(), TestQuery .class); String fileName = String.format("%s%s%s", exportTask.getUploadFileName(), System.currentTimeMillis(), ".xlsx"); return excelService.uploadExcel(fileName, null, new Fetcher<ExportItemModel>(TestModel.class) { @Override public List<TestModel> fetch(int pageNo, int pageSize) throws OspException { return XXXXLogic.queryExportItem(query, pageNo, pageSize); } }); } } //導(dǎo)出任務(wù)分發(fā)器 public class ExcelTaskDispacther extends ApplicationObjectSupport { public boolean dispacthTask(Long taskId) throws OspException { updateTaskStatus(exportTask,CommonExportStatus.CREATING,TransferExportStatus.CREATING,StringUtils.EMPTY); try { String beanName = getBeanName(); ExportTaskHandler exportTaskHandler = getApplicationContext().getBean(beanName , IExcelExportTask .class); if(exportTaskHandler == null) { log.warn(String.format("任務(wù)ID[%s]寫入配置錯誤!", taskId)); return false; } updateTaskStatus(exportTask,CommonExportStatus.CREATE_SUCCESS,TransferExportStatus.CREATE_SUCCESS,StringUtils.EMPTY); log.info(String.format("任務(wù)ID[%s]RFID為[%s]處理成功", exportTask.getId(),rfid)); return true; } catch(BusiException ex) { log.info("任務(wù)ID[{}]失敗,原因:{}", exportTask.getId(),ex.getMessage(),ex); updateTaskResult(); } catch(Exception ex) { log.info("任務(wù)ID[{}]失敗,原因:{}", exportTask.getId(),ex.getMessage(),ex); updateTaskResult(); } return false; } }
案例二:系統(tǒng)通知
在微服務(wù)化流行的今天,為了提升系統(tǒng)吞吐量,系統(tǒng)職責(zé)越來越細(xì),各系統(tǒng)模塊需要頻繁交互數(shù)據(jù),那么對于復(fù)雜的數(shù)據(jù)交互場景,比如我們調(diào)撥單,調(diào)撥單在扭轉(zhuǎn)的過程中需要與很多系統(tǒng)交互,跟門店、倉庫、庫存模塊有非常多的交互,我們又該如何抽像呢,以下是調(diào)撥與各系統(tǒng)交互的代碼示例
//接口定義 public interface BizNotificationHandler { /** * 拋異常會當(dāng)失敗處理 * 是否需要重試由BizNotificationStatus返回狀態(tài)來決定 * @param bizNotification * @return * @throws OspException */ BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException; } //推送調(diào)撥差異數(shù)據(jù)給庫存系統(tǒng) public class SyncDiffToSimsAndBackQuotaHandler implements BizNotificationHandler { @Override public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException { //業(yè)務(wù)邏輯實現(xiàn) return BizNotificationStatus.PROCESS_SUCCESS; } } //占用庫存 public class TransferOccupyInventoryHandler implements BizNotificationHandler { @Override public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException { //業(yè)務(wù)實現(xiàn) } } //在GPDC生成新條碼 public class GpdcGenerateNewBarcodeHandler implements BizNotificationHandler { @Override public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException { //業(yè)務(wù)代碼實現(xiàn) } }
其實我們在與其它系統(tǒng)交互的時候,我們可以把每一個交互動作抽像成一個通知事件,每次交互的時候,寫一個事件通知事件即可。
3.2 組合/聚合復(fù)用原則
關(guān)于組合/聚合復(fù)用原則,其實我們在項目過程會經(jīng)常遇到,比如項目里會經(jīng)常管理各種單據(jù),像采購單、調(diào)撥單、收貨單等,而對于每種單據(jù)都會有各種各樣的較驗,我們先來看一段建調(diào)撥單代碼,具體如何下:
//接口定義 public interface TransferValidator { boolean validator(CreateTransferCtx ctx) throws OspException; } //接口實現(xiàn)1 public class W2sCrossPoQtyValidator implements TransferValidator { @Override public boolean validator(CreateTransferCtx ctx) throws OspException { //較驗器代碼實現(xiàn) } //接口實現(xiàn)2 public class W2sStoreBarcodeSaleLimitValidator implements TransferValidator { @Override public boolean validator(CreateTransferCtx ctx) throws OspException { //較驗器代碼實現(xiàn) } } //較驗器組裝 public class TransferValidators { public ValidatorChain newChain() { return new ValidatorChain(); } public class ValidatorChain { private final List<TransferValidator> validators = new ArrayList<>(); public ValidatorChain qtyValidator() { validators.add(qtyValidator); return this; } public ValidatorChain transferRouteCfgValidator() { validators.add(transferRouteCfgValidator); return this; } public ValidatorChain prodValidator() { validators.add(prodValidator); return this; } public ValidatorChain w2sWarehouseStoreValidator() { validators.add(w2sWarehouseStoreValidator); return this; } public ValidatorChain w2sStoreBarcodeSaleLimitValidator() { validators.add(w2sStoreBarcodeSaleLimitValidator); return this; } public ValidatorChain w2sAssignPoValidator() { validators.add(w2sAssignPoValidator); return this; } public ValidatorChain w2sCrossPoValidator() { validators.add(w2sCrossPoValidator); return this; } public ValidatorChain w2sCrossPoQtyValidator() { validators.add(w2sCrossPoQtyValidator); return this; } public ValidatorChain w2sCross4XupValidator() { validators.add(w2sCross4XupValidator); return this; } public ValidatorChain repeatLineValidator() { validators.add(repeatLineValidator); return this; } public ValidatorChain sstradeBarcodeValidator() { validators.add(sstradeBarcodeValidator); return this; } public ValidatorChain s2wWarehouseStoreValidator() { validators.add(s2wWarehouseStoreValidator); return this; } public boolean validator(CreateTransferCtx ctx) throws OspException { for (TransferValidator validator : validators) { if (!validator.validator(ctx)) { return false; } } return true; } } } //業(yè)務(wù)代碼使用 public interface TransferCreator { boolean createOrder(CreateTransferCtx ctx) throws OspException; } public abstract class DefaultTransferCreator implements TransferCreator { @Override public boolean createOrder(CreateTransferCtx ctx) throws OspException { validator(ctx) //實現(xiàn)業(yè)務(wù)邏輯 } protected abstract boolean validator(CreateTransferCtx ctx) throws OspException; } //店倉調(diào)撥單 public class S2wRefundCreator extends DefaultTransferCreator { //較驗器自由組裝 @Override protected boolean validator(CreateTransferCtx ctx) throws OspException { return transferValidators.newChain() .qtyValidator() .transferRouteCfgValidator() .prodValidator() .validator(ctx); } }
通過上面的示例,其實抽像并不難,難的是我們要花時間去思考,去理解,只有自己花足夠的多時間,反復(fù)訓(xùn)練我相信比較容易做到,最近在兩個新項目,我們團隊的部分成員反饋做夢都在想如何實現(xiàn)更合理。
四、總結(jié)
寫出滿足這些評價標(biāo)準(zhǔn)的高質(zhì)量代碼,我們需要掌握一些更加細(xì)化、更加能落地的編程方法論,包括面向?qū)ο笤O(shè)計思想、設(shè)計原則、設(shè)計模式、編碼規(guī)范、重構(gòu)技巧等。而所有這些編程方法論的最終目的都是為了編寫出高質(zhì)量的代碼。
比如,面向?qū)ο笾械睦^承、多態(tài)能讓我們寫出可復(fù)用的代碼;編碼規(guī)范能讓我們寫出可讀性好的代碼;設(shè)計原則中的單一職責(zé)、DRY、基于接口而非實現(xiàn)、里式替換原則等,可以讓我們寫出可復(fù)用、靈活、可讀性好、易擴展、易維護(hù)的代碼;設(shè)計模式可以讓我們寫出易擴展的代碼;持續(xù)重構(gòu)可以時刻保持代碼的可維護(hù)性等等,以上示例僅供參考,也希望大家更多參與討論。
到此這篇關(guān)于Java項目工程代碼深度刨析總結(jié)的文章就介紹到這了,更多相關(guān)Java工程代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實戰(zhàn)分布式醫(yī)療掛號通用模塊統(tǒng)一返回結(jié)果異常日志處理
這篇文章主要為大家介紹了實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)之統(tǒng)一返回結(jié)果統(tǒng)一異常處理,統(tǒng)一日志處理到通用模塊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-04-04MyBatis-Plus實現(xiàn)字段自動填充功能的示例
本文主要介紹了MyBatis-Plus實現(xiàn)字段自動填充功能的示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11Java二叉搜索樹遍歷操作詳解【前序、中序、后序、層次、廣度優(yōu)先遍歷】
這篇文章主要介紹了Java二叉搜索樹遍歷操作,結(jié)合實例形式詳細(xì)分析了Java二叉搜索樹前序、中序、后序、層次、廣度優(yōu)先遍歷等相關(guān)原理與操作技巧,需要的朋友可以參考下2020-03-03java中循環(huán)刪除list中元素的方法總結(jié)
下面小編就為大家?guī)硪黄猨ava中循環(huán)刪除list中元素的方法總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12Java中static與instance的區(qū)別及作用詳解
這篇文章主要為大家介紹了Java中static與instance的區(qū)別及作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07