JavaScript Code Quality

JavaScript Code Quality

關於 JavaScript 品質,我想說的是...

C43978727926bed201399816f7efb45d?s=128

Joseph Chiang

May 18, 2013
Tweet

Transcript

  1. 9.

    久了必會發生慘案 • 由新人負責、跟其他專案成員關在小房間 (沒有其他 F2E) • View 全部用 JS 組、後端只給

    JSON • 不支援 NoJS、沒法上一頁 • 重複程式碼非常多,沒人敢大改 • 專案結束沒多久,新人離職 • 2012 年底,此服務關閉
  2. 11.

    開始去思考「團隊」這件事 • Code Convention 團隊規範 • Code Review 定期 Review

    程式碼 • Checklist 上線前需達成的清單 ⾮非常重視⼀一致性、維護性
  3. 19.

    模組:寫法一致、避免全域變數 YUI.add("welcome/_notification",  function  (Y)  {    var  trans  =  {},

               module;    module  =  new  Y.Module({            selector:  "#notification",            init:  function  ()  {                                },            on:  {                    viewload:  function  (node)  {                    }            }    }); },  "0.0.1",  {"requires":  [            "module"    ] }); welcome/_notification.js 以 CLI ⾃自動從樣板建⽴立模組 ⼀一個模組 (Partial) 配⼀一個 JavaScript 檔 好⽤用⽅方法與屬性都圍繞在此 instance ⼀一旦當此模組存在於 DOM 之中 相依模組 此模組的 Selector 或物件參考
  4. 20.

    模組:寫法一致、避免全域變數 YUI.add("welcome/_notification",  function  (Y)  {    var  trans  =  {},

               module;    module  =  new  Y.Module({            selector:  "#notification",            init:  function  ()  {                                },            on:  {                    viewload:  function  (node)  {                    }            }    }); },  "0.0.1",  {"requires":  [            "module"    ] });                            module.broadcast("greeting",  trans.greeting);                            module.listen("need-­‐love",  function  (e)  {  /*  do  something  */  });                            new  Y.ScrollPagination({node:  node}); welcome/_notification.js 所有的功能都圍繞著模組化開發 多國語系 送廣播 聽廣播 加上其他相依模組 trans.greeting  =  module.getTrans("greeting",  "Hello  World"); ,"module-­‐popup",  "module-­‐intl",  "gallery/scroll-­‐pagination"
  5. 21.

    模組:寫法一致、避免全域變數 YUI.add("welcome/_notification",  function  (Y)  {    var  trans  =  {},

               module;    module  =  new  Y.Module({            selector:  "#notification",            init:  function  ()  {                                },            on:  {                    viewload:  function  (node)  {                    }            }    }); },  "0.0.1",  {"requires":  [            "module"    ] });                            module.alert(trans.greeting);                            module.broadcast("greeting",  trans.greeting);                            module.listen("need-­‐love",  function  (e)  {  /*  do  something  */  });                            new  Y.ScrollPagination({node:  node}); welcome/_notification.js 所有的功能都圍繞著模組化開發 多國語系 共⽤用的 popup 送廣播 聽廣播 加上其他相依模組 trans.greeting  =  module.getTrans("greeting",  "Hello  World"); ,"module-­‐popup",  "module-­‐intl",  "gallery/scroll-­‐pagination" 限制團隊成員在沙箱(Sandbox) 中開發 功能擴充也能有一致介面 只專注開發該模組功能、無法污染外界
  6. 23.

    需從軟體工程的角度出發 • Lint 自動檢查程式碼 • Combine/Compress 自動合併及壓縮 JS/CSS • Optimize

    image 圖檔最佳化 • Compass 用 High Level 的語言讓 CSS 簡化 • Skeleton 自動建立樣板檔案 導⼊入⾃自動化、減少「⼿手⼯工」、才能提昇品質
  7. 26.

    miiiCasa Space 家庭雲:用單一 URL 存取家中裝置上的資料 • 4 個不同的媒體: 照片、音樂、影片、文件 •

    2 個不同的類型: 首頁列出 Storage、內頁只列目錄 • 都是檔案列表、但差異很大: • View 都各自有差異 • 照片要 Slideshow • 音樂要播放器 • 影片用 VLC / Flowplayer • 文件要用 Lightbox 顯示照片、同時支援 影片、音樂、Google Drive Preview...
  8. 29.

    用「複製、貼上」達成需求與滿足時程 • 一模組約 1,500 行 JS • 4 個不同的媒體 •

    2 個不同的類型 • 差異很大 = 修修改改 1,500*4*2 = 12,000 行 後續有新功能都幾乎是「複製貼上 x 8」
  9. 32.

    壓縮開發時程之謎 Stoyan Stefanov The few man-hours spent writing the code

    initially end up in man-weeks spent reading it. 數⼩小時寫出的程式碼、得花數週或更多的時間維護它 要在 8 個 JavaScript 上: • 增加新功能 • 修改 Bug • 改變邏輯 • 看不懂、乾脆重寫 ⽼老闆跟專案經理永遠無法理解的事...
  10. 34.

    EDD – 設計文件 圖表 Branch URLs 檔案結構 Controller 模組詳細說明 預先規劃的假資料

    JS 的架構日益複雜 需要跟後端程式一樣先規劃 負責人先仔細思考、撰寫出此文件 所有開發人員一起討論可行性、問題點 把思考不周延之處找出來、了解配合模式 時程預估較為準確、有機會先預防
  11. 36.

    重構 = 打掉重寫? 應避免 NIH 綜合症(非我所創、Not Invented Here Syndrome) NO!

    技術人員應該用更客觀的心態面對重構 重構 = 持續且小的改進。
  12. 38.

    重構案例:Pop-up 視窗 util.showDialog(module,  title,  message,          

                           okText,  cancelText); module.broadcast("dialog-­‐show-­‐request",  {        title:  title,        msg:  message,        buttons:  {                ok  :  {                        label:  okText,                        callback:  okHandler                },                cancel  :  {                        label:  cancelText                }        } }); new  Y.Panel({        headerContent:  title,        bodyContent:  message,        buttons:  []        render:  true,        visible:  true }) 過去⾄至少有三種不同的作法 統⼀一作法、將預設⾏行為確認 module.alert(message); module.confirm({        title      :  title        content  :  message },  okHandler); module.inform(message); 對程式碼精簡、⾏行為的⼀一致性獲得⼤大幅改善
  13. 39.

    重構案例:Pop-up 視窗 util.showDialog(module,  title,  message,          

                           okText,  cancelText); module.broadcast("dialog-­‐show-­‐request",  {        title:  title,        msg:  message,        buttons:  {                ok  :  {                        label:  okText,                        callback:  okHandler                },                cancel  :  {                        label:  cancelText                }        } }); new  Y.Panel({        headerContent:  title,        bodyContent:  message,        buttons:  []        render:  true,        visible:  true }) 過去⾄至少有三種不同的作法 統⼀一作法、將預設⾏行為確認 module.alert(message); module.confirm({        title      :  title        content  :  message },  okHandler); module.inform(message); 對程式碼精簡、⾏行為的⼀一致性獲得⼤大幅改善 重構的價值 減少程式碼中光怪陸離的現象 未來開發更容易 減少 UI 的不一致
  14. 41.

    為什麼會產出「一大包」JS? • 麻煩!JS 載入需考慮「前後順序」 <script/> 有先後順序,例如載入 jQuery UI 的 Tabview.

    • JS 本身缺乏「模組」的觀念 直到最近 ECMAScript 才要內建 • 「頁面」眾多、分割不易 不像其他的語言,大多只要 require 就可以使用 • 對 JavaScript 缺乏「軟體工程」的態度 非本科:對程式抽離、正規化概念薄弱。本科:對 JS 不用想這麼多吧!
  15. 42.

    抽離範例:Space Photo • 主要功能:列出 Router USB 裡的照片。 • 其他功能: 提供下載檔案、幻燈秀、縮圖產生機制、

    檔案描述、LightBox、分享、重新命名等機制。 這才是此程式中最重要的 每一個都可以另開 Module 處理
  16. 43.

    代碼抽象三原則 • DRY - 不要重複自己 出現重複時就抽象出一個解決方法 • YAGNI - 你不會需要他

    快 + 簡單!不要把精力放在抽象化上 • Rule of 3 - 三次原則 當同樣的情況出現三次才進行抽象化 http://www.ruanyifeng.com/blog/2013/01/abstraction_principles.html
  17. 44.

    功能抽離範例 space-centralize.js (共 103 行) /**  *  @module  space-­‐centralize  */

    YUI.add("space-­‐centralize",  function  (Y)  {        //  Implementation  here...        Y.Space.bindCentralize  =  bindCentralize; },  "0.0.1",  {        "requires":  [                "space",  "node-­‐base",  "event-­‐resize"        ] }); 需要讓圖⽚片隨螢幕寬度置中對⿑齊,瀏覽較舒適 原本負擔很重、1500+ ⾏行的 JS 檔 /**  *  @module  space/photos/_photos_main  */ YUI.add("space/photos/_photos_main",  function  (Y)   {        //  ...        Y.Space.bindCentralize();        //  ... },},  "0.0.1",  {        "requires":  [                //  ...                "space-­‐centralize",                //  ...        ] }); 只增加兩行
  18. 45.

    採用模組化 Library 解決 HTML 中 JavaScript 缺乏模組機制的問題 define(“editor”, [‘a’,’b’,’c’], function

    () { function Editor { /* Constructor */ } return Editor; }); require(["editor"], function (Editor) { new Editor(); }); editor.js – 定義 editor 模組 demo.js – 使⽤用 editor 模組 任何⼀一個網站都應該要⽤用!
  19. 46.

    應加入的規範 • 一個 JS 檔案不應超過 500 行 • 一行不應超過 100

    個字元 維護一個大雜匯的 JavaScript 有何意義? 會超過 500 行應代表你該抽離功能了
  20. 51.

    PMD - 原始碼分析器 其中 CPD 工具可以分析 JS 的重複 /bin/run.sh  cpd

     -­‐-­‐minimum-­‐tokens  50                                  -­‐-­‐files  ~/project/foo/js                                  -­‐-­‐language  ecmascript
  21. 53.

    Error Log 管理 JavaScript 錯誤應該被即時記錄並回報 瀏覽器錯誤 window.onerror try-catch 錯誤發生 ӱൔ㢯ק൞ڎေສഈ❟

    ӱൔ㢯ק൞ڎေສഈ❟ 把錯誤丟到 Server 做記錄、回報 就不用辛苦地翻程式碼找問題 控管好這兩個時間點
  22. 54.

           //  Log  to  server.      

     window.onerror  =  function  (message,  url,  line)  {                var  queryString,                        el;                queryString  =  location.search.slice(1);                el  =  document.createElement("img");                el.src  =  LOG_URL  +  "?"  +  [                        "hostname="  +  encodeURIComponent(location.host),                        "message="  +  encodeURIComponent(message),                        "line="  +  encodeURIComponent(line),                        "url="  +  encodeURIComponent(url),                        "query="  +  encodeURIComponent(queryString)                ].join("&");                return  true;  //  Avoid  browser  error.                                                                                                                                                                  }; 把錯誤回報給 Server 常見作法:用假圖片 Request 達成、可跨網域 但是!錯誤需要被 Grouping 才有價值
  23. 57.

    Esprima 運行原理 程式碼 抽象語法樹 Source Code Abstract Syntax Tree var

     answer  =  6  *  7; {    "type":  "Program",    "body":  [        {              "type":  "VariableDeclaration",              "declarations":  [                  {                      "type":  "VariableDeclarator",                      "id":  {                          "type":  "Identifier",                          "name":  "answer"                      },                      "init":  {                          "type":  "BinaryExpression",                          "operator":  "*",                          "left":  {                              "type":  "Literal",                              "value":  6,                              "raw":  "6"                          },                          "right":  {                              "type":  "Literal",                              "value":  7,                              "raw":  "7"                          } 將程式碼轉成結構⼀一致的 AST 格式,很容易做後續處理 經過 Esprima Parse
  24. 59.

    Esprima 的可能應用 • JSHint: 將會改用 Esprima • 語法高亮 • 塞

    Log,協助偵錯: • 在每個 Method 都放: console.log("someMethod()  is  executed.") http://www.slideshare.net/ariyahidayat/javascript-parser-infrastructure-for-code-quality-analysis
  25. 60.

    Continuous Integration 持續集成 「所有」⾃自動化檢查,都應該與 CI 整合 中央 Git 個⼈人 Git

    git  push post-­‐receive trigger git  clone Build JSHint Code Duplication Unit Test Functional Test Code Coverage 通知 成功或失敗 Performance 讓檢查像喝⽔水⼀一樣簡單、確保品質 CI 伺服器
  26. 64.

    Donald Knuth 現代電腦科學的鼻祖 A style of programming that maximize our

    ability to perceive the structure of a complex piece of software. 文學編程的理念 w൞ℭީਔđ⃸໡ⁿ֥ӱൔ۷ݺĆx ໓⇥ṇӱ൞၂⊕⇔ӱൔ֥৘མྙⅢ ⃸໡ⁿỚᧄℂⅴ֥ૄἠ♶≗ὔᾳ׻ି ԉٳֹ৘ࢳ
  27. 69.

    https://gist.github.com/josephj/5580918 Demo >   File  count.js  saved literate-­‐programming  ⽂文學編程.md  

                                                                                                                                                                                                                                                                      寫程式就像寫部落格⼀一樣! 可重新組合章節 讓別⼈人⾮非常容易理解
  28. 70.

    Literate CoffeeScript CoffeeScript 的作者 = 文學編程的大力推廣者 Jeremy Ashkenas Journo 部落格系統

    Docco API 文件產生器 以⽂文學咖啡寫成: 缺點:得依照原始碼的順序寫
  29. 73.

    Code Review 設計⽂文件 產出 API ⽂文件 模組化 模組架構 代碼規範 Pair

    Programming 檢查清單 ⾃自動合併與最⼩小化 Lint 單元測試 物件導向 重構 代碼重複檢查 JS 語法分析、重置 持續整合 品質的 TODO LIST 抽離原則 ⽂文學編程 團隊活動 觀念及架構 ⾃自動化 整合訊息 看團隊需求來做導入或整合 Prototyping
  30. 77.

    Thank you! • GitHub - josephj • Facebook - 蔣定宇

    • Slideshare - josephj • Linkedin - josephj6802 聯繫我