亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

編寫小而美函數(shù)的藝術

Neilyo / 3228人閱讀

摘要:函數(shù)需要小要避免編寫職責冗雜的龐大函數(shù),而需要將它們分離成很多小函數(shù)。另一方面小而能夠自解釋的函數(shù)讀起來也會讓人愉悅,方便開展之后的工作。最終我們最初的龐大函數(shù)被拆分成下面這些函數(shù)在中嘗試這就是編寫小而美的函數(shù)的藝術。

原文鏈接:https://dmitripavlutin.com/th...
譯者:阿里云-也樹

隨著軟件應用的復雜度不斷上升,為了確保應用穩(wěn)定且易拓展,代碼質量就變的越來越重要。

不幸的是,包括我在內的幾乎每個開發(fā)者在職業(yè)生涯中都會面對質量很差的代碼。這些代碼通常有以下特征:

函數(shù)冗長,做了太多事情

函數(shù)有副作用并且很難理解和調試排錯

含糊的函數(shù)/變量命名

代碼脆弱,一個小改動會意外地破壞應用的其它組件

缺乏測試的覆蓋

這些話聽起來非常常見:“我不明白這部分代碼怎么工作的”,“這代碼太爛了”,“這代碼太難改了”等等。

有一次我現(xiàn)在的同事因為在之前的團隊處理過難以維護的Ruby 編寫的 REST API 而辭職,他是接手了之前開發(fā)團隊的工作。在修復現(xiàn)有的 bug 時會創(chuàng)造新的 bug,添加新的特性也會創(chuàng)造一系列新的 bug,而客戶也不想以更好的設計去重構應用,因而我的同事做了辭職這個正確的決定。

這樣的場景時有發(fā)生,我們能做些什么呢?

需要牢記于心的是:僅僅讓應用可以運行和關注代碼質量是不同的。一方面你需要滿足應用的功能,另一方面你需要花時間確認是否任意的函數(shù)沒有包含太多職責、是否所有函數(shù)都使用了易理解的變量和函數(shù)名并且是否避免了函數(shù)的副作用。

函數(shù)(包括對象的方法)是讓應用運行的小齒輪。首先你應該專注于它們的結構和編寫,而下面這篇文章闡述了編寫清晰易懂且容易測試的函數(shù)的最佳實踐。

函數(shù)需要“小”

要避免編寫職責冗雜的龐大函數(shù),而需要將它們分離成很多小函數(shù)。龐大的函數(shù)就像黑盒子一樣,很難理解和修改,尤其在測試時更加捉襟見肘。

想象一個場景:一個函數(shù)需要返回一個數(shù)組、map 或者普通對象的“重量”。“重量”由屬性值計算得到。規(guī)則如下:

null 或者 undefined 計為 1

基礎類型的數(shù)據(jù)計為 2

對象或者函數(shù)類型的數(shù)據(jù)計為 4

舉個例子:數(shù)組 [null, "Hello World", {}] 的重量計算為: 1(null) + 2(字符串類型) + 4(對象) = 7

Step 0: 最初的龐大函數(shù)

讓我們從最壞的情況開始,所有的邏輯都寫在一個龐大的 getCollectionWeight() 函數(shù)里。

在 repl.it 中嘗試運行

function getCollectionWeight(collection) {  
  let collectionValues;
  if (collection instanceof Array) {
    collectionValues = collection;
  } else if (collection instanceof Map) {
    collectionValues = [...collection.values()];
  } else {
    collectionValues = Object.keys(collection).map(function (key) {
      return collection[key];
    });
  }
  return collectionValues.reduce(function(sum, item) {
    if (item == null) {
      return sum + 1;
    } 
    if (typeof item === "object" || typeof item === "function") {
      return sum + 4;
    }
    return sum + 2;
  }, 0);
}
let myArray = [null, { }, 15];  
let myMap = new Map([ ["functionKey", function() {}] ]);  
let myObject = { "stringKey": "Hello world" };  
getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)  
getCollectionWeight(myMap);    // => 4  
getCollectionWeight(myObject); // => 2  

問題顯而易見。getCollectionWeight() 函數(shù)過于龐大,看起來像個裝有很多驚喜的黑盒子。你很難第一眼理解它是做什么的,再想象一下你的應用里有一堆這樣的函數(shù)是什么光景。

當你在和這樣的代碼打交道時,是在浪費時間和精力。另一方面小而能夠自解釋的函數(shù)讀起來也會讓人愉悅,方便開展之后的工作。

Step 1: 通過數(shù)據(jù)類型提取“重量”并且去除魔數(shù)

現(xiàn)在我們的目標是把龐大的函數(shù)分解成更小的不耦合且可重用的函數(shù)。第一步是通過不同的類型,抽象出決定“重量”值的代碼。這個新函數(shù)是 getWeight()。

僅僅看到1、24 這三個魔數(shù)而不了解上下文的情況下根本搞不清楚他們的含義。幸運的是 ES2015 允許我們利用 const 來定義只讀的的變量,所以可以創(chuàng)建有含義的常量來取代魔數(shù)。

讓我們創(chuàng)建 getWeightByType() 函數(shù)并且改善一下 getCollectionWeight() 函數(shù):

在 repl.it 中嘗試

function getWeightByType(value) {  
  const WEIGHT_NULL_UNDEFINED  = 1;
  const WEIGHT_PRIMITIVE       = 2;
  const WEIGHT_OBJECT_FUNCTION = 4;
  if (value == null) {
    return WEIGHT_NULL_UNDEFINED;
  } 
  if (typeof value === "object" || typeof value === "function") {
    return WEIGHT_OBJECT_FUNCTION;
  }
  return WEIGHT_PRIMITIVE;
}
function getCollectionWeight(collection) {  
  let collectionValues;
  if (collection instanceof Array) {
    collectionValues = collection;
  } else if (collection instanceof Map) {
    collectionValues = [...collection.values()];
  } else {
    collectionValues = Object.keys(collection).map(function (key) {
      return collection[key];
    });
  }
  return collectionValues.reduce(function(sum, item) {
    return sum + getWeightByType(item);
  }, 0);
}
let myArray = [null, { }, 15];  
let myMap = new Map([ ["functionKey", function() {}] ]);  
let myObject = { "stringKey": "Hello world" };  
getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)  
getCollectionWeight(myMap);    // => 4  
getCollectionWeight(myObject); // => 2  

是不是看起來好些了?getWeightByType() 函數(shù)是無依賴的,僅僅通過數(shù)據(jù)類型來決定數(shù)據(jù)的“重量”。你可以在任何一個函數(shù)中復用它。getCollectionWeight() 函數(shù)也變得簡練了一些。

WEIGHT_NULL_UNDEFINED, WEIGHT_PRIMITIVEWEIGHT_OBJECT_FUNCTION 從變量名就可以看出“重量”所描述的數(shù)據(jù)類型,而不需要再猜 1, 24 代表什么。

Step 2: 繼續(xù)分割函數(shù)并且增加拓展性

上面的改進版仍然有瑕疵。想象一下你想要將“重量”的計算應用在 Set 或者其它定制的數(shù)據(jù)集合時,由于 getCollectionWeight() 函數(shù)包含了收集值的邏輯,它的代碼量會快速增長。

讓我們從代碼中抽象出一些函數(shù),比如獲取 map 類型的數(shù)據(jù)的函數(shù) getMapValues() 和獲取普通對象類型數(shù)據(jù)的函數(shù) getPlainObjectValues()。再看看新的改進版:

在 repl.it 中嘗試

function getWeightByType(value) {  
  const WEIGHT_NULL_UNDEFINED = 1;
  const WEIGHT_PRIMITIVE = 2;
  const WEIGHT_OBJECT_FUNCTION = 4;
  if (value == null) {
    return WEIGHT_NULL_UNDEFINED;
  } 
  if (typeof value === "object" || typeof value === "function") {
    return WEIGHT_OBJECT_FUNCTION;
  }
  return WEIGHT_PRIMITIVE;
}
function getMapValues(map) {  
  return [...map.values()];
}
function getPlainObjectValues(object) {  
  return Object.keys(object).map(function (key) {
    return object[key];
  });
}
function getCollectionWeight(collection) {  
  let collectionValues;
  if (collection instanceof Array) {
    collectionValues = collection;
  } else if (collection instanceof Map) {
    collectionValues = getMapValues(collection);
  } else {
    collectionValues = getPlainObjectValues(collection);
  }
  return collectionValues.reduce(function(sum, item) {
    return sum + getWeightByType(item);
  }, 0);
}
let myArray = [null, { }, 15];  
let myMap = new Map([ ["functionKey", function() {}] ]);  
let myObject = { "stringKey": "Hello world" };  
getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)  
getCollectionWeight(myMap);    // => 4  
getCollectionWeight(myObject); // => 2  

現(xiàn)在再讀 getCollectionWeight() 函數(shù),你會很容易的弄清楚它實現(xiàn)的功能,現(xiàn)在的函數(shù)看起來像一個有趣的故事。每個函數(shù)都很清晰并且直截了當,你不會在思考代碼的含義上浪費時間。簡潔的代碼理應如此。

Step 3: 永遠不要停止改進

現(xiàn)在依然有很多可以改進的地方。

你可以創(chuàng)建一個獨立的 getCollectionValues() 函數(shù),包含區(qū)分數(shù)據(jù)集合類型的判斷邏輯:

function getCollectionValues(collection) {  
  if (collection instanceof Array) {
    return collection;
  }
  if (collection instanceof Map) {
    return getMapValues(collection);
  }
  return getPlainObjectValues(collection);
}

getCollectionWeight() 函數(shù)會變得十分簡單,因為它唯一要做的事情就是從 getCollectionValues() 中獲取集合的值,然后執(zhí)行累加操作。

你也可以創(chuàng)建一個獨立的 reduce 函數(shù):

function reduceWeightSum(sum, item) {  
  return sum + getWeightByType(item);
}

因為理想情況下 getCollectionWeight() 中不應該定義匿名函數(shù)。

最終我們最初的龐大函數(shù)被拆分成下面這些函數(shù):

在 repl.it 中嘗試

function getWeightByType(value) {  
  const WEIGHT_NULL_UNDEFINED = 1;
  const WEIGHT_PRIMITIVE = 2;
  const WEIGHT_OBJECT_FUNCTION = 4;
  if (value == null) {
    return WEIGHT_NULL_UNDEFINED;
  } 
  if (typeof value === "object" || typeof value === "function") {
    return WEIGHT_OBJECT_FUNCTION;
  }
  return WEIGHT_PRIMITIVE;
}
function getMapValues(map) {  
  return [...map.values()];
}
function getPlainObjectValues(object) {  
  return Object.keys(object).map(function (key) {
    return object[key];
  });
}
function getCollectionValues(collection) {  
  if (collection instanceof Array) {
    return collection;
  }
  if (collection instanceof Map) {
    return getMapValues(collection);
  }
  return getPlainObjectValues(collection);
}
function reduceWeightSum(sum, item) {  
  return sum + getWeightByType(item);
}
function getCollectionWeight(collection) {  
  return getCollectionValues(collection).reduce(reduceWeightSum, 0);
}
let myArray = [null, { }, 15];  
let myMap = new Map([ ["functionKey", function() {}] ]);  
let myObject = { "stringKey": "Hello world" };  
getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)  
getCollectionWeight(myMap);    // => 4  
getCollectionWeight(myObject); // => 2  

這就是編寫小而美的函數(shù)的藝術。

經(jīng)過一系列的代碼質量優(yōu)化,你獲得了一連串的好處:

通過自解釋的代碼增加了 getCollectionWeight() 函數(shù)的可讀性。

極大地減少了 getCollectionWeight() 函數(shù)的代碼量。

避免了在你想要增加其它數(shù)據(jù)集合類型時,getCollectionWeight() 函數(shù)代碼量會過于迅速地增長。

抽象出的函數(shù)是獨立可重用的。你的同事可能想要引入你這些實用的函數(shù)到另一個項目中,你可以輕易的讓他們做到這一點。

如果某個函數(shù)意外報錯,函數(shù)的調用棧信息會更加清晰,因為它包含了函數(shù)名稱,你立刻就能確定出問題的函數(shù)在哪里。

分割開的函數(shù)更容易編寫測試和實現(xiàn)更高的測試覆蓋率。相比于測試一個龐大函數(shù)的所有場景,更好的辦法是獨立構造測試并且獨立核對每一個函數(shù)。

你可以利用 CommonJS 或者 ES2015 模塊標準使代碼模塊化。把函數(shù)抽象成獨立的模塊,這樣會讓你的項目文件更輕量和結構化。

這些優(yōu)勢會讓你在復雜的應用中如魚得水。

有條通用的準則:一個函數(shù)不應該超過20行,小則優(yōu)。

你現(xiàn)在可能會問我一個合情合理的問題:“我不想為每一行代碼都創(chuàng)建函數(shù),有沒有一個標準讓我不再繼續(xù)拆分函數(shù)?”這就是下一章節(jié)的主題。

2. 函數(shù)應該是簡練的

讓我們稍作休息,思考一個問題:軟件應用究竟是什么?

每個應用都是為了完成一系列的需求。作為開發(fā)者,需要把這些需求分解為可以正確運行特定任務的小組件(命名空間,類,函數(shù),代碼塊)。

一個組件包含了其它更小的組件。如果你想要編寫一個組件,需要通過抽象程度比它低一層級的組件來創(chuàng)建。

換句話講:你需要把一個函數(shù)分解為多個步驟,這些步驟的抽象程度需要保持在同一層級或者低一層級。這樣可以在保證函數(shù)簡練的同時踐行“做一件事,并且做好”的原則。

為什么分解是必要的?因為簡練的函數(shù)含義更加明確,也就意味著易讀和易改。

讓我們看一個例子。假設你想要編寫函數(shù)實現(xiàn)只保存數(shù)組中的素數(shù),移除非素數(shù)。函數(shù)通過以下方式執(zhí)行:

getOnlyPrime([2, 3, 4, 5, 6, 8, 11]); // => [2, 3, 5, 11]  

getOnlyPrime() 函數(shù)中有哪些低一層級的抽象步驟?接下來系統(tǒng)闡述:

使用 isPrime() 函數(shù)過濾數(shù)組中的數(shù)字。

需要在這個層級提供 isPrime() 函數(shù)的細節(jié)嗎?答案是否定的。因為 getOnlyPrime() 函數(shù)會有不同層級的抽象步驟,這個函數(shù)會包含許多的職責。

既然腦子里有了最基礎的想法,讓我們先完成 getOnlyPrime() 函數(shù)的內容:

function getOnlyPrime(numbers) {  
  return numbers.filter(isPrime);
}
getOnlyPrime([2, 3, 4, 5, 6, 8, 11]); // => [2, 3, 5, 11]  

此時 getOnlyPrime() 函數(shù)非常簡潔。它包含了一個獨立層級的抽象:數(shù)組的 .filter() 方法和 isPrime() 函數(shù)。

現(xiàn)在是時候向更低的層級抽象了。

數(shù)組方法是 .filter() 直接由 JavaScript 引擎提供的,原樣使用即可。ECMA標準中精確地描述了它的功能。

現(xiàn)在我們來研究 isPrime() 函數(shù)的具體實現(xiàn):

為了實現(xiàn)檢查一個數(shù)字 n 是否為素數(shù)的功能,需要確認是否從 2Math.sqrt(n) 的任意數(shù)字都可以整除 n。

理解了這個算法(效率不高,但簡便起見)后,來完成 isPrime() 函數(shù)的代碼:

在 repl.it 中嘗試

function isPrime(number) {  
  if (number === 3 || number === 2) {
    return true;
  }
  if (number === 1) {
    return false;
  }
  for (let divisor = 2; divisor <= Math.sqrt(number); divisor++) {
    if (number % divisor === 0) {
      return false;
    }
  }
  return true;
}
function getOnlyPrime(numbers) {  
  return numbers.filter(isPrime);
}
getOnlyPrime([2, 3, 4, 5, 6, 8, 11]); // => [2, 3, 5, 11]  

getOnlyPrime() 函數(shù)小而精煉。它僅僅保留了必需的低一層級的抽象。

如果你遵照讓函數(shù)簡練化的原則,復雜函數(shù)的可讀性可以大大提升。每一層級的精確抽象和編碼可以防止編寫出一大堆難以維護的代碼。

3. 使用簡明扼要的函數(shù)名稱

函數(shù)名稱應該簡明扼要,不應過于冗長或者簡短。理想情況下,函數(shù)名稱應該在不對代碼刨根問底的情況下清楚反映出函數(shù)的功能。

函數(shù)名稱應該使用駝峰式命名法,以小寫字母開頭:addItem(), saveToStore() 或者 getFirstName()。

因為函數(shù)代表了動作,函數(shù)名稱應該至少包含一個動詞。比如:deletePage(), verifyCredentials()。獲取或者設置屬性值時,使用標準的 setget 前綴:getLastName() 或者 setLastName()。

避免編寫含混的函數(shù)名,比如 foo(), bar(), a(), fun() 等等。這些名稱沒有意義。

如果函數(shù)小而清晰,名稱簡明扼要,代碼就可以像散文一樣閱讀。

4. 結論

當然,上面提供的示例十分簡單。真實的應用中會更加復雜。你可能會抱怨僅僅為了抽象出一個層級而編寫簡練的函數(shù)是沉悶乏味的任務。但是如果從項目開始之初就正確實踐的話就不會是一件困難的事。

如果應用已經(jīng)有很多函數(shù)擁有太多職責,你會發(fā)現(xiàn)很難理解這些代碼。在很多情況下,不大可能在合理的時間完成重構的工作。但是至少從點滴做起:盡你所能抽象一些東西。

最好的解決辦法當然是從一開始就正確的實現(xiàn)應用。不僅要在實現(xiàn)需求上花費時間,同樣應該像我建議的那樣:正確組織你的函數(shù),讓它們小而簡練。

三思而后行。(Measure seven times, cut once)

ES2015 實現(xiàn)了一個很棒的模塊系統(tǒng),清晰地建議出分割函數(shù)是好的實踐。

記住永遠值得投資時間讓代碼變得簡練有組織。在這個過程中,你可能覺得實踐起來很難,可能需要很多練習,也可能回過頭來修改一個函數(shù)很多次。

但沒有比一團亂麻的代碼更糟的了。

5. 譯者注

文章作者提出的 small function 的觀點可能會讓初學者產生一點誤解,在我的理解里,更準確的表述應該是從代碼實現(xiàn)功能的邏輯層面抽象出更小的功能點,將抽象出的功能點轉化為函數(shù)來為最后的業(yè)務提供組裝的零件。最終的目的依然是通過解耦邏輯來提高代碼的拓展性和復用性,而不能僅僅停留在視覺層面的”小“,單純?yōu)榱俗尯瘮?shù)代碼行數(shù)變少是沒有意義的。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://www.ezyhdfw.cn/yun/92804.html

相關文章

  • 一款移動端微型 swiper 插件,而美,無依賴.

    摘要:項目地址移動端微型插件,小而美,無依賴前言相對于其他的插件而言,最大的優(yōu)勢就是小,壓縮后僅能滿足部分開發(fā)需求。插件的開發(fā)采用組合使用構造函數(shù)模式和原型模式,通過構建,感興趣的可以閱讀源碼。 mSwiper.js showImg(https://segmentfault.com/img/remote/1460000011106820); showImg(https://segmen...

    Nino 評論0 收藏0
  • 一款移動端微型 swiper 插件,而美,無依賴.

    摘要:項目地址移動端微型插件,小而美,無依賴前言相對于其他的插件而言,最大的優(yōu)勢就是小,壓縮后僅能滿足部分開發(fā)需求。插件的開發(fā)采用組合使用構造函數(shù)模式和原型模式,通過構建,感興趣的可以閱讀源碼。 mSwiper.js showImg(https://segmentfault.com/img/remote/1460000011106820); showImg(https://segmen...

    pubdreamcc 評論0 收藏0
  • 一款移動端微型 swiper 插件,而美,無依賴.

    摘要:項目地址移動端微型插件,小而美,無依賴前言相對于其他的插件而言,最大的優(yōu)勢就是小,壓縮后僅能滿足部分開發(fā)需求。插件的開發(fā)采用組合使用構造函數(shù)模式和原型模式,通過構建,感興趣的可以閱讀源碼。 mSwiper.js showImg(https://segmentfault.com/img/remote/1460000011106820); showImg(https://segmen...

    DataPipeline 評論0 收藏0
  • Eruda 一個可能被人遺忘調試神器

    摘要:引言日常工作中再牛逼的大佬都不敢說自己的代碼是完全沒有問題的,既然有問題,那就也就有調試,說到調試工具,大家可能對于還有遠程調試等比較熟悉,甚至有些是我可能也沒有用過的這里噴一句吧,誰都別給我提啊,那個不叫調試工具,那叫坑爹神器,話說最近不 showImg(https://segmentfault.com/img/bVbk8zn?w=1008&h=298); 引言 ?  日常工作中再牛...

    mingzhong 評論0 收藏0
  • iSlider—可能是最流暢移動端滑動組件

    摘要:還有一些小功能,比如滑動邊界遞減,自動滑動,垂直水平滑動可配置后續(xù)我們計劃增加手勢縮放圖片頁面內部切換等更強大的功能,希望有更多的人來使用,也歡迎大家提交和爭取打造最好用的移動端滑動組件。 iSlider是一個專為移動端設計的滑動組件,項目地址: https://github.com/BE-FE/iSlider iSlider是我參與的第二個比較正式的開源項目,主要編寫了里面的動畫部...

    xiangchaobin 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<