寫優雅的代碼規範的思路(入門篇)

注意:進行重構的第一步是你先能寫出一種範式統一,註釋清晰,內容優雅的代碼。如果自覺不行請保持原狀,不要傷害他,或者自己在本地練習。對於生產項目,這種時候最應該做的是順着舊有項目的 “毛髮” 捋下去,而不是進行優化或者另闢蹊徑。項目的開發應該具備全局思維,不僅要考慮開發時候是否與業務貼切 ,也要考慮代碼的質量和規範。好的質量和規範是爲了後期維護和測試提前買了保險。(爲什麼有的人不用加班,有的人經常加班,除了錢的問題,大部分就在此)。這段話是本篇博客的核心,其他一切都可以丟棄哦~,網上說的重構隨時可以開始,都是騙小屁孩的 ,你重構你就涼~~

一、介紹(主要兩點)

1.提高代碼複用率

2.避免低級BUG,出BUG必出重大級別的BUG的問題。(基礎測試入門篇)

3.優秀的代碼風格

 

二、主要類型

重構設計的思想原本是爲了說修復代碼而用,然而寫出重構水準的代碼是一個初級程序員必須具備的能力,這個也是由菜鳥到初級的分水嶺。正常情況下需要重構的場景:

1.重複代碼

2.過長的類(複雜)

3.過多的參數(>=3)

4.過於發散的類,意思是一個類處理兩個截然不同的業務環節。比如Realm類處理登錄校驗,還做過濾處理。

5.散彈修改的類,意思是一個業務功能修改,需要在ABDCEF類中分別修改

6.依戀情結的類,意思是A類的方法調用B類的參數或者方法超過了類的一半以上,A類作爲一個同等級對象,如果同學A和同學B

7.數據泥團,在多個地方出現多個數據在一起出現,應該將之捆綁爲數據對象

8.基本數據類型,過多使用基本數據類型

9.平行集成體系,意思是增加A必須跟着增加B,應該進行實例繼承。

10.由於重構或者功能刪除而導致可以不必須獨立存在的類

11.未來的類,爲未來場景預設的類

12.未來的變量或字段,在某次添加的變量而沒有使用上

13.過度耦合,如消息鏈,A想傳遞數據給C,但是A先傳給了B再由B傳給C

14.情侶類,兩個類的私有變量交流過多,應該進行處理,比如私有變量get出來交互,話費過多。合併

15.異曲同工類,名字不一樣,但是乾的內容一致。比如報文類,應該設置基本報文類

16.純數據類,單純用於存放數據的類或者交付數據的類,這種類類似以前的數據字典,應該儘量避免或者剔除。(取消或成DTO)

17.被拒絕的饋贈,子類部分引用父類,很大一部分父類不用到,建立超父類,分別引用,保持父類純粹

18.過多註釋,應該儘量做到代碼即註釋。應該在節點進行註釋,在入口進行需求+明確功能的註釋;良好的註釋爲以後進行復用和維護奠定了基礎。

 

三、處理的感覺

1、對於數量過多的函數,進行重組函數,提煉複雜的邏輯,將邏輯按照業務模塊-方法功能組件-方法兩類進行拆分。

2、內聯函數,將功能明確而獨立的邏輯 合併或者遷移到同個類中。在不違背面向對象編程(OOP)特性下,提煉到同個對象類成爲新的函數。內聯函數是在邏輯調用的節點,插入函數的本體。

注意:常規的手法有 遷移合併,內聯函數調用(比如替換臨時變量將表達式直接寫入調用的節點,將函數名直接寫入調用的節點)。但如果該方法存在着被子類繼承的場景,那麼應減少第二種內聯手法。因爲內聯會導致內聯的函數邏輯不向子類開發。

 

3、對於小功能函數,拒絕產生二次委託。除非是考慮進行 "委派和工廠模式" 的工具函數。(註釋:小功能函數即是指功能獨立,能脫離業務單獨提供服務的方法函數組件。此類函數往往被用於提供輸出和處理任務統一的功能,他可能是一個集合入口,當接收到參數後,將任務委派給其他函數執行最後統一響應。也可能是獲取參數後,按照工廠流程進行處理最後輸出不同的結果。此類功能函數類似於接口,只要求實現結果,內部邏輯如何實現不在乎,其內在可能隨着架構的變遷或者項目的優化而變化,考慮到內部邏輯被改造或者被其他渠道取代而出入口不變的問題,因此需要進行 “包裝”)。

4、複雜表達式 按照條件進行拆分。對於一個關聯性極強的表達式或者邏輯,適當第將其按照一定步驟,一定階段進行拆分。可降低代碼複雜性。便於理解

5、臨時變量賦值保持唯一。如果一個臨時變量賦值被超過一次,也就說明了,她在這個活動中承擔了不同的責職。一個變量多角色在二次開發或者維護會極大地增加開發者的負擔。

6、應該移除對參數的賦值。當一個參數進入某個函數進行處理,這個函數應該有自己的局部參數(方法臨時變量)用於數據的流轉和結果的返回。而不應該藉助入參的參數來回地賦值,或者將結果賦予入參參數返回。這樣的函數會導致,邏輯與功能無法很好地分割。無法有效地執行替換和優化。(不清晰)。

7、拒絕被餵狗糧。如果一個類成爲另外兩個類頻繁交流的場所,應該將這個交流的函數,遷移到這兩個交流類中最主動或最常引用的類中。(耦合太多現象)

8、字段搬移。某個類的字段被另外類更多地使用到,可以在 該另外類建立一個新的字段。將責職遷移

9、類內聯化。某個類存在(類即角色),已經沒有再承擔足夠的責職,不再具備獨立存在的理由,那麼將其遷移合併到其他類中。

10、委託關係。客戶類通過一個服務類調用另外一個對象類,那麼爲了隱藏委託關係。在該服務類上,應該在服務類上建立客戶所需要的各種函數,以保證對象類信息不丟失。

註釋:迪米特法則要求,一個對象應當對其他對象有儘可能少的瞭解,不和陌生人說話。也就是說,當委託關係存在,客戶應該通過委託關係去了解自己所需要的數據和內容,並且保持這種關係的純粹。但對象委託關係發生變化,客戶類可以很快地知道這一層委託關係。而不是一個個去梳理。

11、提煉類。某個類做了應該由其他兩個類做的事,建立一個新類,將相關字段和函數搬移到新類中。

12、如果如果爲你提供服務的類,當前的方法函數不能滿足你的要求,但你又無法修改這個類,那麼你需要在調用該類服務的地方創建一個方法,利用參數傳遞,轉化,處理,調用該服務類函數以補足該類的功能

13、擴展類。你需要服務類爲你提供一些額外的功能,服務類本身又不能被修改。那麼你應該新建一個類,將這些額外的函數包含進去。讓這個新類成爲服務類的擴展和出口。如線程池工具類

14、封裝自定義字段。在類中,初始化某些變量。使用這些變量以get/set方法進行處理。(全局變量)

----------直接使用:適合那些fianl常量或者靜態常量

----------間接使用:適合那些 “數值型” 頻繁變化數據的。通過統一的輸出端更適合理解與維護。比如打印爲日誌,補充數據,替換數據。

15、以對象代替數據值。如果某個數據項目需要與其他某些數據一起使用纔有意義,那麼應該將之與其他數據合併爲對象。

16、將值對象替換成引用對象。你從一個類中產生了很多的相同的實例,希望將他們變成同一個對象,將這個值對象變成引用對象。

註釋:共享(重複使用一個對象不丟棄,共享數據或共享訪問)

--------對象:對象實例

--------對象引用:對象的引用,當一個對象需要被使用的時候,使用引用可以避免重複創建浪費空間,這也是聲明符存在的意義

--------值對象:描述對象的一種可變狀態,這種約束可以是單實例約束也可以是軟件開發口頭約束。這類對象多以包裝類的形式存在。比如 “汽車對象”。可以通過設置私有值,不允許被外來改變。而成爲一個固定的值對象。值對象可以起到既支持共享同一個實例引用以節省內存,又可以保證數據安全而不會產生別名問題。一般可以通過設置私有變量,同時關閉設置函數來保證對象不被外來操作修改。從而實現代碼設計上的安全。

單例模式

-------引用對象:對象在系統中是類似 “資源” 一般的存在,引用對象體現了這個 “資源” 被共享的概念。即一個實例被重複引用。

註釋:實例對象被重複存在是會佔據和消耗資源的,相同的實例被重複創建是不合理的。

-------無狀態對象:作爲值對象中的一種,無論在什麼時候他的結果都是一樣的,不會因爲使用的時序不同而不同。類似於工具類

和常量類。常規認識上是一個對象,不包含外部對象的引用,所有方法計算都只在方法中局部計算。或者變量已經不能被改變的對象。這類對象是線程安全的,無論什麼時候計算也是穩定的結果。一般這類對象都是無實例的,成員變量私有化或final化不會再被外部改變。

註釋:無狀態對象不允許存在數據成員(局部方法裏面的除外),不允許外部對象引用。

 

17、引用對象替換爲值對象。存在着一個引用對象很小且不可改變,那麼將之轉化爲值對象吧

18、去除不必要關聯,兩個類都存在着關聯現在有某個關聯已近失效,將之移除。

19、拒絕魔法數字。

20、封裝字段。對於對象的某個屬性pulibc字段,將之轉換爲private,並提供對應的 訪問方法。

21、封裝集合。某個集合被頻繁使用,在這個類中對這個類集合進行封裝,用get方式返回其只讀副本,並創建移除函數。提高集合的靈活性。

22、簡化表達式。對於if-else這一類條件分支,建議以斷言的形式提煉出獨立的函數。

23、合併條件表達式,如果你存在多個條件判斷都得出相同的結果,將之合併成爲一個大的條件表達式。如果過於複雜將條件進行提煉,但仍然保持一個表達式。

24、合併重複代碼。如果代碼的每個分支都執行了一個相同的條件,那麼將這個條件遷移到表達式之外。

25、移除控制標記。在一系列布爾表達式中,某個變量帶有“控制標記”(control flag)的作用。以break語句或return語句取代控制標記。

註釋:比如說我擁有一個臨時變量在不同條件下賦予不同的值,那麼我將之重構,尋找統一的判斷的方式,而後統一反饋或者不符合條件則退出。

比如 用swith的時候會產生很多個if ,那麼我可以將條件轉化爲數組,而後進行for循環篩選,符合條件則返回,不符合條件則continue;

26、以多態取代條件表達式。尚未找到好的方式。

import org.springframework.util.StringUtils;

/**
 * @Author: CYQ
 * @Description: 抽象工廠
 * @Date: Created in 2019/9/10 22:46
 * @Modified By:
 */
public abstract class AbstarctFactory {


    protected abstract Car getCarByName(); //抽象類方法


    public Car getCarByName(String name){

        if (StringUtils.endsWithIgnoreCase(name,"Audi")){
            return new AudiFactory().getCarByName();
        }else  if (StringUtils.endsWithIgnoreCase(name,"Bmw")){
            return new BmwFactory().getCarByName();
        }else if (StringUtils.endsWithIgnoreCase(name,"benz")){
            return new BenzFactory().getCarByName();
        }else{
            return null;
        }

    };



}


如何引用呢?
=======================================創建默認子類繼承該抽象類=================================

/**
 * @Author: CYQ
 * @Description: 默認工廠
 * @Date: Created in 2019/9/10 23:12
 * @Modified By:
 */
public class DefaultFactory  extends  AbstarctFactory{

    private AudiFactory  audiFactory=new AudiFactory();

    @Override
    protected Car getCarByName() {
        return audiFactory.getCarByName();
    }
}


使用區別在哪裏?
===================================創建具體工廠,分場景使用==============================
1、具體工廠重寫默認類方法。
註釋:也就意味了擁有了兩種場景。一種是默認生產模式,一種是具體工廠提供的創新模式。


/**
 * @Author: CYQ
 * @Description: 奧迪工廠
 * @Date: Created in 2019/9/10 22:14
 * @Modified By:
 */
public class AudiFactory extends AbstarctFactory {
    @Override
    public Car getCarByName() {
        return new Audi();
    }
}


=========================================================================================

/**
 * @Author: CYQ
 * @Description: 默認工廠
 * @Date: Created in 2019/9/10 23:13
 * @Modified By:
 */
public class DefaultFactoryTest {

    public static void main(String[] args){
        DefaultFactory defaultFactory=new DefaultFactory();
        System.out.println(defaultFactory.getCarByName());
        System.out.println(defaultFactory.getCarByName("Audi"));
    }
}

  註釋1:多態存在的前提條件是 子類對父類存在着繼承或實現的關係。一種是重載多態(正常重載),一種是父類引用指向子類對象。因爲子類對象繼承了父類,在編譯和初始化的 時候也對父類進行了編譯。因此父類對象是被默認創建好的。父類引用指向子類。如果子類不存這個方法則默認引用父類的方法,如果子類存在則會引用子類的方法(子類該方法已經進行了重寫)。

 註釋2:這裏重寫的定義,可以是原模原樣的從父類複製到子類。不是一定要對方法進行改造。便已經是執行了覆蓋。

27、簡化函數調用。函數名應該顯示這個函數的意義。

28、函數的參數超過3個,最好是打包發送

29、分離查詢函數和修改函數,保證函數功能職責單一

30、聚合相似函數。某些函數大體相似,但是因爲開發先後關係均爲獨立,將這些函數進行合併處理。

31、以明確函數代替參數。在函數裏面,如果存在着一個函數,其返回的結取決於內部參數設置而返回不同的值。那麼應該將參數提煉作爲入參或者獨立成爲一個函數,然後調用函數獲取數值。如果參數是配置參數,那麼可以獨立成爲一個提供數據的函數。因爲外部函數的不同入參而選擇不同的行爲或者運算方式。

註釋:讀取配置文件的函數以此獲取數據。經過各種數據邏輯計算而獲取到的結果。

32、保持對象完整性。如果獲取對象的若干的屬性,將他們作爲某一次函數調用的參數,那麼將該對象整個打包傳送,以保持對象完整性。

33、儘量減少不必要的傳遞調用。A需要C的返回值,A通過調用B去調用C,從而獲取數據這種是不合理的。

34、以對象取代參數。對於某些很頻繁且同時出現的參數,以對象形式打包。後續使用以引用參數傳遞。

35、移除設置函數,如果一個屬性在創建之初已經被被賦值且不再改變,移除其setter函數。

36、隱藏內部函數。如果某個函數沒有被外類引用過,那麼將之隱藏起來。

37、以工廠函數取代構造函數。在創建對象的時候不僅僅是簡單的建構操作,而是將構造函數替換爲工廠函數。

38、概括關係。

------如果超過兩個字類存在相同字段,那麼將該字段遷移到超類

------如果某些函數在子類中,產生完全相同的效果,那麼將該函數遷移至超類。

------各個子類中的 構造函數本地內容幾乎一樣,將構造函數遷移至超類中,並在子類構造中進行調用

------超類中某個函數只爲某個子類服務,那麼將該函數遷移至該子類

------超類中某個字段只爲某個子類服務,那麼將函數遷移到該子類中

------類中某些特性只被某些實例使用,那麼將這些特性提煉成一個子類,將上述特性 字段遷移到子類。(參考報文)

------多個子類中均存在某些特性,將這些特性提煉成一個新的超類,並將特性遷移進超類

------若干客戶使用類接口的同一子集,或者兩個類接口都存在相似,那麼將接口提煉出一個新的接口,將特性進行遷移到接口中

------如果超類和子類沒有什麼大區別,將子類和超類合併吧

------子類中某些寒暑假具相同的操作順序,但細節可能各有不同,將操作分別封裝進不同的函數中,通過一個函數調用不同 的操作步驟函數,並將這個步驟函數提煉到超類中,成爲模板函數。

================================================================================================

總結:

以上只是總結以及開發中重構和開發思想(僅供參考),但不是凡事都可以套到現實開發中。整體來說,以上爲個模塊,分別是:

--------->規範代碼的用途(廢話)

良好的代碼規範和代碼設計思路,可以使得你的開發越來越便捷,越來越高效,後續代碼維護也極爲方便。控制好測試與代碼規範,初級BUG是完成可以避免的,這句話並不是開玩笑。

--------->不良規範的表現是怎麼樣的?(持續補充)

--------->常規應該怎麼做?

================================================================================================

經驗:在現實開發中,我們很少會去背誦這些規範,畢竟也不顯示,大多數人都是依靠自己的經驗來處理。無非就是一種感覺的問題,以下描述一些開發中需要遊優化的經驗:

1、參數過多

2、代碼無讀懂的註釋

3、數據場景混亂,開發時一個參數搭配集中場景

4、魔法數值

5、數據庫不規範字段。

6、函數過長

7、節點無註釋。

 

 

 

 

1.儘量使用對象

2.保持類,方法職責清晰

3.數據傳遞通道直達

4.冗餘字段,類,變量,重複代碼刪除。(如果你具備考慮全局的思維,對於兩次需求週期內沒有使用到的也不要進行註釋,直接刪除)

5.儘量提供輸出口的方法,也就是對外輸出,內部邏輯封閉修改

6.類和對象儘量純粹

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章