編寫可維護的代碼

編寫可維護的代碼

前言

咱們在修改他人代碼的時候,閱讀他人代碼所花的時間常常比實現功能的時間還要更多javascript

若是程序結構不清晰,代碼混亂 。牽一髮而動全身。那維護起來就更難維護了java

可讀性

  1. 可理解性:他人能夠接手代碼並理解它
  2. 直觀性 : 代碼邏輯清晰
  3. 可調試性 :出錯時,方便定位問題所在

如何提升可讀性

  1. 代碼格式化
  2. 適當添加註釋
  • 函數與方法
  • 大段代碼
  • 註釋需有意義

如何優化代碼

  1. 找出代碼的壞味道
  2. 使用重構手法將其解決掉

代碼的壞味道

在咱們的程序中,能夠聞到不少的壞味道。主要有如下這些點git

命名不規範或無心義

命名存在使用縮寫、不規範、無心義github

例子:var a = xxx,b = xxx算法

重複代碼

相同(或類似)的代碼在項目中出現了屢次,若是需求發生更改,則須要同時修改多個地方函數

過長函數

程序越長越難理解,一個函數應該只完成一個功能優化

過長的類

一個類的職責過多,一個類應該是一個獨立的總體。設計

過長參數列表

太長的參數列表難以理解,不易使用。當須要修改的時候,會更加容易出錯調試

數據泥團

有些數據項老是三五成羣的待在一塊兒。例如兩個類中相同的字段、許多函數簽名相同的參數。code

這些都應該提煉到一個對象中,將不少參數列縮短,簡化函數調用

相似的函數

總體上實現的功能差很少,可是因爲有一點點區別。因此寫成了多個函數

重構手法

提煉函數

針對一個比較長的函數,提煉成一個個完成特定功能的函數。

例子

// 提煉前
function test11() {
    var day = $('day');
    var yearVal = '2016';
    var monthVal = '10';
    var dayVal = '10';
    day.val(dayVal);
    switch (monthVal) {
        case 4:
        case 6:
        case 9:
        case 11:
            if (dayVal > 30) {
                day.val(30);
            }
            break;
        case 2:
            if (
                yearVal % 4 == 0 &&
                (yearVal % 100 != 0 || yearVal % 400 == 0) &&
                monthVal == 2
            ) {
                if (dayVal > 29) {
                    day.val(29);
                }
            } else {
                if (dayVal > 28) {
                    day.val(28);
                }
            }
            break;
        default:
            if (dayVal > 31) {
                day.val(31);
            }
    }
}
// 提煉後
function test12() {
    var day = $('day');
    var yearVal = '2016';
    var monthVal = '10';
    var dayVal = '10';

    var maxDay = getMaxDay(yearVal, monthVal);
    if (dayVal > maxDay) {
        day.val(maxDay);
    } else {
        day.val(dayVal);
    }
}

function getMaxDay(year, month) {
    var maxDay = 0;
    switch (month) {
        case 4:
        case 6:
        case 9:
        case 11:
            maxDay = 30;
            break;
        case 2:
            if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
                maxDay = 29;
            } else {
                maxDay = 28;
            }
            break;
        default:
            maxDay = 31;
    }
    return maxDay;
}
例子中,提煉前的代碼,須要很費勁的看完整個函數,纔會明白作了什麼處理,提煉後的代碼。只須要稍微看一下,就知道 getMaxDay 是獲取當前月份的最大天數

優勢:

  1. 若是每一個函數的粒度都很小,那麼函數被複用的機會就更大;
  2. 這會使高層函數讀起來就想一系列註釋;
  3. 若是函數都是細粒度,那麼函數的覆寫也會更容易些

內聯函數

有時候,一個函數的本體與函數名同樣簡單易懂,就要用到這種手法。

這種手法用於處理優化過分的問題

舉個例子:

function biggerThanZero(num) {
    return num > 0;
}
function test() {
    var num = 10;
    if (biggerThanZero(num)) {
        //do something
    }
}
//內聯後
function test() {
    var num = 10;
    if (num > 0) {
        //do something
    }
}

引入解釋性變量

當表達式比較複雜難以閱讀的時候,就能夠經過臨時變量來幫助你將表達式分解爲容易管理的形式

有些時候,運用提煉函數會更好一點

舉兩個簡單的例子:

// 例子 1
// before
function test2() {
    if (
        platform.toUpperCase().indexOf('MAC') > -1 &&
        browser.toUpperCase().indexOf('IE') > -1 &&
        wasInitialized() &&
        resize > 0
    ) {
        // do something
    }
}
// after
function test2() {
    var isMacOs = platform.toUpperCase().indexOf('MAC') > -1;
    var isIEBrowser = browser.toUpperCase().indexOf('IE') > -1;
    var wasResized = resize > 0;
    if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
        // do something
    }
}
// --------------------------------------------------

// 例子2
// before
function caluPrice(quantity, itemPrice) {
    return (
        quantity * itemPrice -
        Math.max(0, quantity - 500) * itemPrice * 0.05 +
        Math.min(quantity * itemPrice * 0.1, 100)
    );
}

// after
function caluPrice(quantity, itemPrice) {
    var basePrice = quantity * itemPrice;
    var discount = Math.max(0, quantity - 500) * itemPrice * 0.05;
    var shiping = Math.min(basePrice * 0.1, 100);
    return basePrice - discount + shiping;
}
在兩個例子中,引入解釋性的變量以後,可讀性大大增長。函數的意圖就比較明顯,單看變量命名就已經能大概知道具體的實現

分解臨時變量

  • 除了 for 循環裏用來收集結果的變量,其餘的臨時變量都應該只被賦值一次。
  • 由於被賦值超過一次,就意味着他在函數中承擔了多個責任。
  • 一個變量承擔多個責任。會令代碼看起來容易迷惑

舉個例子:

// 分解臨時變量
// before
function test3() {
    var temp = 2 * (width + height);
    console.log(temp);
    // do something
    temp = height * width;
    // do something
    console.log(temp);
}
// after
function test4() {
    var perimeter = 2 * (width + height);
    console.log(perimeter);
    // do something
    var area = height * width;
    // do something
    console.log(area);
}
在這個例子中,temp 分別被賦予了兩次,若是代碼塊較長的狀況,會增長風險,由於你不知道他在哪裏被改掉了

替換算法

當你重構的時候,發現實現一樣的功能有一個更清晰的方式,就應該將原有的算法替換成你的算法。

舉個例子:

// 替換算法
// before
function getWeekDay() {
    var weekStr = '';
    switch (date.format('d')) {
        case 0:
            weekStr = '日';
            break;
        case 1:
            weekStr = '一';
            break;
        case 2:
            weekStr = '二';
            break;
        case 3:
            weekStr = '三';
            break;
        case 4:
            weekStr = '四';
            break;
        case 5:
            weekStr = '五';
            break;
        case 6:
            weekStr = '六';
            break;
    }
    return weekStr;
}
// after
function getWeekDay() {
    var weekDays = ['日', '一', '二', '三', '四', '五', '六'];
    return weekDays[date.format('d')];
}

以字面常量取代魔法數(eg:狀態碼)

在計算機科學中,魔法數是歷史最悠久的不良現象之一。

魔法數是指程序中莫名其妙的數字。擁有特殊意義,卻又不能明確表現出這種意義的數字

舉個例子:

// before
function test5(x) {
    if (x == 1) {
        console.log('完成');
    } else if (x == 2) {
        console.log('上傳中');
    } else if (x == 3) {
        console.log('上傳失敗');
    } else {
        console.log('未知的錯誤');
    }
}

function test6(x) {
    if (x == 3) {
        // do something
    }
}

// after
var UploadStatus = {
    START: 0,
    UPLOADING: 1,
    SUCCESS: 2,
    ERROR: 3,
    UNKNOWN: 4
};
function test7(x) {
    if (x == UploadStatus.START) {
        console.log('未開始');
    } else if (x == UploadStatus.UPLOADING) {
        console.log('上傳中');
    } else if (x == UploadStatus.SUCCESS) {
        console.log('上傳成功');
    } else if (x == UploadStatus.ERROR) {
        console.log('上傳失敗');
    } else {
        console.log('未知的錯誤');
    }
}

function test8(x) {
    if (x == UploadStatus.ERROR) {
        // do something
    }
}
對於魔法數,應該用一個枚舉對象或一個常量來賦予其可見的意義。這樣,你在用到的時候,就可以明確的知道它表明的是什麼意思
並且,當需求變化的時候,只須要改變一個地方便可

分解條件表達式

複雜的條件邏輯是致使複雜度上升的地點之一。由於必須編寫代碼來處理不一樣的分支,很容易就寫出一個至關長的函數

將每一個分支條件分解成新函數能夠突出條件邏輯,更清楚代表每一個分支的做用以及緣由

舉個例子:

// 分解條件表達式
// 商品在冬季和夏季單價不同
// before
var SUMMER_START = '06-01';
var SUMMER_END = '09-01';
function test9() {
    var quantity = 2;
    var winterRate = 0.5;
    var winterServiceCharge = 9;
    var summerRate = 0.6;
    var charge = 0;
    if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
        charge = quantity * winterRate + winterServiceCharge;
    } else {
        charge = quantity * summerRate;
    }
    return charge;
}

// after
function test9() {
    var quantity = 2;
    return notSummer(date) ? winterCharge(quantity) : summerCharge(quantity);
}

function notSummer(date) {
    return date.before(SUMMER_START) || date.after(SUMMER_END);
}

function summerCharge(quantity) {
    var summerRate = 0.6;
    return quantity * summerRate;
}

function winterCharge(quantity) {
    var winterRate = 0.5;
    var winterServiceCharge = 9;
    return quantity * winterRate + winterServiceCharge;
}

合併條件表達式

當發現一系列的條件檢查,檢查條件不同,可是行爲卻一致。就能夠將它們合併爲一個條件表達式

舉個例子:

// 合併條件表達式
// before
function test10(x) {
    var isFireFox = 'xxxx';
    var isIE = 'xxxx';
    var isChrome = 'xxxx';
    if (isFireFox) {
        return true;
    }
    if (isIE) {
        return true;
    }
    if (isChrome) {
        return true;
    }
    return false;
}
// after
function test10(x) {
    var isFireFox = 'xxxx';
    var isIE = 'xxxx';
    var isChrome = 'xxxx';
    if (isFireFox || isIE || isChrome) {
        return true;
    }
    return false;
}
合併後的代碼會告訴你,實際上只有一個條件檢查,只是有多個並列條件須要檢查而已

合併重複的條件片斷

條件表達式上有着相同的一段代碼,就應該將它搬離出來

// 合併重複片斷
// before
function test11(isSpecial) {
    var total,
        price = 1;
    if (isSpecial) {
        total = price * 0.95;
        // 這裏處理一些業務
    } else {
        total = price * 0.8;
        // 這裏處理一些業務
    }
}

// after
function test12(isSpecial) {
    var total,
        price = 1;
    if (isSpecial) {
        total = price * 0.95;
    } else {
        total = price * 0.8;
    }
    // 這裏處理一些業務
}
在不一樣的條件裏面作了一樣的事情,應該將其抽離出條件判斷。這樣代碼量少並且邏輯更加清晰

以衛語句取代嵌套條件表達式

若是某個條件較爲罕見,應該單獨檢查該條件,並在該條件爲真時當即從函數中返回。這樣的檢查就叫衛語句

舉個例子:

// 以衛語句取代嵌套條件表達式
// before
function getPayMent() {
    var result = 0;
    if (isDead) {
        result = deadAmount();
    } else {
        if (isSepartated) {
            result = separtedAmount();
        } else {
            if (isRetired) {
                result = retiredAmount();
            } else {
                result = normalPayAmount();
            }
        }
    }
    return result;
}

// after
function getPayMent() {
    if (isDead) {
        return deadAmount();
    }
    if (isSepartated) {
        return separtedAmount();
    }
    if (isRetired) {
        return retiredAmount();
    }
    return normalPayAmount();
}

函數更名(命名)

當函數名稱不能表達函數的用途,就應該更名

  1. 變量和函數應使用合乎邏輯的名字。

    eg:獲取產品列表 -> getProductList()
  2. 變量名應爲名詞,由於變量名描述的大部分是一個事物。

    eg: 產品 -> product
  3. 函數名應爲動詞開始,由於函數描述的是一個動做

    eg:獲取產品列表 -> getProductList()

將查詢函數和修改函數分開

若是某個函數只向你提供一個值,沒有任何反作用。這個函數就能夠任意的調用。

這樣的函數稱爲純函數

若是遇到一個既有返回值,又有反作用的函數。就應該將查詢與修改動做分離出來

舉個例子:

// before
function test13(people) {
    for (var i = 0, len = people.length; i < len; i++) {
        if (people[i].name == 'andy') {
            // do something 例如進行DOM 操做之類的
            return 'andy';
        }
        if (people[i].name == 'ChunYang') {
            // do something 例如進行DOM 操做之類的
            return 'ChunYang';
        }
    }
}

// after
function test14(people) {
    var p = find(people);
    // do something 例如進行DOM 操做之類的
    // doSomeThing(p);
}

function find(people) {
    for (var i = 0, len = people.length; i < len; i++) {
        if (people[i].name == 'andy') {
            return 'andy';
        }
        if (people[i].name == 'ChunYang') {
            return 'ChunYang';
        }
    }
}

令函數攜帶參數

若是發現兩個函數,作着相似的工做。區別只在於其中幾個變量的不一樣。就能夠經過參數來處理。

這樣能夠去除重複的代碼,提升靈活性

關鍵點: 找出不一樣的地方和重複的地方。

推薦書籍

《重構 改善既有代碼的設計 》 基於 java 的

《代碼大全》

相關連接

我的博客
代碼片斷

相關文章
相關標籤/搜索