實際上,咱們平時嘴中常說的「好」和“爛”,是對程式碼品質的一種描述。 「好」籠統地表示程式碼品質高,「爛」籠統地表示程式碼品質低。對於程式碼品質的描述,除了「好」「爛」這樣比較簡單粗暴的描述方式之外,我們也常常會聽到很多其他的描述方式。這些描述方法語意更豐富、更專業、更細緻。我蒐集整理了一下,羅列在了下面,一般有幾下幾標準,分別是可讀性、可維護性、可擴展性、可復用性、靈活性、可測試性等等
#可讀性readability
軟體設計大師Martin Fowler 曾經說過:「Any fool can write code that a computer can understand. Good programmers write code that humans can understand. 」翻譯成中文就是:「任何傻瓜都會寫電腦能理解的程式碼。好的程式設計師能夠編寫人能夠理解的程式碼。」Google 內部甚至專門有個認證就叫作Readability。只有拿到這個認證的工程師,才有資格在 code review 的時候,就批准別人提交代碼。可見程式碼的可讀性有多重要,畢竟,程式碼被閱讀的次數遠遠超過被編寫和執行的次數。
我個人認為,程式碼的可讀性應該是評價程式碼品質最重要的指標之一。我們在寫程式碼的時候,時時刻刻要考慮到程式碼是否易讀、易理解。除此之外,程式碼的可讀性在非常大程度上會影響程式碼的可維護性。畢竟,不管是修改 bug,還是修改新增功能程式碼,我們首先要做的事情就是讀懂程式碼。程式碼讀不大懂,就很有可能因為考慮不周全,而引入新的 bug。
既然可讀性如此重要,那我們又該如何評價一段程式碼的可讀性呢?我們需要看程式碼是否符合編碼規範、命名是否達意、註解是否詳盡、函數是否長短適當、模組劃分是否清晰、是否符合高內聚低耦合等等。你應該也能感覺到,從正面上,我們很難給出一個涵蓋所有評價指標的清單。這也是我們無法量化可讀性的原因。
實際上,code review 是一個很好的測驗程式碼可讀性的手段。如果你的同事可以輕鬆地讀懂你寫的程式碼,那表示你的程式碼可讀性很好;如果同事在讀你的程式碼時,有很多疑問,那就說明你的程式碼可讀性有待提高了
可維護性maintainability
一般指的是不破壞原程式碼設計的前提下,快速修改bug或增加程式碼,不會帶來新bug,表示該程式碼的維護性比較好。落實到編碼開發,所謂的「維護」無外乎就是修改 bug、修改舊的程式碼、新增新的程式碼之類的工作。所謂「程式碼易維護」就是指,在不破壞原有程式碼設計、不引入新的 bug 的情況下,能夠快速地修改或新增程式碼。所謂「程式碼不易維護」就是指,修改或新增程式碼需要冒著極大的引入新 bug 的風險,並且需要花費很長的時間才能完成。
可擴展性extensibility
# 程式碼面對未來新需求的變化能力,一般來說,開發新需求的時候,不修改原程式碼或很少修改,即可達到需求開發的能力,通常會預留一些功能擴充點。
可重複使用性reusability
# 盡量避免重複造輪子,即能夠沉澱出一些通用的程式碼邏輯,保持與上層業務代碼的解耦
靈活性flexibility
# 這個字比較寬泛。通常與可維護性、可擴展性以及可重複使用性類似
可測試性
主要反映在寫單測的時候。從兩個方面體現:
1.單元測試是否容易寫;
2.寫單元測試的時候,不能依賴環境,遠端呼叫其他服務的藉口,盡可能進行mock數據,保持服務之間的解耦。雖然要團隊每人都照這個規範走很難,但我們團隊有一個強制要求,就是每個功能函數不能超過50行程式碼,而且要求程式碼越短越好。
這幾個維度是評判程式碼維度比較重要的幾個指標。
高內聚低耦合幾乎是每個程式設計師都會掛在嘴邊的,但這個詞太過於寬泛,太過於正確,所以聰明的程式設計師提出了若干物件導向設計原則來衡量程式碼的優劣:
開閉原則OCP (The Open-Close Principle)
#單一職責原則SRP (Single Responsibility Principle)
依賴倒置原則DIP (Dependence Inversion Principle)
#最少知識原則LKP (Least Knowledge Principle)) / 迪米特法則(Law Of Demeter)
里氏替換原則LSP (Liskov Substitution Principle)
介面隔離原則ISP (Interface Segregation Principle)
#組合/聚合復用原則CARP (Composite/Aggregate Reuse Principle)
這些理論想必大家都很熟悉了,是我們編寫程式碼時的指導方針,依照這些原則開發的程式碼具有高內聚低耦合的特性,換句話說,我們可以用這些原則來衡量程式碼的優劣。
我相信每個工程師都想寫出高品質的程式碼,不想一直寫沒有成長、被人吐槽的爛程式碼。那要如何才能寫出高品質的程式碼呢?針對什麼是高品質的程式碼,我們剛剛講到了七個最常用、最重要的評價指標。所以,問如何寫出高品質的程式碼,也就等於在問,如何寫出易維護、易讀、易擴展、靈活、簡潔、可重複使用、可測試的程式碼,但要寫好程式碼,也不是一蹴而就,需要非常多的實踐與積累,以下簡舉例說明:
抽象思維是我們工程師最重要的思維能力,因為軟體技術本質上就是一門抽象的藝術。我們工程師每天都要動用抽象思維,對問題域進行分析、歸納、綜合、判斷、推理,從而抽像出各種概念,挖掘概念和概念之間的關係,然後透過程式語言實現業務功能,所以,我們大部分的時間不是在寫程式碼,而是在梳理需求,理清概念,對需求有一個全局的認知。而抽像能力讓我及團隊切身感受到,它帶給我們在程式設計和設計上所帶來的質的改變。
案例一:非同步Excel導出
其實導出Excel功能在我們工程裡隨處可見,特別是咱們的營運希望一次性導出越多資料越好,為了不給我們系統帶來太大壓力,對於大數據量的導出一般異步進行,針對於這樣一個簡單的功能,那麼應該如何抽像呢?
普通的寫法:
public String exportXXX(参数) throws Exception { //业务实现 } public String exportXXX2(参数) throws Exception { //业务实现 }
抽像寫法:
我們其實可以把每個非同步匯出看成是一個非同步任務,而每個任務可導出的內容是不一樣的,因此完全可以把導出抽像一個方法,由每個具體實現類別去實現導出不同的內容,具體如下:
// export excel public interface IExcelExportTask { String export(BizCommonExportTask exportTask) throws Exception; } //样例实现类 XXXXExportTask implements IExcelExportTask { String export(BizCommonExportTask exportTask) throws Exception{ public String export(BizCommonExportTask exportTask) throws Exception { //组织数据筛选条件 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) { //循环获取数据 @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); } }); } } //导出任务分发器 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("任务ID[%s]写入配置错误!", taskId)); return false; } updateTaskStatus(exportTask,CommonExportStatus.CREATE_SUCCESS,TransferExportStatus.CREATE_SUCCESS,StringUtils.EMPTY); log.info(String.format("任务ID[%s]RFID为[%s]处理成功", exportTask.getId(),rfid)); return true; } catch(BusiException ex) { log.info("任务ID[{}]失败,原因:{}", exportTask.getId(),ex.getMessage(),ex); updateTaskResult(); } catch(Exception ex) { log.info("任务ID[{}]失败,原因:{}", exportTask.getId(),ex.getMessage(),ex); updateTaskResult(); } return false; } }
案例二:系統通知
#在微服務化流行的今天,為了提升系統吞吐量,系統職責越來越細,各系統模組需要頻繁交互數據,那麼對於複雜的數據交互場景,比如我們調撥單,調撥單在扭轉的過程中需要與很多系統交互,跟門市、倉庫、庫存模組有非常多的交互,我們又該如何抽像呢,以下是調撥與各系統交互的程式碼範例
//接口定义 public interface BizNotificationHandler { /** * 抛异常会当失败处理 * 是否需要重试由BizNotificationStatus返回状态来决定 * @param bizNotification * @return * @throws OspException */ BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException; } //推送调拨差异数据给库存系统 public class SyncDiffToSimsAndBackQuotaHandler implements BizNotificationHandler { @Override public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException { //业务逻辑实现 return BizNotificationStatus.PROCESS_SUCCESS; } } //占用库存 public class TransferOccupyInventoryHandler implements BizNotificationHandler { @Override public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException { //业务实现 } } //在GPDC生成新条码 public class GpdcGenerateNewBarcodeHandler implements BizNotificationHandler { @Override public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException { //业务代码实现 } }
其實我們在與其它系統交互的時候,我們可以把每一個互動動作抽象化成一個通知事件,每次互動的時候,寫一個事件通知事件即可。
關於組合/聚合復用原則,其實我們在專案過程會經常遇到,例如專案裡會經常管理各種單據,像是採購單、調撥單、收貨單等,而對於每種單據都會有各種各樣的較驗,我們先來看一段建調撥單代碼,具體如何下:
//接口定义 public interface TransferValidator { boolean validator(CreateTransferCtx ctx) throws OspException; } //接口实现1 public class W2sCrossPoQtyValidator implements TransferValidator { @Override public boolean validator(CreateTransferCtx ctx) throws OspException { //较验器代码实现 } //接口实现2 public class W2sStoreBarcodeSaleLimitValidator implements TransferValidator { @Override public boolean validator(CreateTransferCtx ctx) throws OspException { //较验器代码实现 } } //较验器组装 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; } } } //业务代码使用 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) //实现业务逻辑 } protected abstract boolean validator(CreateTransferCtx ctx) throws OspException; } //店仓调拨单 public class S2wRefundCreator extends DefaultTransferCreator { //较验器自由组装 @Override protected boolean validator(CreateTransferCtx ctx) throws OspException { return transferValidators.newChain() .qtyValidator() .transferRouteCfgValidator() .prodValidator() .validator(ctx); } }
以上是Java 專案工程實例程式碼分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!