Refactoring with Backbone.js

Refactoring with Backbone.js

JSDC 2012

F830ec52d5bf72ee64fd1a43a6a82a49?s=128

大澤木小鐵

May 19, 2012
Tweet

Transcript

  1. efactoring with Backbone.js R

  2. JSDC 2012 Jace Ju 大澤木小鐵 http://plurk.com/jaceju http://twitter.com/jaceju http://www.jaceju.net

  3. 擦屁股達人 如何成為專業的

  4. jQuery DOM 基礎 underscore.js 的使用方法 Backebone.js 原理 力有未逮之處

  5. 好好的沒事 跟人家學什麼重構 If it ain’t broke, don’t fix it.

  6. 那一年 我們學的 jQuery Write Once & runs everywhere.

  7. 那一年 我們學的 jQuery Write Once & runs everywhere. 萬屎 亂

  8. 身為工程師 都有摳別人屁股程式的時候 Programmers love copy

  9. 擦舊專案的屁股 Old project kills me

  10. 屎出不窮 Bug Happens

  11. Time is all We need. 時間就像衛生紙

  12. No Magic 沒有魔術

  13. Keep the DOM structure stable. 儘可能不更動 DOM 結構

  14. Same JavaScript Logic. JavaScript 邏輯 改動越少越好

  15. Just do one thing at a time. 一次只做一件事

  16. Step Ӊ᜷ by Step

  17. 0މ೻ό൯ࡈڭᎈ Version Control

  18. None
  19. 0 Іਗʷ಻༊ Automated Testing

  20. None
  21. 0 ࠠ࿴ృᐑ Cycle of refactoring

  22. ࠠ࿴ృᐑ ੽வ༁ක֐ 重構 程式 測試 加入 版本 刪除 舊碼 測試

  23. 1ʱؓᔚϞ೻όᇁ Legacy code analysis

  24. $(function () { $.get('json.php', function (json) { var dataList =

    json; var $input = $('#input').empty(); var $output = $('#output'); for(idx in dataList) { var dataItem = dataList[idx]; $input.append('<li><a id="item-' + dataItem.id + '" href="#">' + dataItem.title + '</a></li>'); } $('a', $input).bind('click', function (e) { e.preventDefault(); var id = this.id.replace(/^item-/, ''); var dataItem = _.find(dataList, function(p) { return p.id == id; }); $('h1', $output).text(dataItem.title); $('p.desc', $output).text(dataItem.desc); }).eq(0).click(); }, 'json'); }); ੬ԈٙK2VFSZᄳج
  25. Data Input Init Output ̋ɝᕙЍਜ෯ᗆй $(function () { $.get('json.php', function

    (json) { var dataList = json; var $input = $('#input').empty(); var $output = $('#output'); for(idx in dataList) { var dataItem = dataList[idx]; $input.append('<li><a id="item-' + dataItem.id + '" href="#">' + dataItem.title + '</a></li>'); } $('a', $input).bind('click', function (e) { e.preventDefault(); var id = this.id.replace(/^item-/, ''); var dataItem = _.find(dataList, function(p) { return p.id == id; }); $('h1', $output).text(dataItem.title); $('p.desc', $output).text(dataItem.desc); }).eq(0).click(); }, 'json'); });
  26. 2 ˾౬༟ࣘᜊᅰ Replace Data Variables with Model/Collection

  27. [ { "id": 1, "title": "...", "desc": "..." }, {

    "id": 2, "title": "...", "desc": "..." }, { "id": 3, "title": "...", "desc": "..." }, { "id": 4, "title": "...", "desc": "..." }, { "id": 5, "title": "...", "desc": "..." } ] У؂ኜ၌הΫෂٙ+40/ࣸό
  28. [ { "id": 1, "title": "...", "desc": "..." }, {

    "id": 2, "title": "...", "desc": "..." }, { "id": 3, "title": "...", "desc": "..." }, { "id": 4, "title": "...", "desc": "..." }, { "id": 5, "title": "...", "desc": "..." } ] У؂ኜ၌הΫෂٙ+40/ࣸό 重要
  29. var Model = Backbone.Model.extend({ defaults: { title: '', desc: ''

    } }); var Collection = Backbone.Collection.extend({ model: Model }); ່֛.PEFM$PMMFDUJPOᗳй
  30. var Model = Backbone.Model.extend({ defaults: { title: '', desc: ''

    } }); var Collection = Backbone.Collection.extend({ model: Model }); ່֛.PEFM$PMMFDUJPOᗳй
  31. var dataList = json; for(idx in dataList) { var dataItem

    = dataList[idx]; // ... } var id = ...; var dataItem = _.find(dataList, function(d) { return d.id == id; }); $('h1').text(dataItem.title); $('p').text(dataItem.desc); ੬Ԉٙ༟ࣘஈଣᜌ፨
  32. var dataList = json; ͜$PMMFDUJPO̍ༀ+40/ᜊᅰ [ { "id": 1, "title":

    "...", "desc": "..." }, { "id": 2, "title": "...", "desc": "..." }, { "id": 3, "title": "...", "desc": "..." }, { "id": 4, "title": "...", "desc": "..." }, { "id": 5, "title": "...", "desc": "..." } ]
  33. var dataList = new Collection(json); ͜$PMMFDUJPO̍ༀ+40/ᜊᅰ Collection Model Model Model

    Model
  34. for(idx in dataList) { var dataItem = dataList[idx]; // ...

    } ͜$PMMFDUJPOGPS&BDI՟˾༟ࣘࠖ˾ [ { "id": 1, "title": "...", "desc": "..." }, { "id": 2, "title": "...", "desc": "..." }, { "id": 3, "title": "...", "desc": "..." }, { "id": 4, "title": "...", "desc": "..." }, { "id": 5, "title": "...", "desc": "..." } ]
  35. dataList.forEach(function (dataItem) { // ... }); ͜$PMMFDUJPOGPS&BDI՟˾༟ࣘࠖ˾ dataList : Collection

    dataItem : Model dataItem : Model dataItem : Model dataItem : Model
  36. [ { "id": 1, "title": "...", "desc": "..." }, {

    "id": 2, "title": "...", "desc": "..." }, { "id": 4, "title": "...", "desc": "..." }, { "id": 5, "title": "...", "desc": "..." } ] var id = ...; var dataItem = _.find(dataList, function(d) { return d.id == id; }); ҷ͜$PMMFDUJPOHFU ՟੻ఊഅ༟ࣘ { "id": 3, "title": "...", "desc": "..." }
  37. [ { "id": 1, "title": "...", "desc": "..." }, {

    "id": 2, "title": "...", "desc": "..." }, { "id": 4, "title": "...", "desc": "..." }, { "id": 5, "title": "...", "desc": "..." } ] var id = ...; var dataItem = _.find(dataList, function(d) { return d.id == id; }); ҷ͜$PMMFDUJPOHFU ՟੻ఊഅ༟ࣘ { "id": 3, "title": "...", "desc": "..." }
  38. var id = ...; var dataItem = dataList.get(id); ҷ͜$PMMFDUJPOHFU ՟੻ఊഅ༟ࣘ

    dataList : Collection dataItem : Model dataItem : Model dataItem : Model
  39. { "id": 3, "title": "...", "desc": "..." } $('h1').text(dataItem.title); $('p').text(dataItem.desc);

    ҷ͜.PEFMHFU ՟੻᙮׌࠽
  40. $('h1').text(dataItem.get('title')); $('p').text(dataItem.get('desc')); ҷ͜.PEFMHFU ՟੻᙮׌࠽ dataItem : Model get title desc

  41. 3 ˏ͜ᅵو Introduce JavaScript Template

  42. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); ੬Ԉٙ೥ࠦяତᜌ፨

  43. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); ੬Ԉٙ೥ࠦяତᜌ፨

    這個叫 HTML
  44. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); <script

    type="text/template" id="view-template"> </script> ່֛ᅵو
  45. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); <script

    type="text/template" id="view-template"> </script> ່֛ᅵو
  46. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); <script

    type="text/template" id="view-template"> <h1>' + dataItem.get('title') + '</h1> </script> ່֛ᅵو
  47. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); <script

    type="text/template" id="view-template"> <h1>' + dataItem.get('title') + '</h1> </script> ່֛ᅵو
  48. var $dom = $('#view'); $dom.html('<h1>' + dataItem.get('title') + '</h1>'); <script

    type="text/template" id="view-template"> <h1><%= title %></h1> </script> ່֛ᅵو
  49. var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html('<h1>' +

    dataItem.get('title') + '</h1>'); <script type="text/template" id="view-template"> <h1><%= title %></h1> </script> ່֛ᅵو˙ج
  50. var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html('<h1>' +

    dataItem.get('title') + '</h1>'); <script type="text/template" id="view-template"> <h1><%= title %></h1> </script> ͜ᅵو˙ج՟˾)5.-οЕ
  51. var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); <script

    type="text/template" id="view-template"> <h1><%= title %></h1> </script> ͜ᅵو˙ج՟˾)5.-οЕ
  52. 4ˏɝ7JFXᗳй Introduce View Class

  53. var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); ່֛7JFXᗳй

  54. var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); ່֛7JFXᗳй

    template
  55. var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); ່֛7JFXᗳй

    template display
  56. var View = Backbone.View.extend({ render: function () { return this;

    } }); var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); ່֛7JFXᗳй
  57. var View = Backbone.View.extend({ render: function () { return this;

    } }); var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); ย୅ᅵو˙ج
  58. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    return this; } }); var $dom = $('#view'); var template = _.template($('#view-template').html()); $dom.html(template(dataItem.toJSON())); ย୅ᅵو˙ج
  59. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    return this; } }); var $dom = $('#view'); $dom.html(template(dataItem.toJSON())); ย୅ᅵو˙ج
  60. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    return this; } }); var $dom = $('#view'); $dom.html(template(dataItem.toJSON())); ย୅ᜑͪᜌ፨
  61. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    $dom.html(template(this.model.toJSON())); return this; } }); var $dom = $('#view'); $dom.html(template(dataItem.toJSON())); ย୅ᜑͪᜌ፨
  62. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    $dom.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); $dom.html(template(dataItem.toJSON())); ҷ͜7JFXٙᅵو˙ج
  63. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); $dom.html(template(dataItem.toJSON())); ҷ͜7JFXٙFM᙮׌
  64. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); $dom.html(template(dataItem.toJSON())); ୅ৰᔚٙяତᜌ፨
  65. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); ୅ৰᔚٙяତᜌ፨
  66. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); var view = new View({ }); ᜑͪ೥ࠦ
  67. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); var view = new View({ el: '#view' }); ᜑͪ೥ࠦ
  68. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); var view = new View({ el: '#view', model: dataItem }); ᜑͪ೥ࠦ
  69. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var $dom = $('#view'); var view = new View({ el: '#view', model: dataItem }); ᜑͪ೥ࠦ
  70. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var view = new View({ el: '#view', model: dataItem }); ᜑͪ೥ࠦ
  71. var View = Backbone.View.extend({ template: _.template($('#view-template').html()), render: function () {

    this.$el.html(this.template(this.model.toJSON())); return this; } }); var view = new View({ el: '#view', model: dataItem }); view.render(); ᜑͪ೥ࠦ
  72. 5ˏɝً࿒ي΁ Introduce Status Object

  73. Input Event Handler Output View ً࿒ي΁༶Ъࡡଣ

  74. Input Event Handler Output View ً࿒ي΁༶Ъࡡଣ

  75. Input Event Handler Output View Status ً࿒ي΁༶Ъࡡଣ

  76. var $input = $('#input'); var outputView = new OutputView({ el:

    '#output' }); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); outputView.model = dataItem; outputView.render(); }); ̋ɝً࿒ي΁
  77. var status = new DataItem(); var $input = $('#input'); var

    outputView = new OutputView({ el: '#output' }); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); outputView.model = dataItem; outputView.render(); }); ̋ɝً࿒ي΁
  78. var status = new DataItem(); var $input = $('#input'); var

    outputView = new OutputView({ el: '#output', model: status }); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); outputView.model = dataItem; outputView.render(); }); ਗ਼ً࿒ي΁ഐΥՑ7JFXي΁ɪ
  79. var status = new DataItem(); var $input = $('#input'); var

    outputView = new OutputView({ el: '#output', model: status }); status.on('change', outputView.render, outputView); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); outputView.model = dataItem; outputView.render(); }); ً່֛࿒ي΁ၾ7JFXي΁ٙʝਗ
  80. var status = new DataItem(); var $input = $('#input'); var

    outputView = new OutputView({ el: '#output', model: status }); status.on('change', outputView.render, outputView); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); status.set(dataItem.toJSON()); outputView.model = dataItem; outputView.render(); }); ً͜࿒ي΁՟˾7JFXي΁ٙᜑͪᜌ፨
  81. var status = new DataItem(); var $input = $('#input'); var

    outputView = new OutputView({ el: '#output', model: status }); status.on('change', outputView.render, outputView); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); status.set(dataItem.toJSON()); outputView.model = dataItem; outputView.render(); }); ً͜࿒ي΁՟˾7JFXي΁ٙᜑͪᜌ፨
  82. var status = new DataItem(); var $input = $('#input'); var

    outputView = new OutputView({ el: '#output', model: status }); status.on('change', outputView.render, outputView); $('a', $input).bind('click', function (e) { e.preventDefault(); var id = /* ... */; var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ً͜࿒ي΁՟˾7JFXي΁ٙᜑͪᜌ፨
  83. 6዆Υࠫࠦԫ΁ᜌ፨ Move DOM event handlers

  84. var InputView = Backbone.View.extend({ // ... }); var $input =

    $('#input'); $('a', $input).bind('click', function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ย୅ԫ΁ஈଣዚՓ
  85. var InputView = Backbone.View.extend({ // ... }); var $input =

    $('#input'); $('a', $input).bind('click', function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ย୅ԫ΁ஈଣዚՓ
  86. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { } }); var $input = $('#input'); $('a', $input).bind('click', function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ย୅ԫ΁ஈଣዚՓ
  87. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { } }); var $input = $('#input'); $('a', $input).bind('click', function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ย୅ԫ΁ஈଣዚՓ
  88. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); } }); var $input = $('#input'); $('a', $input).bind('click', function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ย୅ԫ΁ஈଣዚՓ
  89. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); } }); var $input = $('#input'); $('a', $input).bind('click', function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); }); ย୅ԫ΁ஈଣዚՓ
  90. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); } }); ࡌ͍ਞϽᜊᅰ
  91. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); } }); var inputView = new InputView({ el: '#input' }); ࡌ͍ਞϽᜊᅰ
  92. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = dataList.get(id); status.set(dataItem.toJSON()); } }); var inputView = new InputView({ el: '#input', collection: dataList }); ࡌ͍ਞϽᜊᅰ
  93. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = this.collection.get(id); status.set(dataItem.toJSON()); } }); var inputView = new InputView({ el: '#input', collection: dataList }); ࡌ͍ਞϽᜊᅰ
  94. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = this.collection.get(id); status.set(dataItem.toJSON()); } }); var inputView = new InputView({ el: '#input', collection: dataList, model: status }); ࡌ͍ਞϽᜊᅰ
  95. var InputView = Backbone.View.extend({ // ... events: { 'click a':

    'changeOutput' }, changeOutput: function (e) { var dataItem = this.collection.get(id); this.model.set(dataItem.toJSON()); } }); var inputView = new InputView({ el: '#input', collection: dataList, model: status }); ࡌ͍ਞϽᜊᅰ
  96. ̍ༀ"+"9 Replace $.ajax with Backbone.sync 7

  97. var DataList = Backbone.Collection.extend({ model: DataItem }); $(function () {

    $.get('json.php', function (json) { var dataList = new DataList(json); // ... status.on('change', outputView.render, outputView); inputView.render(); }, 'json'); }); ย୅4FSWFS၌"1*ٙխ̣Зໄ
  98. var DataList = Backbone.Collection.extend({ model: DataItem }); $(function () {

    $.get('json.php', function (json) { var dataList = new DataList(json); // ... status.on('change', outputView.render, outputView); inputView.render(); }, 'json'); }); ย୅4FSWFS၌"1*ٙխ̣Зໄ
  99. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { $.get('json.php', function (json) { var dataList = new DataList(json); // ... status.on('change', outputView.render, outputView); inputView.render(); }, 'json'); }); ย୅4FSWFS၌"1*ٙխ̣Зໄ
  100. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(json); // ... status.on('change', outputView.render, outputView); inputView.render(); }); ย୅4FSWFS၌"1*ٙխ̣Зໄ
  101. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(json); // ... status.on('change', outputView.render, outputView); inputView.render(); }); ୅ৰʔ̀ࠅٙᜊᅰ
  102. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(); // ... status.on('change', outputView.render, outputView); inputView.render(); }); ୅ৰʔ̀ࠅٙᜊᅰ
  103. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(); // ... status.on('change', outputView.render, outputView); inputView.render(); }); ່֛༟ࣘΝӉܝٙᙃ೯ԫ΁
  104. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(); // ... status.on('change', outputView.render, outputView); inputView.render(); dataList.on('all', inputView.render, inputView); }); ່֛༟ࣘΝӉܝٙᙃ೯ԫ΁
  105. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(); // ... status.on('change', outputView.render, outputView); dataList.on('all', inputView.render, inputView); }); ່֛༟ࣘΝӉܝٙᙃ೯ԫ΁
  106. var DataList = Backbone.Collection.extend({ model: DataItem, url: 'json.php' }); $(function

    () { var dataList = new DataList(); // ... status.on('change', outputView.render, outputView); dataList.on('all', inputView.render, inputView); dataList.fetch(); }); ່֛༟ࣘΝӉܝٙᙃ೯ԫ΁
  107. var DataItem = Backbone.Model.extend({ ... }); var DataList = Backbone.Collection.extend({

    ... }); var InputView = Backbone.View.extend({ ... }); var OutputView = Backbone.View.extend({ ... }); $(function () { var dataList = new DataList(); var status = new DataItem(); var inputView = new InputView({ el: '#input', collection: dataList, model: status }); var outputView = new OutputView({ el: '#output', model: status }); status.on('change', outputView.render, outputView); dataList.on('all', inputView.render, inputView); dataList.fetch(); }); 最後的成果
  108. 這就是 重構!

  109. 在 GitHub 上 叉 我 https://github.com/jaceju/refactoring_with_backbone.js

  110. 總結 Conclusion

  111. 小步前進 Refactoring for next step.

  112. 精益求精 Refactoring always.

  113. 職責與解耦 SRP & Decoupling.

  114. 克服惰性 Do it right now!

  115. 謝謝大家 Thank you. 投影片網址: http://goo.gl/ivDuz