1. 程式人生 > 實用技巧 >日常編碼規範

日常編碼規範

規範:

    1. 命名:
      1. 介面命名。介面必須是名詞,並且介面是能準確的描述要做的事情,命名能清晰的看出輸入輸出,可以是抽象的行為描述。介面必須以一個動作的名詞形式結尾,比如reader,handler等。
        介面的命名,必須是抽象的,除非介面本身和具體實現緊密相關,否則不應該在介面中包含任何和具體實現相關的名詞。
        介面命名根據行為分為以下幾種:
        1. 讀取某個資料,命名: {資料名稱}Reader/Loader,如果是讀取列表可以是 {資料名稱}ListReader,或者 名稱複數形式+Reader
          1. xxxReader 讀取資料的接扣
          2. xxxLoader 載入資料的介面
        2. 進行某件事情,比如進行一個訂單金額的計算,OrderCalculatePerformer, {名詞}{動詞}Performer。
        3. xxxHelper 某些要共用的幫助
        4. xxxMaker 生成DTO VO的介面,生成資料的介面,比如 PaperDTOMaker,OrderVOMaker
        5. xxxHandler 處理某個事件
        6. xxxConverter 資料庫物件、DTO、VO等資料承載類的轉換器
        7. xxxInterceptor 攔截某個時間,一般只有一個intercept方法,返回boolean表明攔截是否成功
        8. xxxFiller 對DTO VO 根據業務邏輯進行欄位set填充的介面,一般filler只有一個方法fillXXX,傳入DTO VO和其他引數,且沒有返回值,比如:
        9. xxxFinder 通過其他服務來查詢資料的介面
      2. 介面方法命名:
        1. 方法必須是描述一個動作,比如 getOrder, listOrders,方法以動詞開頭,{動詞},或者 {動詞}{名詞} 的形式結尾,比如 handle getOrder。
        2. 常用的幾個動詞:
          1. getXXX 返回單個物件
          2. listXXX(複數) 返回一組資料,可以是list,或者set等collection
          3. saveXXX 儲存資料
          4. deleteXXX, removeXXX 刪除、清理資料
          5. createXXX addXXX
      3. 資料物件命名:
        1. 常規的公認的物件型別簡寫,可使用
          1. DTO 資料傳輸物件
          2. VO viewObject 展示物件,一般提供給前端
          3. BO businessObject ,業務物件
          4. info 資料資訊結尾
        2. 資料庫物件,全部用Do結尾,所有以DO結尾的物件,都是特製mybatis 映射出來的和資料庫表對應的物件。
        3. 不以DO結尾的資料物件,比如DTO VO等,是暴露給外部api或者內部使用的,非資料庫物件。和資料庫物件之間使用xxxConverter介面進行轉換。
      4. 包名:
        1. 包名、資料夾名稱,必須是小寫,不能是多個單詞。有且只能有單個單詞,除了約定俗成的簡寫,不能使用簡寫。
          舉例:正確的: order.detail 錯誤:orderdetail orderDetail od 等。
          正確的:com.dt.school.web.action.impl
          包名的單詞可以是名詞、動詞等,約定俗成的簡寫有:vo bo dto impl
        2. 介面所在的包名,要準確的描述此介面在業務上的含義,此包名不應該包含任何和具體實現有關的資訊。
          1. 正確的:com.dt.school.grade.GradeReader
          2. 錯誤的:com.dt.school.grade.mysql.GradeReader 錯誤原因:介面所在的包,包含了具體的儲存實現mysql。
        3. 實現類所在的包名可以包含具體的實現資訊,比如一個介面有mysql和redis兩種實現類,那實現類所在的包名可以分為mysql和redis。
      5. 介面實現類命名:
        1. 當某個介面在短期內只有唯一的一個實現時,實現類的命名為:{介面名稱}Impl, 比如介面:OrderCalculatePerformer 對應的唯一實現類為:OrderCalculatePerformerImpl,並且放置在介面目錄下的 子目錄 impl 中。
        2. 當介面有多個實現時,多個實現類應該根據具體的實現細節來確定實現名稱,此名稱應該很簡單易懂的表明此實現的實現細節,對於使用者來說,從名稱就非常容易判斷此實現的具體資訊。
          比如:介面: orderDetailReader, 有兩個實現:MysqlOrderDetailReaderImpl RedisOrderDetailReaderImpl 從名字上能簡單的分清楚此實現的細節,具體從資料庫還是從redis獲取。
        3. 介面有多個實現類時,實現類可以在不同的包名下的impl資料夾中。
        4. 不要介意實現類的類名太長,只要是能準確的描述實現類的功能或者使用方式,都是好的命名。
    2. 設計
      1. 基於介面設計
        1. 永遠基於介面程式設計。對任何具體實現類的引用,都應該通過介面來呼叫,而不是直接使用實現類。
        2. 我們的業務專案,都是通過Spring框架來進行。bean管理,在專案內,不要使用靜態類、不要使用靜態方法,任何靜態類和靜態方法,比如幫助類,都應該轉換為介面和對應的唯一實現類。
        3. 一個介面,通常應該只有一個方法,確保介面本身是純粹的。除非萬不得已,不應該為介面增加新的方法。目前我們的center服務中,只有兩種特例,一種是:mapper介面,作為資料庫對映,當有新的資料庫查詢語句時,mapper介面會增加新的方法。另外一種是對外暴露的服務介面,會因為業務發展,新增或修改方法。除了這兩種介面外,其他內部使用或者作為中介軟體對外暴露的介面,一旦使用,不應該修改。
        4. 每個介面應該是幹且只幹一件事情,所以介面設計應該分層次,不同的層次實現不同的事情,層次之間應該只通過介面連線。
      2. 實現類
        1. 任何實現類的程式碼不應該超過250行,包括所有import。超過250行的實現類,證明你的程式碼邏輯拆分的不夠細。不管是不是通用的程式碼塊,只要是能相對獨立的業務模組,都應該拆分為新的獨立的介面和對應的實現。
        2. 單個方法內,程式碼行數不能超過80行,超過80行,證明你的程式碼分離度不夠、抽象有問題。
        3. 實現類,不能有超過3層縮排的程式碼塊。不能有多層縮排的if else 等邏輯判斷。程式碼編寫的原則是儘早返回,如果不滿足約束條件,應該return提前終止。
        4. 多層次的條件判斷,應該把中間的程式碼塊抽成介面和對應的多個實現,然後通過簡單的Map<key, 介面實現> 來實現。舉個例子:
          現在有一個boolean判斷,true的時候進入某個邏輯,false的時候進入另外一個邏輯。這時候應該抽出一個公共介面,然後增加兩個此介面的實現,分別對應true和false的情況。然後在這個判斷所在的實現類的建構函式中,初始化一個hashMap<Boolean, xxxInterface> ,插入兩個kv,k分別是true false,v分別是兩個實現類的例項。最後再使用的地方,通過 map.get(boolean) 獲得介面的實現,再呼叫介面的方法。
        5. 不能有冗餘程式碼,冗餘的程式碼都是不好的設計,應該把冗餘程式碼抽象提取出介面和實現。
        6. 實現類只能通過建構函式注入新的bean引用。
        7. 實現類本身有兩種標記初始化方式,一種是@Component 標記到類上,一般此種用於建構函式較為簡單的、注入外部bean比較少的實現類初始化。
          另外一種是:通過 config目錄下,新建xxxConfiguration 並且標記 @configuration ,然後在這個配置中通過新增 標記為@ bean 的新方法,在此方法內,組織需要被引用的bean,然後通過new 物件來生成實現類的例項。
          建議:如果實現類比較複雜,配置需要更加靈活,建議通過第二種方式初始化,這樣保證實現類本身的程式碼不需要修改,只需要修改configuration就能生效。
        8. 薦單例的實現類,都通過bean管理,而不是在其他bean單例中初始化唯一的例項。因為通過bean管理的單例,spring會維持bean之間的依賴關係,在應用程式關閉時,會根據依賴關係按照層次來關閉。對於持有連線池的類,非常重要。除此之外,通過bean管理的單例,可以在UT的時候進行mock和fake。
      3. 介面方法
        1. 不要使用方法過載。方法過載的可讀性較差。不方便維護。
        2. 方法引數上必須使用jetbrains NotNull 和Nullable註釋,標記方法引數是否允許為空或者不能為空。方法返回值上必須標明返回值是否可能為空。

        3. 介面的註解和方法、引數註解必須齊全。
        4. 方法的引數,不能超過兩個,並且當為兩個時,不能是同一種資料型別,比如 handle(String,String) 禁止此類方法。如果不滿足這個條件,使用builder模式生成方法引數。
        5. 當介面有超過2個以上的引數,特別是引數中有相同資料型別時,是非常不方便呼叫方明確含義的。在呼叫方未拿到source code jar時,多個引數,很容易傳錯,而且呼叫方的程式碼,含義不明確,更要命的是,如果方法有多個過載,支援更多的引數時,非常容易出錯。
        6. 建議使用lombok 的 at Builder 註釋,增加到DTO物件上。自動提供builder模式。

        7. 使用lombok 的 at Builder 註釋,要注意一個地方:只標記builder,會將dto物件的無引數建構函式取消,這樣在對外提供服務時,json反序列化會有問題。所以如果你這個DTO物件要通過feign或者web介面提供給外部使用,必須同時標記

          @Builder
          @NoArgsConstructor
          @AllArgsConstructor


          否則RPC服務呼叫時會json反序列化錯誤。如果DTO僅僅在你的專案內被引用。可以只標記builder。
        8. 如果你的dto物件中有set list等集合屬性,建議用以下方式自定義builder,對集合物件使用putXX putXXs 兩個方法來允許新增一個或多個集合,方便使用。

          builder 可選

          1. 通過使用Builder模式來構造DTO,解決此問題
          2. 構造DTO物件 abc
          3. 構造DTO物件對應的builder abcBuilder
          4. 兩者必須是同一個包下。且builder必須獨立於對應的dto,注意:有些書籍中建議builder作為dto的內部類,但因為我們的dto要經過dubbo spring cloud 序列化。內部類builder會導致序列化異常。
          5. dto中設定private 欄位,並實現get set,注意:set方法對builder模式不是必須的,但是如果沒有相應set方法,會導致spring cloud序列化出錯。所以要保留set方法。
          6. builder中設定同名的protected欄位。builder例子:

          7. 在builder中,增加方法用於傳入引數,方法名建議用介詞:for, on, of, with, by and etc不建議用set。以免混淆。比如 forOrder, byPartner, withCode, onChannel在DTO中,保證有一個預設建構函式,一個傳入builder的建構函式,例如:

          8. 在Builder中,最後有一個 build() 方法,這個build方法內,直接return new abc(this) 即可。
          9. 如果DTO要求某些欄位必須有值,可以在build方法return前,對引數進行簡單的校驗。注意:此處校驗,不要包含任何可能會變化的業務邏輯,只對資料是否null等進行基礎校驗,如果不確定物件是否永遠不能為空,請勿在build方法中校驗,因為Builder類是隨著API一起釋出出去的,在呼叫方引入的,任何包含可能會變的業務校驗,都將依賴於呼叫方更新API包才能實現。所以更復雜的DTO校驗,請在服務提供方進行。
          10. DTO必須有空的建構函式,否則序列化會出錯。
          11. DTO建議通過IntelliJIDEA 自動生成toString過載,例子:

          12. 這樣比較方便debug除錯。
          13. 當DTO物件有List等集合欄位時,可以通過下面的builder例子來設定此集合,避免呼叫方必須先建立集合,再傳入。

          14. 全部實現builder模式後,對於呼叫方。程式碼和引數含義非常清楚明細,層次結構優雅,以下是一個複雜的多層次的builder模式構造出來的物件,構造物件的程式碼

          15. builder模式下,構建DTO是通過鏈式呼叫,並且最後build()來完成,類似java stream的操作。鏈式呼叫和stream操作一樣,每個鏈式呼叫都要換行,保證程式碼層次清晰。
      4. 框架使用
        1. 所有String型別的操作,都不使用JDK原生的String操作,而是統一使用commons-lang3 StringUtils等框架類,對字串進行操作。
        2. 所有時間操作和儲存,禁止使用calendar類,傳輸可使用Date型別,時間操作,必須使用jodaTime 或者java8 中的LocalTime等。
        3. 返回集合資料的時候,禁止返回null,如果沒資料要返回,返回empty的list set等。
        4. 接收集合資料判斷時,必須使用Spring自帶的CollectionUtils.isEmpty 進行判斷,禁止直接判斷=null 或者size()>0。
      5. 資料物件
        1. 任何POJO物件,不管是mapper生成的資料庫對映物件還是對外輸出的物件。應該是純粹的物件,只能包含private的欄位,和對應的get set方法,boolean型別的欄位,不能是isXXX的命名。所有欄位get set必須是純粹的,不能加入任何邏輯判斷。所有欄位不能有初始值,不能有自定義的建構函式。所有欄位必須是物件型別,不能有基本型別。目的:保證dto物件純粹無預設值,確保雙方約定下,沒有理解差異。
        2. 內部使用或者輸出時,提供對應的builder類,用來生成此物件,builder類中可以有初始化初始值的功能,和部分簡單的欄位範圍校驗。
        3. 根據需要過載toString方法,使用commons-lang3中的toStringBuilder生成物件的字串表達。

        4. 如果物件需要進行比較、放入set等操作,建議過載hashCode equals,使用commons-lang3 EqualsBuilder自動生成下列程式碼:

      6. lombok的使用
        1. 通過lombok外掛,簡化部分程式碼,以下是允許使用的lombok 功能
          1. val (final) var (非final) 在方法內部用來簡化程式碼,實現類似java10種引入的var語法糖
          2. Getter Setter,在dto vo等類上新增這兩個註釋,可以自動將private變數生成對應的get set,特別注意:當使用了這兩個註釋後,必須在 src/main/java 下增加lombok.config 檔案,並且增加以下配置:lombok.getter.noIsPrefix=true。這個配置用來覆蓋lombok預設行為:對boolean型別的欄位,追加is字首。而是讓lombok生成常規的getEnabled() 這種方法。
          3. EqualsAndHashCode ,在dto vo等類上增加此註釋,用來自動重寫這兩個方法。如果此類繼承自其他父類,請確保父類也是使用此註釋重寫的這兩個方法。並且使用:@EqualsAndHashCode(callSuper=true)來表明繼承父類。對於不參與判斷的欄位,使用註釋@EqualsAndHashCode.Exclude進行排除。特別說明:當類參與比較排序放入set等情況時,務必合理選擇需要參與的欄位,並不是每個欄位都一定要參與計算。
          4. Data 如果不需要自定義toString的場景,可以使用Data註釋
        2. 不允許使用的lombok註釋
          1. NonNull ,和jetBrains的notNull重複,直接使用notNull即可。
          2. Cleanup,對資源池的關閉等,使用try with resource的原生功能
          3. toString, 請使用commons-lang3的ToStringBuilder,相比lombok,提供了更加靈活的配置型,比如對super的tostring處理,字串可以輸出為json格式。
          4. RequiredArgsConstructor and AllArgsConstructor,我們的dto vo要求不顯性制定任何建構函式,全部通過builder生成,dto vo本身要非常純粹,不能有自定義建構函式
          5. 其他註釋不不常用,不能使用。