成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

Neilyo / 3132人閱讀

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

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

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

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

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

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

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

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

缺乏測試的覆蓋

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

有一次我現(xiàn)在的同事因?yàn)樵谥暗膱F(tuán)隊(duì)處理過難以維護(hù)的Ruby 編寫的 REST API 而辭職,他是接手了之前開發(fā)團(tuán)隊(duì)的工作。在修復(fù)現(xiàn)有的 bug 時會創(chuàng)造新的 bug,添加新的特性也會創(chuàng)造一系列新的 bug,而客戶也不想以更好的設(shè)計(jì)去重構(gòu)應(yīng)用,因而我的同事做了辭職這個正確的決定。

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

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

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

函數(shù)需要“小”

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

想象一個場景:一個函數(shù)需要返回一個數(shù)組、map 或者普通對象的“重量”?!爸亓俊庇蓪傩灾涤?jì)算得到。規(guī)則如下:

null 或者 undefined 計(jì)為 1

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

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

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

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

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

在 repl.it 中嘗試運(yùn)行

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ù)過于龐大,看起來像個裝有很多驚喜的黑盒子。你很難第一眼理解它是做什么的,再想象一下你的應(yīng)用里有一堆這樣的函數(shù)是什么光景。

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

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

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

僅僅看到1、24 這三個魔數(shù)而不了解上下文的情況下根本搞不清楚他們的含義。幸運(yùn)的是 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ù)中復(fù)用它。getCollectionWeight() 函數(shù)也變得簡練了一些。

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

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

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

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

在 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ù),你會很容易的弄清楚它實(shí)現(xiàn)的功能,現(xiàn)在的函數(shù)看起來像一個有趣的故事。每個函數(shù)都很清晰并且直截了當(dāng),你不會在思考代碼的含義上浪費(fèi)時間。簡潔的代碼理應(yīng)如此。

Step 3: 永遠(yuǎn)不要停止改進(jìn)

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

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

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

getCollectionWeight() 函數(shù)會變得十分簡單,因?yàn)樗ㄒ灰龅氖虑榫褪菑?getCollectionValues() 中獲取集合的值,然后執(zhí)行累加操作。

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

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

因?yàn)槔硐肭闆r下 getCollectionWeight() 中不應(yīng)該定義匿名函數(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ù)的藝術(shù)。

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

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

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

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

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

如果某個函數(shù)意外報(bào)錯,函數(shù)的調(diào)用棧信息會更加清晰,因?yàn)樗撕瘮?shù)名稱,你立刻就能確定出問題的函數(shù)在哪里。

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

你可以利用 CommonJS 或者 ES2015 模塊標(biāo)準(zhǔn)使代碼模塊化。把函數(shù)抽象成獨(dú)立的模塊,這樣會讓你的項(xiàng)目文件更輕量和結(jié)構(gòu)化。

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

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

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

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

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

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

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

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

為什么分解是必要的?因?yàn)楹喚毜暮瘮?shù)含義更加明確,也就意味著易讀和易改。

讓我們看一個例子。假設(shè)你想要編寫函數(shù)實(shí)現(xiàn)只保存數(shù)組中的素?cái)?shù),移除非素?cái)?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ù)的細(xì)節(jié)嗎?答案是否定的。因?yàn)?getOnlyPrime() 函數(shù)會有不同層級的抽象步驟,這個函數(shù)會包含許多的職責(zé)。

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

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

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

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

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

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

為了實(shí)現(xiàn)檢查一個數(shù)字 n 是否為素?cái)?shù)的功能,需要確認(rèn)是否從 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ù)簡練化的原則,復(fù)雜函數(shù)的可讀性可以大大提升。每一層級的精確抽象和編碼可以防止編寫出一大堆難以維護(hù)的代碼。

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

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

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

因?yàn)楹瘮?shù)代表了動作,函數(shù)名稱應(yīng)該至少包含一個動詞。比如:deletePage(), verifyCredentials()。獲取或者設(shè)置屬性值時,使用標(biāo)準(zhǔn)的 setget 前綴:getLastName() 或者 setLastName()

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

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

4. 結(jié)論

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

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

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

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

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

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

但沒有比一團(tuán)亂麻的代碼更糟的了。

5. 譯者注

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

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/92804.html

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

    xiangchaobin 評論0 收藏0

發(fā)表評論

0條評論

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