前端技能訓練: 重構一

Phodal發表於2015-06-24

或許你應該知道了,重構是怎樣的,你也知道重構能帶來什麼。在我剛開始學重構和設計模式的時候,我需要去找一些好的示例,以便於我更好的學習。有時候不得不創造一些更好的場景,來實現這些功能。

有一天,我發現當我需要我一次又一次地重複講述某些內容,於是我就計劃著把這些應該掌握的技能放到Github上,也就有了Artisan Stack 計劃。

每個程式設計師都不可避免地是一個Coder,一個沒有掌握好技能的Coder,算不上是手工藝人,但是是手工人。

藝,需要有創造性的方法。

前端技能訓練: 重構一

為什麼重構?

為了更好的程式碼。

在經歷了一年多的工作之後,我平時的主要工作就是修Bug。剛開始的時候覺得無聊,後來才發現修Bug需要更好的技術。有時候你可能要面對著一坨一坨的程式碼,有時候你可能要花幾天的時間去閱讀程式碼。而,你重寫那幾十程式碼可能只會花上你不到一天的時間。但是如果你沒辦法理解當時為什麼這麼做,你的修改只會帶來更多的bug。修Bug,更多的是維護程式碼。還是前人總結的那句話對:

寫程式碼容易,讀程式碼難。

假設我們寫這些程式碼只要半天,而別人讀起來要一天。為什麼不試著用一天的時候去寫這些程式碼,讓別人花半天或者更少的時間來理解。

如果你的程式碼已經上線,雖然是一坨坨的。但是不要輕易嘗試,沒有測試的重構

從前端開始的原因在於,寫得一坨坨且最不容易測試的程式碼都在前端。

讓我們來看看我們的第一個訓練,相當有挑戰性。

重構uMarkdown

程式碼及setup請見github: js-refactor

程式碼說明

uMarkdown是一個用於將Markdown轉化為HTML的庫。程式碼看上去就像一個很典型的過程程式碼:

/* code */
while ((stra = micromarkdown.regexobject.code.exec(str)) !== null) {
  str = str.replace(stra[0], '<code>\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '<br/>').replace(/\ /gm, '&nbsp;') + '</code>\n');
}

/* headlines */
while ((stra = micromarkdown.regexobject.headline.exec(str)) !== null) {
  count = stra[1].length;
  str = str.replace(stra[0], '<h' + count + '>' + stra[2] + '</h' + count + '>' + '\n');
}

/* mail */
while ((stra = micromarkdown.regexobject.mail.exec(str)) !== null) {
  str = str.replace(stra[0], '<a href="mailto:' + stra[1] + '">' + stra[1] + '</a>');
}

選這個做重構的開始,不僅僅是因為之前在寫EchoesWorks的時候進行了很多的重構。而且它更適合於,重構到設計模式的理論。讓我們在重構完之後,給作者進行pull request吧。

Markdown的解析過程,有點類似於Pipe and Filters模式(架構模式)。

Filter即我們在程式碼中看到的正規表示式集:

regexobject: {
    headline: /^(\#{1,6})([^\#\n]+)$/m,
    code: /\s\`\`\`\n?([^`]+)\`\`\`/g

他會匹配對應的Markdown型別,隨後進行替換和處理。而``str```,就是管理口的輸入和輸出。

接著,我們就可以對其進行簡單的重構。

重構

(ps: 推薦用WebStrom來做重構,自帶重構功能)

作為一個示例,我們先提出codeHandler方法,即將上面的

/* code */
while ((stra = micromarkdown.regexobject.code.exec(str)) !== null) {
  str = str.replace(stra[0], '<code>\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '<br/>').replace(/\ /gm, '&nbsp;') + '</code>\n');
}

提取方法成

codeFilter: function (str, stra) {
    return str.replace(stra[0], '<code>\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '<br/>').replace(/\ /gm, '&nbsp;') + '</code>\n');
  },    

while語句就成了

  while ((stra = regexobject.code.exec(str)) !== null) {
        str = this.codeFilter(str, stra);
    }

然後,執行所有的測試。

grunt test

同理我們就可以mailheadline等方法進行重構。接著就會變成類似於下面的程式碼,

  /* code */
  while ((execStr = regExpObject.code.exec(str)) !== null) {
    str = codeHandler(str, execStr);
  }

  /* headlines */
  while ((execStr = regExpObject.headline.exec(str)) !== null) {
    str = headlineHandler(str, execStr);
  }

  /* lists */
  while ((execStr = regExpObject.lists.exec(str)) !== null) {
    str = listHandler(str, execStr);
  }

  /* tables */
  while ((execStr = regExpObject.tables.exec(str)) !== null) {
    str = tableHandler(str, execStr, strict);
  }

然後你也看到了,上面有一堆重複的程式碼,接著讓我們用JavaScript的奇技浮巧,即apply方法,把上面的重複程式碼變成。

    ['code', 'headline', 'lists', 'tables', 'links', 'mail', 'url', 'smlinks', 'hr'].forEach(function (type) {
        while ((stra = regexobject[type].exec(str)) !== null) {
            str = that[(type + 'Handler')].apply(that, [stra, str, strict]);
        }
    });

進行測試,blabla,都是過的。

 Markdown
   ✓ should parse h1~h3
   ✓ should parse link
   ✓ should special link
   ✓ should parse font style
   ✓ should parse code
   ✓ should parse ul list
   ✓ should parse ul table
   ✓ should return correctly class name

這樣,我們就完成第一個重構訓練了~~。

快來試試吧, https://github.com/artisanstack/js-refactor

其他

下一篇將是基本的測試訓練,見: https://github.com/artisanstack/js-test-basic

只是希望不要寫出一坨坨的程式碼。

相關文章