笨辦法學C 練習27:創(chuàng)造性和防御性編程

練習27:創(chuàng)造性和防御性編程

原文:Exercise 27: Creative And Defensive Programming

譯者:飛龍

你已經學到了大多數(shù)C語言的基礎定硝,并且準備好開始成為一個更嚴謹?shù)某绦騿T了淀零。這里就是從初學者走向專家的地方腥光,不僅僅對于C砾省,更對于核心的計算機科學概念宵晚。我將會教給你一些核心的數(shù)據(jù)結構和算法伏蚊,它們是每個程序員都要懂的字逗,還有一些我在真實程序中所使用的一些非常有趣的東西鸠按。

在我開始之前狈网,我需要教給你一些基本的技巧和觀念宙搬,它們能幫助你編寫更好的軟件。練習27到31會教給你高級的概念和特性拓哺,而不是談論編程勇垛,但是這些之后你將會應用它們來編寫核心庫或有用的數(shù)據(jù)結構。

編寫更好的C代碼(實際上是所有語言)的第一步是士鸥,學習一種新的觀念叫做“防御性編程”闲孤。防御性編程假設你可能會制造出很多錯誤,之后嘗試在每一步盡可能預防它們烤礁。這個練習中我打算教給你如何以防御性的思維來思考編程讼积。

創(chuàng)造性編程思維

在這個簡單的練習中要告訴你如何做到創(chuàng)造性是不可能的肥照,但是我會告訴你一些涉及到任務風險和開放思維的創(chuàng)造力∏谥冢恐懼會快速地扼殺創(chuàng)造力舆绎,所以我采用,并且許多程序員也采用的這種思維方式使我不會懼怕風險们颜,并且看上去像個傻瓜吕朵。

  • 我不會犯錯誤。
  • 人們所想的并不重要掌桩。
  • 我腦子里面誕生的想法才是最好的边锁。

我只是暫時接受了這種思維姑食,并且在應用中用了一些小技巧波岛。為了這樣做我會提出一些想法,尋找創(chuàng)造性的解決方案音半,開一些奇奇怪怪的腦洞则拷,并且不會害怕發(fā)明一些古怪的東西。在這種思維方式下曹鸠,我通常會編寫出第一個版本的糟糕代碼煌茬,用于將想法描述出來。

然而彻桃,當我完成我的創(chuàng)造性原型時坛善,我會將它扔掉,并且將它變得嚴謹和可考邻眷。其它人在這里常犯的一個錯誤就是將創(chuàng)造性思維引入它們的實現(xiàn)階段眠屎。這樣會產生一種非常不同的破壞性思維,它是創(chuàng)造性思維的陰暗面:

  • 編寫完美的軟件是可行的肆饶。
  • 我的大腦告訴我了真相改衩,它不會發(fā)現(xiàn)任何錯誤,所以我寫了完美的軟件驯镊。
  • 我的代碼就是我自己葫督,批判它的人也在批判我。

這些都是錯誤的板惑。你經常會碰到一些程序員橄镜,它們對自己創(chuàng)造的軟件具有強烈的榮譽感。這很正常冯乘,但是這種榮譽感會成為客觀上改進作品的阻力洽胶。由于這種榮譽感和它們對作品的依戀,它們會一直相信它們編寫的東西是完美的往湿。只要它們忽視其它人的對這些代碼的觀點妖异,它們就可以保護它們的玻璃心惋戏,并且永遠不會改進。

同時具有創(chuàng)造性思維和編寫可靠軟件的技巧是他膳,采用防御性編程的思維响逢。

防御性編程思維

在你做出創(chuàng)造性原型,并且對你的想法感覺良好之后棕孙,就應該切換到防御性思維了舔亭。防御性思維的程序員大致上會否定你的代碼,并且相信下面這些事情:

  • 軟件中存在錯誤蟀俊。
  • 你并不是你的軟件,但你需要為錯誤負責肢预。
  • 你永遠不可能消除所有錯誤矛洞,只能降低它們的可能性。

這種思維方式讓你誠實地對待你的代碼烫映,并且為改進批判地分析它沼本。注意上面并沒有說充滿了錯誤,只是說你的代碼充滿錯誤锭沟。這是一個需要理解的關鍵抽兆,因為它給了你編寫下一個實現(xiàn)的客觀力量啃洋。

就像創(chuàng)造性思維送淆,防御性編程思維也有陰暗面。防御性程序員是一個懼怕任何事情的偏執(zhí)狂被济,這種恐懼使他們遠離可能的錯誤或避免犯錯誤祝辣。當你嘗試做到嚴格一致或正確時這很好贴妻,但是它是創(chuàng)造力和專注的殺手。

八個防御性編程策略

一旦你接受了這一思維较幌,你可以重新編寫你的原型揍瑟,并且遵循下面的八個策略,它們被我用于盡可能把代碼變得可靠乍炉。當我編寫代碼的“實際”版本绢片,我會嚴格按照下面的策略,并且嘗試消除盡可能多的錯誤岛琼,以一些會破壞我軟件的人的方式思考底循。

永遠不要信任輸入

永遠不要提供的輸入,并總是校驗它槐瑞。

避免錯誤

如果錯誤可能發(fā)生熙涤,不管可能性多低都要避免它。

過早暴露錯誤

過早暴露錯誤,并且評估發(fā)生了什么祠挫、在哪里發(fā)生以及如何修復那槽。

記錄假設

清楚地記錄所有先決條件,后置條件以及不變量等舔。

防止過多的文檔

不要在實現(xiàn)階段就編寫文檔骚灸,它們可以在代碼完成時編寫。

使一切自動化

使一切自動化慌植,尤其是測試甚牲。

簡單化和清晰化

永遠簡化你的代碼,在沒有犧牲安全性的同時變得最小和最整潔蝶柿。

質疑權威

不要盲目遵循或拒絕規(guī)則丈钙。

這些并不是全部,僅僅是一些核心的東西交汤,我認為程序員應該在編程可靠的代碼時專注于它們雏赦。要注意我并沒有真正說明如何具體做到這些,我接下來會更細致地講解每一條蜻展,并且會布置一些覆蓋它們的練習喉誊。

應用這八條策略

這些觀點都是一些流行心理學的陳詞濫調,但是你如何把它們應用到實際編程中呢纵顾?我現(xiàn)在打算向你展示這本書中的一些代碼所做的事情,這些代碼用具體的例子展示每一條策略栋盹。這八條策略并不止于這些例子施逾,你應該使用它們作為指導,使你的代碼更可靠例获。

永遠不要信任輸入

讓我們來看一個壞設計和“更好”的設計的例子汉额。我并不想稱之為好設計,因為它可以做得更好榨汤∪渌眩看一看這兩個函數(shù),它們都復制字符串收壕,main函數(shù)用于測試哪個更好妓灌。

undef NDEBUG
#include "dbg.h"
#include <stdio.h>
#include <assert.h>

/*
 * Naive copy that assumes all inputs are always valid
 * taken from K&R C and cleaned up a bit.
 */
void copy(char to[], char from[])
{
    int i = 0;

    // while loop will not end if from isn't '\0' terminated
    while((to[i] = from[i]) != '\0') {
        ++i;
    }
}

/*
 * A safer version that checks for many common errors using the
 * length of each string to control the loops and termination.
 */
int safercopy(int from_len, char *from, int to_len, char *to)
{
    assert(from != NULL && to != NULL && "from and to can't be NULL");
    int i = 0;
    int max = from_len > to_len - 1 ? to_len - 1 : from_len;

    // to_len must have at least 1 byte
    if(from_len < 0 || to_len <= 0) return -1;

    for(i = 0; i < max; i++) {
        to[i] = from[i];
    }

    to[to_len - 1] = '\0';

    return i;
}


int main(int argc, char *argv[])
{
    // careful to understand why we can get these sizes
    char from[] = "0123456789";
    int from_len = sizeof(from);

    // notice that it's 7 chars + \0
    char to[] = "0123456";
    int to_len = sizeof(to);

    debug("Copying '%s':%d to '%s':%d", from, from_len, to, to_len);

    int rc = safercopy(from_len, from, to_len, to);
    check(rc > 0, "Failed to safercopy.");
    check(to[to_len - 1] == '\0', "String not terminated.");

    debug("Result is: '%s':%d", to, to_len);

    // now try to break it
    rc = safercopy(from_len * -1, from, to_len, to);
    check(rc == -1, "safercopy should fail #1");
    check(to[to_len - 1] == '\0', "String not terminated.");

    rc = safercopy(from_len, from, 0, to);
    check(rc == -1, "safercopy should fail #2");
    check(to[to_len - 1] == '\0', "String not terminated.");

    return 0;

error:
    return 1;
}

copy函數(shù)是典型的C代碼,而且它是大量緩沖區(qū)溢出的來源蜜宪。它有缺陷虫埂,因為它總是假設接受到的是合法的C字符串(帶有'\0'),并且只是用一個while循環(huán)來處理圃验。問題是掉伏,確保這些是十分困難的,并且如果沒有處理好,它會使while循環(huán)無限執(zhí)行斧散。編寫可靠代碼的一個要點就是供常,不要編寫可能不會終止的循環(huán)。

safecopy函數(shù)嘗試通過要求調用者提供兩個字符串的長度來解決問題鸡捐。它可以執(zhí)行有關這些字符串的话侧、copy函數(shù)不具備的特定檢查。他可以保證長度正確闯参,to字符串具有足夠的容量瞻鹏,以及它總是可終止。這個函數(shù)不像copy函數(shù)那樣可能會永遠執(zhí)行下去鹿寨。

這個就是永遠不信任輸入的實例新博。如果你假設你的函數(shù)要接受一個沒有終止標識的字符串(通常是這樣),你需要設計你的函數(shù)脚草,不要依賴字符串本身赫悄。如果你想讓參數(shù)不為NULL,你應該對此做檢查馏慨。如果大小應該在正常范圍內埂淮,也要對它做檢查。你只需要簡單假設調用你代碼的人會把它弄錯写隶,并且使他們更難破壞你的函數(shù)倔撞。

這個可以擴展到從外部環(huán)境獲取輸入的的軟件。程序員著名的臨終遺言是慕趴,“沒人會這樣做痪蝇。”我看到他們說了這句話后冕房,第二天有人就這樣做躏啰,黑掉或崩潰它們的應用。如果你說沒有人會這樣做耙册,那就加固代碼來保證他們不會簡單地黑掉你的應用给僵。你會因所做的事情而感到高興。

這種行為會出現(xiàn)收益遞減详拙。下面是一個清單帝际,我會嘗試對我用C寫的每個函數(shù)做如下工作:

  • 對于每一個參數(shù)定義它的先決條件,以及這個條件是否導致失效或返回錯誤值溪厘。如果你在編寫一個庫胡本,比起失效要更傾向于錯誤。
  • 對于每個先決條件畸悬,使用assert(test && "message");在最開始添加assert檢查侧甫。這句代碼會執(zhí)行檢查珊佣,失敗時OS通常會打印斷言行,通常它包括信息披粟。當你嘗試弄清assert為什么在這里時咒锻,這會非常有用。
  • 對于其它先決條件守屉,返回錯誤代碼或者使用我的check宏來執(zhí)行它并且提供錯誤信息惑艇。我在這個例子中沒有使用check,因為它會混淆比較拇泛。
  • 記錄為什么存在這些先決條件滨巴,當一個程序員碰到錯誤時,他可以弄清楚這些是否是真正必要的俺叭。
  • 如果你修改了輸入恭取,確保當函數(shù)退出或中止時它們也會正確產生。
  • 總是要檢查所使用的函數(shù)的錯誤代碼熄守。例如蜈垮,人們有時會忘記檢查fopenfread的返回代碼,這會導致他們在錯誤下仍然使用這個資源裕照。這會導致你的程序崩潰或者易受攻擊攒发。
  • 你也需要返回一致的錯誤代碼,以便對你的每個函數(shù)添加相同的機制晋南。一旦你熟悉了這一習慣惠猿,你就會明白為什么我的check宏這樣工作。

只是這些微小的事情就會改進你的資源處理方式搬俊,并且避免一大堆錯誤紊扬。

避免錯誤

上一個例子中你可能會聽到別人說,“程序員不會經常錯誤地使用copy唉擂。”盡管大量攻擊都針對這類函數(shù)檀葛,他們仍舊相信這種錯誤的概率非常低玩祟。概率是個很有趣的事情,因為人們不擅長猜測所有事情的概率屿聋,這非常難以置信空扎。然而人們對于判斷一個事情是否可能,是很擅長的润讥。他們可能會說copy中的錯誤不常見转锈,但是無法否認它可能發(fā)生。

關鍵的原因是對于一些常見的事情楚殿,它首先是可能的。判斷可能性非常簡單,因為我們都知道事情如何發(fā)生砌溺。但是隨后判斷出概率就不是那么容易了影涉。人們錯誤使用copy的情況會占到20%、10%规伐,或1%蟹倾?沒有人知道。為了弄清楚你需要收集證據(jù)猖闪,統(tǒng)計許多軟件包中的錯誤率鲜棠,并且可能需要調查真實的程序員如何使用這個函數(shù)。

這意味著培慌,如果你打算避免錯誤豁陆,你不需要嘗試避免可能發(fā)生的事情,而是要首先集中解決概率最大的事情检柬。解決軟件所有可能崩潰的方式并不可行献联,但是你可以嘗試一下。同時何址,如果你不以最少的努力解決最可能發(fā)生的事件里逆,你就是在不相關的風險上浪費時間。

下面是一個決定避免什么的處理過程:

  • 列出所有可能發(fā)生的錯誤用爪,無論概率大小原押,并帶著它們的原因。不要列出外星人可能會監(jiān)聽內存來偷走密碼這樣的事情偎血。
  • 評估每個的概率诸衔,使用危險行為的百分比來表示。如果你處理來自互聯(lián)網的情況颇玷,那么則為可能出現(xiàn)錯誤的請求的百分比笨农。如果是函數(shù)調用,那么它是出現(xiàn)錯誤的函數(shù)調用百分比帖渠。
  • 評估每個的工作量谒亦,使用避免它所需的代碼量或工作時長來表示。你也可以簡單給它一個“容易”或者“難”的度量空郊。當需要修復的簡單錯誤仍在列表上時份招,任何這種度量都可以讓你避免做無謂的工作。
  • 按照工作量(低到高)和概率(高到低)排序狞甚,這就是你的任務列表锁摔。
  • 之后避免你在列表中列出的任何錯誤,如果你不能消除它的可能性哼审,要降低它的概率谐腰。
  • 如果存在你不能修復的錯誤孕豹,記錄下來并提供給可以修復的人。

這一微小的過程會產生一份不錯的待辦列表怔蚌。更重要的是巩步,當有其它重要的事情需要解決時,它讓你遠離勞而無功桦踊。你也可以更正式或更不正式地處理這一過程椅野。如果你要完成整個安全審計,你最好和團隊一起做籍胯,并且有個更詳細的電子表格竟闪。如果你只是編寫一個函數(shù),簡單地復查代碼之后劃掉它們就夠了杖狼。最重要的是你要停止假設錯誤不會發(fā)生炼蛤,并且著力于消除它們,這樣就不會浪費時間蝶涩。

過早暴露錯誤

如果你遇到C中的錯誤理朋,你有兩個選擇:

  • 返回錯誤代碼。
  • 中止進程绿聘。

這就是處理方法嗽上,你需要執(zhí)行它來確保錯誤盡快發(fā)生,記錄清楚熄攘,提供錯誤信息兽愤,并且易于程序員來避免它。這就是我提供的check宏這樣工作的原因挪圾。對于每一個錯誤浅萧,你都要讓它你打印信息、文件名和行號哲思,并且強制返回錯誤代碼洼畅。如果你使用了我的宏,你會以正確的方式做任何事情棚赔。

我傾向于返回錯誤代碼而不是終止程序土思。如果出現(xiàn)了大錯誤我會中止程序,但是實際上我很少碰到大錯誤忆嗜。一個需要中止程序的很好例子是,我獲取到了一個無效的指針崎岂,就像safecopy中那樣捆毫。我沒有讓程序在某個地方產生“段錯誤”,而是立即捕獲并中止冲甘。但是绩卤,如果傳入NULL十分普遍途样,我可能會改變方式而使用check來檢查,以保證調用者可以繼續(xù)運行濒憋。

然而在庫中何暇,我盡我最大努力永不中止。使用我的庫的軟件可以決定是否應該中止凛驮。如果這個庫使用非常不當裆站,我才會中止程序。

最后黔夭,關于“暴露”的一大部分內容是宏胯,不要對多于一個錯誤使用相同的信息或錯誤代碼。你通常會在外部資源的錯誤中見到這種情況本姥。比如一個庫捕獲了套接字上的錯誤肩袍,之后簡單報告“套接字錯誤”。它應該做的是返回具體的信息婚惫,比如套接字上發(fā)生了什么錯誤氛赐,使它可以被合理地調試和修復。當你設計錯誤報告時先舷,確保對于不同的錯誤你提供了不同的錯誤消息艰管。

記錄假設

如果你遵循并執(zhí)行了這個建議,你就構建了一份“契約”密浑,關于函數(shù)期望這個世界是什么樣子蛙婴。你已經為每個參數(shù)預設了條件,處理潛在的錯誤尔破,并且優(yōu)雅地產生失敗街图。下一步是完善這一契約,并且添加“不變量”和“后置條件”懒构。

不變量就是在函數(shù)運行時餐济,一些場合下必須恒為真的條件。這對于簡單的函數(shù)并不常見胆剧,但是當你處理復雜的結構時絮姆,它會變得很必要。一個關于不變量的很好的例子是秩霍,結構體在使用時都會合理地初始化篙悯。另一個是有序的數(shù)據(jù)結構在處理時總是排好序的。

后置條件就是退出值或者函數(shù)運行結果的保證铃绒。這可以和不變了混在一起鸽照,但是也可以是一些很簡單的事情,比如“函數(shù)應總是返回0颠悬,或者錯誤時返回-1”矮燎。通常這些都有文檔記錄定血,但是如果你的函數(shù)返回一個分配的資源,你應該添加一個后置條件诞外,做檢查來確保它返回了一個不為NULL的東西澜沟。或者峡谊,你可以使用NULL來表示錯誤茫虽,這種情況下,你的后置條件就是資源在任何錯誤時都會被釋放靖苇。

在C編程中席噩,不變量和后置條件都通常比實際的代碼和斷言更加文檔化。處理它們的最好當時就是盡可能添加assert調用贤壁,之后記錄剩下的部分悼枢。如果你這么做了,當其它人碰到錯誤時脾拆,他們可以看到你在編寫函數(shù)時做了什么假設馒索。

避免過多文檔

程序員編寫代碼時的一個普遍問題,就是他們會記錄一個普遍的bug名船,而不是簡單地修復它绰上。我最喜歡的方式是,Ruby on Rails系統(tǒng)只是簡單地假設所有月份都有30天渠驼。日歷太麻煩了蜈块,所以與其修復它,不如在一些地方放置一個小的注釋迷扇,說這是故意的百揭,并且?guī)啄陜榷疾粫恼C看我恍┤嗽噲D抱怨它時蜓席,他們都會說器一,“文檔里面都有!”

如果你能夠實際修復問題厨内,文檔并不重要祈秕,并且,如果函數(shù)具有嚴重的缺陷雏胃,你在修復它之前可以不記錄它请毛。在Ruby on Rails的例子中,不包含日期函數(shù)會更好一些瞭亮,而不是包含一個沒人會用的錯誤的函數(shù)获印。

當你為防御性編程執(zhí)行清理時,盡可能嘗試修復任何事情。如果你發(fā)現(xiàn)你記錄了越來越多的兼丰,你不能修復的事情,需要考慮重新設計特性唆缴,或簡單地移除它鳍征。如果你真的需要保留這一可怕的錯誤的特性,那么我建議你編寫它面徽、記錄它艳丛,并且在你受責備之前找一份新的工作。

使一切自動化

你是個程序員趟紊,這意味著你的工作是通過自動化消滅其它人的工作氮双。它的終極目標是使用自動化來使你自己也失業(yè)。很顯然你不應該完全消除你做的東西霎匈,但如果你花了一整天在終端上重復運行手動測試戴差,你的工作就不是編程。你只是在做QA铛嘱,并且你應該使自己自動化暖释,消除這個你可能并不是真的想干的QA工作。

實現(xiàn)它的最簡單方式就是編寫自動化測試墨吓,或者單元測試球匕。這本書里我打算講解如何使它更簡單,并且我會避免多數(shù)編寫測試的信條帖烘。我只會專注于如何編寫它們亮曹,測試什么,以及如何使測試更高效秘症。

下面是程序員沒有但是應該自動化的一些事情:

  • 測試和校驗照卦。
  • 構建過程。
  • 軟件部署历极。
  • 系統(tǒng)管理窄瘟。
  • 錯誤報告。

嘗試花一些時間在自動化上面趟卸,你會有更多的時間用來處理一些有趣的事情蹄葱。或者锄列,如果這對你來說很有趣图云,也許你應該編寫自動化完成這些事情的軟件。

簡單化和清晰化

“簡單性”的概念對許多人來說比較微妙邻邮,尤其是一些聰明人竣况。它們通常將“內涵”與“簡單性”混淆起來。如果他們很好地理解了它筒严,很顯然非常簡單丹泉。簡單性的測試是通過將一個東西與比它更簡單的東西比較情萤。但是,你會看到編寫代碼的人會使用最復雜的摹恨、匪夷所思的數(shù)據(jù)結構筋岛,因為它們認為做同樣事情的簡單版本非常“惡心”晒哄。對復雜性的愛好是程序員的弱點睁宰。

你可以首先通過告訴自己,“簡單和清晰并不惡心寝凌,無論誰在干什么事情”來戰(zhàn)勝這一弱點柒傻。如果其它人編寫了愚蠢的觀察者模式涉及到19個類,12個接口较木,而你只用了兩個字符串操作就可以實現(xiàn)它红符,那么你贏了。他們就是錯了劫映,無論他們認為自己的復雜設計有多么高大上违孝。

對于要使用哪個函數(shù)的最簡單測試是:

  • 確保所有函數(shù)都沒有問題。如果它有錯誤泳赋,它有多快或多簡單就不重要了雌桑。
  • 如果你不能修復問題,就選擇另外一個祖今。
  • 它們會產生相同結果嘛校坑?如果不是就挑選具有所需結果的函數(shù)。
  • 如果它們會產生相同結果千诬,挑選包含更少特性耍目,更少分支的那個,或者挑選你認為最簡單的那個徐绑。
  • 確保你沒有只是挑選最具有表現(xiàn)力的那個邪驮。無論怎么樣,簡單和清晰傲茄,都會戰(zhàn)勝復雜和惡心毅访。

你會注意到,最后我一般會放棄并告訴你根據(jù)你的判斷盘榨。簡單性非常諷刺地是一件復雜的事情喻粹,所以使用你的品位作為指引是最好的方式。只需要確保在你獲取更多經驗之后草巡,你會調整你對于什么是“好”的看法守呜。

質疑權威

最后一個策略是最重要的,因為它讓你突破防御性編程思維,并且讓你轉換為創(chuàng)造性思維查乒。防御性編程是權威性的弥喉,并且比較無情。這一思維方式的任務是讓你遵循規(guī)則侣颂,因為否則你會錯失一些東西或心煩意亂档桃。

這一權威性的觀點的壞處是扼殺了獨立的創(chuàng)造性思維。規(guī)則對于完成事情是必要的憔晒,但是做它們的奴隸會扼殺你的創(chuàng)造力。

這條最后的策略的意思是你應該周期性地質疑你遵循的規(guī)則蔑舞,并且假設它們都是錯誤的拒担,就像你之前復查的軟件那樣。在一段防御性編程的時間之后攻询,我通常會這樣做从撼,我會擁有一個不編程的休息并讓這些規(guī)則消失。之后我會準備好去做一些創(chuàng)造性的工作钧栖,或按需做更多的防御型編程低零。

順序并不重要

在這一哲學上我想說的最后一件事,就是我并不是告訴你要按照一個嚴格的規(guī)則拯杠,比如“創(chuàng)造掏婶!防御!創(chuàng)造潭陪!防御雄妥!”去做這件事。最開始你可能想這樣做依溯,但是我實際上會做不等量的這些事情老厌,取決于我想做什么,并且我可能會將二者融合到一起黎炉,沒有明確的邊界枝秤。

我也不認為其中一種思維會優(yōu)于另一種,或者它們之間有嚴格的界限慷嗜。你需要在編程上既有創(chuàng)造力也要嚴格淀弹,所以如果想要提升的話,需要同時做到它們洪添。

附加題

  • 到現(xiàn)在為止(以及以后)書中的代碼都可能違反這些規(guī)則垦页。回退并挑選一個練習干奢,將你學到的應用在它上面痊焊,來看看你能不能改進它或發(fā)現(xiàn)bug。
  • 尋找一個開源項目,對其中一些文件進行類似的代碼復查薄啥。如果你發(fā)現(xiàn)了bug辕羽,提交一個補丁來修復它。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末垄惧,一起剝皮案震驚了整個濱河市刁愿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌到逊,老刑警劉巖铣口,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異觉壶,居然都是意外死亡脑题,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門铜靶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叔遂,“玉大人,你說我怎么就攤上這事争剿∫鸭瑁” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵蚕苇,是天一觀的道長哩掺。 經常有香客問我,道長捆蜀,這世上最難降的妖魔是什么疮丛? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮辆它,結果婚禮上誊薄,老公的妹妹穿的比我還像新娘。我一直安慰自己锰茉,他們只是感情好呢蔫,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著飒筑,像睡著了一般片吊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上协屡,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天俏脊,我揣著相機與錄音,去河邊找鬼肤晓。 笑死爷贫,一個胖子當著我的面吹牛认然,可吹牛的內容都是我干的。 我是一名探鬼主播漫萄,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卷员,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腾务?” 一聲冷哼從身側響起毕骡,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岩瘦,沒想到半個月后未巫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡启昧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年橱赠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箫津。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宰啦,靈堂內的尸體忽然破棺而出苏遥,到底是詐尸還是另有隱情,我是刑警寧澤赡模,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布田炭,位于F島的核電站,受9級特大地震影響漓柑,放射性物質發(fā)生泄漏教硫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一辆布、第九天 我趴在偏房一處隱蔽的房頂上張望瞬矩。 院中可真熱鬧,春花似錦锋玲、人聲如沸景用。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伞插。三九已至,卻和暖如春盾碗,著一層夾襖步出監(jiān)牢的瞬間媚污,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工廷雅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耗美,地道東北人京髓。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像幽歼,于是被迫代替她去往敵國和親朵锣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內容

  • 搜集了一些關于“防御性編程”資料甸私,將其中一些思想備份下诚些,學習 軟件工程師的智慧,就是在于其是否開始意識到:使程序能...
    申申申申申閱讀 1,921評論 1 7
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,095評論 25 707
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持皇型,譯者再次奉上一點點福利:阿里云產品券诬烹,享受所有官網優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 6,549評論 3 22
  • 杭州曾是吳越國和南宋的都城弃鸦,是中國七大古都之一绞吁,在南宋時期發(fā)展為世界上最繁華的城市之一,2016年被《第一財經周刊...
    好書郎閱讀 304評論 0 1
  • 臨下班前多出了一些事,匆匆忙忙弄好之后窗外已華燈初上购岗,淅淅瀝瀝的小雨一點沒有停下來的意思汰聋。走出單位大門对蒲,疾馳...
    半秋_33閱讀 421評論 0 0