為什么要在javascript中進(jìn)行靜態(tài)類型檢查.Part2&3[譯]

本文為翻譯文章粪摘,原文鏈接見(jiàn)文末

第一部分中我們了解了許多基礎(chǔ)知識(shí)蓖宦,結(jié)束了語(yǔ)法的學(xué)習(xí),我們可以進(jìn)入下一個(gè)更有趣的部分:使用靜態(tài)類型的優(yōu)勢(shì)和劣勢(shì)

使用靜態(tài)類型的優(yōu)勢(shì)

靜態(tài)類型會(huì)給我們寫(xiě)代碼提供很多好處抹腿。下面我們就來(lái)探討一下彤路。

優(yōu)勢(shì)一:你可以盡早發(fā)現(xiàn)bug和錯(cuò)誤

靜態(tài)類型檢查允許我們?cè)诔绦驔](méi)有運(yùn)行之前就可以確定我們所設(shè)定的確定性是否是對(duì)的。一旦有違反這些既定規(guī)則的行為沫浆,它能在運(yùn)行之前就發(fā)現(xiàn)捷枯,而不是在運(yùn)行時(shí)。

一個(gè)例子:假設(shè)我們有一個(gè)簡(jiǎn)單的方法专执,輸入半徑淮捆,計(jì)算面積:

const calculateArea = (radius) => 3.14 * radius * radius;

var area = calculateArea(3);
// 28.26

現(xiàn)在,如果你要給radius傳一個(gè)非數(shù)值的值(例如‘im evil’)

var area = calculateArea('im evil');
// NaN

會(huì)返回一個(gè)NaN本股。如果其他某些功能要依賴這個(gè)方法攀痊,并且需要始終返回一個(gè)數(shù)值類型,那在這種返回結(jié)果下拄显,就會(huì)導(dǎo)致一個(gè)bug甚至崩潰苟径。不理想。躬审。棘街。

當(dāng)我們使用了靜態(tài)類型蟆盐,我們就可以準(zhǔn)確確認(rèn)一個(gè)方法的輸入與輸出:

const calculateArea = (radius: number): number => 3.14 * radius * radius;

現(xiàn)在在試著給calculateArea方法傳入一個(gè)非數(shù)值類型,這時(shí)候Flow就會(huì)顯示如下信息:

calculateArea('Im evil');
^^^^^^^^^^^^^^^^^^^^^^^^^ function call
calculateArea('Im evil');
              ^^^^^^^^^ string. This type is incompatible with 
                
const calculateArea = (radius: number): number => 3.14 * radius * radius;
                               ^^^^^^ number 

現(xiàn)在我們能確保這個(gè)方法只會(huì)接受有效的數(shù)值作為參數(shù)遭殉,并且返回一個(gè)有效的數(shù)值石挂。

因?yàn)轭愋蜋z查器會(huì)在你編碼的時(shí)候就告訴你錯(cuò)誤,所以這也就比你把代碼交付到客戶手中才發(fā)現(xiàn)一些錯(cuò)誤要更方面(或者說(shuō)付出更少的開(kāi)發(fā)與維護(hù)成本)险污。

優(yōu)勢(shì)二:起到在線文檔的功能

對(duì)我們和其他接觸我們代碼的人而言痹愚,類型就好像是一個(gè)文檔。

通過(guò)我之前工作項(xiàng)目里一大段代碼中的一個(gè)方法罗心,我們可以看看具體是怎么用的:

function calculatePayoutDate(quote, amount, paymentMethod) {
  let payoutDate;
  
  /* business logic */

  return payoutDate;
}

第一眼看到這個(gè)方法(即使是第二眼里伯、第三眼……),我沒(méi)法搞清楚要如何使用它渤闷。

quote參數(shù)是一個(gè)數(shù)值型么疾瓮?還是一個(gè)boolean型?paymentMethod是一個(gè)對(duì)象么飒箭?還是僅僅就是一個(gè)代表著支付方式的字符串狼电?這個(gè)方法會(huì)返回一個(gè)表示日期的字符串么?還是一個(gè)Date對(duì)象弦蹂?

沒(méi)有任何的提示……

這個(gè)時(shí)候我就只能各種看業(yè)務(wù)邏輯肩碟,在代碼庫(kù)里到處檢索,直到我最終搞清楚了凸椿。但是削祈,就為了要理解這簡(jiǎn)單的一個(gè)方法,費(fèi)了九牛二虎之力脑漫。

如果是另一種情況髓抑,我們向下面這樣來(lái)寫(xiě):

function calculatePayoutDate(
  quote: boolean,
  amount: number,
  paymentMethod: string): Date {
  let payoutDate;
    
  /* business logic */

  return payoutDate;
}

這一下就讓方法的輸入?yún)?shù)和返回值的類型變得清晰了。這一場(chǎng)景展示了优幸,我們?nèi)绾斡渺o態(tài)類型來(lái)表達(dá)方法的含義吨拍。通過(guò)這樣做,我們可以很好得與其他開(kāi)發(fā)者交流网杆,什么是我們的方法所期望的羹饰,而什么值又是他們希望從方法中獲取的。下一次他們使用到這個(gè)方法的時(shí)候碳却,就不會(huì)產(chǎn)生疑問(wèn)了队秩。

當(dāng)然,也有人會(huì)說(shuō)追城,在方法上加上一段注釋文檔不也能解決同樣的問(wèn)題么:

/*
  @function Determines the payout date for a purchase
  @param {boolean} quote - Is this for a price quote?
  @param {boolean} amount - Purchase amount
  @param {string} paymentMethod - Type of payment method used for this purchase
*/
function calculatePayoutDate(quote, amount, paymentMethod) {
  let payoutDate;
  /* .... Business logic .... */

  return payoutDate;
};

這確實(shí)是個(gè)辦法刹碾。但是這種方式會(huì)更冗長(zhǎng)。除了冗長(zhǎng)之外座柱,由于可依賴性較低迷帜,同時(shí)缺乏結(jié)構(gòu)化物舒,因此像這樣的代碼注釋會(huì)難以維護(hù)。一些開(kāi)發(fā)者會(huì)寫(xiě)出不錯(cuò)的注釋戏锹,而另一些人的注釋可能含糊不清冠胯,甚至有些根本忘了寫(xiě)注釋。

尤其是當(dāng)你重構(gòu)代碼的時(shí)候锦针,你很容易就會(huì)忘記去更新相應(yīng)的注釋荠察。然而,類型聲明有著定義好的語(yǔ)法和結(jié)構(gòu)奈搜,不會(huì)隨著時(shí)間推移而消失——它們是被寫(xiě)在代碼里的悉盆。

優(yōu)勢(shì)三:減少了復(fù)雜的錯(cuò)誤處理

類型可以幫助我們減少?gòu)?fù)雜的錯(cuò)誤處理。讓我們重新回去看一看calculateArea方法馋吗,看看它是怎么做到的:

const calculateAreas = (radii) => {
  var areas = [];
  for (let i = 0; i < radii.length; i++) {
    areas[i] = PI * (radii[i] * radii[i]);
  }

  return areas;
};

這個(gè)方法是有效的焕盟,但是它不能正確處理無(wú)效的輸入?yún)?shù)。如果你想要確保這個(gè)方法能夠正確處理輸入?yún)?shù)非有效數(shù)組的情況宏粤,你可能需要把它改成下面這個(gè)樣子:

const calculateAreas = (radii) => {
  // Handle undefined or null input
  if (!radii) {
    throw new Error("Argument is missing");
  }

  // Handle non-array inputs
  if (!Array.isArray(radii)) {
    throw new Error("Argument must be an array");
  }

  var areas = [];

  for (var i = 0; i < radii.length; i++) {
    if (typeof radii[i] !== "number") {
      throw new Error("Array must contain valid numbers only");
    } else {
      areas[i] = 3.14 * (radii[i] * radii[i]);
    }
  }

  return areas;
};

哈脚翘?就這點(diǎn)功能就要碼這么多行。

但是有了靜態(tài)類型绍哎,就變得簡(jiǎn)單了:

const calculateAreas = (radii: Array<number>): Array<number> => {
  var areas = [];
  for (var i = 0; i < radii.length; i++) {
    areas[i] = 3.14 * (radii[i] * radii[i]);
  }

  return areas;
};

現(xiàn)在来农,這個(gè)方法實(shí)際上看起來(lái)就和最初的很類似,沒(méi)有了令人視覺(jué)上混亂的錯(cuò)誤處理崇堰。這還是很容易能看出它的優(yōu)勢(shì)的沃于,對(duì)吧?

優(yōu)勢(shì)四:使你在重構(gòu)時(shí)更有信心

我會(huì)通過(guò)一個(gè)趣事來(lái)解釋這一點(diǎn):我曾經(jīng)的工作涉及一個(gè)非常大型的代碼庫(kù)海诲,我們需要更新里面的User類的一個(gè)方法揽涮。特別的,我們需要將方法中的一個(gè)參數(shù)從string變?yōu)?code>object饿肺。

我改了這個(gè)方法,但是當(dāng)我提交更新的時(shí)候卻感到手腳發(fā)涼——這個(gè)方法在代碼庫(kù)里有太多的地方調(diào)用了盾似,以至于我不確定我是否已經(jīng)修改了所有的實(shí)例敬辣。如果有些不在測(cè)試幫助文件里的深層調(diào)用被我遺留了怎么辦呢?

唯一可以搞清楚這一點(diǎn)的方法就是零院,提交這份代碼并且祈禱不會(huì)出現(xiàn)一些錯(cuò)誤溉跃。

使用靜態(tài)類型將會(huì)避免這種情況。這會(huì)保證告抄,如果更新了方法撰茎,并且相應(yīng)地更新了類型定義,那么類型檢查器將會(huì)幫我去捕獲我遺漏的錯(cuò)誤打洼。我所需要做的就是龄糊,瀏覽這些錯(cuò)誤并修復(fù)它們逆粹。

優(yōu)勢(shì)五:將數(shù)據(jù)和行為分離

一個(gè)較少談及的關(guān)于靜態(tài)類型的優(yōu)點(diǎn)是,它可以幫助我們進(jìn)行數(shù)據(jù)和行為的分離炫惩。

讓我們?cè)賮?lái)回顧一下包含靜態(tài)類型的calculateAreas方法:

const calculateAreas = (radii: Array<number>): Array<number> => {
  var areas = [];
  for (var i = 0; i < radii.length; i++) {
    areas[i] = 3.14 * (radii[i] * radii[i]);
  }

  return areas;
};

思考一下我們是如何來(lái)編寫(xiě)僻弹、設(shè)計(jì)這個(gè)方法的。由于我們聲明了類型他嚷,我們必須先考慮我們打算要使用的數(shù)據(jù)類型蹋绽,以便于我們可以大體定義好輸入與輸出的類型。


image
image

這之后我們才會(huì)去填充具體的邏輯實(shí)現(xiàn)部分:


image
image

這個(gè)能力恰恰表明了筋蓖,數(shù)據(jù)和行為的分離是我們更明確我們的腦海中對(duì)方法的設(shè)想卸耘,同時(shí)也更準(zhǔn)確地傳達(dá)我們的意圖,這種方式減輕了無(wú)關(guān)的思維負(fù)擔(dān)粘咖,使我們對(duì)程序的思考更加純粹與清晰蚣抗。(譯者:這應(yīng)該是程序設(shè)計(jì)時(shí)對(duì)我們思維方式的一種提示、引導(dǎo)與規(guī)范)

優(yōu)勢(shì)六:幫助我們消除了一整類bug

作為JavaScript開(kāi)發(fā)者涂炎,我們最常遇見(jiàn)的錯(cuò)誤就是運(yùn)行時(shí)的類型錯(cuò)誤忠聚。

舉個(gè)例子,應(yīng)用程序最初是的狀態(tài)被定義為:

var appState = {
  isFetching: false,
  messages: [],
};

假設(shè)接下來(lái)我們創(chuàng)建了一個(gè)用來(lái)獲取messages數(shù)據(jù)的API唱捣。然后两蟀,我們的app有一個(gè)簡(jiǎn)單的組件會(huì)將messages(在state中定義的)作為屬性來(lái)裝載,并且顯示未讀數(shù)量和messages的列表:

import Message from './Message';

const MyComponent = ({ messages }) => {
  return (
    <div>
      <h1> You have { messages.length } unread messages </h1>
      { messages.map(message => <Message message={ message } /> )}
    </div>
  );
};

如果這個(gè)獲取messages數(shù)據(jù)的API失敗了或是返回了undefined震缭,在生產(chǎn)環(huán)境中這段程序就會(huì)出現(xiàn)一個(gè)類型錯(cuò)誤:

TypeError: Cannot read property ‘length’ of undefined

……然后我們的程序就崩潰了赂毯。你就這么失去了你的客戶。僵……

讓我們看看類型檢查在這有什么作用拣宰。首先是給應(yīng)用的state添加類型党涕。我會(huì)使用類型別名的功能,創(chuàng)建一個(gè)AppState類型巡社,然后用它來(lái)定義state:

type AppState = {
  isFetching: boolean,
  messages: ?Array<string>
};

var appState: AppState = {
  isFetching: false,
  messages: null,
};

由于知道了獲取messages的API并不可靠膛堤,因此在這里我給messages定義了一個(gè)為字符串?dāng)?shù)組的maybe類型。

最后同樣的晌该,我們?cè)趘iew層組件中獲取messages肥荔,并將它渲染在組件中。

import Message from './Message';

const MyComponent = ({ messages }) => {
  return (
    <div>
      <h1> You have { messages.length } unread messages </h1>
      { messages.map(message => <Message message={ message } /> )}
    </div>
  );
};

Flow會(huì)幫我們捕獲并將錯(cuò)誤告知我們:

<h1> You have {messages.length} unread  messages </h1>
                        ^^^^^^ property `length`. Property cannot be accessed on possibly null value                                                                  
<h1> You have {messages.length} unread messages </h1>
               ^^^^^^^^ null

<h1> You have {messages.length} unread  messages </h1>
                        ^^^^^^ property `length`. Property cannot be accessed on possibly undefined value

<h1> You have {messages.length} unread messages </h1>
               ^^^^^^^^ undefined

     { messages.map(message => <Message message={ message } /> )}
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `map`. Method cannot be called on possibly null value

     { messages.map(message => <Message message={ message } /> )}
       ^^^^^^^^ null

     { messages.map(message => <Message message={ message } /> )}
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `map`. Method cannot be called on possibly undefined value

     { messages.map(message => <Message message={ message } /> )}
       ^^^^^^^^ undefined

Wow朝群!

由于我們將messages定義為maybe類型燕耿,因此messages可以是null或者undefined。但在沒(méi)有進(jìn)行空檢查的情況下姜胖,我們?nèi)匀徊荒茉谒厦孢M(jìn)行一些操作(像是.length或.map)誉帅。因?yàn)槿绻?code>messages的值是null或者undefined的話,而我們又在它上面進(jìn)行了相關(guān)操作,程序同樣會(huì)報(bào)一個(gè)類型錯(cuò)誤蚜锨。

因此我們回頭去更新一下view層的代碼:

const MyComponent = ({ messages, isFetching }: AppState) => {
  if (isFetching) {
    return <div> Loading... </div>
  } else if (messages === null || messages === undefined) {
    return <div> Failed to load messages. Try again. </div>
  } else {
    return (
      <div>
        <h1> You have { messages.length } unread messages </h1>
        { messages.map(message => <Message message={ message } /> )}
      </div>
    );
  }
};

現(xiàn)在Flow知道我們已經(jīng)對(duì)null或者undefined的情況進(jìn)行了處理档插,因此類型檢查器不會(huì)報(bào)任何錯(cuò)誤。當(dāng)然踏志,運(yùn)行時(shí)的類型錯(cuò)誤也就不會(huì)再來(lái)煩你啦阀捅。

優(yōu)勢(shì)七:減少單元測(cè)試的數(shù)量

在之前的部分我們已經(jīng)知道了,靜態(tài)類型是如何通過(guò)確保輸入與輸出的類型來(lái)幫助消除復(fù)雜的錯(cuò)誤處理针余。因此饲鄙,它也可以幫助我們減少單元測(cè)試的數(shù)量。

例如圆雁,在包含了錯(cuò)誤處理的動(dòng)態(tài)類型方法calculateAreas中:

const calculateAreas = (radii) => {
  // Handle undefined or null input
  if (!radii) {
    throw new Error("Argument is missing");
  }

  // Handle non-array inputs
  if (!Array.isArray(radii)) {
    throw new Error("Argument must be an array");
  }

  var areas = [];

  for (var i = 0; i < radii.length; i++) {
    if (typeof radii[i] !== "number") {
      throw new Error("Array must contain valid numbers only");
    } else {
      areas[i] = 3.14 * (radii[i] * radii[i]);
    }
  }

  return areas;
};

如果你是一個(gè)勤勞的程序員忍级,為了確保輸入?yún)?shù)在程序中處理正確,也許你會(huì)想對(duì)輸入?yún)?shù)的有效性添加測(cè)試:

it('should not work - case 1', () => {
  expect(() => calculateAreas([null, 1.2])).to.throw(Error);
});

it('should not work - case 2', () => {
  expect(() => calculateAreas(undefined).to.throw(Error);
});

it('should not work - case 2', () => {
  expect(() => calculateAreas('hello')).to.throw(Error);
});

等等一系列測(cè)試伪朽。此外轴咱,我們很可能會(huì)忘了添加一下邊緣用例的測(cè)試,而恰好我們的用戶發(fā)現(xiàn)了它烈涮。

由于這些測(cè)試是基于我們所有想到的用例朴肺,因此就存在一些很容易被忽略的。

在另一方面坚洽,當(dāng)我們需要去定義一些類型:

const calculateAreas = (radii: Array<number>): Array<number> => {
  var areas = [];
  for (var i = 0; i < radii.length; i++) {
    areas[i] = 3.14 * (radii[i] * radii[i]);
  }

  return areas;
};

不僅僅是確保了代碼實(shí)際運(yùn)行的結(jié)果和我們的意圖一致戈稿,也使得它們不容易被我們漏掉。不同于以經(jīng)驗(yàn)為主的測(cè)試讶舰,類型檢查是通用的鞍盗。

這個(gè)場(chǎng)景的意思是:在邏輯測(cè)試中,測(cè)試是很不錯(cuò)的跳昼;而在數(shù)據(jù)類型的測(cè)試中般甲,類型檢查則較好。將兩者結(jié)合可以起到一加一大于二的效果鹅颊。

優(yōu)勢(shì)八:提供了領(lǐng)域建模(domain modeling)工具

關(guān)于類型檢查敷存,我最喜歡的一個(gè)應(yīng)用場(chǎng)景是領(lǐng)域建模。領(lǐng)域模型是堪伍,一個(gè)同時(shí)包括了數(shù)據(jù)和基于該數(shù)據(jù)的行為的該領(lǐng)域的概念模型历帚。

A domain model is a conceptual model of a domain that includes both the data and behavior on that data.

理解如何使用類型來(lái)進(jìn)行領(lǐng)域建模的最好的方法就是看一個(gè)例子。

假設(shè)現(xiàn)在有一個(gè)應(yīng)用杠娱,用戶在這個(gè)平臺(tái)上有一種或多種購(gòu)買(mǎi)商品的支付方式。現(xiàn)在Paypal谱煤、信用卡和銀行賬戶這三種方式是可供選擇的摊求。

我們首先給這三種支付方式創(chuàng)建相應(yīng)的類型別名:

type Paypal = { id: number, type: 'Paypal' };
type CreditCard = { id: number, type: 'CreditCard' };
type Bank = { id: number, type: 'Bank' };

現(xiàn)在我們可以用這三種情況和或運(yùn)算符來(lái)定義我們的PaymentMethod類型:

type PaymentMethod = Paypal | CreditCard | Bank;

這樣足夠了么?好吧刘离,我們知道室叉,當(dāng)我們使用支付方法時(shí)睹栖,我們需要請(qǐng)求一個(gè)API,并且茧痕,根據(jù)我們所處的不同的請(qǐng)求階段野来,我們的app的狀態(tài)也是不同的。因此踪旷,會(huì)有四種可能的狀態(tài):

  1. 我們已經(jīng)獲取到了支付方法
  2. 我們正在獲取支付方法
  3. 我們成功獲取了支付方法
  4. 我們嘗試去獲取了支付方法但是出現(xiàn)了錯(cuò)誤

但是我們上面這個(gè)簡(jiǎn)單的paymentMethods類型的模型并沒(méi)有覆蓋所有的場(chǎng)景曼氛。而是假設(shè)paymentMethods是始終存在的。

有什么辦法能給我們的應(yīng)用狀態(tài)建一個(gè)模型令野,使它居且僅居這四個(gè)狀態(tài)之一么舀患?讓我們來(lái)看一下:

type AppState<E, D>
  = { type: 'NotFetched' }
  | { type: 'Fetching' }
  | { type: 'Failure', error: E }
  | { type: 'Success', paymentMethods: Array<D> };

我們使用了“|”或運(yùn)算符來(lái)定義我們的應(yīng)用狀態(tài)是上面描述的這四種中的一種。注意我是怎么使用type屬性來(lái)確定app的狀態(tài)是四個(gè)中的哪一個(gè)的气破。使用這種方式聊浅,我們就可以分析確定什么時(shí)候我們獲取到了支付方法,而是什么時(shí)候沒(méi)有现使。

你還會(huì)注意到低匙,我給應(yīng)用狀態(tài)傳入的泛型ED。類型D代表了用戶的支付方法(之前定義的PaymentMethod)碳锈。我們沒(méi)有定義類型E顽冶,這會(huì)是我們的錯(cuò)誤類型(error type)。下面我們可以這么做:

type HttpError = { id: string, message: string };

總得來(lái)說(shuō)殴胧,現(xiàn)在我們的應(yīng)用狀態(tài)的標(biāo)識(shí)是AppState<E, D>——EHttpError類型渗稍,DPaymentMethod支付方式。并且AppState有四個(gè)可能(且只有這四種)的狀態(tài):NotFetched团滥、Fetching竿屹、FailureSuccess

image
image

我發(fā)現(xiàn)這種領(lǐng)域建模的類型對(duì)我們思考user interfaces的構(gòu)建而不是特定的業(yè)務(wù)邏輯很有幫助灸姊。業(yè)務(wù)規(guī)則告訴我們拱燃,我們的應(yīng)用只會(huì)是這些狀態(tài)中的一個(gè)。這使得我們可以明確地構(gòu)建應(yīng)用的狀態(tài)力惯,并且保證其只會(huì)是預(yù)先定義的狀態(tài)中的一種碗誉。當(dāng)我們構(gòu)建了這樣一個(gè)模型(例如一個(gè)view層組件),很明顯父晶,我們需要去處理所有這四種可能的狀態(tài)哮缺。

更進(jìn)一步地,這段代碼變得很清晰(self-documenting)甲喝,你可以根據(jù)這些可能的場(chǎng)景立即搞清楚應(yīng)用狀態(tài)的結(jié)構(gòu)尝苇。

使用靜態(tài)類型的劣勢(shì)

正如生活與編程中的其他東西,靜態(tài)類型檢查也有著它的利弊取舍。

重要的一點(diǎn)是糠溜,我們學(xué)習(xí)并理解靜態(tài)類型淳玩,這樣就可以對(duì)何時(shí)靜態(tài)類型是有意義的與何時(shí)它們并沒(méi)那么有價(jià)值的問(wèn)題有一個(gè)明智的判斷。

下面就是一些相關(guān)的思考:

劣勢(shì)1:需要預(yù)先學(xué)習(xí)靜態(tài)類型相關(guān)知識(shí)

對(duì)初學(xué)者來(lái)說(shuō)非竿,JavaScript真的是一門(mén)不錯(cuò)的語(yǔ)言蜕着,原因之一就是在寫(xiě)代碼之前你不需要去了解完整的類型系統(tǒng)。

當(dāng)我最開(kāi)始學(xué)習(xí)Elm(一門(mén)靜態(tài)類型語(yǔ)言)红柱,類型相關(guān)的問(wèn)題經(jīng)常會(huì)出現(xiàn)承匣,我總是會(huì)寫(xiě)出一些類型定義相關(guān)的編譯錯(cuò)誤。

有效的學(xué)習(xí)一門(mén)語(yǔ)言的類型系統(tǒng)可以說(shuō)和學(xué)習(xí)語(yǔ)言本身一樣都是一場(chǎng)硬仗豹芯。因此悄雅,靜態(tài)類型使得Elm的學(xué)習(xí)曲線比JavaScript要陡峭。

尤其是對(duì)于一些初學(xué)者,學(xué)習(xí)語(yǔ)法本身就是一件高負(fù)荷的事情了。在去混雜著學(xué)習(xí)類型會(huì)搞垮他們秆乳。

劣勢(shì)2:代碼的冗長(zhǎng)會(huì)讓你頭疼

靜態(tài)類型經(jīng)常會(huì)讓代碼看起來(lái)更冗長(zhǎng)和雜亂。

例如容诬,我們要替換下面這段代碼:

async function amountExceedsPurchaseLimit(amount, getPurchaseLimit){
  var limit = await getPurchaseLimit();

  return limit > amount;
}

我們需要這么寫(xiě):

async function amountExceedsPurchaseLimit(
  amount: number,
  getPurchaseLimit: () => Promise<number>
): Promise<boolean> {
  var limit = await getPurchaseLimit();

  return limit > amount;
}

在譬如,替換下面這段:

var user = {
  id: 123456,
  name: 'Preethi',
  city: 'San Francisco',
};

需要寫(xiě)成:

type User = {
  id: number,
  name: string,
  city: string,
};

var user: User = {
  id: 123456,
  name: 'Preethi',
  city: 'San Francisco',
};

顯然沿腰,我們添加了一些額外的代碼览徒。但是,有幾個(gè)觀點(diǎn)并不認(rèn)為這是不好的颂龙。

首先习蓬,正如我們之前所提到的,靜態(tài)類型幫助我們消除了一整類測(cè)試措嵌。一些開(kāi)發(fā)者會(huì)認(rèn)為這是一個(gè)很合理的這兩方面的權(quán)衡躲叼。

其次,正如之前看到的企巢,靜態(tài)類型有的時(shí)候可以消除復(fù)雜的錯(cuò)誤處理枫慷,反而可以較大的減少代碼的雜亂性。

很難說(shuō)靜態(tài)類型是否真的讓代碼變得冗長(zhǎng)了浪规,但我們可以在實(shí)際工作中保留對(duì)這個(gè)問(wèn)題的思考或听。

劣勢(shì)3:需要花時(shí)間去掌握類型

學(xué)會(huì)怎么最好地在程序里定義類型需要花很多時(shí)間來(lái)進(jìn)行實(shí)踐。而且笋婿,建立一個(gè)良好的意識(shí)——何時(shí)使用靜態(tài)類型誉裆、何時(shí)使用動(dòng)態(tài)類型,也需要認(rèn)真的思考和豐富的實(shí)踐經(jīng)驗(yàn)缸濒。

例如找御,我們可能采取的一種方式是元镀,對(duì)關(guān)鍵的業(yè)務(wù)邏輯使用類型,而對(duì)臨時(shí)性的霎桅、不重要的邏輯部分使用動(dòng)態(tài)類型來(lái)降低不必要的復(fù)雜度。

這還是很難區(qū)分的讨永,尤其是當(dāng)開(kāi)發(fā)者對(duì)類型使用的經(jīng)驗(yàn)較少時(shí)滔驶。

劣勢(shì)4:靜態(tài)類型可能會(huì)延緩一些開(kāi)發(fā)速度

正如前文提到的,當(dāng)我學(xué)習(xí)Elm時(shí)卿闹,學(xué)習(xí)使用類型給我?guī)?lái)了些小阻礙揭糕,尤其是當(dāng)我需要加一些或改一些代碼時(shí)。經(jīng)常出現(xiàn)編譯錯(cuò)誤讓我心煩意亂锻霎,很難感覺(jué)到自己的進(jìn)步著角。

有人認(rèn)為,靜態(tài)類型檢查在絕大多數(shù)情況下旋恼,可能會(huì)導(dǎo)致程序員精力不集中吏口,而我們知道,集中精神是寫(xiě)出好代碼的關(guān)鍵冰更。

不僅僅如此产徊,靜態(tài)類型檢查器也不總是完美的。有時(shí)候你知道自己需要怎么做而類型檢查器反而成為了絆腳石蜀细。

我相信還有一些其他需要取舍的地方舟铜,但上面這幾條是對(duì)我來(lái)說(shuō)很重要的。

下一部分奠衔,最終的結(jié)論

最后一部分谆刨,通過(guò)討論使用靜態(tài)類型是否有意義,進(jìn)行總結(jié)归斤。


原文:Why use static types in JavaScript? The Advantages and Disadvantages)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痊夭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子官册,更是在濱河造成了極大的恐慌生兆,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膝宁,死亡現(xiàn)場(chǎng)離奇詭異鸦难,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)员淫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)合蔽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人介返,你說(shuō)我怎么就攤上這事拴事∥纸铮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵刃宵,是天一觀的道長(zhǎng)衡瓶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)牲证,這世上最難降的妖魔是什么哮针? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮坦袍,結(jié)果婚禮上十厢,老公的妹妹穿的比我還像新娘。我一直安慰自己捂齐,他們只是感情好蛮放,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著奠宜,像睡著了一般包颁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挎塌,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天徘六,我揣著相機(jī)與錄音,去河邊找鬼榴都。 笑死待锈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嘴高。 我是一名探鬼主播竿音,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拴驮!你這毒婦竟也來(lái)了春瞬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤套啤,失蹤者是張志新(化名)和其女友劉穎宽气,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體潜沦,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萄涯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唆鸡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涝影。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖争占,靈堂內(nèi)的尸體忽然破棺而出燃逻,到底是詐尸還是另有隱情序目,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布伯襟,位于F島的核電站猿涨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏姆怪。R本人自食惡果不足惜嘿辟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望片效。 院中可真熱鬧,春花似錦英古、人聲如沸淀衣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)膨桥。三九已至,卻和暖如春唠叛,著一層夾襖步出監(jiān)牢的瞬間只嚣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工艺沼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留册舞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓障般,卻偏偏與公主長(zhǎng)得像调鲸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挽荡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文為翻譯文章藐石,原文鏈接見(jiàn)文末 作為一個(gè)JavaScript開(kāi)發(fā)者,你可以編寫(xiě)一整天編寫(xiě)也不會(huì)遇到任何靜態(tài)類型檢查...
    AlienZHOU閱讀 6,044評(píng)論 0 5
  • 本文為翻譯文章定拟,原文鏈接見(jiàn)文末 這是《為什么要在javascript中進(jìn)行靜態(tài)類型檢查》系列的最后一篇文章于微,如果你...
    AlienZHOU閱讀 1,265評(píng)論 0 3
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)青自,斷路器株依,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,298評(píng)論 25 707
  • 如果一只螞蟻?zhàn)呤Я耍募胰藭?huì)哭嗎性穿?——休息時(shí)一只螞蟻在我的迷彩服上迷了路勺三,于是我這樣想到。螞蟻的家族那么大需曾,丟了...
    南竹未歸0閱讀 215評(píng)論 0 0