代码重构技巧宝典,学透本篇就足够了!
本文來源:http://n5d.net/ma76k
關(guān)于重構(gòu)
為什么要重構(gòu)
1_代碼重構(gòu)漫畫.jpeg項(xiàng)目在不斷演進(jìn)過程中,代碼不停地在堆砌。如果沒有人為代碼的質(zhì)量負(fù)責(zé),代碼總是會(huì)往越來越混亂的方向演進(jìn)。當(dāng)混亂到一定程度之后,量變引起質(zhì)變,項(xiàng)目的維護(hù)成本已經(jīng)高過重新開發(fā)一套新代碼的成本,想要再去重構(gòu),已經(jīng)沒有人能做到了。
造成這樣的原因往往有以下幾點(diǎn):
編碼之前缺乏有效的設(shè)計(jì)
成本上的考慮,在原功能堆砌式編程
缺乏有效代碼質(zhì)量監(jiān)督機(jī)制
對于此類問題,業(yè)界已有有很好的解決思路:通過持續(xù)不斷的重構(gòu)將代碼中的“壞味道”清除掉。
什么是重構(gòu)
重構(gòu)一書的作者M(jìn)artin Fowler對重構(gòu)的定義:
重構(gòu)(名詞):對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。重構(gòu)(動(dòng)詞):使用一系列重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。
根據(jù)重構(gòu)的規(guī)模可以大致分為大型重構(gòu)和小型重構(gòu):
大型重構(gòu):對頂層代碼設(shè)計(jì)的重構(gòu),包括:系統(tǒng)、模塊、代碼結(jié)構(gòu)、類與類之間的關(guān)系等的重構(gòu),重構(gòu)的手段有:分層、模塊化、解耦、抽象可復(fù)用組件等等。這類重構(gòu)的工具就是我們學(xué)習(xí)過的那些設(shè)計(jì)思想、原則和模式。這類重構(gòu)涉及的代碼改動(dòng)會(huì)比較多,影響面會(huì)比較大,所以難度也較大,耗時(shí)會(huì)比較長,引入bug的風(fēng)險(xiǎn)也會(huì)相對比較大。
小型重構(gòu):對代碼細(xì)節(jié)的重構(gòu),主要是針對類、函數(shù)、變量等代碼級別的重構(gòu),比如規(guī)范命名和注釋、消除超大類或函數(shù)、提取重復(fù)代碼等等。小型重構(gòu)更多的是使用統(tǒng)一的編碼規(guī)范。這類重構(gòu)要修改的地方比較集中,比較簡單,可操作性較強(qiáng),耗時(shí)會(huì)比較短,引入bug的風(fēng)險(xiǎn)相對來說也會(huì)比較小。什么時(shí)候重構(gòu) 新功能開發(fā)、修bug或者代碼review中出現(xiàn)“代碼壞味道”,我們就應(yīng)該及時(shí)進(jìn)行重構(gòu)。持續(xù)在日常開發(fā)中進(jìn)行小重構(gòu),能夠降低重構(gòu)和測試的成本。
代碼的壞味道
2_代碼常見問題.png代碼重復(fù)
實(shí)現(xiàn)邏輯相同、執(zhí)行流程相同
方法過長
方法中的語句不在同一個(gè)抽象層級
邏輯難以理解,需要大量的注釋
面向過程編程而非面向?qū)ο?/p>
過大的類
類做了太多的事情
包含過多的實(shí)例變量和方法
類的命名不足以描述所做的事情
邏輯分散
發(fā)散式變化:某個(gè)類經(jīng)常因?yàn)椴煌脑蛟诓煌姆较蛏习l(fā)生變化
散彈式修改:發(fā)生某種變化時(shí),需要在多個(gè)類中做修改
嚴(yán)重的情結(jié)依戀
某個(gè)類的方法過多的使用其他類的成員
數(shù)據(jù)泥團(tuán)/基本類型偏執(zhí)
兩個(gè)類、方法簽名中包含相同的字段或參數(shù)
應(yīng)該使用類但使用基本類型,比如表示數(shù)值與幣種的Money類、起始值與結(jié)束值的Range類
不合理的繼承體系
繼承打破了封裝性,子類依賴其父類中特定功能的實(shí)現(xiàn)細(xì)節(jié)
子類必須跟著其父類的更新而演變,除非父類是專門為了擴(kuò)展而設(shè)計(jì),并且有很好的文檔說明
過多的條件判斷
過長的參數(shù)列
臨時(shí)變量過多
令人迷惑的暫時(shí)字段
某個(gè)實(shí)例變量僅為某種特定情況而設(shè)置
將實(shí)例變量與相應(yīng)的方法提取到新的類中
純數(shù)據(jù)類
僅包含字段和訪問(讀寫)這些字段的方法
此類被稱為數(shù)據(jù)容器,應(yīng)保持最小可變性
不恰當(dāng)?shù)拿?/p>
命名無法準(zhǔn)確描述做的事情
命名不符合約定俗稱的慣例
過多的注釋
壞代碼的問題
難以復(fù)用
系統(tǒng)關(guān)聯(lián)性過多,導(dǎo)致很難分離可重用部分
難于變化
一處變化導(dǎo)致其他很多部分的修改,不利于系統(tǒng)穩(wěn)定
難于理解
命名雜亂,結(jié)構(gòu)混亂,難于閱讀和理解
難以測試
分支、依賴較多,難以覆蓋全面
什么是好代碼
3_代碼質(zhì)量如何衡量.jpg代碼質(zhì)量的評價(jià)有很強(qiáng)的主觀性,描述代碼質(zhì)量的詞匯也有很多,比如可讀性、可維護(hù)性、靈活、優(yōu)雅、簡潔。這些詞匯是從不同的維度去評價(jià)代碼質(zhì)量的。其中,可維護(hù)性、可讀性、可擴(kuò)展性又是提到最多的、最重要的三個(gè)評價(jià)標(biāo)準(zhǔn)。
要寫出高質(zhì)量代碼,我們就需要掌握一些更加細(xì)化、更加能落地的編程方法論,這就包含面向?qū)ο笤O(shè)計(jì)思想、設(shè)計(jì)原則、設(shè)計(jì)模式、編碼規(guī)范、重構(gòu)技巧等。
如何重構(gòu)
SOLID原則
4_SOLID原則.png單一職責(zé)原則
一個(gè)類只負(fù)責(zé)完成一個(gè)職責(zé)或者功能,不要存在多于一種導(dǎo)致類變更的原因。
單一職責(zé)原則通過避免設(shè)計(jì)大而全的類,避免將不相關(guān)的功能耦合在一起,來提高類的內(nèi)聚性。同時(shí),類職責(zé)單一,類依賴的和被依賴的其他類也會(huì)變少,減少了代碼的耦合性,以此來實(shí)現(xiàn)代碼的高內(nèi)聚、松耦合。但是,如果拆分得過細(xì),實(shí)際上會(huì)適得其反,反倒會(huì)降低內(nèi)聚性,也會(huì)影響代碼的可維護(hù)性。
開放-關(guān)閉原則
添加一個(gè)新的功能,應(yīng)該是通過在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊、類、方法、屬性等),而非修改已有代碼(修改模塊、類、方法、屬性等)的方式來完成。
開閉原則并不是說完全杜絕修改,而是以最小的修改代碼的代價(jià)來完成新功能的開發(fā)。
很多設(shè)計(jì)原則、設(shè)計(jì)思想、設(shè)計(jì)模式,都是以提高代碼的擴(kuò)展性為最終目的的。特別是 23 種經(jīng)典設(shè)計(jì)模式,大部分都是為了解決代碼的擴(kuò)展性問題而總結(jié)出來的,都是以開閉原則為指導(dǎo)原則的。最常用來提高代碼擴(kuò)展性的方法有:多態(tài)、依賴注入、基于接口而非實(shí)現(xiàn)編程,以及大部分的設(shè)計(jì)模式(比如,裝飾、策略、模板、職責(zé)鏈、狀態(tài))。
里氏替換原則
子類對象(object of subtype/derived class)能夠替換程序(program)中父類對象(object of base/parent class)出現(xiàn)的任何地方,并且保證原來程序的邏輯行為(behavior)不變及正確性不被破壞。
子類可以擴(kuò)展父類的功能,但不能改變父類原有的功能
父類中凡是已經(jīng)實(shí)現(xiàn)好的方法(相對于抽象方法而言),實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類必須遵從這些契約,但是如果子類對這些非抽象方法任意修改,就會(huì)對整個(gè)繼承體系造成破壞。
接口隔離原則
調(diào)用方不應(yīng)該依賴它不需要的接口;一個(gè)類對另一個(gè)類的依賴應(yīng)該建立在最小的接口上。接口隔離原則提供了一種判斷接口的職責(zé)是否單一的標(biāo)準(zhǔn):通過調(diào)用者如何使用接口來間接地判定。如果調(diào)用者只使用部分接口或接口的部分功能,那接口的設(shè)計(jì)就不夠職責(zé)單一。
依賴反轉(zhuǎn)原則
高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。
迪米特法則
一個(gè)對象應(yīng)該對其他對象保持最少的了解
合成復(fù)用原則
盡量使用合成/聚合的方式,而不是使用繼承。
單一職責(zé)原則告訴我們實(shí)現(xiàn)類要職責(zé)單一;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口編程;接口隔離原則告訴我們在設(shè)計(jì)接口的時(shí)候要精簡單一;迪米特法則告訴我們要降低耦合。而開閉原則是總綱,告訴我們要對擴(kuò)展開放,對修改關(guān)閉。
設(shè)計(jì)模式
設(shè)計(jì)模式:軟件開發(fā)人員在軟件開發(fā)過程中面臨的一般問題的解決方案。這些解決方案是眾多軟件開發(fā)人員經(jīng)過相當(dāng)長的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來的。每種模式都描述了一個(gè)在我們周圍不斷重復(fù)發(fā)生的問題,以及該問題的核心解決方案。
創(chuàng)建型:主要解決對象的創(chuàng)建問題,封裝復(fù)雜的創(chuàng)建過程,解耦對象的創(chuàng)建代碼和使用代碼
結(jié)構(gòu)型:主要通過類或?qū)ο蟮牟煌M合,解耦不同功能的耦合
行為型:主要解決的是類或?qū)ο笾g的交互行為的耦合
| 創(chuàng)建型 | 單例 | 一個(gè)類只允許創(chuàng)建一個(gè)實(shí)例或?qū)ο?#xff0c;并為其提供一個(gè)全局的訪問點(diǎn) | 無狀態(tài)/全局唯一/控制資源訪問 |
| 工廠 | 創(chuàng)建一個(gè)或者多個(gè)相關(guān)的對象,而使用者不用關(guān)心具體的實(shí)現(xiàn)類 | 分離對象的創(chuàng)建和使用 | |
| 建造者 | 用于創(chuàng)建一種類型的復(fù)雜對象,通過設(shè)置不同的可選參數(shù)進(jìn)行“定制化” | 對象的構(gòu)造參數(shù)較多且多數(shù)可選 | |
| 原型 | 通過復(fù)制已有對象來創(chuàng)建新的對象 | 對象的創(chuàng)建成本較大且同一類的不同對象之前差別不大 | |
| 結(jié)構(gòu)型 | 代理 | 不改變原始類和不使用繼承的情況下,通過引入代理類來給原始類附加功能 | 增加代理訪問,比如監(jiān)控、緩存、限流、事務(wù)、RPC |
| 裝飾者 | 不改變原始類和不使用繼承的情況下,通過組合的方式動(dòng)態(tài)擴(kuò)展原始類的功能 | 動(dòng)態(tài)擴(kuò)展類的功能 | |
| 適配器 | 不改變原始類的情況下,通過組合的方式使其適配新的接口 | 復(fù)用現(xiàn)有類,但與期望接口不適配 | |
| 橋接 | 當(dāng)類存在多個(gè)獨(dú)立變化的維度時(shí),通過組合的方式使得其可以獨(dú)立進(jìn)行擴(kuò)展 | 存在多個(gè)維度的繼承體系時(shí) | |
| 門面 | 為子系統(tǒng)中一組接口定義一個(gè)更高層的接口,使得子系統(tǒng)更加容易使用 | 解決接口復(fù)用性(細(xì)粒度)與接口易用性(粗粒度)的矛盾 | |
| 組合 | 將對象組合成樹形結(jié)構(gòu)以表示部分-整體的層次結(jié)構(gòu),統(tǒng)一單個(gè)對和組合對象的處理邏輯 | 滿足部分與整體這種樹形結(jié)構(gòu) | |
| 享元 | 運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度的對象 | 當(dāng)系統(tǒng)存在大量的對象,這些對象的很多字段取值范圍固定 | |
| 行為型 | 觀察者 | 多個(gè)觀察者監(jiān)聽同一主題對象,當(dāng)主題對象狀態(tài)發(fā)生變化時(shí)通知所有觀察者,使它們能夠自動(dòng)更新自己 | 解耦事件創(chuàng)建者與接收者 |
| 模板 | 定義一個(gè)操作中算法的骨架,將某些步驟實(shí)現(xiàn)延遲到子類中 | 解決復(fù)用與擴(kuò)展問題 | |
| 策略 | 定義一組算法類,將每個(gè)算法分別封裝起來,使得它們可以互相替換 | 消除各種if-else分支判斷 解耦策略的定義、創(chuàng)建、使用 | |
| 狀態(tài) | 允許一個(gè)對象在其內(nèi)部狀態(tài)改變的時(shí)候改變其行為 | 分離對象的狀態(tài)與行為 | |
| 職責(zé)鏈 | 將一組對象連成一條鏈,請求沿著該鏈傳遞,直到某個(gè)對象能夠處理它為止 | 解耦請求的發(fā)送者與接收者 | |
| 迭代器 | 提供一種方法順序訪問一個(gè)集合對象的各個(gè)元素,但不暴露該對象的內(nèi)部表示 | 解耦集合對象的內(nèi)部表示與遍歷訪問 | |
| 訪問者 | 封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中各元素的操作,在不改變數(shù)據(jù)結(jié)構(gòu)的前提下,定義作用于這些元素的新操作。 | 分離對象的數(shù)據(jù)結(jié)構(gòu)與行為 | |
| 備忘錄 | 在不違背封裝原則的前提下,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài),以便之后恢復(fù)對象為先前的狀態(tài) | 用于對象的備份與恢復(fù) | |
| 命令 | 將不同的請求封裝成對應(yīng)的命令對象,對命令的執(zhí)行進(jìn)行控制且對使用方透明 | 用于控制命令的執(zhí)行,比如異步、延遲、排隊(duì)、撤銷、存儲(chǔ)與撤銷 | |
| 解釋器 | 為某個(gè)語言定義它的語法表示,并定義一個(gè)解釋器來處理這個(gè)語法 | 用于編譯器、規(guī)則引擎、正則表達(dá)式等特定場景 | |
| 中介 | 定義一個(gè)單獨(dú)的中介對象,來封裝一組對象之間的交互,避免對象之間的直接交互 | 使各個(gè)對象不需要顯式地相互引用,從而使其耦合松散 |
代碼分層
image.png模塊結(jié)構(gòu)說明
server_main:配置層,負(fù)責(zé)整個(gè)項(xiàng)目的module管理,maven配置管理、資源管理等;
server_application:應(yīng)用接入層,承接外部流量入口,例如:RPC接口實(shí)現(xiàn)、消息處理、定時(shí)任務(wù)等;不要在此包含業(yè)務(wù)邏輯;
server_biz:核心業(yè)務(wù)層,用例服務(wù)、領(lǐng)域?qū)嶓w、領(lǐng)域事件等
server_irepository:資源接口層,負(fù)責(zé)資源接口的暴露
server_repository:資源層,負(fù)責(zé)資源的proxy訪問,統(tǒng)一外部資源訪問,隔離變化。注意:這里強(qiáng)調(diào)的是弱業(yè)務(wù)性,強(qiáng)數(shù)據(jù)性;
server_common:公共層,vo、工具等
代碼開發(fā)要遵守各層的規(guī)范,并注意層級之間的依賴關(guān)系。
命名規(guī)范
一個(gè)好的命名應(yīng)該要滿足以下兩個(gè)約束:
準(zhǔn)確描述所做得事情
格式符合通用的慣例
如果你覺得一個(gè)類或方法難以命名的時(shí)候,可能是其承載的功能太多了,需要進(jìn)一步拆分。
約定俗稱的慣例
| 項(xiàng)目名 | 全部小寫,多個(gè)單詞用中劃線分隔‘-’ | spring-cloud |
| 包名 | 全部小寫 | com.alibaba.fastjson |
| 類名/接口名 | 單詞首字母大寫 | ParserConfig,DefaultFieldDeserializer |
| 變量名 | 首字母小寫,多個(gè)單詞組成時(shí),除首個(gè)單詞,其他單詞首字母都要大寫 | password, userName |
| 常量名 | 全部大寫,多個(gè)單詞,用'_'分隔 | CACHE_EXPIRED_TIME |
| 方法 | 同變量 | read(), readObject(), getById() |
類命名
類名使用大駝峰命名形式,類命通常使用名詞或名詞短語。接口名除了用名詞和名詞短語以外,還可以使用形容詞或形容詞短語,如 Cloneable,Callable 等,表示實(shí)現(xiàn)該接口的類有某種功能或能力。
| 抽象類 | Abstract 或者 Base 開頭 | BaseUserService |
| 枚舉類 | Enum 作為后綴 | GenderEnum |
| 工具類 | Utils 作為后綴 | StringUtils |
| 異常類 | Exception 結(jié)尾 | RuntimeException |
| 接口實(shí)現(xiàn)類 | 接口名+ Impl | UserServiceImpl |
| 設(shè)計(jì)模式相關(guān)類 | Builder,Factory 等 | 當(dāng)使用到設(shè)計(jì)模式時(shí),需要使用對應(yīng)的設(shè)計(jì)模式作為后綴,如 ThreadFactory |
| 處理特定功能的類 | Handler,Predicate, Validator | 表示處理器,校驗(yàn)器,斷言,這些類工廠還有配套的方法名如 handle,predicate,validate |
| 特定層級的類 | Controller,Service,ServiceImpl,Dao 后綴 | UserController, UserServiceImpl,UserDao |
| 特定層級的值對象 | Ao, Param, Vo,Config, Message | Param調(diào)用入?yún)?#xff1b;Ao為thrift返回結(jié)果;Vo通用值對象;Config配置類;Message為MQ消息 |
| 測試類 | Test 結(jié)尾 | UserServiceTest, 表示用來測試 UserService 類的 |
方法命名
方法命名采用小駝峰的形式,首字小寫,往后的每個(gè)單詞首字母都要大寫。和類名不同的是,方法命名一般為動(dòng)詞或動(dòng)詞短語,與參數(shù)或參數(shù)名共同組成動(dòng)賓短語,即動(dòng)詞 + 名詞。一個(gè)好的函數(shù)名一般能通過名字直接獲知該函數(shù)實(shí)現(xiàn)什么樣的功能。
| 返回真?zhèn)沃?/td> | is/can/has/needs/should | isValid/canRemove |
| 用于檢查 | ensure/validate | ensureCapacity/validateInputs |
| 按需執(zhí)行 | IfNeeded/try/OrDefault/OrElse | drawIfNeeded/tryCreate/getOrDefault |
| 數(shù)據(jù)相關(guān) | get/search/save/update/batchSave/ batchUpdate/saveOrUpdateselect /insert/update/delete | getUserById/searchUsersByCreateTime |
| 生命周期 | initialize/pause/stop/destroy | initialize/pause/onPause/stop/onStop |
| 常用動(dòng)詞對 | split/join、inject/extract、bind/seperate、 increase/decrease、lanch/run、observe/listen、build/publish、 encode/decode、submit/commit、push/pull、enter/exit、 expand/collapse、encode/decode |
重構(gòu)技巧
提煉方法
多個(gè)方法代碼重復(fù)、方法中代碼過長或者方法中的語句不在一個(gè)抽象層級。方法是代碼復(fù)用的最小粒度,方法過長不利于復(fù)用,可讀性低,提煉方法往往是重構(gòu)工作的第一步。
意圖導(dǎo)向編程:把處理某件事的流程和具體做事的實(shí)現(xiàn)方式分開。
把一個(gè)問題分解為一系列功能性步驟,并假定這些功能步驟已經(jīng)實(shí)現(xiàn)
我們只需把把各個(gè)函數(shù)組織在一起即可解決這一問題
在組織好整個(gè)功能后,我們在分別實(shí)現(xiàn)各個(gè)方法函數(shù)
以函數(shù)對象取代函數(shù)
將函數(shù)放進(jìn)一個(gè)單獨(dú)對象中,如此一來局部變量就變成了對象內(nèi)的字段。然后你可以在同一個(gè)對象中將這個(gè)大型函數(shù)分解為多個(gè)小型函數(shù)。
引入?yún)?shù)對象
方法參數(shù)比較多時(shí),將參數(shù)封裝為參數(shù)對象
移除對參數(shù)的賦值
public?int?discount(int?inputVal,?int?quantity,?int?yearToDate)?{if?(inputVal?>?50)?inputVal?-=?2;if?(quantity?>?100)?inputVal?-=?1;if?(yearToDate?>?10000)?inputVal?-=?4;return?inputVal; }public?int?discount(int?inputVal,?int?quantity,?int?yearToDate)?{?int?result?=?inputVal;if?(inputVal?>?50)?result?-=?2;?if?(quantity?>?100)?result?-=?1;?if?(yearToDate?>?10000)?result?-=?4;?return?result;? } 復(fù)制代碼將查詢與修改分離
任何有返回值的方法,都不應(yīng)該有副作用
不要在convert中調(diào)用寫操作,避免副作用
常見的例外:將查詢結(jié)果緩存到本地
移除不必要臨時(shí)變量
臨時(shí)變量僅使用一次或者取值邏輯成本很低的情況下
引入解釋性變量
將復(fù)雜表達(dá)式(或其中一部分)的結(jié)果放進(jìn)一個(gè)臨時(shí)變量,以此變量名稱來解釋表達(dá)式用途
if?((platform.toUpperCase().indexOf("MAC")?>?-1)?&&?(browser.toUpperCase().indexOf("IE")?>?-1)?&&?wasInitialized()?&&?resize?>?0)?{???//?do?something? }?final?boolean?isMacOs?=?platform.toUpperCase().indexOf("MAC")?>?-1;? final?boolean?isIEBrowser?=?browser.toUpperCase().indexOf("IE")?>?-1;? final?boolean?wasResized?=?resize?>?0;? if?(isMacOs?&&?isIEBrowser?&&?wasInitialized()?&&?wasResized)?{???//?do?something? } 復(fù)制代碼使用衛(wèi)語句替代嵌套條件判斷
把復(fù)雜的條件表達(dá)式拆分成多個(gè)條件表達(dá)式,減少嵌套。嵌套了好幾層的if - then-else語句,轉(zhuǎn)換為多個(gè)if語句
//未使用衛(wèi)語句 public?void?getHello(int?type)?{if?(type?==?1)?{return;}?else?{if?(type?==?2)?{return;}?else?{if?(type?==?3)?{return;}?else?{setHello();}}} }?//使用衛(wèi)語句 public?void?getHello(int?type)?{if?(type?==?1)?{return;}if?(type?==?2)?{return;}if?(type?==?3)?{return;}setHello(); } 復(fù)制代碼使用多態(tài)替代條件判斷斷
當(dāng)存在這樣一類條件表達(dá)式,它根據(jù)對象類型的不同選擇不同的行為。可以將這種表達(dá)式的每個(gè)分支放進(jìn)一個(gè)子類內(nèi)的復(fù)寫函數(shù)中,然后將原始函數(shù)聲明為抽象函數(shù)。
public?int?calculate(int?a,?int?b,?String?operator)?{int?result?=?Integer.MIN_VALUE;if?("add".equals(operator))?{result?=?a?+?b;}?else?if?("multiply".equals(operator))?{result?=?a?*?b;}?else?if?("divide".equals(operator))?{result?=?a?/?b;}?else?if?("subtract".equals(operator))?{result?=?a?-?b;}return?result; } 復(fù)制代碼當(dāng)出現(xiàn)大量類型檢查和判斷時(shí),if else(或switch)語句的體積會(huì)比較臃腫,這無疑降低了代碼的可讀性。另外,if else(或switch)本身就是一個(gè)“變化點(diǎn)”,當(dāng)需要擴(kuò)展新的類型時(shí),我們不得不追加if else(或switch)語句塊,以及相應(yīng)的邏輯,這無疑降低了程序的可擴(kuò)展性,也違反了面向?qū)ο蟮拈_閉原則。
基于這種場景,我們可以考慮使用“多態(tài)”來代替冗長的條件判斷,將if else(或switch)中的“變化點(diǎn)”封裝到子類中。這樣,就不需要使用if else(或switch)語句了,取而代之的是子類多態(tài)的實(shí)例,從而使得提高代碼的可讀性和可擴(kuò)展性。很多設(shè)計(jì)模式使用都是這種套路,比如策略模式、狀態(tài)模式。
public?interface?Operation?{?int?apply(int?a,?int?b);? }public?class?Addition?implements?Operation?{?@Override?public?int?apply(int?a,?int?b)?{?return?a?+?b;?}? }public?class?OperatorFactory?{private?final?static?Map<String,?Operation>?operationMap?=?new?HashMap<>();static?{operationMap.put("add",?new?Addition());operationMap.put("divide",?new?Division());//?more?operators}public?static?Operation?getOperation(String?operator)?{return?operationMap.get(operator);} }public?int?calculate(int?a,?int?b,?String?operator)?{if?(OperatorFactory?.getOperation?==?null)?{throw?new?IllegalArgumentException("Invalid?Operator");}return?OperatorFactory?.getOperation(operator).apply(a,?b); } 復(fù)制代碼使用異常替代返回錯(cuò)誤碼
非正常業(yè)務(wù)狀態(tài)的處理,使用拋出異常的方式代替返回錯(cuò)誤碼
不要使用異常處理用于正常的業(yè)務(wù)流程控制
異常處理的性能成本非常高
盡量使用標(biāo)準(zhǔn)異常
避免在finally語句塊中拋出異常
如果同時(shí)拋出兩個(gè)異常,則第一個(gè)異常的調(diào)用棧會(huì)丟失
finally塊中應(yīng)只做關(guān)閉資源這類的事情
引入斷言
某一段代碼需要對程序狀態(tài)做出某種假設(shè),以斷言明確表現(xiàn)這種假設(shè)。
不要濫用斷言,不要使用它來檢查“應(yīng)該為真”的條件,只使用它來檢查“一定必須為真”的條件
如果斷言所指示的約束條件不能滿足,代碼是否仍能正常運(yùn)行?如果可以就去掉斷言
引入Null對象或特殊對象
當(dāng)使用一個(gè)方法返回的對象時(shí),而這個(gè)對象可能為空,這個(gè)時(shí)候需要對這個(gè)對象進(jìn)行操作前,需要進(jìn)行判空,否則就會(huì)報(bào)空指針。當(dāng)這種判斷頻繁的出現(xiàn)在各處代碼之中,就會(huì)影響代碼的美觀程度和可讀性,甚至增加Bug的幾率。
空引用的問題在Java中無法避免,但可以通過代碼編程技巧(引入空對象)來改善這一問題。
//空對象的例子 public?class?OperatorFactory?{?static?Map<String,?Operation>?operationMap?=?new?HashMap<>();?static?{?operationMap.put("add",?new?Addition());?operationMap.put("divide",?new?Division());?//?more?operators?}?public?static?Optional<Operation>?getOperation(String?operator)?{?return?Optional.ofNullable(operationMap.get(operator));?}? }? public?int?calculate(int?a,?int?b,?String?operator)?{?Operation?targetOperation?=?OperatorFactory.getOperation(operator)?.orElseThrow(()?->?new?IllegalArgumentException("Invalid?Operator"));?return?targetOperation.apply(a,?b);? }//特殊對象的例子 public?class?InvalidOp?implements?Operation?{?@Override?public?int?apply(int?a,?int?b)??{?throw?new?IllegalArgumentException("Invalid?Operator");}? } 復(fù)制代碼提煉類
根據(jù)單一職責(zé)原則,一個(gè)類應(yīng)該有明確的責(zé)任邊界。但在實(shí)際工作中,類會(huì)不斷的擴(kuò)展。當(dāng)給某個(gè)類添加一項(xiàng)新責(zé)任時(shí),你會(huì)覺得不值得分離出一個(gè)單獨(dú)的類。于是,隨著責(zé)任不斷增加,這個(gè)類包含了大量的數(shù)據(jù)和函數(shù),邏輯復(fù)雜不易理解。
此時(shí)你需要考慮將哪些部分分離到一個(gè)單獨(dú)的類中,可以依據(jù)高內(nèi)聚低耦合的原則。如果某些數(shù)據(jù)和方法總是一起出現(xiàn),或者某些數(shù)據(jù)經(jīng)常同時(shí)變化,這就表明它們應(yīng)該放到一個(gè)類中。另一種信號是類的子類化方式:如果你發(fā)現(xiàn)子類化只影響類的部分特性,或者類的特性需要以不同方式來子類化,這就意味著你需要分解原來的類。
//原始類 public?class?Person?{private?String?name;private?String?officeAreaCode;private?String?officeNumber;public?String?getName()?{return?name;}public?String?getTelephoneNumber()?{return?("("?+?officeAreaCode?+?")"?+?officeNumber);}public?String?getOfficeAreaCode()?{return?officeAreaCode;}public?void?setOfficeAreaCode(String?arg)?{officeAreaCode?=?arg;}public?String?getOfficeNumber()?{return?officeNumber;}public?void?setOfficeNumber(String?arg)?{officeNumber?=?arg;} }//新提煉的類(以對象替換數(shù)據(jù)值) public?class?TelephoneNumber?{private?String?areaCode;private?String?number;public?String?getTelephnoeNumber()?{return?("("?+?getAreaCode()?+?")"?+?number);}String?getAreaCode()?{return?areaCode;}void?setAreaCode(String?arg)?{areaCode?=?arg;}String?getNumber()?{return?number;}void?setNumber(String?arg)?{number?=?arg;} } 復(fù)制代碼組合優(yōu)先于繼承
繼承使實(shí)現(xiàn)代碼重用的有力手段,但這并非總是完成這項(xiàng)工作的最佳工具,使用不當(dāng)會(huì)導(dǎo)致軟件變得很脆弱。與方法調(diào)用不同的是,繼承打破了封裝性。子類依賴于其父類中特定功能的實(shí)現(xiàn)細(xì)節(jié),如果父類的實(shí)現(xiàn)隨著發(fā)行版本的不同而變化,子類可能會(huì)遭到破壞,即使他的代碼完全沒有改變。
舉例說明,假設(shè)有一個(gè)程序使用HashSet,為了調(diào)優(yōu)該程序的性能,需要統(tǒng)計(jì)HashSet自從它創(chuàng)建以來添加了多少個(gè)元素。為了提供該功能,我們編寫一個(gè)HashSet的變體。
//?Inappropriate?use?of?inheritance! public?class?InstrumentedHashSet<E>?extends?HashSet<E>?{//?The?number?of?attempted?element?insertionsprivate?int?addCount?=?0;public?InstrumentedHashSet()?{?}public?InstrumentedHashSet(int?initCap,?float?loadFactor)?{super(initCap,?loadFactor);}@Overridepublic?boolean?add(E?e)?{addCount++;return?super.add(e);}@Overridepublic?boolean?addAll(Collection<??extends?E>?c)?{addCount?+=?c.size();return?super.addAll(c);}public?int?getAddCount()?{return?addCount;} } 復(fù)制代碼通過在新的類中增加一個(gè)私有域,它引用現(xiàn)有類的一個(gè)實(shí)例,這種設(shè)計(jì)被稱為組合,因?yàn)楝F(xiàn)有的類變成了新類的一個(gè)組件。這樣得到的類將會(huì)非常穩(wěn)固,它不依賴現(xiàn)有類的實(shí)現(xiàn)細(xì)節(jié)。即使現(xiàn)有的類添加了新的方法,也不會(huì)影響新的類。許多設(shè)計(jì)模式使用就是這種套路,比如代理模式、裝飾者模式
//?Reusable?forwarding?class public?class?ForwardingSet<E>?implements?Set<E>?{private?final?Set<E>?s;public?ForwardingSet(Set<E>?s)?{?this.s?=?s;?}@Overridepublic?int?size()?{?return?s.size();?}@Overridepublic?boolean?isEmpty()?{?return?s.isEmpty();?}@Overridepublic?boolean?contains(Object?o)?{?return?s.contains(o);?}@Overridepublic?Iterator<E>?iterator()?{?return?s.iterator();?}@Overridepublic?Object[]?toArray()?{?return?s.toArray();?}@Overridepublic?<T>?T[]?toArray(T[]?a)?{?return?s.toArray(a);?}@Overridepublic?boolean?add(E?e)?{?return?s.add(e);?}@Overridepublic?boolean?remove(Object?o)?{?return?s.remove(o);?}@Overridepublic?boolean?containsAll(Collection<?>?c)?{?return?s.containsAll(c);?}@Overridepublic?boolean?addAll(Collection<??extends?E>?c)?{?return?s.addAll(c);?}@Overridepublic?boolean?retainAll(Collection<?>?c)?{?return?s.retainAll(c);?}@Overridepublic?boolean?removeAll(Collection<?>?c)?{?return?s.removeAll(c);?}@Overridepublic?void?clear()?{?s.clear();?} }//?Wrappter?class?-?uses?composition?in?place?of?inheritance public?class?InstrumentedHashSet<E>?extends?ForwardingSet<E>?{private?int?addCount?=?0;public?InstrumentedHashSet1(Set<E>?s)?{super(s);}@Overridepublic?boolean?add(E?e)?{addCount++;return?super.add(e);}@Overridepublic?boolean?addAll(Collection<??extends?E>?c)?{addCount?+=?c.size();return?super.addAll(c);}public?int?getAddCount()?{return?addCount;} } 復(fù)制代碼繼承與組合如何取舍
只有當(dāng)子類真正是父類的子類型時(shí),才適合繼承。對于兩個(gè)類A和B,只有兩者之間確實(shí)存在“is-a”關(guān)系的時(shí)候,類B才應(yīng)該繼承A;
在包的內(nèi)部使用繼承是非常安全的,子類和父類的實(shí)現(xiàn)都處在同一個(gè)程序員的控制之下;
對于專門為了繼承而設(shè)計(jì)并且具有很好的文檔說明的類來說,使用繼承也是非常安全的;
其他情況就應(yīng)該優(yōu)先考慮組合的方式來實(shí)現(xiàn)
接口優(yōu)于抽象類
Java提供了兩種機(jī)制,可以用來定義允許多個(gè)實(shí)現(xiàn)的類型:接口和抽象類。自從Java8為接口增加缺省方法(default method),這兩種機(jī)制都允許為實(shí)例方法提供實(shí)現(xiàn)。主要區(qū)別在于,為了實(shí)現(xiàn)由抽象類定義的類型,類必須稱為抽象類的一個(gè)子類。因?yàn)镴ava只允許單繼承,所以用抽象類作為類型定義受到了限制。
接口相比于抽象類的優(yōu)勢:
現(xiàn)有的類可以很容易被更新,以實(shí)現(xiàn)新的接口。
接口是定義混合類型(比如Comparable)的理想選擇。
接口允許構(gòu)造非層次結(jié)構(gòu)的類型框架。
接口雖然提供了缺省方法,但接口仍有有以下局限性:
接口的變量修飾符只能是public static final的
接口的方法修飾符只能是public的
接口不存在構(gòu)造函數(shù),也不存在this
可以給現(xiàn)有接口增加缺省方法,但不能確保這些方法在之前存在的實(shí)現(xiàn)中都能良好運(yùn)行。
因?yàn)檫@些默認(rèn)方法是被注入到現(xiàn)有實(shí)現(xiàn)中的,它們的實(shí)現(xiàn)者并不知道,也沒有許可
接口缺省方法的設(shè)計(jì)目的和優(yōu)勢在于:
為了接口的演化
Java 8 之前我們知道,一個(gè)接口的所有方法其子類必須實(shí)現(xiàn)(當(dāng)然,這個(gè)子類不是一個(gè)抽象類),但是 java 8 之后接口的默認(rèn)方法可以選擇不實(shí)現(xiàn),如上的操作是可以通過編譯期編譯的。這樣就避免了由 Java 7 升級到 Java 8 時(shí)項(xiàng)目編譯報(bào)錯(cuò)了。Java8在核心集合接口中增加了許多新的缺省方法,主要是為了便于使用lambda。
可以減少第三方工具類的創(chuàng)建
例如在 List 等集合接口中都有一些默認(rèn)方法,List 接口中默認(rèn)提供 replaceAll(UnaryOperator)、sort(Comparator)、、spliterator()等默認(rèn)方法,這些方法在接口內(nèi)部創(chuàng)建,避免了為了這些方法而專門去創(chuàng)建相應(yīng)的工具類。
可以避免創(chuàng)建基類
在 Java 8 之前我們可能需要?jiǎng)?chuàng)建一個(gè)基類來實(shí)現(xiàn)代碼復(fù)用,而默認(rèn)方法的出現(xiàn),可以不必要去創(chuàng)建基類。
由于接口的局限性和設(shè)計(jì)目的的不同,接口并不能完全替換抽象類。但是通過對接口提供一個(gè)抽象的骨架實(shí)現(xiàn)類,可以把接口和抽象類的優(yōu)點(diǎn)結(jié)合起來。 接口負(fù)責(zé)定義類型,或許還提供一些缺省方法,而骨架實(shí)現(xiàn)類則負(fù)責(zé)實(shí)現(xiàn)除基本類型接口方法之外,剩下的非基本類型接口方法。擴(kuò)展骨架實(shí)現(xiàn)占了實(shí)現(xiàn)接口之外的大部分工作。這就是模板方法(Template Method)設(shè)計(jì)模式。
Image [5].png接口Protocol:定義了RPC協(xié)議層兩個(gè)主要的方法,export暴露服務(wù)和refer引用服務(wù)
抽象類AbstractProtocol:封裝了暴露服務(wù)之后的Exporter和引用服務(wù)之后的Invoker實(shí)例,并實(shí)現(xiàn)了服務(wù)銷毀的邏輯
具體實(shí)現(xiàn)類XxxProtocol:實(shí)現(xiàn)export暴露服務(wù)和refer引用服務(wù)具體邏輯
優(yōu)先考慮泛型
聲明中具有一個(gè)或者多個(gè)類型參數(shù)(type parameter)的類或者接口,就是泛型(generic)類或者接口。泛型類和接口統(tǒng)稱為泛型(generic type)。泛型從Java 5引入,提供了編譯時(shí)類型安全檢測機(jī)制。泛型的本質(zhì)是參數(shù)化類型,通過一個(gè)參數(shù)來表示所操作的數(shù)據(jù)類型,并且可以限制這個(gè)參數(shù)的類型范圍。泛型的好處就是編譯期類型檢測,避免類型轉(zhuǎn)換。
//?比較三個(gè)值并返回最大值 public?static?<T?extends?Comparable<T>>?T?maximum(T?x,?T?y,?T?z)?{???T?max?=?x;?//?假設(shè)x是初始最大值???if?(?y.compareTo(?max?)?>?0?)?{??????max?=?y;?//y?更大??}???if?(?z.compareTo(?max?)?>?0?)?{?????max?=?z;?//?現(xiàn)在?z?更大??????????????}???return?max;?//?返回最大對象 }public?static?void?main(?String?args[]?)?{???System.out.printf(?"%d,?%d?和?%d?中最大的數(shù)為?%d\n\n",??3,?4,?5,?maximum(?3,?4,?5?));???System.out.printf(?"%.1f,?%.1f?和?%.1f?中最大的數(shù)為?%.1f\n\n",??6.6,?8.8,?7.7,??maximum(?6.6,?8.8,?7.7?));???System.out.printf(?"%s,?%s?和?%s?中最大的數(shù)為?%s\n","pear",?"apple",?"orange",?maximum(?"pear",?"apple",?"orange"?)?); } 復(fù)制代碼不要使用原生態(tài)類型
由于為了保持Java代碼的兼容性,支持和原生態(tài)類型轉(zhuǎn)換,并使用擦除機(jī)制實(shí)現(xiàn)的泛型。但是使用原生態(tài)類型就會(huì)失去泛型的優(yōu)勢,會(huì)受到編譯器警告。
要盡可能地消除每一個(gè)非受檢警告
每一條警告都表示可能在運(yùn)行時(shí)拋出ClassCastException異常。要盡最大的努力去消除這些警告。如果無法消除但是可以證明引起警告的代碼是安全的,就可以在盡可能小的范圍中,使用@SuppressWarnings("unchecked")注解來禁止警告,但是要把禁止的原因記錄下來。
利用有限制通配符來提升API的靈活性
參數(shù)化類型不支持協(xié)變的,即對于任何兩個(gè)不同的類型Type1和Type2而言,List既不是List的子類型,也不是它的超類。為了解決這個(gè)問題,提高靈活性,Java提供了一種特殊的參數(shù)化類型,稱作有限制的通配符類型,即List<? extends E>和List<? super E>。使用原則是producer-extends,consumer-super(PECS)。如果即是生產(chǎn)者,又是消費(fèi)者,就沒有必要使用通配符了。
還有一種特殊的無限制通配符List<?>,表示某種類型但不確定。常用作泛型的引用,不可向其添加除Null以外的任何對象。
//List<??extends?E> //?Number?可以認(rèn)為?是Number?的?"子類" List<??extends?Number>?numberArray?=?new?ArrayList<Number>();? //?Integer?是?Number?的子類 List<??extends?Number>?numberArray?=?new?ArrayList<Integer>();? //?Double?是?Number?的子類 List<??extends?Number>?numberArray?=?new?ArrayList<Double>();??//List<??super?E> //?Integer?可以認(rèn)為是?Integer?的?"父類" List<??super?Integer>?array?=?new?ArrayList<Integer>();、 //?Number?是?Integer?的?父類 List<??super?Integer>?array?=?new?ArrayList<Number>(); //?Object?是?Integer?的?父類 List<??super?Integer>?array?=?new?ArrayList<Object>();public?static?<T>?void?copy(List<??super?T>?dest,?List<??extends?T>?src)?{????int?srcSize?=?src.size();????if?(srcSize?>?dest.size())????????throw?new?IndexOutOfBoundsException("Source?does?not?fit?in?dest");????if?(srcSize?<?COPY_THRESHOLD?||?(src?instanceof?RandomAccess?&&?dest?instanceof?RandomAccess))?{????????for?(int?i=0;?i<srcSize;?i++)????????????dest.set(i,?src.get(i));????}?else?{????????ListIterator<??super?T>?di=dest.listIterator();????????ListIterator<??extends?T>?si=src.listIterator();????????for?(int?i=0;?i<srcSize;?i++)?{????????????di.next();????????????di.set(si.next());????????}????} } 復(fù)制代碼靜態(tài)成員類優(yōu)于非靜態(tài)成員類
嵌套類(nested class)是指定義在另一個(gè)類的內(nèi)部的類。嵌套類存在的目的只是為了它的外部類提供服務(wù),如果其他的環(huán)境也會(huì)用到的話,應(yīng)該成為一個(gè)頂層類(top-level class)。 嵌套類有四種:靜態(tài)成員類(static member class)、非靜態(tài)成員類(nonstatic member class)、匿名類(anonymous class)和 局部類(local class)。除了第一種之外,其他三種都稱為內(nèi)部類(inner class)。
匿名類(anonymous class)
沒有名字,聲明的同時(shí)進(jìn)行實(shí)例化,只能使用一次。當(dāng)出現(xiàn)在非靜態(tài)的環(huán)境中,會(huì)持有外部類實(shí)例的引用。通常用于創(chuàng)建函數(shù)對象和過程對象,不過現(xiàn)在會(huì)優(yōu)先考慮lambda。
局部類(local class)
任何可以聲明局部變量的地方都可以聲明局部類,同時(shí)遵循同樣的作用域規(guī)則。跟匿名類不同的是,有名字可以重復(fù)使用。不過實(shí)際很少使用局部類。
靜態(tài)成員類(static member class)
最簡單的一種嵌套類,聲明在另一個(gè)類的內(nèi)部,是這個(gè)類的靜態(tài)成員,遵循同樣的可訪問性規(guī)則。常見的用法是作為公有的輔助類,只有與它的外部類一起使用才有意義。
非靜態(tài)成員類(nonstatic member class)
盡管語法上,跟靜態(tài)成員類的唯一區(qū)別就是類的聲明不包含static,但兩者有很大的不同。非靜態(tài)成員類的每個(gè)實(shí)例都隱含地與外部類的實(shí)例相關(guān)聯(lián),可以訪問外部類的成員屬性和方法。另外必須先創(chuàng)建外部類的實(shí)例之后才能創(chuàng)建非靜態(tài)成員類的實(shí)例。
總而言之,這四種嵌套類都有自己的用途。假設(shè)這個(gè)嵌套類屬于一個(gè)方法的內(nèi)部,如果只需要在一個(gè)地方創(chuàng)建實(shí)例,并且已經(jīng)有了一個(gè)預(yù)置的類型可以說明這個(gè)類的特征,就要把它做成匿名類。如果一個(gè)嵌套類需要在單個(gè)方法之外仍然可見,或者它太長了,不適合放在方法內(nèi)部,就應(yīng)該使用成員類。如果成員類的每個(gè)實(shí)例都需要一個(gè)指向其外圍實(shí)例的引用,就要把成員類做成非靜態(tài)的,否則就做成靜態(tài)的。
優(yōu)先使用模板/工具類
通過對常見場景的代碼邏輯進(jìn)行抽象封裝,形成相應(yīng)的模板工具類,可以大大減少重復(fù)代碼,專注于業(yè)務(wù)邏輯,提高代碼質(zhì)量。
分離對象的創(chuàng)建與使用
面向?qū)ο缶幊滔鄬τ诿嫦蜻^程,多了實(shí)例化這一步,而對象的創(chuàng)建必須要指定具體類型。我們常見的做法是“哪里用到,就在哪里創(chuàng)建”,使用實(shí)例和創(chuàng)建實(shí)例的是同一段代碼。這似乎使代碼更具有可讀性,但是某些情況下造成了不必要的耦合。
public?class?BusinessObject?{public?void?actionMethond?{//Other?thingsService?myServiceObj?=?new?Service();myServiceObj.doService();//Other?things} }public?class?BusinessObject?{public?void?actionMethond?{//Other?thingsService?myServiceObj?=?new?ServiceImpl();myServiceObj.doService();//Other?things} }public?class?BusinessObject?{private?Service?myServiceObj;public?BusinessObject(Service?aService)?{myServiceObj?=?aService;}public?void?actionMethond?{//Other?thingsmyServiceObj.doService();//Other?things} }public?class?BusinessObject?{private?Service?myServiceObj;public?BusinessObject()?{myServiceObj?=?ServiceFactory;}public?void?actionMethond?{//Other?thingsmyServiceObj.doService();//Other?things} } 復(fù)制代碼對象的創(chuàng)建者耦合的是對象的具體類型,而對象的使用者耦合的是對象的接口。也就是說,創(chuàng)建者關(guān)心的是這個(gè)對象是什么,而使用者關(guān)心的是它能干什么。這兩者應(yīng)該視為獨(dú)立的考量,它們往往會(huì)因?yàn)椴煌脑蚨淖儭?/p>
當(dāng)對象的類型涉及多態(tài)、對象創(chuàng)建復(fù)雜(依賴較多)可以考慮將對象的創(chuàng)建過程分離出來,使得使用者不用關(guān)注對象的創(chuàng)建細(xì)節(jié)。設(shè)計(jì)模式中創(chuàng)建型模式的出發(fā)點(diǎn)就是如此,實(shí)際項(xiàng)目中可以使用工廠模式、構(gòu)建器、依賴注入的方式。
可訪問性最小化
區(qū)分一個(gè)組件設(shè)計(jì)得好不好,一個(gè)很重要的因素在于,它對于外部組件而言,是否隱藏了其內(nèi)部數(shù)據(jù)和實(shí)現(xiàn)細(xì)節(jié)。Java提供了訪問控制機(jī)制來決定類、接口和成員的可訪問性。實(shí)體的可訪問性由該實(shí)體聲明所在的位置,以及該實(shí)體聲明中所出現(xiàn)的訪問修飾符(private、protected、public)共同決定的。
對于頂層的(非嵌套的)類和接口,只有兩種的訪問級別:包級私有的(沒有public修飾)和公有的(public修飾)。
對于成員(實(shí)例/域、方法、嵌套類和嵌套接口)由四種的訪問級別,可訪問性如下遞增:
私有的(private修飾)--只有在聲明該成員的頂層類內(nèi)部才可以訪問這個(gè)成員;
包級私有的(默認(rèn))--聲明該成員的包內(nèi)部的任何類都可以訪問這個(gè)成員;
受保護(hù)的(protected修飾)--聲明該成員的類的子類可以訪問這個(gè)成員,并且聲明該成員的包內(nèi)部的任何類也可以訪問這個(gè)成員;
公有的(public修飾)--在任何地方都可以訪問該成員;
正確地使用這些修飾符對于實(shí)現(xiàn)信息隱藏是非常關(guān)鍵的,原則就是:盡可能地使每個(gè)類和成員不被外界訪問(私有或包級私有)。這樣好處就是在以后的發(fā)行版本中,可以對它進(jìn)行修改、替換或者刪除,而無須擔(dān)心會(huì)影響現(xiàn)有的客戶端程序。
如果類或接口能夠做成包級私有的,它就應(yīng)該被做成包級私有的;
如果一個(gè)包級私有的頂層類或接口只是在某一個(gè)類的內(nèi)部被用到,就應(yīng)該考慮使它成為那個(gè)類的私有嵌套類;
公有類不應(yīng)直接暴露實(shí)例域,應(yīng)該提供相應(yīng)的方法以保留將來改變該類的內(nèi)部表示法的靈活性;
當(dāng)確定了類的公有API之后,應(yīng)該把其他的成員都變成私有的;
如果同一個(gè)包下的類之間存在比較多的訪問時(shí),就要考慮重新設(shè)計(jì)以減少這種耦合;
可變性最小化
不可變類是指其實(shí)例不能被修改的類。每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建該實(shí)例時(shí)提供,并在對象的整個(gè)生命周期內(nèi)固定不變。不可變類好處就是簡單易用、線程安全、可自由共享而不容易出錯(cuò)。Java平臺(tái)類庫中包含許多不可變的類,比如String、基本類型包裝類、BigDecimal等。
為了使類成為不可變,要遵循下面五條規(guī)則:
聲明所有的域都是私有的
聲明所有的域都是final的
如果一個(gè)指向新創(chuàng)建實(shí)例的引用在缺乏同步機(jī)制的情況下,從一個(gè)線程被傳遞到另一個(gè)線程,就必須確保正確的行為
不提供任何會(huì)修改對象狀態(tài)的方法
保證類不會(huì)被擴(kuò)展(防止子類化,類聲明為final)
防止粗心或者惡意的子類假裝對象的狀態(tài)已經(jīng)改變,從而破壞該類的不可變行為
確保對任何可變組件的互斥訪問
如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。并且,永遠(yuǎn)不要用客戶端提供的對象引用來初始化這樣的域,也不要從任何訪問方法中返回該對象引用。在構(gòu)造器、訪問方法和readObject 方法中使用保護(hù)性拷貝技術(shù)
可變性最小化的一些建議:
除非有很好的理由要讓類成為可變的類,否則它就應(yīng)該是不可變的;
如果類不能被做成不可變的,仍然應(yīng)該盡可能地限制它的可變性;
除非有令人信服的理由要使域變成非final的,否則要使每個(gè)域都是private final的;
構(gòu)造器應(yīng)該創(chuàng)建完全初始化的對象,并建立起所有的約束關(guān)系;
質(zhì)量如何保證
測試驅(qū)動(dòng)開發(fā)
測試驅(qū)動(dòng)開發(fā)(TDD)要求以測試作為開發(fā)過程的中心,要求在編寫任何代碼之前,首先編寫用于產(chǎn)碼行為的測試,而編寫的代碼又要以使測試通過為目標(biāo)。TDD要求測試可以完全自動(dòng)化地運(yùn)行,并在對代碼重構(gòu)前后必須運(yùn)行測試。
TDD的最終目標(biāo)是整潔可用的代碼(clean code that works)。大多數(shù)的開發(fā)者大部分時(shí)間無法得到整潔可用的代碼。辦法是分而治之。首先解決目標(biāo)中的“可用”問題,然后再解決“代碼的整潔”問題。這與體系結(jié)構(gòu)驅(qū)動(dòng)(architecture-driven)的開發(fā)相反。
采用TDD另一個(gè)好處就是讓我們擁有一套伴隨代碼產(chǎn)生的詳盡的自動(dòng)化測試集。將來無論出于任何原因(需求、重構(gòu)、性能改進(jìn))需要對代碼進(jìn)行維護(hù)時(shí),在這套測試集的驅(qū)動(dòng)下工作,我們代碼將會(huì)一直是健壯的。
TDD的開發(fā)周期
Image [6].png添加一個(gè)測試 -> 運(yùn)行所有測試并檢查測試結(jié)果 -> 編寫代碼以通過測試 -> 運(yùn)行所有測試且全部通過 -> 重構(gòu)代碼,以消除重復(fù)設(shè)計(jì),優(yōu)化設(shè)計(jì)結(jié)構(gòu)
兩個(gè)基本的原則
僅在測試失敗時(shí)才編寫代碼并且只編寫剛好使測試通過的代碼
編寫下一個(gè)測試之前消除現(xiàn)有的重復(fù)設(shè)計(jì),優(yōu)化設(shè)計(jì)結(jié)構(gòu)
關(guān)注點(diǎn)分離是這兩條規(guī)則隱含的另一個(gè)非常重要的原則。其表達(dá)的含義指在編碼階段先達(dá)到代碼“可用”的目標(biāo),在重構(gòu)階段再追求“整潔”目標(biāo),每次只關(guān)注一件事!
分層測試點(diǎn)
| Dao測試 | 驗(yàn)證mybatis-config、mapper、handler的正確性 | 基于內(nèi)存數(shù)據(jù)庫 ?可以使用assert驗(yàn)證 |
| Adapter測試 | 驗(yàn)證外部依賴交互正確 驗(yàn)證converter正確 | 依賴外部環(huán)境 正確性依賴人工判讀 |
| Repository測試 | 驗(yàn)證內(nèi)部計(jì)算、轉(zhuǎn)換邏輯 | 可mock外部依賴 可以使用assert驗(yàn)證 |
| biz層測試 | 驗(yàn)證內(nèi)部業(yè)務(wù)邏輯 | 盡可能隔離所有外部依賴 需要多個(gè)測試,每個(gè)測試驗(yàn)證一個(gè)場景或分支 使用assert驗(yàn)證,不依賴人工判斷 |
| Application層測試 | 驗(yàn)證入口參數(shù)處理正確 驗(yàn)證系統(tǒng)內(nèi)鏈路無阻塞 | 可以隔離外部依賴 場景覆蓋通過參數(shù)控制 ?可使用單步調(diào)試觀察代碼執(zhí)行走向 ?不驗(yàn)證詳細(xì)邏輯 |
參考資料
1. 重構(gòu)-改善既有代碼的設(shè)計(jì)
2. 設(shè)計(jì)模式
3. Effective Java
4. 敏捷軟件開發(fā)與設(shè)計(jì)的最佳實(shí)踐
5. 實(shí)現(xiàn)模式
6. 測試驅(qū)動(dòng)開發(fā)
精彩文章推薦
梁鑫:美股交易架構(gòu)實(shí)踐
2021-04-26
數(shù)據(jù)中臺(tái):企業(yè)數(shù)據(jù)質(zhì)量參差不齊?聽聽專家怎么說
2021-04-22
王啟軍:云原生架構(gòu)下如何拆分微服務(wù)?
2021-04-20
原創(chuàng)精華:剖析億級請求下的多級緩存
2021-04-19
ThoughtWorks專家:使用 DDD 指導(dǎo)微服務(wù)拆分的邏輯
2021-04-15
梁鑫:重構(gòu) - 在美股行情系統(tǒng)的實(shí)踐
2021-04-09
淺談架構(gòu):架構(gòu)的緣起與目標(biāo)
2021-04-07
Jartto: 如何成為一名合格的技術(shù)面試官?
2021-04-06
Francisco: 構(gòu)建前瞻性應(yīng)用架構(gòu)的優(yōu)秀實(shí)踐
2021-03-31
總結(jié)
以上是生活随笔為你收集整理的代码重构技巧宝典,学透本篇就足够了!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux平台swift语言开发学习环境
- 下一篇: 这两天有点热吆,star直线上涨!~Je