<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    這樣寫代碼,同事樂開花

    共 13476字,需瀏覽 27分鐘

     ·

    2023-10-12 14:48

    記得之前看過一本書叫《代碼整潔之道》,讓我受益匪淺。

    工作多年后,越發(fā)覺得代碼整潔真的是太重要了!尤其是在團隊開發(fā)中,寫出優(yōu)雅工整的代碼,能讓同事更樂于跟你合作。

    今天分享的這篇文章,希望用最快的速度來幫助大家了解到整潔的代碼對項目、公司和個人的重要性,并且學會如何書寫整潔的代碼.

    下面,將通過命名、類、函數(shù)、測試這四個章節(jié),使我們的代碼變得整潔。

    1.為什么要保持代碼整潔?

    不整潔的代碼隨著時間的增加而增加時,生產(chǎn)力會隨之降低. 導致的結(jié)果就是:

    • 代碼不易擴展或擴展容易引發(fā)其他問題
    • 程序崩潰
    • 加班
    • 增加公司成本(加人) 甚至可能造成公司倒閉 一圖勝千言

    1.1 所以從一開始就要保持整潔

    所以在一開始就要寫整潔的代碼,如果有不整潔的代碼就要及時的整改. 絕對不要有以后再改,以后再說的想法, 因為!

    later equal never

    想想是不是這個道理,你有多少以后再說、以后再改的東西都拋諸腦后了.

    如果是一定要做的事情,那就趁早做!

    1.2 如何寫出整潔的代碼?

    那么現(xiàn)在的問題就是,怎樣的代碼才算是整潔的代碼呢:

    • 可讀性要高: 代碼要像散文一樣優(yōu)雅易讀,見碼知意
    • 拒絕重復代碼
    • 滿足設計模式原則
      • 單一職責
      • 開閉原則
      • 里氏替換原則
      • 依賴倒轉(zhuǎn)原則
      • 接口隔離原則
      • 迪米特法則
      • 合成復用法則

    2.命名

    好的命名可提高代碼的可讀性,讓人見碼知意, 降低理解成本,提高效率, 減少加班.

    2.1 不好的命名方式

    1. 沒有任何意義的命名方式
    public interface Animal {
        void abc();
    }

    現(xiàn)在我們有一個動物的接口, 里面有一個方法abc(),看了讓人一頭霧水, 調(diào)用這個方法的人也完全不知道這個方法是干什么的,因為他的命名毫無意義

    有意義的命名方式:

    public interface Animal {
        void cry();
    }

    我們將方法名命名為cry(喊叫,呼喊),調(diào)用的人就知道這個方法的作用是什么了.

    所以命名一定要有意義且讓人見碼知意.

    1. 命名前后不一致 這種情況體現(xiàn)在明明是同一行為,但是卻有不同的命名,前后不一致,讓人造成混淆.
    public interface StudentRepository extends JpaRepository<AlertAll, String> {
        Student findOneById(
                @Param("id") String id
        );

        List<Student> queryAllStudent(
        );

    }

    上面兩個方法都是查詢 xxx, 但是命名一會叫 query 一會叫 find,這種情況應該加以規(guī)范,保持一致, 修改后:

    public interface StudentRepository extends JpaRepository<AlertAll, String> {
        Student findOneById(
                @Param("id") String id
        );

        List<Student> findAll(
        );

    }


    1. 命名冗余 體現(xiàn)在命名有很多沒必要的成分在里面, 并且這些"廢話"并不能幫助區(qū)分它們的區(qū)別, 例如在變量命名中添加了 Variable 這個詞, 在表名中添加了 Table 這個詞.所以命名中不要出現(xiàn)冗余的單詞, 并且提前約定好命名的規(guī)范.
    // 獲取單個對象的方法用get做前綴
    getXxx();
    //獲取多個對象用list做前綴
    listXxxx();

    3.類

    整潔的類應滿足一下內(nèi)容:

    • 單一職責
    • 開閉原則
    • 高內(nèi)聚性

    3.1單一職責

    類應該短小,類或模塊應有且只有一條加以修改的理由, 如果一個類過于龐大的話,那么說明它承擔的職責過多了.

    優(yōu)點:

    • 降低類的復雜度
    • 提高類的可讀性
    • 提高系統(tǒng)的可維護性
    • 降低變更引起的風險

    如何判定類是否足夠短小?

    通過計算類的職責來判斷是否夠短小,類的名稱描述其全責, 如果無法為某個類命以準確的名稱, 這個類大概就太長了, 類名越含糊,可能擁有越多的職責.

    職責過多的例子,可以看到以下類有兩個職責:

    public abstract class Sql {
        // 操作SQL的職責
        public abstract void insert();


        // 統(tǒng)計SQL操作的職責
        public abstract void countInsert();

    }

    將統(tǒng)計的職責抽取到另一個類

    public abstract class CountSql {

        public abstract void countInsert();

    }

    3.2 開閉原則

    開閉原則: 面向修改關閉, 面向擴展開放.

    面向修改關閉意味著增加新的邏輯不會修改原有的代碼,降低了出錯的可能性.

    面向擴展開放則是提高了代碼的可擴展性,可很容易的增加新的代碼邏輯.

    不滿足開閉原則的例子:

    public abstract class Sql {
        public abstract void insert();
        public abstract void update();
        public abstract void delete();
    }

    如果我們現(xiàn)在要新增查詢的操作,就需要修改Sql這個類,沒有做到面向修改關閉

    重構后:

    public abstract class Sql {
        public abstract void generate();


    }

    public class CreateSql extends Sql {

        @java.lang.Override
        public void generate() {
            // 省略實現(xiàn)
        }
    }


    public class UpdateSql extends Sql {

        @Override
        public void generate() {
            // 省略實現(xiàn)
        }
    }

    當我們要增加刪除方法時可以很容易的擴展.

    使用大量的短小的類看似比使用少量龐大的類增加了工作量(增加了更多的類),但是真的是這樣嗎? 這里有一個很好的類比:

    你是想把工具歸置到有許多抽屜、每個抽屜中裝有定義和標記良好的組件的工具箱呢, 還是想要少數(shù)幾個能隨便把所有東西扔進去的抽屜?

    最終的結(jié)論:

    系統(tǒng)應該由許多短小的類而不是少量巨大的類組成,每個小類封裝一個權責,只有一個修改的原因,并與少數(shù)其他類一起協(xié)同達成期望的系統(tǒng)行為.

    3.3 內(nèi)聚

    方法操作的變量越多,就越粘聚到類上. 如果一個類中的每個變量都被每個方法所使用, 則該類具有最大的內(nèi)聚性. 我們應該將類的內(nèi)聚性保持在較高的位置. 內(nèi)聚性高意味著方法和變量互相依賴, 互相結(jié)合成一個邏輯整體.

    為什么要保持高內(nèi)聚? 保持內(nèi)聚性就會得到許多短小的類,就越滿足單一職責.

    內(nèi)聚性低怎么辦? 如果類的內(nèi)聚性就不夠高,就將原有的類拆分為新的類和方法.

    4.函數(shù)

    要想讓函數(shù)變得整潔,應保證:

    • 只做一件事
    • 好的命名
    • 整潔的參數(shù)
    • 注意返回內(nèi)容

    4.1 只做一件事

    what? 函數(shù)的第一規(guī)則是短小 第二規(guī)則是更短小 短小到只做一件事情. (沒錯和類的原則很像)

    why? 函數(shù)越短小,越能滿足單一職責.

    how? 以下是重構前的代碼, 這個方法有三個職責,并且該方法很長達到了80+50+5 = 135行

    public class PicService {

        public String upload(){
            // 校驗圖片的方法 偽代碼80行

            // 壓縮圖片的方法 偽代碼50行

            // 返回成功或失敗標識 0,1 偽代碼5行
            return "0";
        }
    }

    原有的upload方法做了很多的事情, 重構后只做了一件事情: 把大一些的概念(換言之,函數(shù)的名稱)拆分為另一抽象層上的一系列步驟:

     public String upload(){
            // 校驗圖片的方法
            check();
            // 壓縮圖片的方法
            compress();
            // 返回成功或失敗標識 0,1
            return "0";
        }

    而里面的每個方法,也都有著自己各自的職責(校驗圖片 、壓縮圖片 、返回結(jié)果).

    4.2 函數(shù)命名

    1. 函數(shù)名應見名知意

    函數(shù)要有描述性的名稱,不要害怕長名稱.

    不好的命名方式:

    public String addCharacter(String originString, char ch);

    這個函數(shù),一咋看,還不錯,從函數(shù)字面意思看是給某個字符串添加一個字符。但是到底是在原有字符串首部添加,還是在原有字符串末尾追加呢?亦或是在某個固定位置插入呢?從函數(shù)名字完全看不出來這個函數(shù)的真正意圖,只能繼續(xù)往下讀這個函數(shù)的具體實現(xiàn)才知道。

    而下面這幾個名字就比上面要好得多:

    // 追加到末尾
    public String appendCharacter(String originString, char ch);   

    // 插入指定位置
    public String insertCharacter(String originString, char ch, int insertPosition);

    2. 函數(shù)應該無副作用

    函數(shù)應該無副作用, 意思就是函數(shù)應該只做一件事,但是做這件事的時候做了另一件有副作用的事情.

    例如: 校驗密碼時會初始化 session,導致會話丟失。如果無法移除這種副作用,應該在方法名中展示出來,避免用戶誤用 checkPasswordasswordAndInitializeSession, 從命名上就要體現(xiàn)副作用.

    4.3 參數(shù)

    1. 參數(shù)越少越好

    參數(shù)越少,越容易理解,參數(shù)超過三個可以將參數(shù)進行封裝,要按參數(shù)的語義進行封裝,不一定封裝成一個大而全的參數(shù),可以封裝為多個,原則是按語義補充; 示例:

    public List<Student> findStudent(int age, String name, String country, int gender);

    //封裝參數(shù)
    public List<Student> findStudent(Student student);

    2. 不要使用標識參數(shù)

    標識參數(shù)是參數(shù)為 Boolean 類型, 用戶傳遞 true or false . 不要使用標識參數(shù)因為這意味著你的函數(shù)違背了單一職責(true false 兩套邏輯). 正確的做法是拆分為兩個方法:

    //標識參數(shù)方法
    render(Boolean isSuite);

    //重構為兩個方法
    reanderForSuite();
    renderForSingleTest();

    3. 不要使用輸出參數(shù)

    什么是輸出參數(shù)?

    將變量作為參數(shù)傳入方法,并且將變量輸出, 這就是輸出參數(shù)

    public void findStudent(){
    Student student = new Student();
    doSomething(student);
    return student;
    }

    int doSomething(Student student){
    // 省略一些student邏輯
    return student;
    }

    為什么不應該有輸出參數(shù)?

    因為增加了理解成本在里面,我們需要查看 doSomething到底對 student 做了什么. student 是輸入還是輸出參數(shù)? 都不明確.

    重構:

    // 將doSomething()方法內(nèi)聚到student對象本身
    student.doSomething();

    4.4 返回值

    1. 分離指令與訊問

    示例代碼:

    Pulic Boolean addElement(Element element)

    指令為增加某個元素,詢問是否成功,

    這樣做的壞處是職責不單一,所以應該拆分為兩個方法

    public void addElement(Element element);
    public Boolean isAdd(Element element);

    2. 使用異常替代返回錯誤碼

    直接拋出異常,而不是返回錯誤碼進行判斷, 可以使代碼更簡潔. 因為使用錯誤碼有可能會進行多層嵌套片段 代碼示例:

    // 使用錯誤碼導致多層嵌套...
    public class DeviceController{

     public void sendShutDown(){
      DeviceHandle handle=getHandle(DEV1);
       //Check the state of the device 
      if (handle != DeviceHandle.INVALID){
       // Save the device status to the record field 
       retrieveDeviceRecord(handle);
       // If nat suspended,shut down
       if (record.getStatus()!=DEVICE_SUSPENDED){
         pauseDevice(handle);
         clearDeviceWorkQueue(handle);
         closeDevice(handle);
       }else{
        logger.log("Device suspended. Unable to shut down"); 
       }
      }else{
       logger.log("Invalid handle for: " +DEV1.tostring()); 
     }

    重構后:

    //  將代碼拆分為一小段一小段, 降低復雜度,更加清晰
    public class DeviceController{

     public void sendShutDowm(){ 
      try{
       tryToShutDown();
      } catch (DeviceShutDownError e){ 
       logger.log(e);
      }

     private void tryToShutDown() throws DeviceShutDownError{
       DeviceHandle handle =getHandle(DEV1);
       retrieveDeviceRecord(handle);
       pauseDevice(handle);
       clearDeviceWorkQueue(handle);
       closeDevice(handle);
     }

     private DeviceHandle getHandle(DeviceID id){
                  // 省略業(yè)務邏輯
      throw new DeviceShutDownError("Invalid handle for:"+id.tostring()); 
     }
    }

    4.5 怎樣寫出這樣的函數(shù)?

    沒人能一開始就寫出完美的代碼, 先寫出滿足功能的代碼,之后緊接著進行重構

    為什么是緊接著? 因為 later equal never!

    4.6 代碼質(zhì)量掃描工具

    使用 SonarLint 可以幫助我們發(fā)現(xiàn)代碼的問題,并且還提供了相應的解決方案. 對于每一個問題,SonarLint 都給出了示例,還有相應的解決方案,教我們怎么修改,極大的方便了我們的開發(fā)

    比如,對于日期類型盡量用 LocalDate、LocalTime、LocalDateTime,還有重復代碼、潛在的空指針異常、循環(huán)嵌套等等問題。

    有了代碼規(guī)范與質(zhì)量檢測工具以后,很多東西就可以量化了,比如 bug 率、代碼重復率等.

    5.測試

    測試很重要,可以幫助我們驗證寫的代碼是否沒問題,同樣的測試代碼也應該保持整潔.

    5.1 TDD

    TDD 是測試驅(qū)動開發(fā)(Test-Driven Development),是敏捷開發(fā)中的一項核心實踐和技術,也是一種設計方法論。

    • 優(yōu)點:在任意一個開發(fā)節(jié)點都可以拿出一個可以使用,含少量bug并具一定功能和能夠發(fā)布的產(chǎn)品。

    • 缺點:增加代碼量。測試代碼是系統(tǒng)代碼的兩倍或更多,但是同時節(jié)省了調(diào)試程序及挑錯時間。

    how?

    1. 在開發(fā)代碼前先寫測試
    2. 只可編寫剛好無法通過的單元測試,不能編譯也算不通過
    3. 開發(fā)代碼不可超過測試

    關于2的解釋: 單測與生產(chǎn)代碼同步進行,寫到一個不可編譯的單測就開始寫生產(chǎn)代碼,如此反復循環(huán),單測就能包含到所有生產(chǎn)代碼。

    5.2 FIRST原則

    FIRST 原則就是一個指導編寫單元測試的原則

    • fast 快速 單測執(zhí)行應該快速的完成
    • independent 獨立 單測之間相互獨立
    • repeatable 可重復 單測不依賴于環(huán)境,隨處可運行
    • self validating 程序可通過輸出的Boolean自我驗證,而不需要通過人工的方式驗證(看日志輸出、對比兩個文件不同等)
    • timely 及時 單測在生產(chǎn)代碼之前編寫

    單元測試是代碼測試中的基礎測試,F(xiàn)IRST 是寫好單元測試的重要原則,它要求我們的單元測試快速,獨立,可重復,自我校驗,及時/完整。

    5.3 測試代碼模式

    開發(fā)測試代碼可以使用 given-when-then 模式

    • given 制造模擬數(shù)據(jù)
    • when 執(zhí)行測試代碼
    • then 驗證測試結(jié)果

    代碼示例

    /**
      * If an item is loaded from the repository, the name of that item should 
      * be transformed into uppercase.
      */
    @Test
    public void shouldReturnItemNameInUpperCase() {
     
        // Given
        Item mockedItem = new Item("it1""Item 1""This is item 1", 2000, true);
        when(itemRepository.findById("it1")).thenReturn(mockedItem);
     
        // When
        String result = itemService.getItemNameUpperCase("it1");
     
        // Then
        verify(itemRepository, times(1)).findById("it1");
        assertThat(result, is("ITEM 1"));
    }

    使用give-when-then 模式可提高測試代碼的可讀性.

    5.4 自動生成單測

    介紹兩款 IDEA 自動生成單測的插件

    • Squaretest 插件(收費)
    • TestMe 插件(免費)

    6.結(jié)束語

    編寫整潔的代碼可以讓我們 提高代碼的可讀性,使代碼變得更易擴展,提高我們的開發(fā)效率,減少加班,提高我們的level。

    每個開發(fā)人員都應該去看看<<代碼整潔之道>>,使我們的編碼能力以及編程思想都得到提升.

    再重復一次,我們應該及時的寫出整潔的代碼,而不是想著事后采取補救!

    來源:https://www.cnblogs.com/liuboren/p/17017421.html

    推薦閱讀:

    被 GPT-4 Plus 賬號價格勸退了!

    世界的真實格局分析,地球人類社會底層運行原理

    不是你需要中臺,而是一名合格的架構師(附各大廠中臺建設PPT)

    企業(yè)IT技術架構規(guī)劃方案

    論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

    華為干部與人才發(fā)展手冊(附PPT)

    【中臺實踐】華為大數(shù)據(jù)中臺架構分享.pdf

    華為的數(shù)字化轉(zhuǎn)型方法論

    華為如何實施數(shù)字化轉(zhuǎn)型(附PPT)

    華為大數(shù)據(jù)解決方案(PPT)

    瀏覽 152
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    av影音先锋电影网一区二区三区 | 欧美美穴一区 | 日日日想日日碰日日撸 | 日韩无码中文字幕电影 | 淫秽三级片中文字幕在线免费观看 |