編寫可讀代碼的藝術學習總結(超讚!!!)

我是技術搬運工,好東西當然要和大家分享啦.原文地址

第 1 章 可讀性的重要性

編程有很大一部分時間是在閱讀代碼,不僅要閱讀自己的代碼,而且要閱讀別人的代碼。因此,可讀性良好的代碼能夠大大提高編程效率。

可讀性良好的代碼往往會讓代碼架構更好,因爲程序員更願意去修改這部分代碼,而且也更容易修改。

只有在核心領域爲了效率纔可以放棄可讀性,否則可讀性是第一位。

第 2 章 用名字表達代碼含義

一些比較有表達力的單詞:

單詞可替代單詞
senddeliver、dispatch、announce、distribute、route
findsearch、extract、locate、recover
startlaunch、create、begin、open
makecreate、set up、build、generate、compose、add、new

使用 i、j、k 作爲循環迭代器不總是好的,爲迭代器添加更有表達力的名字會更好,比如 user_i、member_i。因爲循環層次越多,代碼越難理解,有表達力的迭代器名字可讀性會更高

爲名字添加形容詞等信息能讓名字更具有表達力,但是名字也會變長。名字長短的準則是:作用域越大,名字越長。因此只有在短作用域才能使用一些簡單名字。

第 3 章 名字不能帶來歧義

起完名字要思考一下別人會對這個名字有何解讀,會不會誤解了原本想表達的含義。

用 min、max 表示數量範圍;

用 first、last 表示訪問空間的包含範圍,begin、end 表示訪問空間的排除範圍,即 end 不包含尾部。

布爾相關的命名加上 is、can、should、has 等前綴。

第 4 章 良好的代碼風格

適當的空行和縮進

排列整齊的註釋:

int a = 1;   // 註釋
int b = 11;  // 註釋
int c = 111; // 註釋

語句順序不能隨意,比如與 html 表單相關聯的變量的賦值應該和表單在 html 中的順序一致;

把相關的代碼按塊組織放在一起。

第 5 章 編寫註釋

閱讀代碼首先會注意到註釋,如果註釋沒太大作用,那麼就會浪費代碼閱讀的時間。那些能直接看出含義的代碼不需要寫註釋,特別是並不需要爲每個方法都加上註釋,比如那些簡單的 getter 和 setter 方法,爲這些方法寫註釋反而讓代碼可讀性更差。

不能因爲有註釋就隨便起個名字,而是爭取起個好名字而不寫註釋。

可以用註釋來記錄採用當前解決辦法的思考過程,從而讓讀者更容易理解代碼。

註釋用來提醒一些特殊情況。

用 TODO 等做標記:

標記用法
TODO待做
FIXME待修復
HACH粗糙的解決方案
XXX危險!這裏有重要的問題

第 6 章 如何編寫註釋

儘量簡潔明瞭:

// The first String is student's name
// The Second Integer is student's score
Map<String, Integer> scoreMap = new HashMap<>();
// Student' name -> Student's score
Map<String, Integer> scoreMap = new HashMap<>();

添加測試用例來說明:

//...
// Example: add(1, 2), return 3
int add(int x, int y) {
    return x + y;
}

在很複雜的函數調用中對每個參數標上名字:

int a = 1;
int b = 2;
int num = add(\* x = *\ a, \* y = *\ b);

使用專業名詞來縮短概念上的解釋,比如用設計模式名來說明代碼。

第 7 章 提高控制流的可讀性

條件表達式中,左側是變量,右側是常數。比如下面第一個語句正確:

if(len < 10)
if(10 > len)

if / else 條件語句,先處理以下邏輯:① 正邏輯;② 關鍵邏輯;③:簡單邏輯

if(a == b) {
    // 正邏輯
} else{
    // 反邏輯
}

只有在邏輯簡單的情況下使用 ? : 三目運算符來使代碼更緊湊,否則應該拆分成 if / else;

do / while 的條件放在後面,不夠簡單明瞭,並且會有一些迷惑的地方,最好使用 while 來代替。

如果只有一個 goto 目標,那麼 goto 尚且還能接受,但是過於複雜的 goto 會讓代碼可讀性特別差,應該避免使用 goto。

在嵌套的循環中,用一些 return 語句往往能減少嵌套的層數。

第 8 章 拆分長表達式

長表達式的可讀性很差,可以引入一些解釋變量從而拆分表達式:

if line.split(':')[0].strip() == "root":
    ...
username = line.split(':')[0].strip()
if username == "root":
    ...

使用摩根定理簡化一些邏輯表達式:

if(!a && !b) {
    ...
}
if(a || b) {
    ...
}

第 9 章 變量與可讀性

去除控制流變量。在循環中通過 break 或者 return 可以減少控制流變量的使用。

boolean done = false;
while(/* condition */ && !done) {
    ...
    if(...) {
        done = true;
        continue;
    }
}
while(/* condition */) {
    ...
    if(...) {
        break;
    }
}

減小變量作用域。作用域越小,越容易定位到變量所有使用的地方。

JavaScript 可以用閉包減小作用域。以下代碼中 submit_form 是函數變量,submitted 變量控制函數不會被提交兩次。第一個實現中 submitted 是全局變量,第二個實現把 submitted 放到匿名函數中,從而限制了起作用域範圍。

submitted = false;
var submit_form = function(form_name) {
    if(submitted) {
        return;
    }
    submitted = true;
};
var submit_form = (function() {
    var submitted = false;
    return function(form_name) {
        if(submitted) {
            return;
        }
        submitted = true;
    }
}());  // () 使得外層匿名函數立即執行

JavaScript 中沒有用 var 聲明的變量都是全局變量,而全局變量很容易造成迷惑,因此應當總是用 var 來聲明變量。

變量定義的位置應當離它使用的位置最近。

實例解析

在一個網頁中有以下文本輸入字段:

<input type = "text" id = "input1" value = "a">
<input type = "text" id = "input2" value = "b">
<input type = "text" id = "input3" value = "">
<input type = "text" id = "input4" value = "d">

現在要接受一個字符串並把它放到第一個空的 input 字段中,初始實現如下:

var setFirstEmptyInput = function(new_alue) {
    var found = false;
    var i = 1;
    var elem = document.getElementById('input' + i);
    while(elem != null) {
        if(elem.value === '') {
            found = true;
            break;
        }
        i++;
        elem = document.getElementById('input' + i);
    }
    if(found) elem.value = new_value;
    return elem;
}

以上實現有以下問題:

  • found 可以去除;
  • elem 作用域過大;
  • 可以用 for 循環代替 while 循環;
var setFirstEmptyInput = function(new_value) {
    for(var i = 1; true; i++) {
        var elem = document.getElementById('input' + i);
        if(elem === null) {
            return null;
        }
        if(elem.value === '') {
            elem.value = new_value;
            return elem;
        }
    }
};

第 10 章 抽取函數

工程學就是把大問題拆分成小問題再把這些問題的解決方案放回一起。

首先應該明確一個函數的高層次目標,然後對於不是直接爲了這個目標工作的代碼,抽取出來放到獨立的函數中。

介紹性的代碼:

int findClostElement(int[] arr) {
    int clostIdx;
    int clostDist = Interger.MAX_VALUE;
    for(int i = 0; i < arr.length; i++) {
        int x = ...;
        int y = ...;
        int z = ...;
        int value = x * y * z;
        int dist = Math.sqrt(Math.pow(value, 2), Math.pow(arr[i], 2));
        if(dist < clostDist) {
            clostIdx = i;
            clostDist = value;
        }
    }
    return clostIdx;
}

以上代碼中循環部分主要計算距離,這部分不屬於代碼高層次目標,高層次目標是尋找最小距離的值,因此可以把這部分代替提取到獨立的函數中。這樣做也帶來一個額外的好處有:可以單獨進行測試、可以快速找到程序錯誤並修改。

public int findClostElement(int[] arr) {
    int clostIdx;
    int clostDist = Interger.MAX_VALUE;
    for(int i = 0; i < arr.length; i++) {
        int dist = computDist(arr, i);
        if(dist < clostDist) {
            clostIdx = i;
            clostDist = value;
        }
    }
    return clostIdx;
}

並不是函數抽取的越多越好,如果抽取過多,在閱讀代碼的時候可能需要不斷跳來跳去。只有在當前函數不需要去了解某一塊代碼細節而能夠表達其內容時,把這塊代碼抽取成子函數纔是好的。

函數抽取也用於減小代碼的冗餘。

第 11 章 一次只做一件事

只做一件事的代碼很容易讓人知道其要做的事;

基本流程:列出代碼所做的所有任務;把每個任務拆分到不同的函數,或者不同的段落。

第 12 章 用自然語言表述代碼

先用自然語言書寫代碼邏輯,也就是僞代碼,然後再寫代碼,這樣代碼邏輯會更清晰。

第 13 章 減少代碼量

不要過度設計,編碼過程會有很多變化,過度設計的內容到最後往往是無用的。

多用標準庫實現。

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