一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java雜談之代碼重構的方法多長才算長

Java雜談之代碼重構的方法多長才算長

2022-01-26 12:47JavaEdge. Java教程

關于代碼重構的理解:在不改變軟件系統/模塊所具備的功能特性的前提下,遵循/利用某種規則,使其內部結構趨于完善。其在軟件生命周期中的價值體現主要在于可維護性和可擴展性

每當看到長函數,我們都得:

  • 被迫理解一個長函數
  • 在一個長函數中,小心翼翼地找出需要的邏輯,按需求微調

幾乎所有程序員都會有類似經歷。
沒人喜歡長函數,但你卻要一直和各種長函數打交道。

幾百上千行的函數肯定是不足以稱霸的。

多長算“長”?

100 行?對于函數長度容忍度太高了!這是導致長函數產生的關鍵點。

看具體代碼時,一定要能夠看到細微之處。關鍵點就是將任務拆解得越小越好,這個觀點對代碼同樣適用。隨著對代碼長度容忍度的降低,對代碼細節的感知力就會逐漸提升,你才能看到那些原本所謂細枝末節的地方隱藏的各種問題。

“越小越好”是一個追求的目標,不過,沒有一個具體的數字,就沒辦法約束所有人的行為。所以,通常情況下,我們還是要定義出一個代碼行數的上限,以保證所有人都可以按照這個標準執行。

像 Java 這樣表達能力稍弱的靜態類型語言,爭取 20 行代碼解決問題。

這不是一個說說就算的標準,我們應該把它變成一個可執行的標準。比如,在 Java 中,我們就可以把代碼行的約束加到 CheckStyle 的配置文件:

?
1
2
3
4
5
<module name="MethodLength">
    <property name="tokens" value="METHOD_DEF"/>
    <property name="max" value="20"/>
    <property name="countEmpty" value="false"/>
</module>

這樣,在我們提交代碼之前,執行本地的構建腳本,就可以把長函數檢測出來。

即便以 20 行上限,這也已經超過很多人的認知,具體的函數行數可以結合團隊的實際情況來制定。
非常不建議把這個數字放得很大,就像我前面說的那樣,如果你放到 100 行,這個數字基本上是沒有太多意義的,對團隊也起不到什么約束作用。

  • 如果函數里面的行寫得很長呢?還應不應該插入換行?如果插入換行的話就會增加行數,如果不差入換行,在看代碼時就要經常移動水平滾動條,按代碼行而非物理行計數。

長函數的產生

限制函數長度,是一種簡單粗暴的解決方案。最重要的是你要知道,長函數本身是一個結果,如果不理解長函數產生的原因,還是很難寫出整潔的代碼。

以性能為由

像 C 語言這種在今天已經是高性能的程序設計語言,在問世之初,也曾被人質疑性能不彰,尤其是函數調用。

在一些寫匯編語言的人看來,調用函數涉及到入棧出棧的過程,顯然不如直接執行來得性能高。這種想法經過各種演變流傳到今天,任何一門新語言出現,還是會以同樣的理由被質疑。

所以,在很多人看來,把函數寫長是為了所謂性能。不過,這個觀點在今天是站不住的。性能優化不該是寫代碼的第一考量:

  • 有活力的程序設計語言本身是不斷優化的,無論是編譯器,還是運行時,性能都會越來越好
  • 可維護性比性能優化要優先考慮,當性能不足以滿足需要時,我們再來做相應的測量,找到焦點,進行特定的優化。這比在寫代碼時就考慮所謂性能要更能鎖定焦點,優化才有意義。

平鋪直敘

寫代碼平鋪直敘,把自己想到的一點點羅列出來。比如下面這段代碼(如果你不想仔細閱讀,可以直接跳到后面):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public void executeTask() {
    ObjectMapper mapper = new ObjectMapper();
    CloseableHttpClient client = HttpClients.createDefault();
    List<Chapter> chapters = this.chapterService.getUntranslatedChapters();
    for (Chapter chapter : chapters) {
        // Send Chapter
        SendChapterRequest sendChapterRequest = new SendChapterRequest();
        sendChapterRequest.setTitle(chapter.getTitle());
        sendChapterRequest.setContent(chapter.getContent());
 
 
        HttpPost sendChapterPost = new HttpPost(sendChapterUrl);
        CloseableHttpResponse sendChapterHttpResponse = null;
        String chapterId = null;
        try {
            String sendChapterRequestText = mapper.writeValueAsString(sendChapterRequest);
            sendChapterPost.setEntity(new StringEntity(sendChapterRequestText));
            sendChapterHttpResponse = client.execute(sendChapterPost);
            HttpEntity sendChapterEntity = sendChapterPost.getEntity();
            SendChapterResponse sendChapterResponse = mapper.readValue(sendChapterEntity.getContent(), SendChapterResponse.class);
            chapterId = sendChapterResponse.getChapterId();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (sendChapterHttpResponse != null) {
                    sendChapterHttpResponse.close();
                }
            } catch (IOException e) {
                // ignore
            }
        }
 
 
        // Translate Chapter
        HttpPost translateChapterPost = new HttpPost(translateChapterUrl);
        CloseableHttpResponse translateChapterHttpResponse = null;
        try {
            TranslateChapterRequest translateChapterRequest = new TranslateChapterRequest();
            translateChapterRequest.setChapterId(chapterId);
            String translateChapterRequestText = mapper.writeValueAsString(translateChapterRequest);
            translateChapterPost.setEntity(new StringEntity(translateChapterRequestText));
            translateChapterHttpResponse = client.execute(translateChapterPost);
            HttpEntity translateChapterEntity = translateChapterHttpResponse.getEntity();
            TranslateChapterResponse translateChapterResponse = mapper.readValue(translateChapterEntity.getContent(), TranslateChapterResponse.class);
            if (!translateChapterResponse.isSuccess()) {
                logger.warn("Fail to start translate: {}", chapterId);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (translateChapterHttpResponse != null) {
                try {
                    translateChapterHttpResponse.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

把沒有翻譯過的章節發到翻譯引擎,然后,啟動翻譯過程。

翻譯引擎是另外一個服務,需通過 HTTP 的形式向它發送請求。相對而言,這段代碼還算直白,當你知道了我上面所說的邏輯,你是很容易看懂這段代碼。

這段代碼之所以很長,主要原因就是把前面所說的邏輯全部平鋪直敘地擺在那里了,這里既有業務處理的邏輯,比如,把章節發送給翻譯引擎,然后,啟動翻譯過程;又有處理的細節,比如,把對象轉成 JSON,然后,通過 HTTP 客戶端發送出去。

從這段代碼中,可看到平鋪直敘的代碼存在的兩個典型問題:

  • 把多個業務處理流程放在一個函數里實現
  • 把不同層面的細節放到一個函數里實現

這里發送章節和啟動翻譯是兩個過程,顯然,這是可以放到兩個不同的函數中去實現的,所以,我們只要做一下提取函數,就可以把這個看似龐大的函數拆開,而拆出來的幾個函數規模都會小很多,像下面這樣:

?
1
2
3
4
5
6
7
8
9
public void executeTask() {
    ObjectMapper mapper = new ObjectMapper();
    CloseableHttpClient client = HttpClients.createDefault();
    List<Chapter> chapters = this.chapterService.getUntranslatedChapters();
    for (Chapter chapter : chapters) {
        String chapterId = sendChapter(mapper, client, chapter);
        translateChapter(mapper, client, chapterId);
    }
}

拆出來的部分,實際上就是把對象打包發送的過程,我們以發送章節為例,先來看拆出來的發送章節部分:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private String sendChapter(final ObjectMapper mapper,
                           final CloseableHttpClient client,
                           final Chapter chapter) {
    SendChapterRequest request = asSendChapterRequest(chapter);
 
 
    CloseableHttpResponse response = null;
    String chapterId = null;
    try {
        HttpPost post = sendChapterRequest(mapper, request);
        response = client.execute(post);
        chapterId = asChapterId(mapper, post);
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        try {
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            // ignore
        }
    }
    return chapterId;
}
 
 
private HttpPost sendChapterRequest(final ObjectMapper mapper, final SendChapterRequest sendChapterRequest) throws JsonProcessingException, UnsupportedEncodingException {
    HttpPost post = new HttpPost(sendChapterUrl);
    String requestText = mapper.writeValueAsString(sendChapterRequest);
    post.setEntity(new StringEntity(requestText));
    return post;
}
 
 
private String asChapterId(final ObjectMapper mapper, final HttpPost sendChapterPost) throws IOException {
    String chapterId;
    HttpEntity entity = sendChapterPost.getEntity();
    SendChapterResponse response = mapper.readValue(entity.getContent(), SendChapterResponse.class);
    chapterId = response.getChapterId();
    return chapterId;
}
 
 
private SendChapterRequest asSendChapterRequest(final Chapter chapter) {
    SendChapterRequest request = new SendChapterRequest();
    request.setTitle(chapter.getTitle());
    request.setContent(chapter.getContent());
    return request

這個代碼還算不上已經處理得很整潔了,但至少同之前相比,已經簡潔了一些。我們只用了最簡單的提取函數這個重構手法,就把一個大函數拆分成了若干的小函數。

長函數往往還隱含著一個命名問題。如果你看修改后的sendChapter,其中的變量命名明顯比之前要短,理解的成本也相應地會降低。因為變量都是在這個短小的上下文里,也就不會產生那么多的命名沖突,變量名當然就可以寫短一些。

平鋪直敘的代碼,一個關鍵點就是沒有把不同的東西分解出來。如果我們用設計的眼光衡量這段代碼,這就是“分離關注點”沒有做好,把不同層面的東西混在了一起,既有不同業務混在一起,也有不同層次的處理混在了一起。我在《軟件設計之美》專欄中,也曾說過,關注點越多越好,粒度越小越好。

一次加一點

有時,一段代碼一開始的時候并不長,就像下面這段代碼,它根據返回的錯誤進行相應地錯誤處理:

?
1
2
3
if (code == 400 || code == 401) {
  // 做一些錯誤處理
}

然后,新的需求來了,增加了新的錯誤碼,它就變成了這個樣子:

?
1
2
3
if (code == 400 || code == 401 || code == 402) {
  // 做一些錯誤處理
}

這段代碼有很多次被修改的機會,日積月累:

?
1
2
3
4
5
if (code == 400 || code == 401 || code == 402 || ...
  || code == 500 || ...
  || ...
  || code == 10000 || ...) {
}

后人看到就想罵人。任何代碼都經不起這種無意識的累積,每個人都沒做錯,但最終的結果很糟糕。對抗這種逐漸糟糕腐壞的代碼,需要知道“童子軍軍規”:
讓營地比你來時更干凈。

Robert Martin 把它借鑒到了編程領域,我們應該看看自己對于代碼的改動是不是讓原有的代碼變得更糟糕了,如果是,那就改進它。
但這一切的前提是,你要能看出自己的代碼是不是讓原有的代碼變得糟糕了,所以,學習代碼的壞味道還是很有必要的。

至此,我們看到了代碼變長的幾種常見原因:

  • 以性能為由
  • 平鋪直敘
  • 一次加一點

代碼變長根本是一個無意識的問題,寫代碼的人沒有覺得自己把代碼破壞了。但只要你認識到長函數是一個壞味道,后面的許多問題就自然而然地會被發掘出來,至于解決方案,你已經看到了,大部分情況下,就是拆分成各種小函數。

總結

沒有人愿意去閱讀長函數,但許多人又會不經意間寫出長函數。

對于團隊,一個關鍵點是要定義出長函數的標準。
過于寬泛的標準沒有意義,想要有效地控制函數規模,幾十行已經是標準上限,這個標準越低越好。

長函數產生的原因:

  • 性能為借口
  • 代碼平鋪直敘

函數寫長最常見的原因。之所以會把代碼平攤在那里:
- 把多個業務寫到了一起
- 把不同層次的代碼寫到了一起。究其根因,那是“分離關注點”沒有做好

  • 每人每次加一點點

應對主要辦法就是要堅守“童子軍軍規”,但其背后更深層次的支撐就是要對壞味道有著深刻的認識

把函數寫短,越短越好。

到此這篇關于Java雜談之代碼重構的方法多長才算長的文章就介紹到這了,更多相關Java 代碼重構內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/qq_33589510/article/details/120438225

延伸 · 閱讀

精彩推薦
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 国产精品一久久香蕉产线看 | 香蕉91 | 亚洲天堂男人 | 日韩精品亚洲专区在线影视 | 亚洲高清在线精品一区 | 99热视| 色综合视频一区二区观看 | 国产亚洲精品91 | 無码一区中文字幕少妇熟女H | 娇妻被健身教练挺进小说阅读 | 男人搡女人视频免费看 | 香蕉精品高清在线观看视频 | 日韩视频免费看 | 亚洲上最大成网人站4438 | 青草精品 | aaa毛片手机在线现看 | 性欧洲女人18 | 亚洲国产成人久久精品hezyo | 亚洲高清视频在线 | 欧美一二区视频 | 青春草在线观看精品免费视频 | 成年人视频在线播放 | 成人精品一区二区三区 | 热伊人99re久久精品最新地 | 男人猛激烈吃奶gif动态图 | 亚洲精品视频久久 | 国产色网址 | 女人把扒开给男人爽 | 五月天在线视频观看 | xxx95日本老师xxx学生 | 亚洲精品国产AV成人毛片 | 狠狠色狠狠色综合日日小蛇 | 精品国产一区二区三区久久影院 | 黑人又大又硬又粗再深一点 | 日本私人影院 | 国产人人草 | 国产在线播放一区 | 兽操人 | 91看片淫黄大片.在线天堂 | 国产在线视频欧美亚综合 | 污文啊好棒棒啊好了 |