你所不知道的,Python中的防御性編程

今天是周五下午,你的新版本已經(jīng)發(fā)布好幾天了。你禮拜一開(kāi)始就感到自豪和無(wú)事一身輕,但你的自豪感正在隨著時(shí)間的流逝慢慢減少。發(fā)布這樣一個(gè)沒(méi)有bug的版本耗費(fèi)巨大的精力。事實(shí)上,在發(fā)布日期你有信心認(rèn)為未來(lái)幾周將會(huì)很安靜,因?yàn)橛脩?hù)不應(yīng)該會(huì)有別的需求。

當(dāng)然,它完美得讓你難以相信:不久后你的第一個(gè)錯(cuò)誤報(bào)告產(chǎn)生了。第一個(gè)錯(cuò)誤報(bào)告只是無(wú)關(guān)痛癢的東西,一個(gè)新的對(duì)話框出現(xiàn)了小小的拼寫(xiě)錯(cuò)誤。接著,幾個(gè)小錯(cuò)誤接踵而至,你快速修復(fù)它們并存儲(chǔ)到存儲(chǔ)庫(kù)。

緊接著,作為每個(gè)開(kāi)發(fā)人員的噩夢(mèng),最重要的系統(tǒng)部件報(bào)告了一個(gè)錯(cuò)誤。你瘋狂地查看代碼,即使你知道它的內(nèi)存內(nèi)容。代碼分支怎么可能在這種情況下執(zhí)行了?!代碼一定出錯(cuò)了。

快速定位到bug處,但你還是想不通這是怎么發(fā)生的。你甚至不能在測(cè)試環(huán)境中重現(xiàn)場(chǎng)景。要是有更多的失敗調(diào)試消息……

真相將讓你得到釋放!

如果你一直在寫(xiě)有試用期的軟件,你會(huì)意識(shí)到這種情況。你感到很沮喪,不管花費(fèi)了多少精力,軟件究竟還是又一次不能用了。不要擔(dān)心了,已經(jīng)發(fā)生了。

這是故事的一部分,在這里,我找到了為你一勞永逸地解決這個(gè)問(wèn)題的方法。但很不幸,我不認(rèn)為它會(huì)存在。

一個(gè)不爭(zhēng)的事實(shí)是,所有的軟件都有bug。然而,這并不意味著我們應(yīng)該放棄、不追求完美。它只是意味著如果略微改變對(duì)現(xiàn)實(shí)的看法,我們將得到更好的服務(wù)。我們應(yīng)該這樣編寫(xiě)軟件,好像我們正在計(jì)劃防范性措施。我們應(yīng)該編寫(xiě)軟件防范例程,比如設(shè)置陷阱,靜默捕捉不可避免的bug。

防錯(cuò)性編程

用來(lái)描述這種風(fēng)格最好的術(shù)語(yǔ)是“防御性編程”。維基百科的描述并不完全符合我的想法,但這是一個(gè)好的起點(diǎn):

一種防護(hù)性設(shè)計(jì)旨在確保軟件一部分的持續(xù)性功能,而不管軟件的不可預(yù)見(jiàn)性說(shuō)法。這個(gè)想法可以被看做具有減少或消除墨菲定律所產(chǎn)生影響的前景。防御性編程技術(shù)尤其被應(yīng)用尤其當(dāng)一段軟件代碼可能被惡意或無(wú)意濫用繼而產(chǎn)生災(zāi)難性影響。

我真正要談?wù)摰氖窍铝兄改系慕Y(jié)合:

指南

  • 每一行代碼是一種責(zé)任
  • 編寫(xiě)你的假設(shè)
  • 創(chuàng)建可執(zhí)行文檔更可取[1]

這些指南是至關(guān)重要的,它們確保我們的代碼以及讓你頭腦清醒以避免常規(guī)錯(cuò)誤的發(fā)生。記住,從我們的角度來(lái)看,編寫(xiě)的代碼或多或少會(huì)有bug。

我們需要記住以上的指南來(lái)幫助我們快速找到bug。很多時(shí)候發(fā)現(xiàn)bug是最難的環(huán)節(jié)。因此,讓我們優(yōu)化定位bug的算法,而不是投身于徹底預(yù)防錯(cuò)誤這個(gè)不可能的任務(wù)。

Python工具

讓我們?cè)谙旅娴闹改现懈钊氲乜匆恍┯杏玫墓ぞ摺N覀儗⑹褂肞ython作為語(yǔ)言進(jìn)行演示,但大多數(shù)語(yǔ)言都有非常相似的工具。

  • 聲明
  • 日志記錄
  • 單元測(cè)試

假設(shè)我們有一個(gè)函數(shù),它能將從用戶(hù)和規(guī)范化數(shù)據(jù)獲取的值指定在0到1之間,也可以搭配一個(gè)新的小部件方便以后使用。

image

感謝在reddit更新文章中發(fā)現(xiàn)一個(gè)bug的評(píng)論!

現(xiàn)在,假設(shè)我們有以下“列”,它由get_column_data()函數(shù)返回:

age = numpy.array([10.0, 20.0, 30.0, 40.0, 50.0])

height = numpy.array([60.0, 66.0, 72.0, 63.0, 66.0])

接下來(lái)證明它確實(shí)會(huì)將我們給的范圍轉(zhuǎn)化到[0 - 1]區(qū)間內(nèi):

normalize_ranges('age')

{'max': 1.0, 'min': 0.0}

好了,這是一個(gè)很短的測(cè)試,但是它似乎起作用了。我們通過(guò)一系列的“真實(shí)”的數(shù)字和把它規(guī)范到區(qū)間(0 - 1)內(nèi)。

記住上述指導(dǎo)方針,讓我們找到這個(gè)函數(shù)中存在的錯(cuò)誤。代碼中隱含的假設(shè)是什么?

我能看到一些假設(shè):

1. original_range只包含正數(shù)

2. 返回的比率在0.0和1.0之間

經(jīng)過(guò)仔細(xì)檢查,上面的代碼中存在相當(dāng)多的假設(shè)。不幸的是,瀏覽代碼時(shí)它們不會(huì)立刻明顯地表現(xiàn)出來(lái)。要是我們能使這些假設(shè)更明晰……

聲明語(yǔ)句

聲明語(yǔ)句在單元測(cè)試中相當(dāng)常見(jiàn)。事實(shí)上,Python為單元測(cè)試定制了大量的聲明語(yǔ)句。然而,沒(méi)有任何理由簡(jiǎn)單地將這個(gè)有用的工具用在測(cè)試環(huán)境中。

常規(guī)代碼里的聲明語(yǔ)句非常有用。這些聲明語(yǔ)句需要一個(gè)表達(dá)式,如果表達(dá)式為false,則會(huì)拋出一個(gè)Asserti異常以及一個(gè)可選消息。

例如,我們上面的函數(shù)總是返回一個(gè)在(0 - 1)之間的值。不幸的是,我們的假設(shè)更多地顯示程序并沒(méi)有達(dá)到預(yù)期:

image

你可以想象,這種情況很容易被忽視很長(zhǎng)一段時(shí)間,這個(gè)返回值會(huì)在整個(gè)代碼中傳播。這正是本文開(kāi)頭的故事:錯(cuò)誤的準(zhǔn)確類(lèi)型不可能被發(fā)現(xiàn)而導(dǎo)致的可悲故事。

我們可以試著考慮到用戶(hù)可能輸入的每個(gè)值并妥善處理它。事實(shí)上,這是正確的做法,但不能保證我們不會(huì)遺漏數(shù)據(jù)。我們已經(jīng)承認(rèn)了程序員會(huì)犯錯(cuò)這一事實(shí)。

幸運(yùn)的是,既然我們已經(jīng)承認(rèn)我們犯錯(cuò)誤,我們可以使用聲明語(yǔ)句在以后重構(gòu)代碼。

image

我們添加了一些聲明語(yǔ)句,如果返回值不在預(yù)期范圍之內(nèi),它們會(huì)發(fā)出警告。讓我們來(lái)看看這些聲明如何改變我們的測(cè)試代碼:

image

這種方法有以下幾個(gè)優(yōu)點(diǎn):

  • 它是一種可執(zhí)行文檔
  • 在問(wèn)題的根源設(shè)置警告器
  • 包含有價(jià)值的關(guān)于“無(wú)效”參數(shù)的調(diào)試信息

1. 作為一種可執(zhí)行文檔

典型文檔通常有幾種不同的特點(diǎn)如內(nèi)聯(lián)或塊注釋?zhuān)臋n字符串,以及sphinx。這些特點(diǎn)具有特定目的,它們幾乎都是軟件開(kāi)發(fā)的關(guān)鍵。不幸的是,他們都遇到了同樣的問(wèn)題。它們可以很快地與快速變化的代碼和需求保持同步。這會(huì)導(dǎo)致開(kāi)發(fā)人員不信任原始文檔。

將聲明文檔化有不同的目的。他們清晰而簡(jiǎn)明地描述應(yīng)用程序在運(yùn)行時(shí)的預(yù)期狀態(tài)。此外,如果我們改變我們的假設(shè)時(shí)沒(méi)有修改聲明以匹配新的行為,應(yīng)用程序就會(huì)崩潰。

聲明語(yǔ)句最好隨其他變動(dòng)一起更新。因此聲明比非可執(zhí)行文檔更值得信賴(lài)。此外,斷言還具有許多諸如評(píng)論,文檔字符串等的益處。

值得指出的是,有另一種被稱(chēng)為doctest形式的可執(zhí)行文檔,在Python文件系統(tǒng)中十分常見(jiàn)。這些測(cè)試/文檔可能有些丑陋,但他們的關(guān)鍵特性是他們近乎于代碼,就像聲明一樣。

2. 在問(wèn)題的根源設(shè)置警告器

我們都有過(guò)類(lèi)似的經(jīng)歷,你花費(fèi)幾個(gè)小時(shí)調(diào)試bug,最后意識(shí)到真正的bug一開(kāi)始就不在開(kāi)始調(diào)試的代碼中(見(jiàn)5個(gè)為什么)。也許導(dǎo)致bug的原因根本就不是你第一次注意到的錯(cuò)誤行為。

例如,你在系統(tǒng)中發(fā)現(xiàn)一個(gè)字節(jié)字符串,但你認(rèn)為內(nèi)部都是Unicode字符串??赡苄枰荛L(zhǎng)時(shí)間才能找到格式轉(zhuǎn)換功能在哪里失效了。這處境真是令人沮喪。如果更早發(fā)現(xiàn)了bug就好了,或者至少有更多的調(diào)試信息。

聲明語(yǔ)句不會(huì)阻值這種情況的出現(xiàn),但它們確實(shí)提供了一個(gè)改善的機(jī)會(huì)。上面的聲明將警告我們:這時(shí)該函數(shù)不服從它們之間的約定,沒(méi)有返回一個(gè)(0 - 1)區(qū)間內(nèi)的值。如果我們隨后發(fā)現(xiàn)其他代碼的范圍無(wú)效,它會(huì)給我們一個(gè)有價(jià)值的線索。我們將會(huì)知道這個(gè)函數(shù)沒(méi)有按照既定要求執(zhí)行。這一條線索可以節(jié)省時(shí)間,因?yàn)樗梢员苊鈴陌Y狀的根源追溯其發(fā)生原因。

3. 包含有價(jià)值的關(guān)于“無(wú)效”參數(shù)的調(diào)試信息

請(qǐng)注意到我們聲明語(yǔ)句還包括輸入?yún)?shù)的信息。當(dāng)用戶(hù)使用那些我們沒(méi)有訪問(wèn)權(quán)的數(shù)據(jù)而遇到錯(cuò)誤時(shí),這些信息將具有無(wú)可估量的價(jià)值。此外,當(dāng)用戶(hù)很難講清楚錯(cuò)誤發(fā)生時(shí)的情況時(shí),調(diào)試信息尤其有用。因此,這些聲明語(yǔ)句可以防止你無(wú)意把bug標(biāo)記為“不可重現(xiàn)”狀態(tài)。

輸入?yún)?shù)信息也有其他一些微妙的好處:

  • 顯示一條關(guān)于用戶(hù)正在使用何種類(lèi)型數(shù)據(jù)的無(wú)效假設(shè)
  • 解釋我們?cè)谖臋n中期望使用何種類(lèi)型數(shù)據(jù)的疏忽
  • 使?jié)撛诘臒o(wú)法執(zhí)行的新實(shí)例暴露出來(lái)

使用聲明語(yǔ)句的壞處

我們已經(jīng)編寫(xiě)好可以提供大量好處的聲明語(yǔ)句,但有時(shí)候它們表現(xiàn)得并不有效。通常,有以下幾個(gè)缺點(diǎn):

1. 調(diào)試模式

通常情況下,出于對(duì)技術(shù)和顯示情況的考慮,聲明語(yǔ)句并不符合代碼編寫(xiě)規(guī)范。它們僅僅在調(diào)試常量為true時(shí)激活。然而,常量默認(rèn)為true,這意味著你的代碼很有可能在發(fā)布時(shí)處于調(diào)試模式。

如果你的應(yīng)用程序運(yùn)行在少量額外邏輯頻繁引起注意的環(huán)境中,就需要注意一下了。關(guān)閉調(diào)試模式的唯一方法是以-O 選項(xiàng)運(yùn)行Python解釋器。

2. 增加代碼的噪聲

很容易過(guò)度使用聲明語(yǔ)句而使代碼很快變得難以閱讀。這會(huì)使你的代碼混入很多噪聲,而實(shí)際功能被一系列的錯(cuò)誤檢查淹沒(méi)。下面的代碼是一個(gè)過(guò)度使用聲明語(yǔ)句的示例,讓我們看看想明白代碼到底干什么有多困難。

image

適當(dāng)?shù)厥褂寐暶?/strong>

假設(shè)你認(rèn)定某些錯(cuò)誤絕不會(huì)發(fā)生,就該保守地使用聲明語(yǔ)句。不要過(guò)分使用聲明語(yǔ)句去檢查無(wú)效輸入。

這沒(méi)有硬性規(guī)定,每個(gè)開(kāi)發(fā)人員可能會(huì)不同程度地使用它。請(qǐng)嘗試采用你自己的標(biāo)準(zhǔn),包括一些在你的開(kāi)發(fā)中定義的風(fēng)格指南。

你應(yīng)該有自己的風(fēng)格吧?

當(dāng)然,也要記得Python支持duck-type類(lèi)型(譯注:鴨子類(lèi)型,動(dòng)態(tài)類(lèi)型的一種),因此不要因?yàn)檫^(guò)度使用是明語(yǔ)句檢查數(shù)據(jù)類(lèi)型而把這種功能淹沒(méi)了。

我發(fā)現(xiàn)的一個(gè)有用的技術(shù)是將所有捕捉到的Asserti異常優(yōu)先級(jí)設(shè)置為最高,并結(jié)合另一個(gè)有用的技術(shù)進(jìn)行處理。

日志記錄

日志的使用類(lèi)似于聲明語(yǔ)句。它可以提供運(yùn)行時(shí)調(diào)試信息以改進(jìn)你的可執(zhí)行文件。然而,日志并不完全像聲明。它有一些額外的好處。

1. 更出色的控制粒度

Python的日志模塊包含面非常廣泛,并且可定制。你可以把消息發(fā)送到幾個(gè)不同的級(jí)層,每一層可以開(kāi)啟或關(guān)閉。因此,你最好考慮一些更為嚴(yán)重的情況,以幫助你在這個(gè)日志級(jí)層編碼。

記住,這里的情況與聲明語(yǔ)句無(wú)關(guān),它們僅僅依賴(lài)于調(diào)試模式。

2. 更好的動(dòng)態(tài)控制

日志記錄允許你隨地瀏覽日志級(jí)別。常見(jiàn)情況是一個(gè)配置文件,環(huán)境變量或是數(shù)據(jù)庫(kù)。這種靈活性允許你控制日志信息顯示量,而不必重新運(yùn)行或重新部署應(yīng)用程序。

相比之下,Python不允許為聲明語(yǔ)句動(dòng)態(tài)分配調(diào)試常量。因此,你無(wú)法在不重新運(yùn)行應(yīng)用程序的前提下打開(kāi)聲明開(kāi)關(guān)。

決定你的防御編碼策略時(shí),值得考慮一下它。如果你使用了一個(gè)“可代替”的工具,動(dòng)態(tài)日志控制就顯得尤其重要,如PyInstaller分配機(jī)制。

3. 靜默保存回溯信息

發(fā)生錯(cuò)誤時(shí)實(shí)時(shí)保存回溯和調(diào)試信息相當(dāng)重要,智能日志可以自動(dòng)完成保存過(guò)程。

這個(gè)概念是道格·赫爾曼在一個(gè)著名的的異常處理程序中提出的:

image

自動(dòng)調(diào)用 log.exception 會(huì)帶給我們更多異常消息。然后,我們可以配置日志記錄器將回溯和異常消息分別存儲(chǔ)到不同日志文件,方便以后在沒(méi)有正常消息和警告的情況下檢查程序。

如果在運(yùn)行代碼中啟用這個(gè)異常文件,它可能包含非常有用的調(diào)試信息。這為分析日志數(shù)據(jù)創(chuàng)造了很多令人興奮的可能性:

  • 用戶(hù)會(huì)嘗試不同排列的特性,而這些特性我們從來(lái)沒(méi)有測(cè)試甚至沒(méi)有考慮過(guò),這可能導(dǎo)致新特性變多,以使常見(jiàn)實(shí)例更加易用。
  • 找到由于用戶(hù)誤解應(yīng)用程序的工作方式而犯的類(lèi)似錯(cuò)誤,這可能為編制更好的用戶(hù)文檔作出相當(dāng)貢獻(xiàn)。

4. 聲明的高級(jí)組合

你還可以使用聲明語(yǔ)句配合日志記錄。例如,你可以在默認(rèn)調(diào)試模式下運(yùn)行應(yīng)用程序,然后捕捉Asserti并將日志記錄到另一個(gè)文件。這可能為數(shù)據(jù)挖掘創(chuàng)造更多的可能性,如發(fā)現(xiàn)一個(gè)假定在平臺(tái)中運(yùn)行的環(huán)境。

這只是幾個(gè)使用日志的防御性編程的例子。事實(shí)上,你可以使用日志創(chuàng)建低保真環(huán)境以解決各種各樣的問(wèn)題,另一篇文章有相關(guān)介紹。[2]

日志記錄的缺點(diǎn)

日志和聲明有許多相同的缺點(diǎn)。然而,日志的額外的靈活性伴隨著額外的負(fù)擔(dān)需要考慮。

1. 難度管理級(jí)別一致性

使用日志記錄最大的困難是相同級(jí)別的一組日志始終貫穿整個(gè)代碼庫(kù)。這可以歸為主觀的命名問(wèn)題,這兩個(gè)問(wèn)題僅在計(jì)算機(jī)科學(xué)中存在。最好的解決方案是隨代碼風(fēng)格一同提交指南。隨后添加日志消息時(shí),會(huì)有一些具體項(xiàng)目的文檔供新手參考。

2. 設(shè)置多個(gè)日志記錄器

日志模塊是非常靈活的,但它是有代價(jià)的。日志配置將變得很復(fù)雜??紤]采用以下策略:

  • 調(diào)試級(jí)別的消息保存在一個(gè)名為.debug的隱藏文件中
  • 信息、警告和錯(cuò)誤級(jí)別消息保存在stderr文件中
  • 關(guān)鍵和異常級(jí)別的消息以GUI消息框形式彈出

這是一個(gè)很好的起點(diǎn)。同時(shí),Python的文檔包括幾個(gè)絕對(duì)值得一讀的優(yōu)秀日志記錄策略,決定你的設(shè)置之前,最好讀一下。

記住,當(dāng)你調(diào)試bug時(shí),日志可以表現(xiàn)出相當(dāng)大的優(yōu)勢(shì)。因此,一定要花時(shí)間去學(xué)習(xí)日志系統(tǒng),確保您的配置設(shè)計(jì)得足夠仔細(xì)。即使簡(jiǎn)單的應(yīng)用程序也應(yīng)該有一套良好的、精心設(shè)計(jì)的日志記錄策略。

單元測(cè)試

保護(hù)自己免遭bug煩擾的最后一種方法是不要忽略過(guò)去的bug。過(guò)去的bug傾向于長(zhǎng)時(shí)間潛伏在代碼中,這通常是由于有人不理解為什么一些代碼要以一種特殊的方式編寫(xiě),繼而刪除了它們以”清潔“系統(tǒng)。

這就引出了另一個(gè)場(chǎng)景,創(chuàng)建另一種版本的可執(zhí)行文件相當(dāng)有用。因此,當(dāng)一個(gè)毫無(wú)戒心的開(kāi)發(fā)人員修改一些代碼或者刪除一些正在運(yùn)行的代碼來(lái)警告他們的錯(cuò)誤,就沒(méi)有什么災(zāi)難性后果了。

人們通常在測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的背景下會(huì)遇到單元測(cè)試的情況。這是一個(gè)很好的概念,但往往在實(shí)踐中有點(diǎn)過(guò)于樂(lè)觀因而很難遵循(閱讀材料: customer wants code now)。相關(guān)的討論在另一個(gè)博客帖子中。

我想討論的是如何使用單元測(cè)試使你從今后和過(guò)去的bug中解脫出來(lái)。我認(rèn)為它將TDD測(cè)試和一些更有成效的能夠減少前期時(shí)間成本的概念顛倒了。

我建議你修復(fù)bug后寫(xiě)一份測(cè)試報(bào)告 (更多討論)

這里有幾個(gè)可能不會(huì)立即表現(xiàn)出來(lái)的目的:

改善bug修復(fù)的文檔化

要確保你提交的信息包含幫助你修復(fù)bug的內(nèi)容,但不要就此止步。很可能你提交的信息和評(píng)論缺乏類(lèi)似于針對(duì)以下提問(wèn)的解決方案:

  • 你如何測(cè)試補(bǔ)丁?
  • 具體是哪種情況導(dǎo)致了這個(gè)bug?

這就是一個(gè)優(yōu)秀的單元測(cè)試開(kāi)始起作用的時(shí)候了。你已經(jīng)知道如何測(cè)試bug。(你之前做過(guò)測(cè)試,對(duì)吧?)所以,描述你測(cè)試的場(chǎng)景吧,讓每個(gè)人都受益于你的努力。

單元測(cè)試是一個(gè)出色的環(huán)節(jié),在這里所有的bug將展現(xiàn)出來(lái),并且它會(huì)提供修復(fù)bug的文檔。你不僅可以解釋如何以及為什么你要修復(fù)它,而且你測(cè)試它的方式也能細(xì)細(xì)道來(lái)。如果bug又出現(xiàn)了,那么這條信息將會(huì)非常有價(jià)值。

不要忘記,一次正常運(yùn)行的測(cè)試可以為bug的定位線索(讓你知道bug沒(méi)有在這里發(fā)生)。

2. 能經(jīng)得住時(shí)間考驗(yàn)的重復(fù)bug排除編碼

特定bug不知不覺(jué)地嵌入代碼庫(kù)并不是稀罕事。由于需求變化,重構(gòu)錯(cuò)誤,或其他部分的變動(dòng),這是可能發(fā)生的??梢酝ㄟ^(guò)為一個(gè)特定的bug修復(fù)編寫(xiě)一個(gè)單元測(cè)試回歸,且要記得運(yùn)行你的測(cè)試代碼。將單元測(cè)試看做是從稍后又會(huì)遭遇bug的麻煩中解放出來(lái)的機(jī)會(huì)。

單元測(cè)試的負(fù)面影響

單元測(cè)試的主要缺點(diǎn)是,你很容易忘記運(yùn)行它們。這只是一個(gè)無(wú)法從代碼定位的測(cè)試副產(chǎn)品。通過(guò)類(lèi)似的實(shí)踐,像持續(xù)集成化使用Travis CI 和自動(dòng)化測(cè)試,這個(gè)不足可以得到彌補(bǔ)。

另一個(gè)缺點(diǎn)是單元測(cè)試通常只在自己的測(cè)試環(huán)境中執(zhí)行,這是一個(gè)沒(méi)有真實(shí)用戶(hù)的模擬環(huán)境。

結(jié)論

這種風(fēng)格的發(fā)展很難分類(lèi),不幸的是沒(méi)有任何既定的規(guī)則說(shuō)明何時(shí)使用它。所以,我強(qiáng)烈建議你牢牢記住指南。

該指南將導(dǎo)致你的觀念發(fā)生微妙的變化。觀念的變化相當(dāng)重要,而不是工具和機(jī)制本身。最終你會(huì)因?yàn)檫^(guò)度使用聲明或日志記錄犯一些錯(cuò)誤,進(jìn)而開(kāi)始形成自己的風(fēng)格。同時(shí),每一個(gè)項(xiàng)目的需求都不同,所以有必要學(xué)會(huì)所有的工具,進(jìn)而以需要的解決方式混合使用它們。

腳注

[1]可執(zhí)行文件是一個(gè)術(shù)語(yǔ),有時(shí)用來(lái)描述測(cè)試框架doctests。術(shù)語(yǔ)“Literate testing”用來(lái)描述這一概念。

[2]你甚至可以使用日志來(lái)構(gòu)建自己的分析工具。登錄到網(wǎng)絡(luò)或Dropbox文件,每次將使用一個(gè)特性。然后,后臺(tái)將有一個(gè)shell腳本用來(lái)收集這些文件?,F(xiàn)在你有基于簡(jiǎn)單文本格式的大量使用信息,它包含開(kāi)放的大量數(shù)據(jù)分析的可能性,你可以用來(lái)幫助你的用戶(hù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說(shuō)明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí),會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,986評(píng)論 0 9
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 28,829評(píng)論 1 45
  • 大多數(shù) Nginx 新手都會(huì)頻繁遇到這樣一個(gè)困惑,那就是當(dāng)同一個(gè)location配置塊使用了多個(gè) Nginx 模塊...
    SkTj閱讀 8,249評(píng)論 0 12
  • 這個(gè)工作我覺(jué)得我會(huì)一直干下去,至少干一年,我年輕,試錯(cuò)成本低,我本來(lái)就是給自己最后一次做電商的機(jī)會(huì),如果當(dāng)初沒(méi)有這...
    南尚十一閱讀 95評(píng)論 0 0
  • 晚上去上晚課(這種課自愿的),舍友從來(lái)不去,舍友班里成績(jī)倒數(shù)1.2.3.4,我不說(shuō)什么了。臨走時(shí),在電腦上下載些東...
    彷徨著哈哈哈閱讀 109評(píng)論 0 0

友情鏈接更多精彩內(nèi)容