<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    重構(gòu)的藝術(shù):五個小妙招助你寫出好代碼!

    共 11794字,需瀏覽 24分鐘

     ·

    2021-04-04 08:42

    全文共8912字,預計學習時長14分鐘


    作者lloorraa,來源need.pix


    糟糕的代碼可以運作,但早晚會讓我們付出代價。你有沒有遇到過這樣的問題:幾周后,你無法理解自己的代碼,于是不得不花上幾個小時,甚至幾天的時間來弄清楚到底發(fā)生了什么。


    解決這個常見問題的方法是使代碼盡可能清晰。如果做得更好的話,即使是非技術(shù)人員也應該能理解你的代碼。


    是時候停止尋找借口,提高我們的代碼質(zhì)量了!


    編寫清晰的代碼并沒有那么復雜。本教程將向你展示五種改進代碼的簡單技巧,并提供一些實例:


    1. 不用switch語句


    我們通常使用switch語句來代替大型if-else-if語句。但是,switch語句非常冗長,很難維護,甚至很難調(diào)試。這些switch語句把我們的代碼弄得亂七八糟,而且這些語句的語法很奇怪,很不舒服。在添加更多的case時,我們不得不必須手動添加每個case和break語句,而這就很容易出錯。


    接下來看一個switch語句的例子:


    function getPokemon(type) {
      let pokemon;
      switch (type) {
        case 'Water':
          pokemon = 'Squirtle';
          break;
        case 'Fire':
          pokemon = 'Charmander';
          break;
        case 'Plant':
          pokemon = 'Bulbasur';
          break;
        case 'Electric':
          pokemon = 'Pikachu';
          break;
        default:
          pokemon = 'Mew';
      }
      return pokemon;
    }


    console.log(getPokemon('Fire')); // Result: Charmander
    Switch語句


    如果需要在switch語句中添加更多的case的話,需要編寫的代碼量是相當大的。我們可能最終會復制粘貼代碼,而其實我們都知道這種行為的后果是什么。


    那么,如何避免使用switch語句呢?可以通過使用對象文本。對象文本簡單,易于編寫,方便讀取,維護輕松。我們都習慣用javascript處理對象,對象文本語法比switch語句更新鮮。下面舉個例子:


    const pokemon = {
        Water: 'Squirtle',
        Fire: 'Charmander',
        Plant: 'Bulbasur',
        Electric: 'Pikachu'
      };


    function getPokemon(type) {
      return pokemon[type] || 'Mew';
    }
    console.log(getPokemon('Fire')); // Result: Charmander


    // If the type isn't found in the pokemon object, the function will return the default value 'Mew'
    console.log(getPokemon('unknown')); // Result: Mew
    使用對象文本替代switch


    如你所見,可以使用運算符 || 添加默認值。如果在pokemon對象中找不到type,getpokemon函數(shù)將使mew返回為默認值。


    小貼士:你可能已經(jīng)注意到,我們在函數(shù)外部而不是內(nèi)部聲明pokemon對象。這樣做是為了避免每次執(zhí)行函數(shù)時都重新創(chuàng)建pokemon。


    用映射也能達到同樣的效果。映射就像對象一樣是鍵-值對的集合。不同的是映射允許任何類型的鍵,而對象只允許字符串作為鍵。此外,映射還有一系列有趣的屬性和方法。


    以下是使用映射的方法:


    const pokemon = new Map([
        ['Water', 'Squirtle'],
        ['Fire', 'Charmander'],
        ['Plant', 'Bulbasur'],
        ['Electric', 'Pikachu']
      ]);


    function getPokemon(type) {
      return pokemon.get(type) || 'Mew';
    }


    console.log(getPokemon('Fire')); // Result: Charmander
    console.log(getPokemon('unknown')); // Result: Mew
    用映射代替switch語句


    如你所見,當用對象文本或映射替換switch語句時,代碼看起來更清楚、更直接。


    2. 把條件語句寫的更有描述性


    在編寫代碼時,條件語句是絕對必要的。然而,他們很快就會失控,最終讓我們無法理解這些語句。這導致我們要么必須編寫注釋來解釋語句的作用,要么必須花費寶貴的時間來一條一條檢查代碼來了解發(fā)生了什么。這很糟糕。


    看一下下面的語句:


    function checkGameStatus() {
      if (
        remaining === 0 ||
        (remaining === 1 && remainingPlayers === 1) ||
        remainingPlayers === 0
      ) {
        quitGame();
      }
    }
    復雜的條件語句


    如果只看前面函數(shù)里if語句中的代碼,很難理解發(fā)生了什么。代碼表意不清楚,不清楚的代碼只會導致技術(shù)錯誤,還會讓人們非常頭痛。


    怎樣改善條件語句呢?可以把它寫成一個函數(shù)。以下是具體操作方法:


    function isGameLost() {
      return (
        remaining === 0 ||
        (remaining === 1 && remainingPlayers === 1) ||
        remainingPlayers === 0
      );
    }


    // Our function is now much easier to understand:
    function checkGameStatus() {
      if (isGameLost()) {
        quitGame();
      }
    }
    把條件語句寫成函數(shù)


    通過將條件提取到具有描述性名稱isGameLost()的函數(shù)中,checkGameStatus函數(shù)現(xiàn)在就變得很容易理解。為什么?因為代碼表意更清晰。它能夠告訴我們發(fā)生了什么,這是我們應該一直努力的方向。



    3. 用衛(wèi)語句(Guard Clauses)代替嵌套的if語句


    嵌套if語句是在代碼中可能遇到的最可怕的事情之一。你要是想能夠完全掌握代碼的情況,這絕對會讓你精疲力竭。下面是一個嵌套if語句的例子(這個嵌套有三層):


    function writeTweet() {
      const tweet = writeSomething();


      if (isLoggedIn()) {
        if (tweet) {
          if (isTweetDoubleChecked()) {
            tweetIt();
          } else {
            throw new Error('Dont publish without double checking your tweet');
          }
        } else {
          throw new Error("Your tweet is empty, can't publish it");
        }
      } else {
        throw new Error('You need to log in before tweeting');
      }
    }

    嵌套的if語句


    你可能需要花幾分鐘時間上下閱讀,才能了解函數(shù)運作的流程。嵌套的if語句很難讓人閱讀和理解。那么,如何才能擺脫討厭的嵌套if語句呢?可以反向思考,使用衛(wèi)語句來替代這些句子。


    “在計算機程序設(shè)計中,衛(wèi)語句是一個布爾表達式,如果程序要在有問題的分支里繼續(xù)運行的話,它的求值必須為true?!薄S基百科


    通過顛倒函數(shù)的邏輯,并在函數(shù)開始時放置導致早期退出的條件,它們將變?yōu)楸Wo程序,并且只允許函數(shù)在滿足所有條件時繼續(xù)執(zhí)行。這樣可以避免else語句。下面是如何重構(gòu)之前的函數(shù)以使用衛(wèi)語句的方法:


    function writeTweet() {
      const tweet = writeSomething();


      if (!isLoggedIn()) {
        throw new Error('You need to log in before tweeting');
      }
      if (!tweet) {
        throw new Error("Your tweet is empty, can't publish it");
      }
      if (!isTweetDoubleChecked()) {
        throw new Error('Dont publish without double checking your tweet');
      }


      tweetIt();
    }
    用衛(wèi)語句重構(gòu)函數(shù)


    如你所見,代碼更清晰,更容易理解。我們可以簡單向下閱讀來了解函數(shù)的作用,遵循函數(shù)的自然流動,不像以前那樣上下閱讀。


    4. 不要寫重復的代碼


    寫重復的代碼總是以失敗告終。它會導致如下情況:“我在這里修復了這個bug,但是忘記在那里修復”或“我需要在五個不同的地方更改/添加一個新功能”。


    正如DRY(Don’t repeat yourself不要重復)原則所說:


    每一部分知識或邏輯都必須在一個系統(tǒng)中有單一的、明確的表示。


    因此,代碼越少越好:它節(jié)省了時間和精力,更易于維護,并減少了錯誤的出現(xiàn)。

    那么,如何避免重復代碼呢?這有點難,但是將邏輯提取到函數(shù)/變量通常效果很好。讓我們看看下面的代碼,我在重構(gòu)應用程序時看到了這些代碼:


    function getJavascriptNews() {
        const allNews = getNewsFromWeb();
        const news = [];
     
        for (let i = allNews.length - 1; i >= 0; i--){
            if (allNews[i].type === "javascript") {
                news.push(allNews[i]);
            }
        }
     
        return news;
    }
     
    function getRustNews() {
        const allNews = getNewsFromWeb();
        const news = [];
     
        for (let i = allNews.length - 1; i >= 0; i--){
            if (allNews[i].type === "rust") {
                news.push(allNews[i]);
            }
        }
     
        return news;
    }


    function getGolangNews() {
      const news = [];
      const allNews = getNewsFromWeb();


      for (let i = allNews.length - 1; i >= 0; i--) {
        if (allNews[i].type === 'golang') {
          news.push(allNews[i]);
        }
      }


      return news;
    }
    重復代碼示例


    你可能已經(jīng)注意到for循環(huán)在這兩個函數(shù)中完全相同,除了一個小細節(jié):我們想要的新聞類型,即javascript或rust新聞。為了避免這種重復,可以將for循環(huán)提取到一個函數(shù)中,然后從getJavascriptNews,getRustNews和getGolangNews 函數(shù)調(diào)用該函數(shù)。以下是具體操作方法:


    function getJavascriptNews() {
      const allNews = getNewsFromWeb();
      return getNewsContent(allNews, 'javascript');
    }


    function getRustNews() {
      const allNews = getNewsFromWeb();
      return getNewsContent(allNews, 'rust');
    }


    function getGolangNews() {
      const allNews = getNewsFromWeb();
      return getNewsContent(allNews, 'golang');
    }


    function getNewsContent(newsList, type) {
      const news = [];
      for (let i = newsList.length - 1; i >= 0; i--) {
        if (newsList[i].type === type) {
          news.push(newsList[i].content);
        }
      }
      return news;
    }


    在將for循環(huán)提取到getNewsContent函數(shù)中之后,getJavaScriptNews, getRustNews和getGolangNews函數(shù)變成了簡單、清晰的程序。


    進一步重構(gòu)


    但是,你是否意識到,除了傳遞給getNewsContent的類型字符串之外,這兩個函數(shù)完全相同?這是重構(gòu)代碼時經(jīng)常發(fā)生的事情。通常情況下,更改一個會導致另一個更改,以此類推,直到重構(gòu)后的代碼最終變成原始代碼的一半大小。代碼告訴你它需要什么:


    function getNews(type) {
      const allNews = getNewsFromWeb();
      return getNewsContent(allNews, type);
    }


    function getNewsContent(newsList, type) {
      const news = [];
      for (let i = newsList.length - 1; i >= 0; i--) {
        if (newsList[i].type === type) {
          news.push(newsList[i].content);
        }
      }
      return news;
    }

    getJavaScriptNews, getRustNews和getGolangNews函數(shù)去了哪里?將它們替換為getNews函數(shù),該函數(shù)將新聞類型作為參數(shù)接收。這樣,無論添加多少類型的新聞,總是使用相同的功能。這稱為抽象,允許我們重用函數(shù),因此非常有用。抽象是我在寫代碼的時候最常用的技術(shù)之一。


    補充:使用es6特性使for循環(huán)更具可讀性


    for循環(huán)并不完全可讀。通過引入es6數(shù)組函數(shù),可以有95%的機會避免使用它們。在本例中可以使用array.filter和array.map組合來替換原始循環(huán):


    function getNews(type) {
      const allNews = getNewsFromWeb();
      return getNewsContent(allNews, type);
    }


    function getNewsContent(newsList, type) {
      return newsList
        .filter(newsItem => newsItem.type === type)
        .map(newsItem => newsItem.content);
    }
    用 Array.filter 和 Array.map 來代替循環(huán)


    ? 用Array.filter只返回其類型等于作為參數(shù)傳遞的類型的元素。


    ? 用Array.map只返回item對象的content屬性,而不是整個item。


    恭喜你,經(jīng)過三次簡單的重構(gòu),最初的三個函數(shù)已經(jīng)縮減為兩個,這更容易理解和維護。另外,抽象讓getNews函數(shù)變得可以重新利用。



    5. 一個函數(shù)只用來做一件事


    一個函數(shù)應該只做一件事。做不止一件事的函數(shù)是所有罪惡的根源,也是代碼中可能遇到的最糟糕的事情之一(嵌套的if語句也是)。它們很混亂,搞得代碼難以理解。下面是一個來自實際應用程序的復雜函數(shù)示例:


    function startProgram() {
      if (!window.indexedDB) {
        window.alert("Browser not support indexeDB");
      } else {
        let openRequest = indexedDB.open("store", 1);
     
        openRequest.onupgradeneeded = () => {};
     
        openRequest.onerror = () => {
          console.error("Error", openRequest.error);
        };
     
        openRequest.onsuccess = () => {
          let db = openRequest.result;
        };
     
        document.getElementById('stat-op').addEventListener('click', () => {});
        document.getElementById('pre2456').addEventListener('click', () => {});
        document.getElementById('cpTagList100').addEventListener('change', () => {});
        document.getElementById('cpTagList101').addEventListener('click', () => {});
        document.getElementById('gototop').addEventListener('click', () => {});
        document.getElementById('asp10').addEventListener('click', () => {});
     
        fetch("employeList.json")
          .then(res => res.json())
          .then(employes => {
            document.getElementById("employesSelect").innerHTML = employes.reduce(
              (content, employe) => employe.name + "<br>",
              ""
            );
          });
     
        document.getElementById("usernameLoged").innerHTML = `Welcome, ${username}`;
      }
    }
    又多又復雜又讓人難以理解的函數(shù)


    小貼士:由于本例不需要事件偵聽器的處理程序,所以刪除了它們。


    如你所見,這讓人困惑,也很難理解里面發(fā)生了什么。如果有錯誤出現(xiàn),都很難找到并修復它們。如何改進startProgram函數(shù)?可以將公共邏輯提取到函數(shù)中。以下是具體操作方法:


    function startProgram() {
      if (!window.indexedDB) {
        throw new Error("Browser doesn't support indexedDB");
      }


      initDatabase();
      setListeners();
      printEmployeeList();
    }


    function initDatabase() {
      let openRequest = indexedDB.open('store', 1);


      openRequest.onerror = () => {
        console.error('Error', openRequest.error);
      };
      openRequest.onsuccess = () => {
        let db = openRequest.result;
      };
    }


    function setListeners() {
      document.getElementById('stat-op').addEventListener('click', () => {});
      document.getElementById('pre2456').addEventListener('click', () => {});
      document.getElementById('cpTagList100').addEventListener('change', () => {});
      document.getElementById('cpTagList101').addEventListener('click', () => {});
      document.getElementById('gototop').addEventListener('click', () => {});
      document.getElementById('asp10').addEventListener('click', () => {});
    }


    async function printEmployeeList() {
      const employees = await getEmployeeList();


      document.getElementById('employeeSelect').innerHTML = formatEmployeeList(employees);
    }


    function formatEmployeeList(employees) {
      return employees.reduce(
        (content, employee) => content + employee.name + '<br>',
        ''
      );
    }


    function getEmployeeList() {
      return fetch('employeeList.json').then(res => res.json());
    }
    把邏輯提取到函數(shù)里


    仔細看看startProgram函數(shù)的變化:


    首先,通過使用一個衛(wèi)語句替換掉if-else語句。然后,啟動數(shù)據(jù)庫所需的邏輯提取到initDatabase函數(shù)中,并將事件偵聽器添加到setListeners函數(shù)中。


    打印員工列表的邏輯稍微復雜一些,因此創(chuàng)建了三個函數(shù):printEmployeeList, formatEmployeeList和getEmployeeList。


    getEmployeeList負責向employeeList.json發(fā)出GET請求并以json格式返回響應。


    然后由printEmployeeList函數(shù)調(diào)用getEmployeeList,該函數(shù)獲取員工列表并將其傳遞給formatEmployeeList函數(shù),formatEmployeeList函數(shù)格式化并返回該列表。然后輸出列表。


    如你所見,每個功能只負責做一件事。


    我們?nèi)匀豢梢詫瘮?shù)進行一些修改,其實,應用程序很需要把視圖從控制器中分離出來,但總體而言,startprogram函數(shù)現(xiàn)在信息很容易懂,理解它的功能絕對沒有困難。如果幾個月后必須重新用這段代碼,那也不是什么難事。


    小結(jié)


    程序員是唯一負責編寫高質(zhì)量代碼的人。我們都應該養(yǎng)成從第一行就寫好代碼的習慣。編寫清晰易懂的代碼并不難,這樣做對你和你的同事都有好處。


    瀏覽 47
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    艹比视频| 日韩激情毛片 | 中文无码在线综合网 | 黄片网站进入口 | 性爱免费视频 |