日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

《代码整洁之道 Clean Architecture》-读书笔记

發布時間:2023/12/10 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《代码整洁之道 Clean Architecture》-读书笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家好,我是烤鴨:
關于《代碼整潔之道》,記錄一下讀書筆記。

代碼整潔之道

  • 第一章 整潔代碼
    • 整潔代碼的藝術
  • 第二章 有意義的命名
    • 避免誤導
    • 有意義的區分
    • 使用讀得出來和可搜索的名字
    • 避免使用編碼
  • 第三章 函數
  • 第四章 注釋
  • 第五章 格式
  • 第六章 對象和數據結構
  • 第七章 錯誤處理
  • 第八章 邊界
  • 第九章 單元測試
  • 第十章 類
  • 第十一章 系統
  • 第十二章 迭進
  • 第十三章 并發編程
  • 第十四章 逐步改進
  • 第十五章 Junit內幕
  • 第十六章 重構SerialDate
  • 第十七章 味道與啟發
碼整潔之道)

第一章 整潔代碼

整潔代碼的藝術

優雅而高效。代碼邏輯直接了解,bug無處可藏。盡量減少依賴關系,性能調優。
應用單元測試和驗收測試,有意義的命名,明確的定義和提供清晰、盡量少的API。
通過所有測試、沒有重復代碼,提高表達力。

第二章 有意義的命名

避免誤導

比如 XYZControllerEfficientHandlingOfStrings 和 XYZControllerEfficientStorageOfStrings。

有意義的區分

反例:
比如 字母 O和 I 和 數字 0 和 1
比如 getAccount 和 getAccounts 和 getAccountInfo ,不知道這三個方法的區別

使用讀得出來和可搜索的名字

反例:
genhydhms 一個生成年月日時分秒的方法
應該避免下面這種命名不知道什么意義的代碼。

for (int i = 0; i < s; i++) {s +=1; }

避免使用編碼

反例:
成員變量前綴加 m_ 比如 m_dsc。
不對接口命名
反例:
比如 IShapeFactory,按照阿里規范,命名提現設計模式,直接叫 ShapeFactory即可。

第三章 函數

函數要短小(阿里規范80行,作者建議不要超過120行);
只做一件事(單一原則);
函數語句處在同一抽象層級且自頂向下(比如getHtml()和PathParser.render(pagePath)就不是同一抽象層級);
switch或if/else 語句不要做過多的邏輯處理(容易違反單一原則和開閉原則);
起個好名字(不怕長,要有描述性);
函數參數盡量少,不要標識參數(需要的話用兩個方法表示),多參數時采用對象,可變參數不要超過三元,動詞和關鍵詞(比如write(name)或者writeField(name),可以更好的表示函數和參數的意思);
避免函數的副作用(函數的變量范圍過大之類的(成員變量定義為全局變量)),盡量避免輸出參數;
分隔指令與詢問(也是單一原則的體現,把檢查和設值放到一個函數的問題,比如if(set(“username”,“unclebob”)){…}應換成if(attributeExists(username){set(“username”,“unclebob”))})));
使用異常代替錯誤碼(這個還是要看業務場景,按照阿里規范第三方調用的時候需要返回錯誤碼,如果是內部服務調用可以拋異常),抽離try catch 代碼塊,catch finally 不要做其他事,定義錯誤枚舉;
減少重復代碼
遵循結構化編程,小函數偶爾使用return、break或continue沒有壞處;
沒人一上來就能按照規范寫代碼,打磨代碼,按照本章規則組裝函數。

第四章 注釋

好的代碼可以代替注釋 ,比如 employ.isEligibleForFullBenefits 方法名稱即注釋
必要的注釋,1. 法律信息 2. 提供信息 3. 對意圖的注釋(對返回值的解釋) 4. 闡釋:a.compareTo(b) == -1 // a < b 5. 給下一個看這個代碼的人的提示 6. TODO 7. 強調 8. 公共API的javadoc
壞注釋,1. 多余的注釋 2. 誤導注釋 3. 循規(不需要所有的javadoc)4. 日志式注釋 5. 廢話 6. 位置標記(// Action /)7. 注釋和代碼沒有隔行 8. 注釋的代碼 9. HTML、非本地的(代碼變量)、信息過多、非公共Javadoc。 這段感覺作者寫的就很廢話了…
復雜的方法有注釋,簡單的不需要(函數名稱要清晰),需要的地方加注釋(作者信息、Javadoc、TODO 等)

第五章 格式

垂直格式 像報紙學習(名稱簡單且一目了然)、代碼間隔(不同方法中間會有空行)、靠近(有聯系的變量間不需要空行)、變量聲明的位置(本地變量在函數頂部、循環語句的變量在循環體中聲明、實體變量在類頂部聲明、相關函數放到一起、概念相關放到一起)

橫向注意分隔(函數名和左括號不加空格,函數調用括號中參數逗號分隔)

private void measure(String line){lineCount++;int lineSize = line.length();totalChars += lineSize;lineWidthHistogram.addLine(lineSize, lineCount);recordWidestLine(lineSize); }

不對齊的聲明和賦值(變量左對齊)

if、while 要縮進

while或for語句體為空時,不寫循環體的結束分號最好換行(阿里規范是空循環也要有{ })

團隊規則很重要(花很小的時間統一團隊的代碼規范)

第六章 對象和數據結構

數據抽象 不暴露細節且符合業務場景

數據、對象的反對稱性 面向過程便于不改變既有數據結構下添加新函數,面向對象便于不改動既有函數前提下添加新類。(過程代碼不易添加新數據結構,面向對象代碼不易添加新函數)

得墨忒耳律 模塊不應該了解調用對象的內部情形。比如 類C的方法f只應該調用以下對象的方法:

C、由f創建的對象、作為參數傳遞給f的對象、由C的實體變量持有的對象,方法不應該調用由任何函數返回的對象的方法。是數據結構還是對象(數據結構無所謂)、一半數據結構一半對象(公共方法導致私有變量公有化,不安全)、隱藏結構(只暴露對象的方法、隱藏內部和實現)

數據傳送對象 DTO(data transfer objects)業務流轉對象、VO(展示層對象)、DO(數據層交互對象)不參雜業務邏輯

小結 對象暴露行為、隱藏數據,數據結構暴露數據、沒有明顯的行為。面向對象和面向過程看具體的場景選擇。

第七章 錯誤處理

使用異常而非錯誤碼 (內部服務調用可以使用拋出異常,對外提供的接口最好還是返回錯誤碼)

先寫try-catch-finally語句 定義范圍,方便滿足測試

使用不可控異常 checked exception(可控異常),異常和方法綁定,但是違反了開閉原則。如果在方法中拋出課可控異常,需要在拋出異常處之間的每個方法簽名中聲明該異常。如果寫一套關鍵代碼庫,可控異常有用,必須捕獲異常。但一般應用開發,依賴成本高于收益。

給出異常發生的環境說明 記錄錯誤的堆棧信息

依調用者需要定義異常類 封裝第三方調用api,確保返回的異常類型,簡化代碼

定義常規流程 特例模式,創建一個類或配置一個對象處理特例,就不用應付異常行為了。比如下面的代碼,處理業務邏輯沒有餐食消耗的時候,總賬加上補貼。

try{MealExpenses expenses.getTotal; = expenseReportDAO.getMeals();m_total = expenses.getTotal; }catch(MealExpensesNotFound e){m_total = getMealPerDiem(); }

應該把catch和業務代碼分開。定義特例的類。

public class PerDiemMealExpenses implements MealExpenses{public int getTotal(){// renturn the per diem default} }

別返回null值 避免NPE的最好方式,不返回null,返回空集合或者空對象(實際場景協同開發時很難保證)

別傳遞null值 避免NPE和其他運行時問題

第八章 邊界

使用第三方代碼 比如 java.util.Map,有一個對象想用Map做數據結構,還不要暴露細節(隱藏Map),可以寫成下面這樣。

public class Sensors{private Map sensors = new HashMap();public Sensor getById(String id){return (Sensor)sensors.get(id);} }

瀏覽和學習邊界

學習測試好處不只是免費 確保第三方程序包正常工作

使用尚不存在的代碼 先定義API,后完成代碼細節

整潔的邊界 通過引入第三方邊界接口的位置管理第三方邊界。比如 Map 包裝 或者 Adapter 模式。

第九章 單元測試

TDD 三定律 (每天編寫數十個測試用例,代碼量大,不易管理)

在編寫不能通過的單元測試前,不可編寫生產代碼。

只可編寫剛好無法通過的單元測試,不能編譯也算不通過。

只可編寫剛好足以通過當前失敗測試的生產代碼。

保持測試整潔 測試代碼和生產代碼一樣重要。測試覆蓋率越高,越不會出問題。

整潔的測試 可讀性:明確、簡潔、足夠的表達力。

構造-操作-檢驗(build-operate-check)模式。第一個環節 構造測試數據,第二個環節操作測試數據,第三個環節檢驗操作是否得到期望結果。

針對特定場景封裝api符合特定的測試語言。

測試和生產環境代碼的不同標準,測試用例可以使用 String += 的方式拼接字符串,不需要考慮性能。

每個測試用例都應該有斷言,但要限制數量。(正常開發也推薦斷言代替if、else)。每個測試用例要清晰,只測試一個概念,每個概念的斷言數量要控制。

整潔的規則 FIRST

  • first 快速。
  • independent 獨立。用例間相互獨立,單獨運行。
  • repeatable 可重復??稍诓煌h境下重復通過。
  • self-validating 自足驗證。應該有布爾值輸出,成功失敗一目了然。
  • timely 及時。用例應及時編寫。單元測試應該恰好在其通過的生產代碼之前編寫。

第十章 類

類的組織 公共靜態常量>私有靜態常量>實體變量>公共函數>私有函數(符合自頂向下原則)。封裝(保持變量和工具函數的私有性)

類應該短小 根據職責衡量代碼行數。避免一個類擁有太多職責。(類名定義含混,就可能有更多職責,模糊詞比如 Processor、Manager、Super)

  • 單一職責-SRP(Simple Responsibility Principle),類或模塊有且只有一條加以修改的理由。SRP 是面向對象設計中最為重要的概念之一,也是較為容易理解和遵循的概念之一。系統應該由許多短小的類而不是少量巨大的類組成,每個短小的類封裝一個權責,只有一個修改的原因,并與少數其他類一起協同達成期望的系統行為。
  • 內聚。類應該只有少量實體變量。盡量使用局部變量,減少成員變量。如果一個類多個變量,考慮分拆成兩個類。
  • 保持內聚,得到短小的類。這地方作者改寫了一段很長的代碼,增加了幾個類,目的就是為了讓原有的類使用更少的變量。

為了修改而組織 為了避免修改而破壞SRP原則,將原本一個類的多個方法拆成多個子類,比如 Sql.class 類中的多個方法,如果新增一個 update方法,需要修改原來的Sql類。好的方式是Abstract Class Sql ,一堆繼承Class,比如 SelectSql 、InsertSql 、這樣新增的時候就不會對原有的產生影響。有點類似設計模式中的工廠模式。

隔離修改,遵循的是依賴倒置原則(Dependency Inversion Principle)。DIP認為類應當依賴于抽象而不是具體細節。比如有一個 Class Profile,一個接口 Stock,和一個Class TokyoStock 實現于 Stock。當想調用 TokyoStock 中的方法時,Profile中代理的對象是Stock,無須知道TokyoStock 的細節。類似設計模式中的代理模式。

第十一章 系統

建造城市 較高的抽象層級—系統層級—保持整潔。

系統構造和使用分開 啟動過程和啟動之后的運行的邏輯要分離開。比如:

public Service getService{if(service == null){service = new MyServiceImpl(...); return service;} }

延遲初始化,或者單例模式都使用這種方式保證返回同一個對象。

  • 分解。main和application分開,比如啟動的時候執行對象初始化,而使用的時候無需再初始化,只需要使用即可。

  • 工廠。使用工廠模式,由工廠決定何時創建對象。隔離構造細節。

  • 依賴注入。DI(Dependency Injection)。控制反轉(Inversion of Control)是一種應用手段。將對象的管理權交給容器,同時由容器保證單一職責,返回一個該對象的代理對象。可以通過注解、配置文件等方式,最著名的就是spring了。

擴容 軟件系統應該和物理系統已于,支持遞增式地增長,只要切當地切分。

Java代理 單獨的對象或類中包裝方法調用。JDK的動態代理僅能與接口協同工作,代理類的話,需要字節碼操作庫,CGLIB、ASM、Javassist。代理會有很多相對復雜的代碼,不太容易保持整潔。而很多時候需要的是插樁的機制(AOP)。

純Java AOP 框架 解決了代碼冗長和耦合性的問題,使用 xml 或者 annotation ,清晰易于維護。

AspectJ 的方面 和aop不同,aspectj 更關注切分面。

測試驅動系統架構 軟件避免先過度設計,有效切分各個關注面。最佳的系統架構由模塊化的關注面領域組成,每個關注面均用java(或其他語言)的對象實現。

優化決策 模塊化和關注面切分成就了分散化管理和決策。模塊化降低了決策的復雜性。

明智使用添加了可論證價值的標準 建立標準,事半功倍。

系統需要領域特定語言 領域特定語言(Domain-Specific language,DSL),減少不同領域概念的代碼"鴻溝",降低翻譯失誤的風險。

第十二章 迭進

通過迭進設計達到整潔目的 Kent Beck 簡單設計的四條規則:

  • 運行所有測試
  • 不可重復
  • 表達程序員的意圖
  • 盡可能減少類和方法數量
  • 以上規則按其重要程度排列

現在這個規則在測試驅動或者代碼測試用例算基本的了,還需要考慮實際的業務場景、抽象等,甚至會有一個專門用于測試的項目。

簡單設計規則1:運行所有測試 遵循SRP、DIP,減少耦合

簡單設計規則2~4:重構 測試消除了對清理代碼就會破壞代碼的恐懼。減少重復、保證表達力、盡可能減少類和方法的數量。

不可重復 抽取重復方法可能違反了SRP原則,可以考慮使用模板方法模式。

表達力 代碼應當清晰表達作者的意圖,函數名稱易于理解,單元測試也具有表達性。否則可能連自己都看不懂自己寫的代碼。

盡可能少的類和方法 其實 SRP和DIP 本身是有沖突的,只有找到符合自己業務場景的設計才是最好的。保持短小、消除重復,同時也要避免過度設計。

第十三章 并發編程

關于并發編程推薦大家看下 《JAVA并發編程實戰》

為什么要并發 并發是一種解耦策略。把做什么和何時做分解開。servlet是單線程模式,需要手動對接口開發保證QPS。

關于并發:

  • 并發改進性能:看場景,跟鎖的級別、系統、內核都有關系等等,有時能改進性能。
  • 并發程序需要修改設計:并發利用性能換取時間的方式很可能導致線程安全問題,需要好好設計。
  • Web、EJB等容器也需要理解并發
  • 并發在性能和編寫額外代碼增加一些開銷
  • 正確的并發是復雜的,并發缺陷不易重現
  • 并發常常需要對設計策略的根本性修改

挑戰

public class X{private int lastIdUsed;public int getNextId(){return ++lastIdUsed;} }

并發場景下的得到 的lastIdUsed 可能是多種結果。

并發防御原則

  • 單一權責原則(SRP) :并發相關代碼有自己的開發、修改和調優生命周期。建議分離并發相關代碼和其他代碼。
  • 推論:限制數據作用域:對并發區域加 synchronized 關鍵字(謹記數據封裝;嚴格限制對可能被共享數據的訪問)
  • 推論:使用數據副本:多線程復制對象并以只讀方式對待
  • 推論:線程盡可能獨立:嘗試將數據分解到獨立線程操作

了解Java庫 使用線程安全的類庫(juc)、盡可能使用非鎖解決方案

了解執行模型

  • 生產者-消費者模型:生產者和消費者之間的隊列是一種限定資源(并發環境中有固定尺寸或數量的資源)。例如 數據庫連接
  • 讀者-作者模型:作者線程傾向于長期鎖定許多讀者線程(讀者線程需要更新的共享資源),會導致吞吐量的問題。
  • 宴席哲學家:典型的死鎖問題

警惕同步方法之間的依賴 :避免使用一個共享對象的多個方法。如果必須使用的話:

  • 基于客戶端的鎖定:客戶端代碼在調用第一個方法前鎖定服務器,確保鎖的范圍覆蓋了調用最后一個方法的代碼。
  • 基于服務端的鎖定:在服務端內創建鎖定服務端的方法,調用所有方法,然后解鎖。
  • 適配服務端:創建執行鎖定的中間層?;诜斩随i定的例子,但不修改原始服務端代碼。

保持同步區域微小 鎖會帶來延遲和額外開銷。盡可能減小同步區域。

很難編寫正確的關閉代碼 平靜關閉很難做到。常見問題與死鎖有關,線程一直等待永遠不會到來的信號。比如生產-消費模型,如果生產者線程收到信號迅速關閉,消費者線程可能還在等待生產者線程發來消息。盡早考慮關閉問題。

測試線程代碼 編寫有潛力暴露問題的測試,在不同的編程配置、系統配置和負載條件下頻繁運行。

  • 將偽失敗看作可能的線程問題:編程代碼導致”不可能失敗的“。不要將系統錯誤歸咎于偶發事件。
  • 先使非線程代碼可工作:不要同時追蹤非線程缺陷和線程缺陷。
  • 編寫可插拔的線程代碼:目的是不同的配置環境下運行。
  • 編寫可調整的線程代碼:要獲得良好的線程平衡,常常需要試錯。
  • 運行多于處理器數量的線程:任務交換越頻繁,越有可能找到錯過臨界區域或導致死鎖的代碼。
  • 在不同平臺上運行:盡早并經常在所有目標平臺運行線程代碼(不同操作系統有著不同線程策略)
  • 裝置試錯代碼:增加 Object.wait()、Object.sleep() 等方法的調用,改變代碼執行順序。兩種方法:硬編碼、自動化。
  • 硬編碼:例如。
public synchronized String nextUrlOrNull(){if(hasNext()){String url = urlGenerator.next();Thread.yield(); // inserted for testingupdateHasNext();return url;}return null; }

插入對yield 的調用,將改變代碼的執行路徑,有可能導致代碼在以前未失敗的地方失敗。如果代碼出錯,并非是 yield()方法調用(yield 只是當前線程讓步,不應該影響過程或結果)。這種方式的弊端:硬編碼還得找到合適的調用時機,很可能找不到缺陷。

  • 自動化:使用第三方框架裝置代碼。如 Aspect-Oriented Framework、CGLIB 或 ASM。
public synchronized String nextUrlOrNull){if(hasNext()){ThreadJinglePoing.jiggle();String url = urlGenerator.next();ThreadJinglePoing.jiggle();updateHasNext();ThreadJinglePoing.jiggle();return url;}return null; }

ThreadJinglePoing 類可以多種實現,生產環境什么都不做,測試環境加隨機數、睡眠或者讓步。重點是讓不同線程異動。1

第十四章 逐步改進

這一章節作者用了一個代碼示例講解了 循序漸進的代碼過程,好的代碼不是一次就完成的,需要多次改進。不能每次完成需求,就投入到下一個需求,而不考慮當前代碼的優雅和可維護性。

這個章節是純代碼優化分析,代碼草稿 ->縮短程序(注意命名方式、函數大小和代碼格式) -> 派生基類 ->

采用TDD(重構要兼容之前的case) -> 派生變量和類移植 -> 隱藏異常細節 -> 公共方法抽取 -> 清理重復代碼 ->

減少原始類中的成員變量 -> 封裝異常(ApiException) -> Args 類處理異常(ErrorMessage)是否合理?

可以看一下這個人的博客,有修改后的代碼示例。

https://waltyou.github.io/Clean-Code-Practice/

小結:永遠保持代碼整潔和簡單。

第十五章 Junit內幕

以 ComparisonCompactor 為例,講述了優化過程。

重構常會推翻另一次重構,重構是一種不停試錯的迭代過程。

junit的源碼地址,感興趣的可以看下:https://github.com/junit-team/junit4

第十六章 重構SerialDate

重構 org.jfree.date.SerialDate

抽象工廠—> 完善用例 —> 優化結構(使用枚舉) —> 簡化函數 —> 修改函數名 —> 抽取公共函數

源碼網上也找不到,這一章跳過。

第十七章 味道與啟發

注釋

  • 不恰當的信息: 作者、最后修改時間、SPR等數據不應該在數據中出現(實際業務中,作者還是需要的,最好留下聯系方式)
  • 廢棄的注釋 :過時、無關或者不正確的都屬廢棄注釋。
  • 冗余注釋:比如 i++; // i 自增
  • 糟糕的注釋: 使用正確的語法和拼寫。(一般會統一注釋格式)
  • 注釋掉的代碼:直接刪除(和阿里規范一樣)

環境

  • 需要多步才能實現的構建:構建系統應該是單步的小操作,不需要從源代碼一點點遷出代碼。比如 maven/gradle/ant 都可以實現。
  • 需要多步才能做到的測試:單個指令可以運行全部單元測試。

函數

  • 過多的函數:函數盡量少,不超過3個。
  • 輸出參數:如果想修改參數,直接修改對應的對象狀態。
  • 標識參數:不要使用boolean類型參數
  • 死函數:刪除永遠不會調用的代碼

一般性問題

  • 一個源文件中存在多種語言: 盡量減少源文件中額外語言的數量和范圍。(前后端分離了,別再在項目里寫jsp和html了)

  • 明顯的行為未被實現:遵循 “最小驚詫原則”,比如定義日期api的時候,期望字符串Monday翻譯為Day.Monday,使用戶依靠對函數的直覺,不需要閱讀源代碼。

  • 不正確的邊界行為:設立正確的邊界,完善單元測試。

  • 忽視安全:編譯器警告要注意。(規約掃描沒有警告的代碼才可以提交)

  • 重復:DIY(Dont’t repeat yourself),重復意味著遺漏了抽象。

  • 在錯誤的抽象層級上的代碼:通常抽象類容納高層級概念,派生類容納底層級概念。高層級的基類應該對常量、變量或工具函數一無所知。比如下面的代碼,percentFull(“多滿”)不應該出現在基類中,可以放到派生接口中。

    pubilc interfece Stack{Object pop();void push(Object o);double percentFull(); }
  • 基類依賴于派生類:基類應該對派生類一無所知,也有例外,基類擁有在派生類中選擇的代碼。(以springboot-data-redis 為例,2.0以前的連接默認使用jedis,2.0以后使用的lettuce。)

  • 信息過多:設計良好的模塊有非常小的接口,耦合度低。隱藏數據、工具函數、常量、臨時變量。不要創建大量方法和大量實體變量的類。不要為子類創建大量受保護變量和函數。盡力保持接口緊湊。通過限制信息來控制耦合。

  • 死代碼:刪除永不執行的代碼。

  • 垂直分隔:變量和函數在靠近被使用的地方定義。

  • 前后不一致:從一而終。比如 特定函數中名為reponse 持有HttpServletResponse對象,那么用到HttpServletResponse的函數也用同樣的變量名。

  • 混淆視聽:沒用的變量、函數、注釋信息等等,保持文件整潔。

  • 人為耦合:不相互依賴的東西不該耦合。應該花點時間研究變量、常量的放置位置。

  • 特性依戀(G14):沒有必要在三方類中定義某個類對象,(會暴露類的內部情形),破壞了面對對象原則。

  • 選擇算子參數:盡量不使用選擇函數的參數,比如 true/false、枚舉、整數等,比如 true/fasle 在整個funcation里做條件判斷,不如 true,function A/false function B 這樣,拆分單獨的方法

  • 晦澀的意圖:短小節湊的代碼不一定易懂,比如下面。

    public int m_otCalc(){return iThsWkd * iThsRte + (int) Math.round(0.5 * iThsRte + Math.max(0, iThsWkd - 400)) }
  • 位置錯誤的權責:代碼應該放在讀者自然而然期待它所在的地方。比如一般寫的配置類都會放到 config 包下面。

  • 不恰當的靜態方法:
    比如 Math.max(double a,double b) 是個很好的靜態方法,不在單個實體上操作。通常傾向于選用非靜態方法。如果有疑問就用非靜態函數。如果使用靜態函數,確保不要有多態行為的機會。

  • 使用解釋性變量:比如下面代碼,如果用兩個中間變量定義一下,更容易理解。

    Matcher match = headerPattern.matcher(line); if(match.find()) {String key = match.group(1);String value = match.group(2);headers.put(key,toLowerCase(), value); }
  • 函數名稱應該表達其行為:比如下面函數 看不出函數行為,應該改成 addDaysTo 或 increaseByDays

    Date newDate = date.add(5);
  • 理解算法:不僅要通過全部測試,還要找出最佳方案。

  • 把邏輯依賴改為物理依賴:比如下面的類是一個統計報表的類,PAGE_SIZE 不應該是該類中的變量,這是一種錯誤的邏輯依賴,應該改為物理依賴,通過 HourlyReport 中名為 getMaxPageSize()方法物理化這種依賴。

    public class HourlyReporter{private HourlyReportFormatter formatter;private List<LineItem> page;private final int PAGE_SIZE = 55;public HourlyReporter(HourlyReportFormatter formatter){this.formatter = formatter;page = new ArrayList<LineItem>();}//... }
  • 用多態替代 If/Else 或 Switch/Case:
    對于給定的選擇類型,不應有多于一個switch 語句。在那個switch語句中的多個case,必須創建多態對象,取代系統中其他類似switch語句??梢允褂貌呗阅J?。

  • 遵循標準約定:團隊的編碼標準。

  • 用命令常量替代魔術數:定義常量代替魔法值

  • 準確:比如對待金額的地方,需要明確保留幾位(使用 Bigdecimal)

  • 結構甚于約定:基類的抽象方法比良好命名的枚舉強(抽象方法必須按照基類的實現)

  • 封裝條件:if(shouldBeDeleted(timer)) 要好于 if(timer.hasExpired()) && !(timer.isRecurrent())

  • 避免否定性條件:if(buffer.shouldCompact()) 要好于 if(!buffer.shouldNotCompact())

  • 函數只做一件事:一個 “支付函數” 需要做,遍歷雇員 -> 檢查是否給雇員工資 -> 支付薪水,改成 3 個函數。

  • 掩蔽時序耦合:函數的調用順序和放置的地方相關聯。

  • 別隨意:構建代碼需要理由,而且理由應與代碼結構相契合。比如 VariableExpandingWidgetRoot 作為公共的工具類,不應該定義在 AliasLinkWidget 類里。

    public class AliasLinkWidget extends ParentWidget{public static class VariableExpandingWidgetRoot{//...} }
  • 封裝邊界條件:把處理邊界條件的代碼集中到一處。下面的代碼中 level + 1 出現了兩次,封裝到局部變量。

    if(level + 1 < tags.length){parts = new Parse(body, tags, level + 1,offset + endTag); }
  • 函數應該只在一個抽象層級上:函數中的語句應該在同一抽象層級上,該層級應該是函數名所示操作的下一層。下面方法混雜了至少兩個抽象層級,第一個是橫線有尺寸,第二個是hr標記自身的語法。

    public String render(){StringBuffer html = new StringBuffer("<hr");if(size > 0){html.append(" size=\"").append(size + 1).append("\"");html.append(“>”);return html.toString();} }

    重構后:

    public String render(){HtmlTag hr = new HtmlTag("hr");if (extraDashes > 0){hr.addAttribute("size",hrSize(extraDashes));return hr.html();} }private String hrSize(int height){int hrSize = height + 1;return String.format("%d", hrSize); }
  • 在較高層級放置可配置數據:較高抽象層級的默認常量或配置值,不要放到較低層級的函數中。(常量放到類頂部而變量名大寫)

  • 避免傳遞瀏覽:避免 a.getB().getC().doSomething()的代碼,應該改為 myCollaborator.doSomething()

Java

  • 通過使用通配符避免過長的導入清單:同名不同包的類需要指定名稱導入。
  • 不要繼承常量:常量放到靜態常量類里。
  • 常量 VS 枚舉:使用枚舉代替常量,枚舉能表達更多而且更靈活。

名稱

  • 采用描述性名稱:確認名稱具有描述性,比如 count、max(業務含義) 代替 i 、j、k。

  • 名稱應與抽象層級相符:方法名稱要體現抽象層級,比如 調制解調器的抽象層級的方法,
    dial 是實現類的方法名稱(電話撥號)

    public interface Modem{boolean dial(String phoneNumber);boolean disconnect; }

    應改為:connect 更能體現層級

    public interface Modem{boolean connect(String connectionLocator);boolean disconnect; }
  • 盡可能使用標準命名法:如果使用裝飾模式,類命名時加上 Decorator ,例如 AutoHangupModemDecorator。

  • 無歧義的名稱:比如說 getData() 應該具體到業務場景 getTreeDataByColor

  • 為較大作用范圍選用較長名稱:下面簡短的代碼用變量 i ,n 剛好,rollCount 代替 i 反倒混亂

    private void rollMany(int n, int pins){fori (int i = 0,i < n; i ++){g.roll(pins);} }
  • 避免編碼:不應在名稱中包括類型或作用范圍信息。比如 m_ 或者 f_ 完全無用。(redis常用前綴 業務屬性: 比如 user: use_key)

  • 名稱應該說明副作用:名稱更好的應該是 createOrReturnOos 。

    public ObjectOutputStream getOos() throws IOException(){if(m_oos == null){m_oos = new ObjectOutputStream(m_socket.getOutputStream());}return m_oos; }

測試

  • 測試不足:一套測試應該測到所有可能失敗的東西。
  • 使用覆蓋率工具:覆蓋率工具能匯報測試策略中的缺口。
  • 別略過小測試:小測試易于編寫,文檔價值高于編寫成本。
  • 被忽略的測試就是對不確定事物的疑問:可以使用注釋掉的測試或者用 @Ignore 標記測測試來表達我們對于需求的疑問。用哪種方式取決于代碼關聯代碼是否要編譯。
  • 測試邊界條件:算法在中間部分正確但邊界判斷錯誤的情形很常見。比如用 new Integer(200) == new Integer(200)
  • 全面測試相近的缺陷:趨勢傾向于扎堆。某個函數中發現一個缺陷時,最好全面測試那個函數。
  • 測試失敗的模式有啟發性:根據測試用例失敗的模式來診斷問題。
  • 測試覆蓋率的模式有啟發性:查看被或未被已通過的測試執行的代碼,往往能發現失敗的測試為何失敗。
  • 測試應該快速:慢速的測試是不會被運行的測試。

整潔代碼并非遵循一套規則寫就。專業性和技藝來自驅動規程的價值觀。

最后附上最新的阿里規范github 地址:

https://github.com/alibaba/p3c

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的《代码整洁之道 Clean Architecture》-读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。