前端程式碼質量的思考與實踐

科瑞茲曼發表於2020-03-20

前端程式碼質量的思考與實踐

編寫可讀的程式碼,對於以程式碼謀生的程式設計師而言,是一件極為重要的事。從某種角度來說,程式碼最重要的功能是能夠被閱讀,其次才是能夠被正確執行。一段無法正確執行的程式碼,也許會使專案延期幾天,但它造成的危害只是暫時和輕微的,畢竟這種程式碼無法透過測試並影響最終的產品;但是,一段能夠正確執行,但缺乏條理、難以閱讀的程式碼,它造成的危害卻是深遠和廣泛的:這種程式碼會提高產品後續迭代和維護的成本,影響產品的穩定,破壞團隊的團結,除非我們花費數倍於編寫這段程式碼的時間和精力,來消除它對專案造成的負面影響。

JavaScript 是動態和弱型別的語言,使用起來比較輕鬆隨意,在IE6那個刀耕火種的時代,輕鬆隨意的習慣確實不是什麼大問題,反而能節省時間,提高出活兒的速度。但是,隨著當下前端工程化技術的快速發展,前端專案規模的不斷膨脹,以往那種輕鬆隨意的編碼習慣,已經成為專案推進的一大阻力。

分享與共勉

  1. Any fool can write code that a computer can understand. Good programmers write code that humans can understand. —— Martin Fowler
  2. Good code is its own best documentation. As you’re about to add a comment, ask yourself, “How can I improve the code so that this comment isn’t needed?” ——Steve McConnell
  3. Programs must be written for people to read, and only incidentally for machines to execute. —— Harold Abelson

HTML中的實踐

  • 使用語義化標籤
  • 清晰、簡潔的層級巢狀結構
  • 儘可能少地使用無意義的標籤,如div和span
  • 語義不明顯,可以用p也可以用div,優先用p標籤
  • 在無法用標籤表明語義的場景下增加適當的註釋

CSS中的實踐

  • 引入sass或其他css前處理器
  • 命名可以參考借鑑BEM命名法和中劃線命名法
  • 根節點用id選擇器,其它用類選擇器,子代替代後代
  • css屬性書寫採用約定的順序
  • 類命名中劃線分隔不超過4級,超過了從0開始
  • 儘量避免使用float,position,採用flex佈局和grid佈局
  • 如果只能直接編寫css,也可以參考上述體現的思路
  • 如果無法直接引入sass,可用IDE或終端進行手動轉譯

javascript和jQuery的實踐

變數

  變數命名是編寫可讀程式碼的基礎。只有變數被賦予了一個合適的名字,才能表達出它在環境中的意義。

  命名必須傳遞足夠的資訊,形如 getData 這樣的函式命名就沒能提供足夠的資訊,讀者也完全無法猜測這個函式會做出些什麼事情。而 fetchUserInfoAsync 也許就好很多,讀者至少會猜測出,這個函式大約會遠端地獲取使用者資訊;而且因為它有一個 Async 字尾,讀者甚至能猜出這個函式會返回一個 Promise 物件。

  • 命名的基礎。用名詞命名物件,用動詞命名函式,用複數表示集合,也可以加上List或Map字尾來顯示地表示出來,使程式碼接近於自然語言。變數命名建議用小駝峰,類建議用大駝峰。
  • 命名規範。時刻按照某種規則來命名變數和函式,不用擔心變數汙染和能夠見名知意了。如:fetch或async代表非同步,get代表獲取,set代表設定,is、has、can代表一個布林值,handle代表普通函式等。.
  • 命名的上下文。變數都是處在上下文(作用域)之內,變數的命名應與上下文相契合,同一個變數,在不同的上下文中,命名可以不同。
  • 匈牙利命名法。基本原則:變數名=屬性+型別+描述。其中每一個物件的名稱都要求有明確含義,可以取物件全稱或簡寫。例如:sUserName,代表使用者姓名的字串。擁有一定的學習成本,自行取捨。.

常量

  在ECMAScript 6之前,JavaScript中並沒有真正的常量的概念。然而,這並不能組織開發者將變數用作常量。為了區分普通的變數(變數的值是可變的)和常量(常量的值初始化之後就不能變了),一種通用的命名約定應運而生。這個約定源自於C語言,它使用大寫字母和下劃線來命名,下劃線用以分隔單詞。

  • 約定命名。變數名全部用大寫字母,多個單詞用下劃線分隔
  • 魔術常量。同一個常量(值)在不同的上下文中可能代表著不同的含義。引申到js中,就是要使用有意義的變數名代替魔術常量,以提高程式碼的維護性和可讀性。
  • 避免硬編碼。對於程式碼中重複使用次數較多、未來可能會變動的資料,最好抽離成變數或配置項,實現程式碼邏輯和業務松耦合,增強維護性。
  • 常量的宣告。按關鍵字優先使用順序:const => let => var。能用const和let的儘量不要用var,var宣告有副作用。

註釋

  何時新增註釋是具有爭議的一個話提。程式碼註釋不是越多越好。(注:語義化的命名可以減少很多不必要的註釋,最好的程式碼是自解釋的,不要過分地追求註釋,影響程式碼的閱讀。)

  • 難於理解的程式碼。邏輯比較複雜或特殊的業務邏輯時都應當加註釋。關鍵是讓其他人更容易讀懂這段程式碼。
  • 可能被錯誤理解的程式碼。在團隊開發中,當自己寫的程式碼可能會被其他同事認為有錯誤時,需要新增註釋。
  • 瀏覽器特性hack。相容性程式碼會讓人看不明白,此時應當新增適當的註釋解釋其用途。
  • 文件註釋。對於工具類等公共方法,新增文件註釋,解釋其用途、引數和返回值,讓人看註釋就能明白使用方法,而不用關心方法內部的實現。

函式

  JS是一種多正規化程式語言。函數語言程式設計是一種程式設計正規化,一種構建計算機程式結構和元素的方式,將計算視為數學函式的評估並避免改變狀態和可變資料。它使你能夠構建無副作用的功能,而函數語言程式設計的一些優點,也使得系統變得更加容易維護。

  • 純函式。給出相同的引數,它返回相同的結果(確定性);不會引起任何可觀察到的副作用;容易測試。
  • 閉包。透過閉包可以在函式外部能訪問到函式內部的變數,過度地使用閉包會造成記憶體洩露。
  • 單一原則。如果一個方法承擔了過多的職責,在需求發生變化的過程中,需要改寫這個方法的可能性就越大。
  • 面向切面程式設計。非侵入性的改造函式。保持業務邏輯模組的純淨和高內聚性,其次是可以很方便地複用現有功能模組。
  • 健壯性。web開發安全守則:永遠不要相信使用者的輸入。對資料最好做異常處理,增強其健壯性。例如XSS。
  • 引數。形參越少越好,不超過3個,超過3的用物件代替,這樣可以極大地減少該方法的UT自測場景。
  • 注:尾呼叫和尾遞迴

前端異常的處理

  前端異常的處理也屬於程式碼健壯性的一部分。JavaScript不像Java一樣,有專門的語法去定義和實現異常處理,前端程式碼是離使用者最近的程式碼,異常的處理更多的是從使用者體驗和程式碼健壯性出發的,後端程式碼異常的處理更多的是從業務角度和資料安全性出發的。所以JavaScript的異常處理方法主要是對頁面內容(使用者輸入和介面輸入)的校驗上,在異常的時候能給出使用者能看懂的提示,另一方面,從程式碼角度來上來說,研發也能夠從頁面的提示上快速地定位問題、修復問題,提升解決問題的效率。

使用全等===替代相等==(副作用)

  javascript具有強制型別轉換機制,判斷相等的操作是很微妙的。對於某些運算來說,為了得到成功的結果,強制型別轉換會驅使某種型別的變數自動轉換成其他不同型別,這種情形往往會造成意想不到的結果。

  發生強制型別轉換最常見的場景就是,使用了判斷相等運算子==和!=的時候。當要比較的兩個值的型別不同時,這兩個運算子都會有強制型別轉換。但在很多實際情況中,程式碼並不按照我所期望的方式執行。Crockford的程式設計規範、jQuery核心風格指南、SproutCore程式設計風格指南推薦使用===和!==。

jQuery效率提升建議

  • 正確使用選擇器。Id > tag > class > 屬性和偽類
  • 層級選擇器儘量使用find方法,效率較高
  • 快取 jQuery 物件
  • 鏈式呼叫
  • 事件委託
  • 少改動DOM

設計模式之單例模式

  設計模式是在軟體設計過程中針對特定問題的簡潔而優雅的解決方案。《parctical common lisp》的作者曾說,如果你需要一種模式,那一定是哪裡出了問題。他所說的問題是指因為語言的天生缺陷,不得不去尋求和總結一種通用的解決方案。不管是弱型別或強型別,靜態或動態語言,命令式或說明式語言、每種語言都有天生的優缺點。

  單例模式比較好理解,它是一個物件,我們建立這個物件的目地是為了系統管理程式碼中的一些公用的變數,物件,函式,避免出現變數汙染,一般我們宣告一些變數和函式的時候,我們是在封閉的函式中,一般不會造成變數汙染情況,但是以防萬一,原因是在js中,全域性變數和區域性變數的關係比較複雜(有時候還夾雜著一部分變數提升問題),不少coder經常會因為此問題出現bug老半天找不到原因,建立好這個物件以後我們只是對外暴露一個物件入口,使用裡面的變數時我們可以以object.變數名的形式來呼叫變數,其實也是為了實現js程式碼塊的劃分名稱空間來設計的。

  我之前寫了幾年的php和jQuery,經過了專案中不斷地摸索與總結,得出了一個結論:在jquery技術棧的實際專案開發中,近乎95%的需求都可以透過單例模式來滿足實現。

設計模式之介面卡模式

  介面卡模式是將一個類(物件)的介面(方法或屬性)轉化成客戶希望的另外一個介面(方法或屬性),介面卡模式使得原本由於介面不相容而不能一起工作的那些類(物件)可以一些工作。

  適用場景:

  • 使用一個已經存在的物件,但其方法或屬性介面不符合你的要求
  • 你想建立一個可複用的物件,該物件可以與其它不相關的物件或不可見物件(即介面方法或屬性不相容的物件)協同工作
  • 想使用已經存在的物件,但是不能對每一個都進行原型繼承以匹配它的介面。物件介面卡可以適配它的父物件介面方法或屬性

推薦ES6,其次ES5,最後ES3

  • 使用const,let,不使用var
  • 靜態字串一律使用單引號或反引號,不使用雙引號。動態字串使用反引號
  • 使用陣列成員對變數賦值時,優先使用解構賦值
  • 立即執行函式可以寫成箭頭函式的形式。
  • 物件儘量靜態化,一旦定義,就不得隨意新增新的屬性。如果新增屬性不可避免,要使用Object.assign方法。
  • 使用擴充套件運算子(...)複製陣列。

VUE中的實踐

大道至簡,通俗易懂

  • 語義化的標籤,可以配合類名實現
  • 層次結構清晰、簡潔,用最少的巢狀實現最複雜的結構
  • 充分利用sass的作用域,樣式私有化,把css樣式影響範圍最小化
  • 使用大眾化的寫法,減少學習的成本
  • 儘量多使用類選擇器,少用標籤選擇器,實現複用
  • CSS樣式屬性的順序可以參考前面章節《CSS中的實踐》
  • 把檢視展示的部分放在template中,檢視層主要負責展示
  • 把資料(entity)放在data和computed中,資料層主要負責宣告(interface, abstract)
  • 把邏輯放在methods和其他幾個api裡,控制層主要負責實現
  • 單向資料流的思想。資料改變可記錄、可跟蹤,源頭易追溯;資料只有唯一入口和出口,更直觀更容易理解,提高可維護性。
  • 注:95%的業務場景最多隻需要使用到3-4個生命週期,如果超過了,檢查一下自己的程式碼
  • 程式碼風格可參考:https://cn.vuejs.org/v2/style...

前端發展的各個階段

前端框架的對比

常用設計模式與程式碼的結合

JavaScript語言的特點

  • 宣告提升。程式碼在執行的時候,js解析器會先把funtion宣告和var宣告放在前面,然後順序執行對應的語句。推薦用es6的const,其次let,不推薦用var。
  • 私有變數。可以透過閉包來實現私有變數的宣告,但閉包會讓私有變數常駐記憶體,濫用閉包會造成記憶體洩露,所以要謹慎使用閉包。
  • for-in。物件可以採用傳統for-in迴圈遍歷,但該方式會遍歷出原型鏈上的屬性,產生意想不到的結果。推薦使用ES6的Object.keys和Object.values結合陣列的api。
  • 多型。JS沒有多型,某種意義上說多型與函式的單一原則是對立的,所以建議多型別引數執行的是同一類邏輯的時候用多型。參考:jquery原始碼的Sizzle引擎。
  • 單執行緒。透過非同步解決單執行緒的弊端。但傳統非同步解決方案透過回撥實現,大量巢狀的回撥會造成回撥地獄的現象,程式碼可讀性很差,不利於維護。推薦使用ES6的Promise和async/await。
  • 副作用。全面擁抱函數語言程式設計,避免使用有副作用的程式碼,如ES3中var的宣告提升,for-in的副作用,==的隱式轉換, BOM中alert的阻塞效果等。

書籍以及社群

  • 《JavaScript權威指南》:犀牛書可以先大致通讀幾遍,也可以把其當作工具書,時時翻閱。
  • 《JavaScript高階程式設計》:紅寶書雖然號稱高階,但其實是幫助入門的。小紅書配合犀牛書,相互印證。
  • 《你不知道的JavaScript 上中下》:這本絕對是神書,讓你瞭解JavaScript不為人知的另一面,把閉包、非同步這些講得很通透。
  • 《ES6 標準入門(第3版)》阮老師的書,ES6入門書籍。
  • 《鋒利的jQuery》循序漸進地對jQuery的各種API進行了介紹
  • 《 JavaScript模式》陳新 譯,系統地講解了編寫高質量程式碼的技巧和開發過程中最常用的幾種設計模式。
  • 《重構-改善既有程式碼的設計》一些寫程式碼的思想層次的東西。
  • 《編寫可維護的JavaScript 》包括具體風格和原則的介紹,也包括示例和技巧說明
  • 思否社群:中國版stackoverflow

心得與體會

  軟體bug的修改是需要成本的,並且這項成本總是在不斷地增加,特別是對於已經廣泛釋出的產品程式碼而言,更是如此。最好的情況是當我們一發現bug,立刻就可以修改它,這種情況只發生在剛寫完這些程式碼後不久。否則轉移到新的任務上,忘記了這部分程式碼,就需要重新閱讀這些程式碼。

  對於大型專案而言,還存在著另一個問題,就是最終修改程式碼的人,往往並不是當初寫程式碼的人,也不是發現bug的人。因此,減少理解自己以前寫的程式碼的時間,或者減少理解團隊中他人寫的程式碼的時間,就變得非常關鍵。

  另外一個事實在於,軟體開發人員通常讀程式碼比寫程式碼更耗時間。通常的情形是,當我們專注於某個問題時,會坐下來花一下午的時間編寫出大量的程式碼。這些程式碼可能當天就可執行,但要想成為一項成熟的應用,需要我們對程式碼進行重新檢查、重新校正、重新調整。

  編寫高質量的程式碼是十分重要的,不僅對於成功地完成專案十分有利,而且有利於開發者與開發團隊的其他人員進行交流。提高程式碼的質量,可能最初只是幾小時工時寫出來的程式碼,最終需要花費幾周的工時來閱讀。這就是為什麼編寫易維護的高質量的程式碼對專案具有舉足輕重的作用。

  程式碼的質量從某種意義上來說就是一個系統的生命線,任何上線的系統都是透過了測試的,但程式碼的質量決定了這個系統能走多遠!我在新聞上聽說過有人刪庫跑路的,也見過有人接手老專案後馬上離職的,其中有一些就和老程式碼的質量問題有著必然的聯絡。

  程式碼質量和效率之間有著天然的矛盾,怎麼去平衡值得我們去思考,下面是我的一點心得與體會:1、程式碼風格;2、code review;3、抽離、封裝、複用;4、持續學習和持續實踐;5、開發週期與程式碼質量的平衡。

相關文章