代碼質量管理——如何寫出優雅的代碼

做爲一個剛寫代碼不久的小菜鳥,工做的半年多讓我愈加意識到提升代碼質量的重要性。從前只會關注實現功能,慢慢的開始關注性能,現階段則發現其實還有不少細節也是(如可讀性、易用性、可維護性、一致性)提升代碼質量的關鍵。「實現功能」跟「優雅地實現功能」是兩碼事。javascript

注:大部份內容概括自網絡,將多篇文章的觀點彙總加工了一下,也融合了一些我的的看法。css

原則

  • 單一職責原則html

  • 易用性原則java

  • 可讀性原則ajax

  • 複雜性守恆原則:不管你怎麼寫代碼,複雜性都是不會消失的編程

    注:若是邏輯很複雜,那麼代碼看起來就應該是複雜的。若是邏輯很簡單,代碼看起來就應該是簡單的。segmentfault

單一職責原則

面向對象五大設計模式基本原則之一。即一部分代碼只應該用於某一個特定功能,不該與其餘功能耦合在一塊兒windows

假設你的一個function同時實現了功能a和功能b,以後需求變動,你須要修改功能a,可是由於這兩個功能都在一個function裏,你就不得再也不去確認是否會影響到功能b。這就形成了沒必要要的成本。設計模式

以下我總結了三個拆分代碼的原則:api

「and」原則

當你爲你的方法命名時不得不加上「and」時,就該考慮考慮是否是要把這個方法拆分一下了。

「100」原則

當你的一個function超過一百行時,必定要進行拆分了。

注:這裏的100可能有點多,只是對我我的而言,100算是個人極限,總之就是絕對不要將一個函數寫的太長。

命令、查詢拆分原則

咱們開發中大部分操做能夠總結爲「命令」和「查詢」,如寫cookie、修改data、發送post請求均可以叫「命令」,而讀取cookie、ajax獲取數據則認爲是「查詢」操做。

函數式編程中講究「數據不可變」,即:

只有純的沒有反作用的函數,纔是合格的函數。

反作用:指當調用函數時,除了返回函數值以外,還對主調用函數產生附加的影響。例如修改全局變量(函數外的變量)或修改參數。

好處是使得開發更加簡單,可回溯,測試友好,減小了任何可能的反作用。

將「命令」與「查詢」拆分實際上就是函數式編程思想的部分體現,參考以下代碼:

function getFirstName() {
    var firstName = document.querySelector("#firstName").value;
    firstName = firstName.toLowerCase();
    setCookie("firstName", firstName);
    if (firstName === null) {
        return "";
    }
    return firstName;
}
 
var activeFirstName = getFirstName();
複製代碼

經過名字來看,該方法是用於獲取first name的,但實際上它還設置了cookie,這是咱們沒有預料的。對於一個「查詢」方法,它不該該有任何修改方法外變量的行爲,即「反作用」。更好的寫法以下:

function getFirstName() {
    var firstName = document.querySelector("#firstName").value
    if (firstName === null) {
        return "";
    }
    return firstName;
}
 
setCookie("firstName", getFirstName().toLowerCase());
複製代碼

一目瞭然,getFirstName只返回firstName,而設置cookie操做在它以外進行。

易用性原則

簡單

這裏的簡單,主要歸結爲function的一些設計原則,有以下幾點:調用簡單、易理解、減小記憶成本、參數處理。

以下,僅僅想實現修改dom顏色、寬度等屬性,原生代碼以下:

document.querySelector('#id').style.color = 'red'
document.querySelector('#id').style.width = '123px'
document.querySelector('#id').style.height = '456px'
複製代碼

而封裝事後:

function a(selector, color, width, height) {
  document.querySelector(selector).style.color = color
  document.querySelector(selector).style.width = width
  document.querySelector(selector).style.height = height
}
a('#a', 'red')
複製代碼

瞬間變得簡單可用了 ~

但該方法還存在一個問題,那就是命名太抽象了。。。除了開發者本身之外不可能有人在不看源碼的狀況下一眼看出這個方法a是幹嗎的。那麼咱再把這個方法名改寫得更易理解一點:

function letSomeElementChange(selector, color, width, height) {
  document.querySelector(selector).style.color = color
  document.querySelector(selector).style.width = width
  document.querySelector(selector).style.height = height
}
複製代碼

這樣咱們就能一目瞭然該方法的做用 ~ 不過仍有可優化的地方。這麼長的方法名誰記得住,要減小記憶成本啊,再改個名:

function setElement(selector, color, width, height) {
  document.querySelector(selector).style.color = color
  document.querySelector(selector).style.width = width
  document.querySelector(selector).style.height = height
}
複製代碼

OK,目前這個方法已經知足它的職責而且很好用了,但還以爲怪怪的。這一坨參數太礙眼。。。

function setElement(selector, opt) {
  const { color, width, height } = opt
  color && document.querySelector(selector).style.color = color
  width && document.querySelector(selector).style.width = width
  height && document.querySelector(selector).style.height = height
}
複製代碼

把多個參數合併一下,並在內部作兼容處理,這個方法便易用多了。即便不傳第二個參數也不會有任何反作用。

一致性

假若有這樣一個方法,獲取歌曲列表,並將其設置到div的innerText中:

function getSongs() {
    return $.get('/songs).then((response) {
        div.innerText = response.songs
  })
}
複製代碼

這就違背了方法的表裏一致性,也違背了上文的單一職責原則命令、查詢拆分原則,由於它不只獲取了歌單,同時還修改了innerText,要讓其更合理:

  • 要麼換個名字
  • 要麼拆分爲兩個方法

低耦合

耦合是衡量一個程序單元對其餘程序單元的依賴程度。耦合(或高耦合)是應該極力避免的。若是你發現本身正在複製和粘貼代碼並進行小的更改,或者重寫代碼,由於其餘地方發生了更改,這就是高耦合的體現。

耦合會嚴重影響代碼的複用性及可擴展性,讓後人維護時不得不修改甚至重寫這部分代碼,不只浪費時間還會致使倉儲中又多出一塊相似的代碼,很容易讓人迷惑。

同時,修改耦合度高的代碼時常常會牽一髮而動全身,若是修改時沒有理清這些耦合關係,那麼帶來的後果可能會是災難性的,特別是對於需求變化較多以及多人協做開發維護的項目,修改一個地方會引發原本已經運行穩定的模塊錯誤,嚴重時會致使惡性循環,問題永遠改不完,開發和測試都在各類問題之間奔波勞累,最後致使項目延期,用戶滿意度下降,成本也增長了,這對用戶和開發商影響都是很惡劣的,各類風險也就不言而喻了。

高內聚

不該該將沒有任何聯繫的東西堆到一塊兒。

內聚是一個類中變量與方法鏈接強度的尺度。高內聚是值得要的,由於它意味着類能夠更好地執行一項工做。低內聚是很差的,由於它代表類中的元素之間不多相關。每一個方法也應該高內聚,大多數的方法只執行一個功能,不要在方法中添加‘額外’的指令,這樣會致使方法執行更多的函數,同時也違反了上文的單一職責原則

低內聚的體現:若是屬性沒有被類中的多個方法使用,這多是低內聚的標誌。一樣,若是方法在幾種不一樣的狀況下不能被重用,或者若是一個方法根本不被使用,這也多是低內聚的一個標誌。

高內聚有助於緩解高耦合,高耦合是須要高內聚的標誌。可是,若是兩個問題同時存在,應當選擇內聚的方式。對於開發者來講,高內聚一般比低耦合更有幫助,儘管二者一般能夠一塊兒完成。

錯誤處理

  • 可預見的錯誤:諸如ajax回調、函數參數,這類問題很好解決,只需在開發時多考慮一步,對各類極端狀況作好兼容便可。
  • 不可預見的錯誤:相似兼容性問題,這類問題沒法在開發時準確預見的錯誤,能夠準備好拋錯,console.error/log/warn,最後你還能夠爲本身的程序留些後路: try...catch。

可讀性原則

命名

命名應該保證別人經過名稱一眼就能知道這個變量保存的是什麼,或者這個方法是用來作什麼的。

  • 普通變量、屬性用名詞以下:

    var person = {
        name: 'Frank'
    }
    var student = {
        grade: 3,
        class: 2
    }
    複製代碼
  • bool變量、屬性用(形容詞)或者(be動詞)或者(情態動詞)或者(hasX),以下:

    var person = {
        dead: false, // 若是是形容詞,前面就不必加 is,好比isDead 就很廢話
        canSpeak: true, //情態動詞有 can、should、will、need 等,情態動詞後面接動詞
        isVip: true, // be 動詞有 is、was 等,後面通常接名詞
        hasChildren: true, // has 加名詞
    }
    複製代碼
  • 普通函數、方法用(動詞)開頭:

    var person = {
        run(){}, // 不及物動詞
        drinkWater(){}, // 及物動詞
        eat(foo){}, // 及物動詞加參數(參數是名詞)
    }
    複製代碼
  • 回調、鉤子函數:

    var person = {
        beforeDie(){},
        afterDie(){},
        // 或者
        willDie(){}
        dead(){} // 這裏跟 bool 衝突,你只要不一樣時暴露 bool dead 和函數 dead 就行,怕衝突就用上面的 afterDie
    }
    button.addEventListener('click', onButtonClick)
    var component = {
        beforeCreate(){},
        created(){},
        beforeMount(){}
    }
    複製代碼
  • 命名一致性

    • 順序一致性:好比 updateContainerWidth 和 updateHeightOfContainer 的順序就使人很彆扭
    • 時間一致性:有可能隨着代碼的變遷,一個變量的含義已經不一樣於它一開始的含義了,這個時候你須要及時改掉這個變量的名字。 這一條是最難作到的,由於寫代碼容易,改代碼難。若是這個代碼組織得很差,極可能會出現牽一髮而動全身的狀況(如全局變量就很難改)

註釋

不須要多花哨,只要把做用、用法描述清楚便可。方法的標準註釋應該以下:

/** * [function_name description] * @param {[type]} argument [description] * @return {[type]} [description] */
function function_name(argument) {
  // body...
}
複製代碼

將方法的參數與返回值都寫清楚,我目前用的IDE是sublime,使用Docblockr插件能夠自動生成格式化註釋,很方便。

Bad Smell

項目中咱們常常可以遇這類代碼,它們仍可用,可是很「臭」,國外管這類代碼有一個統稱,即「bad smell」。以下這類代碼能夠說是很「臭」了:

  • 表裏不一的代碼
  • 過期的註釋
  • 邏輯很簡單,可是看起來很複雜的代碼
  • 重複的代碼
  • 類似的代碼
  • 老是一塊兒出現的代碼
  • 未使用的依賴
  • 不一樣風格的代碼

樣式規範

  1. 正確命名:class必須用「-」寫法,不要用駝峯和下劃線。

  2. 正確嵌套:正常狀況下必定要將class嵌套閉合,不然就至關於添加到全局,若是有重複命名的class就會受影響。

  3. 拒絕copy:若是想複用已有的樣式,直接在原有class上用「,」語法分割,就能應用,不要再copy一份樣式,會讓兩份樣式都被應用,就要考慮樣式覆蓋的問題,很不友好。

  4. 濫用class:沒有必要加的class不要加,每一個class的添加都應該有明確理由。濫用class的話可能會致使樣式覆蓋,不應應用這個樣式的地方用了某個樣式。

  5. 慎用 !important,會強行覆蓋全部同屬性樣式,一旦使用後會讓代碼難以維護,開發過程當中絕對不要依賴該方法。以下總結了一些使用 !important的經驗:

    • 必定要優化考慮使用樣式規則的優先級來解決問題而不是 !important
    • 只有在須要覆蓋全站或外部 css(例如引用的 ExtJs 或者 YUI )的特定頁面中使用 !important
    • 解決緊急線上問題可使用,但以後也要儘快用可維護的方式將代碼替換回來
    • 永遠不要在全站範圍的 css 上使用 !important
    • 永遠不要在你的插件中使用 !important

說得容易,作起來難

破窗效應

此理論認爲環境中的不良現象若是被聽任存在,會誘令人們仿效,甚至變本加厲。一幢有少量破窗的建築爲例,若是那些窗不被修理好,可能將會有破壞者破壞更多的窗戶。最終他們甚至會闖入建築內,若是發現無人居住,也許就在那裏定居或者縱火。一面牆,若是出現一些塗鴉沒有被清洗掉,很快的,牆上就佈滿了亂七八糟、不堪入目的東西;一條人行道有些許紙屑,不久後就會有更多垃圾,最終人們會視若理所固然地將垃圾順手丟棄在地上。這個現象,就是犯罪心理學中的破窗效應,在編程領域一樣存在。

要作到:只要是通過你手的代碼,都會比以前好一點。

最後弱弱地打個廣告,個人博客,歡迎各路大神指教

參考文章:

相關文章
相關標籤/搜索