Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript Code Quality

JavaScript Code Quality

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

Avatar for Joseph Chiang

Joseph Chiang

May 18, 2013
Tweet

More Decks by Joseph Chiang

Other Decks in Technology

Transcript

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

    JSON • 不支援 NoJS、沒法上一頁 • 重複程式碼非常多,沒人敢大改 • 專案結束沒多久,新人離職 • 2012 年底,此服務關閉
  2. 開始去思考「團隊」這件事 • Code Convention 團隊規範 • Code Review 定期 Review

    程式碼 • Checklist 上線前需達成的清單 ⾮非常重視⼀一致性、維護性
  3. 模組:寫法一致、避免全域變數 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. 模組:寫法一致、避免全域變數 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. 模組:寫法一致、避免全域變數 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. 需從軟體工程的角度出發 • Lint 自動檢查程式碼 • Combine/Compress 自動合併及壓縮 JS/CSS • Optimize

    image 圖檔最佳化 • Compass 用 High Level 的語言讓 CSS 簡化 • Skeleton 自動建立樣板檔案 導⼊入⾃自動化、減少「⼿手⼯工」、才能提昇品質
  7. miiiCasa Space 家庭雲:用單一 URL 存取家中裝置上的資料 • 4 個不同的媒體: 照片、音樂、影片、文件 •

    2 個不同的類型: 首頁列出 Storage、內頁只列目錄 • 都是檔案列表、但差異很大: • View 都各自有差異 • 照片要 Slideshow • 音樂要播放器 • 影片用 VLC / Flowplayer • 文件要用 Lightbox 顯示照片、同時支援 影片、音樂、Google Drive Preview...
  8. 用「複製、貼上」達成需求與滿足時程 • 一模組約 1,500 行 JS • 4 個不同的媒體 •

    2 個不同的類型 • 差異很大 = 修修改改 1,500*4*2 = 12,000 行 後續有新功能都幾乎是「複製貼上 x 8」
  9. 壓縮開發時程之謎 Stoyan Stefanov The few man-hours spent writing the code

    initially end up in man-weeks spent reading it. 數⼩小時寫出的程式碼、得花數週或更多的時間維護它 要在 8 個 JavaScript 上: • 增加新功能 • 修改 Bug • 改變邏輯 • 看不懂、乾脆重寫 ⽼老闆跟專案經理永遠無法理解的事...
  10. EDD – 設計文件 圖表 Branch URLs 檔案結構 Controller 模組詳細說明 預先規劃的假資料

    JS 的架構日益複雜 需要跟後端程式一樣先規劃 負責人先仔細思考、撰寫出此文件 所有開發人員一起討論可行性、問題點 把思考不周延之處找出來、了解配合模式 時程預估較為準確、有機會先預防
  11. 重構 = 打掉重寫? 應避免 NIH 綜合症(非我所創、Not Invented Here Syndrome) NO!

    技術人員應該用更客觀的心態面對重構 重構 = 持續且小的改進。
  12. 重構案例: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. 重構案例: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. 為什麼會產出「一大包」JS? • 麻煩!JS 載入需考慮「前後順序」 <script/> 有先後順序,例如載入 jQuery UI 的 Tabview.

    • JS 本身缺乏「模組」的觀念 直到最近 ECMAScript 才要內建 • 「頁面」眾多、分割不易 不像其他的語言,大多只要 require 就可以使用 • 對 JavaScript 缺乏「軟體工程」的態度 非本科:對程式抽離、正規化概念薄弱。本科:對 JS 不用想這麼多吧!
  15. 抽離範例:Space Photo • 主要功能:列出 Router USB 裡的照片。 • 其他功能: 提供下載檔案、幻燈秀、縮圖產生機制、

    檔案描述、LightBox、分享、重新命名等機制。 這才是此程式中最重要的 每一個都可以另開 Module 處理
  16. 代碼抽象三原則 • DRY - 不要重複自己 出現重複時就抽象出一個解決方法 • YAGNI - 你不會需要他

    快 + 簡單!不要把精力放在抽象化上 • Rule of 3 - 三次原則 當同樣的情況出現三次才進行抽象化 http://www.ruanyifeng.com/blog/2013/01/abstraction_principles.html
  17. 功能抽離範例 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. 採用模組化 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. 應加入的規範 • 一個 JS 檔案不應超過 500 行 • 一行不應超過 100

    個字元 維護一個大雜匯的 JavaScript 有何意義? 會超過 500 行應代表你該抽離功能了
  20. PMD - 原始碼分析器 其中 CPD 工具可以分析 JS 的重複 /bin/run.sh  cpd

     -­‐-­‐minimum-­‐tokens  50                                  -­‐-­‐files  ~/project/foo/js                                  -­‐-­‐language  ecmascript
  21. Error Log 管理 JavaScript 錯誤應該被即時記錄並回報 瀏覽器錯誤 window.onerror try-catch 錯誤發生 ӱൔ㢯ק൞ڎေສഈ❟

    ӱൔ㢯ק൞ڎေສഈ❟ 把錯誤丟到 Server 做記錄、回報 就不用辛苦地翻程式碼找問題 控管好這兩個時間點
  22.        //  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. 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. Esprima 的可能應用 • JSHint: 將會改用 Esprima • 語法高亮 • 塞

    Log,協助偵錯: • 在每個 Method 都放: console.log("someMethod()  is  executed.") http://www.slideshare.net/ariyahidayat/javascript-parser-infrastructure-for-code-quality-analysis
  25. 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. Donald Knuth 現代電腦科學的鼻祖 A style of programming that maximize our

    ability to perceive the structure of a complex piece of software. 文學編程的理念 w൞ℭީਔđ⃸໡ⁿ֥ӱൔ۷ݺĆx ໓⇥ṇӱ൞၂⊕⇔ӱൔ֥৘མྙⅢ ⃸໡ⁿỚᧄℂⅴ֥ૄἠ♶≗ὔᾳ׻ି ԉٳֹ৘ࢳ
  27. https://gist.github.com/josephj/5580918 Demo >   File  count.js  saved literate-­‐programming  ⽂文學編程.md  

                                                                                                                                                                                                                                                                      寫程式就像寫部落格⼀一樣! 可重新組合章節 讓別⼈人⾮非常容易理解
  28. Literate CoffeeScript CoffeeScript 的作者 = 文學編程的大力推廣者 Jeremy Ashkenas Journo 部落格系統

    Docco API 文件產生器 以⽂文學咖啡寫成: 缺點:得依照原始碼的順序寫
  29. Code Review 設計⽂文件 產出 API ⽂文件 模組化 模組架構 代碼規範 Pair

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

    • Slideshare - josephj • Linkedin - josephj6802 聯繫我