日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

C++11(及现代C++风格)和快速迭代式开发

發(fā)布時(shí)間:2025/3/21 c/c++ 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++11(及现代C++风格)和快速迭代式开发 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

過去的一年我在微軟亞洲研究院做輸入法,我們的產(chǎn)品叫“英庫(kù)拼音輸入法” (下載Beta版),如果你用過“英庫(kù)詞典”(現(xiàn)已更名為必應(yīng)詞典),應(yīng)該知道“英庫(kù)”這個(gè)名字(實(shí)際上我們的核心開發(fā)團(tuán)隊(duì)也有很大一部分來(lái)源于英庫(kù)團(tuán)隊(duì)的老成員)。整個(gè)項(xiàng)目是微軟亞洲研究院的自然語(yǔ)言處理組、互聯(lián)網(wǎng)搜索與挖掘組和我們創(chuàng)新工程中心,以及微軟中國(guó)Office商務(wù)軟件部(MODC)多組合作的結(jié)果。至于我們的輸入法有哪些創(chuàng)新的feature,以及這些feature背后的種種有趣故事… 本文暫不討論。雖然整個(gè)過程中我也參與了很多feature的設(shè)想和設(shè)計(jì),但90%的職責(zé)還是開發(fā),所以作為client端的核心開發(fā)人員之一,我想跟大家分享這一年來(lái)在項(xiàng)目中全面使用C++11以及現(xiàn)代C++風(fēng)格(Elements of Modern C++ Style)來(lái)做開發(fā)的種種經(jīng)驗(yàn)。

我們用的開發(fā)環(huán)境是VS2010 SP1,該版本已經(jīng)支持了相當(dāng)多的C++11的特性:lambda表達(dá)式,右值引用,auto類型推導(dǎo),static_assert,decltype,nullptr,exception_ptr等等。C++曾經(jīng)飽受“學(xué)院派”標(biāo)簽的困擾,不過這個(gè)標(biāo)簽著實(shí)被貼得挺冤,C++11的新feature沒有一個(gè)是從學(xué)院派角度出發(fā)來(lái)設(shè)計(jì)的,以上提到的所有這些feature都在我們的項(xiàng)目中得到了適得其所的運(yùn)用,并且?guī)?lái)了很大的收益。尤其是lambda表達(dá)式。

說起來(lái)我跟C++也算是有相當(dāng)大的緣分,03年還在讀本科的時(shí)候,第一篇發(fā)表在程序員上面的文章就是Boost庫(kù)的源碼剖析,那個(gè)時(shí)候Boost庫(kù)在國(guó)內(nèi)還真是相當(dāng)?shù)年?yáng)春白雪,至今已經(jīng)快十年了,Boost庫(kù)如今已經(jīng)是寫C++代碼不可或缺的庫(kù),被譽(yù)為“準(zhǔn)標(biāo)準(zhǔn)庫(kù)”,C++的TR1基本就脫胎于Boost的一系列子庫(kù),而TR2同樣也大量從Boost庫(kù)中取材。之后有好幾年,我在CSDN上的博客幾乎純粹是C++的前沿技術(shù)文章,包括從06年就開始寫的“C++0x漫談”系列。(后來(lái)寫技術(shù)文章寫得少了,也就把博客從CSDN博客獨(dú)立了出來(lái),便是現(xiàn)在的mindhacks.cn)。自從獨(dú)立博客了之后我就沒有再寫過C++相關(guān)的文章(不過仍然一直對(duì)C++的發(fā)展保持了一定的關(guān)注),一方面我喜歡關(guān)注前沿的進(jìn)展,寫完了Boost源碼剖析系列和C++0x漫談系列之后我覺得這一波的前沿進(jìn)展從大方面來(lái)說也都寫得差不多了,所以不想再費(fèi)時(shí)間。另一方面的原因也是我雖然對(duì)C++關(guān)注較深,但實(shí)踐經(jīng)驗(yàn)卻始終絕大多數(shù)都是“替代經(jīng)驗(yàn)”,即從別人那兒看來(lái)的,并非自己第一手的。而過去一年來(lái)深度參與的英庫(kù)輸入法項(xiàng)目彌補(bǔ)了這個(gè)缺憾,所以我就決定重新開始寫一點(diǎn)C++11的實(shí)踐經(jīng)驗(yàn)。算是對(duì)努力一年的項(xiàng)目發(fā)布第一版的一個(gè)小結(jié)。

09年入職微軟亞洲研究院之后,前兩年跟C++基本沒沾邊,第一個(gè)項(xiàng)目倒是用C++的,不過是工作在既有代碼基上,時(shí)間也相對(duì)較短。第二個(gè)項(xiàng)目為Bing Image Search用javascript寫前端,第三個(gè)項(xiàng)目則給Visual Studio 2012寫Code Clone Detection,用C#和WPF。直到一年前英庫(kù)輸入法這個(gè)項(xiàng)目,是我在研究院的第四個(gè)項(xiàng)目了,也是最大的一個(gè),一年來(lái)我很開心,因?yàn)橛只氐搅薈++。

這個(gè)項(xiàng)目我們從零開始,,而client端的核心開發(fā)人員也很緊湊,只有3個(gè)。這個(gè)項(xiàng)目有很多特殊之處,對(duì)高效的快速迭代開發(fā)提出了很大的挑戰(zhàn)(研究院所倡導(dǎo)的“以實(shí)踐為驅(qū)動(dòng)的研究(Deployment-Driven-Research)”要求我們迅速對(duì)用戶的需求作出響應(yīng)):

  • 長(zhǎng)期時(shí)間壓力:從零開始到發(fā)布,只有一年時(shí)間,我們既要在主要feature上能和主流的輸入法相較,還需要實(shí)現(xiàn)我們自己獨(dú)特的創(chuàng)新feature,從而能夠和其他輸入法產(chǎn)品區(qū)分開來(lái)。
  • 短期時(shí)間壓力:輸入法在中國(guó)是一個(gè)非常成熟的市場(chǎng),誰(shuí)也沒法保證悶著頭搞一年搞出來(lái)的東西就一炮而紅,所以我們從第一天起就進(jìn)入demo驅(qū)動(dòng)的準(zhǔn)迭代式開發(fā),整個(gè)過程中必須不斷有階段性輸出,抬頭看路好過悶頭走路。但工程師最頭疼的二難問題之一恐怕就是短期與長(zhǎng)遠(yuǎn)的矛盾:要持續(xù)不斷出短期的成果,就必須經(jīng)常在某些地方趕工,趕工的結(jié)果則可能導(dǎo)致在設(shè)計(jì)和代碼質(zhì)量上面的折衷,這些折衷也被稱為Technical Debt(技術(shù)債)。沒有任何項(xiàng)目沒有技術(shù)債,只是多少,以及償還的方式的區(qū)別。我們的目的不是消除技術(shù)債,而是通過不斷持續(xù)改進(jìn)代碼質(zhì)量,阻止技術(shù)債的滾雪球式積累。
  • C++是一門不容易用好的語(yǔ)言:錯(cuò)誤的使用方式會(huì)給代碼基的質(zhì)量帶來(lái)很大的損傷。而C++的誤用方式又特別多。
  • 輸入法是個(gè)很特殊的應(yīng)用程序,在Windows下面,輸入法是加載到目標(biāo)進(jìn)程空間當(dāng)中的dll,所以,輸入法對(duì)質(zhì)量的要求極高,別的軟件出了錯(cuò)誤崩潰了大不了重啟一下,而輸入法如果崩潰就會(huì)造成整個(gè)目標(biāo)進(jìn)程崩潰,如果用戶的文檔未保存就可能會(huì)丟失寶貴的用戶數(shù)據(jù),所以輸入法最容不得崩潰。可是只要是人寫的代碼怎么可能沒有bug呢?所以關(guān)鍵在于如何減少bug及其產(chǎn)生的影響和如何能盡快響應(yīng)并修復(fù)bug。所以我們的做法分為三步:1). 使用現(xiàn)代C++技術(shù)減少bug產(chǎn)生的機(jī)會(huì)。2). 即便bug產(chǎn)生了,也盡量減少對(duì)用戶產(chǎn)生的影響。3). 完善的bug匯報(bào)系統(tǒng)使開發(fā)人員能夠第一時(shí)間擁有足夠的信息修復(fù)bug。
  • 至于為什么要用C++而不是C呢?對(duì)于我們來(lái)說理由很現(xiàn)實(shí):時(shí)間緊任務(wù)重,用C的話需要發(fā)明的輪子太多了,C++的抽象層次高,代碼量少,bug相對(duì)就會(huì)更少,現(xiàn)代C++的內(nèi)存管理完全自動(dòng),以至于從頭到尾我根本不記得曾遇到過什么內(nèi)存管理相關(guān)的bug,現(xiàn)代C++的錯(cuò)誤處理機(jī)制也非常適合快速開發(fā)的同時(shí)不用擔(dān)心bug亂飛,另外有了C++11的強(qiáng)大支持更是如虎添翼,當(dāng)然,這一切都必須建立在核心團(tuán)隊(duì)必須善用C++的大前提上,而這對(duì)于我們這個(gè)緊湊的小團(tuán)隊(duì)來(lái)說這不是問題,因?yàn)榇蠹叶加休^好的C++背景,沒有陡峭的學(xué)習(xí)曲線要爬。(至于C++在大規(guī)模團(tuán)隊(duì)中各人對(duì)C++的掌握良莠不齊的情況下所帶來(lái)的一些包袱本文也不作討論,呵呵,語(yǔ)言之爭(zhēng)別找我。)

    下面就說說我們?cè)谶@個(gè)項(xiàng)目中是如何使用C++11和現(xiàn)代C++風(fēng)格來(lái)開發(fā)的,什么是現(xiàn)代C++風(fēng)格以及它給我們開發(fā)帶來(lái)的好處。

    資源管理

    說到Native Languages就不得不說資源管理,因?yàn)橘Y源管理向來(lái)都是Native Languages的一個(gè)大問題,其中內(nèi)存管理又是資源當(dāng)中的一個(gè)大問題,由于堆內(nèi)存需要手動(dòng)分配和釋放,所以必須確保內(nèi)存得到釋放,對(duì)此一般原則是“誰(shuí)分配誰(shuí)負(fù)責(zé)釋放”,但即便如此仍然還是經(jīng)常會(huì)導(dǎo)致內(nèi)存泄漏、野指針等等問題。更不用說這種手動(dòng)釋放給API設(shè)計(jì)帶來(lái)的問題(例如Win32 API?WideCharToMultiByte就是一個(gè)典型的例子,你需要提供一個(gè)緩沖區(qū)給它來(lái)接收編碼轉(zhuǎn)換的結(jié)果,但是你又不能確保你的緩沖區(qū)足夠大,所以就出現(xiàn)了一個(gè)兩次調(diào)用的pattern,第一次給個(gè)NULL緩沖區(qū),于是API返回的是所需的緩沖區(qū)的大小,根據(jù)這個(gè)大小分配緩沖區(qū)之后再第二次調(diào)用它,別提多別扭了)。

    托管語(yǔ)言們?yōu)榱私鉀Q這個(gè)問題引入了GC,其理念是“內(nèi)存管理太重要了,不能交給程序員來(lái)做”。但GC對(duì)于Native開發(fā)也常常有它自己的問題。而且另一方面Native界也常常詬病GC,說“內(nèi)存管理太重要了,不能交給機(jī)器來(lái)做”。

    C++也許是第一個(gè)提供了完美折衷的語(yǔ)言(不過這個(gè)機(jī)制直到C++11的出現(xiàn)才真正達(dá)到了易用的程度),即:既不是完全交給機(jī)器來(lái)做,也不是完全交給程序員來(lái)做,而是程序員先在代碼中指定怎么做,至于什么時(shí)候做,如何確保一定會(huì)得到執(zhí)行,則交由編譯器來(lái)確定。

    首先是C++98提供了語(yǔ)言機(jī)制:對(duì)象在超出作用域的時(shí)候其析構(gòu)函數(shù)會(huì)被自動(dòng)調(diào)用。接著,Bjarne Stroustrup在TC++PL里面定義了RAII(Resource Acquisition is Initialization)范式(即:對(duì)象構(gòu)造的時(shí)候其所需的資源便應(yīng)該在構(gòu)造函數(shù)中初始化,而對(duì)象析構(gòu)的時(shí)候則釋放這些資源)。RAII意味著我們應(yīng)該用類來(lái)封裝和管理資源,對(duì)于內(nèi)存管理而言,Boost第一個(gè)實(shí)現(xiàn)了工業(yè)強(qiáng)度的智能指針,如今智能指針(shared_ptr和unique_ptr)已經(jīng)是C++11的一部分,簡(jiǎn)單來(lái)說有了智能指針意味著你的C++代碼基中幾乎就不應(yīng)該出現(xiàn)delete了。

    不過,RAII范式雖然很好,但還不足夠易用,很多時(shí)候我們并不想為了一個(gè)CloseHandle, ReleaseDC, GlobalUnlock等等而去大張旗鼓地另寫一個(gè)類出來(lái),所以這些時(shí)候我們往往會(huì)因?yàn)榕侣闊┒苯邮謩?dòng)去調(diào)這些釋放函數(shù),手動(dòng)調(diào)的一個(gè)壞處是,如果在資源申請(qǐng)和釋放之間發(fā)生了異常,那么釋放將不會(huì)發(fā)生,此外,手動(dòng)釋放需要在函數(shù)的所有出口處都去調(diào)釋放函數(shù),萬(wàn)一某天有人修改了代碼,加了一處return,而在return之前忘了調(diào)釋放函數(shù),資源就泄露了。理想情況下我們希望語(yǔ)言能夠支持這樣的范式:

    void foo() {HANDLE h = CreateFile(...);ON_SCOPE_EXIT { CloseHandle(h); }... // use the file }

    ON_SCOPE_EXIT里面的代碼就像是在析構(gòu)函數(shù)里面的一樣:不管當(dāng)前作用域以什么方式退出,都必然會(huì)被執(zhí)行。

    實(shí)際上,早在2000年,Andrei Alexandrescu?就在DDJ雜志上發(fā)表了一篇文章,提出了這個(gè)叫做ScopeGuard 的設(shè)施,不過當(dāng)時(shí)C++還沒有太好的語(yǔ)言機(jī)制來(lái)支持這個(gè)設(shè)施,所以Andrei動(dòng)用了你所能想到的各種奇技淫巧硬是造了一個(gè)出來(lái),后來(lái)Boost也加入了ScopeExit庫(kù),不過這些都是建立在C++98不完備的語(yǔ)言機(jī)制的情況下,所以其實(shí)現(xiàn)非常不必要的繁瑣和不完美,實(shí)在是戴著腳鐐跳舞(這也是C++98的通用庫(kù)被詬病的一個(gè)重要原因),再后來(lái)Andrei不能忍了就把這個(gè)設(shè)施內(nèi)置到了D語(yǔ)言當(dāng)中,成了D語(yǔ)言特性的一部分(最出彩的部分之一)。

    再后來(lái)就是C++11的發(fā)布了,C++11發(fā)布之后,很多人都開始重新實(shí)現(xiàn)這個(gè)對(duì)于異常安全來(lái)說極其重要的設(shè)施,不過絕大多數(shù)人的實(shí)現(xiàn)受到了2000年Andrei的原始文章的影響,多多少少還是有不必要的復(fù)雜性,而實(shí)際上,將C++11的Lambda Function和tr1::function結(jié)合起來(lái),這個(gè)設(shè)施可以簡(jiǎn)化到腦殘的地步:

    class ScopeGuard { public:explicit ScopeGuard(std::function<void()> onExitScope): onExitScope_(onExitScope), dismissed_(false){ }~ScopeGuard(){if(!dismissed_){onExitScope_();}}void Dismiss(){dismissed_ = true;}private:std::function<void()> onExitScope_;bool dismissed_;private: // noncopyableScopeGuard(ScopeGuard const&);ScopeGuard& operator=(ScopeGuard const&); };

    這個(gè)類的使用很簡(jiǎn)單,你交給它一個(gè)std::function,它負(fù)責(zé)在析構(gòu)的時(shí)候執(zhí)行,絕大多數(shù)時(shí)候這個(gè)function就是lambda,例如:

    HANDLE h = CreateFile(...); ScopeGuard onExit([&] { CloseHandle(h); });

    onExit在析構(gòu)的時(shí)候會(huì)忠實(shí)地執(zhí)行CloseHandle。為了避免給這個(gè)對(duì)象起名的麻煩(如果有多個(gè)變量,起名就麻煩大了),可以定義一個(gè)宏,把行號(hào)混入變量名當(dāng)中,這樣每次定義的ScopeGuard對(duì)象都是唯一命名的。

    #define SCOPEGUARD_LINENAME_CAT(name, line) name##line #define SCOPEGUARD_LINENAME(name, line) SCOPEGUARD_LINENAME_CAT(name, line)#define ON_SCOPE_EXIT(callback) ScopeGuard SCOPEGUARD_LINENAME(EXIT, __LINE__)(callback)

    Dismiss()函數(shù)也是Andrei的原始設(shè)計(jì)的一部分,其作用是為了支持rollback模式,例如:

    ScopeGuard onFailureRollback([&] { /* rollback */ }); ... // do something that could fail onFailureRollback.Dismiss();

    在上面的代碼中,“do something”的過程中只要任何地方拋出了異常,rollback邏輯都會(huì)被執(zhí)行。如果“do something”成功了,onFailureRollback.Dismiss()會(huì)被調(diào)用,設(shè)置dismissed_為true,阻止rollback邏輯的執(zhí)行。

    ScopeGuard是資源自動(dòng)釋放,以及在代碼出錯(cuò)的情況下rollback的不可或缺的設(shè)施,C++98由于沒有l(wèi)ambda和tr1::function的支持,ScopeGuard不但實(shí)現(xiàn)復(fù)雜,而且用起來(lái)非常麻煩,陷阱也很多,而C++11之后立即變得極其簡(jiǎn)單,從而真正變成了每天要用到的設(shè)施了。C++的RAII范式被認(rèn)為是資源確定性釋放的最佳范式(C#的using關(guān)鍵字在嵌套資源申請(qǐng)釋放的情況下會(huì)層層縮進(jìn),相當(dāng)?shù)牟荒躶cale),而有了ON_SCOPE_EXIT之后,在C++里面申請(qǐng)釋放資源就變得非常方便

    Acquire Resource1 ON_SCOPE_EXIT( [&] { /* Release Resource1 */ })Acquire Resource2 ON_SCOPE_EXIT( [&] { /* Release Resource2 */ }) …

    這樣做的好處不僅是代碼不會(huì)出現(xiàn)無(wú)謂的縮進(jìn),而且資源申請(qǐng)和釋放的代碼在視覺上緊鄰彼此,永遠(yuǎn)不會(huì)忘記。更不用說只需要在一個(gè)地方寫釋放的代碼,下文無(wú)論發(fā)生什么錯(cuò)誤,導(dǎo)致該作用域退出我們都不用擔(dān)心資源不會(huì)被釋放掉了。我相信這一范式很快就會(huì)成為所有C++代碼分配和釋放資源的標(biāo)準(zhǔn)方式,因?yàn)檫@是C++十年來(lái)的演化所積淀下來(lái)的真正好的部分之一。

    錯(cuò)誤處理

    前面提到,輸入法是一個(gè)特殊的東西,某種程度上他就跟用戶態(tài)的driver一樣,對(duì)錯(cuò)誤的寬容度極低,出了錯(cuò)誤之后可能造成很嚴(yán)重的后果:用戶數(shù)據(jù)丟失。不像其他獨(dú)立跑的程序可以隨便崩潰大不了重啟(或者程序自動(dòng)重啟),所以從一開始,錯(cuò)誤處理就被非常嚴(yán)肅地對(duì)待。

    這里就出現(xiàn)了一個(gè)兩難問題:嚴(yán)謹(jǐn)?shù)腻e(cuò)誤處理要求不要忽視和放過任何一個(gè)錯(cuò)誤,要么當(dāng)即處理,要么轉(zhuǎn)發(fā)給調(diào)用者,層層往上傳播。任何被忽視的錯(cuò)誤,都遲早會(huì)在代碼接下去的執(zhí)行流當(dāng)中引發(fā)其他錯(cuò)誤,這種被原始錯(cuò)誤引發(fā)的二階三階錯(cuò)誤可能看上去跟root cause一點(diǎn)關(guān)系都沒有,造成bugfix的成本劇增,這是我們項(xiàng)目快速的開發(fā)步調(diào)下所承受不起的成本。

    然而另一方面,要想不忽視錯(cuò)誤,就意味著我們需要勤勤懇懇地檢查并轉(zhuǎn)發(fā)錯(cuò)誤,一個(gè)大規(guī)模的程序中隨處都可能有錯(cuò)誤發(fā)生,如果這種檢查和轉(zhuǎn)發(fā)的成本太高,例如錯(cuò)誤處理的代碼會(huì)導(dǎo)致代碼增加,結(jié)構(gòu)臃腫,那么程序員就會(huì)偷懶不檢查。而一時(shí)的偷懶以后總是要還的。

    所以細(xì)心檢查是短期不斷付出成本,疏忽檢查則是長(zhǎng)期付出成本,看上去怎么都是個(gè)成本。有沒有既不需要短期付出成本,又不會(huì)導(dǎo)致長(zhǎng)期付出成本的辦法呢?答案是有的。我們的項(xiàng)目全面使用異常來(lái)作為錯(cuò)誤處理的機(jī)制。異常相對(duì)于錯(cuò)誤代碼來(lái)說有很多優(yōu)勢(shì),我曾經(jīng)在2007年寫過一篇博客《錯(cuò)誤處理:為何、何時(shí)、如何》進(jìn)行了詳細(xì)的比較,但是異常對(duì)于C++而言也屬于不容易用好的特性:

    首先,為了保證當(dāng)異常拋出的時(shí)候不會(huì)產(chǎn)生資源泄露,你必須用RAII范式封裝所有資源。這在C++98中可以做到,但代價(jià)較大,一方面智能指針還沒有進(jìn)入標(biāo)準(zhǔn)庫(kù),另一方面智能指針也只能管內(nèi)存,其他資源莫非還都得費(fèi)勁去寫一堆wrapper類,這個(gè)不便很大程度上也限制了異常在C++98下的被廣泛使用。不過幸運(yùn)的是,我們這個(gè)項(xiàng)目開始的時(shí)候VS2010 SP1已經(jīng)具備了tr1和lambda function,所以寫完上文那個(gè)簡(jiǎn)單的ScopeGuard之后,資源的自動(dòng)釋放問題就非常簡(jiǎn)便了。

    其次,C++的異常不像C#的異常那樣附帶Callstack。例如你在某個(gè)地方通過.at(i)來(lái)取一個(gè)vector的某個(gè)元素,然后i越界了,你會(huì)收到vector內(nèi)部拋出來(lái)的一個(gè)異常,這個(gè)異常只是說下標(biāo)越界了,然后什么其他信息都木有,連個(gè)行號(hào)都沒有。要是不拋異常直接讓程序崩潰掉好歹還可以抓到一個(gè)minidump呢,這個(gè)因素一定程度上也限制了C++異常的被廣泛使用。Callstack顯然對(duì)于我們迅速診斷程序的bug有至關(guān)重要的作用,由于我們是一個(gè)不大的團(tuán)隊(duì),所以我們對(duì)質(zhì)量的測(cè)試很依賴于微軟內(nèi)部的dogfood用戶,我們r(jià)elease給dogfood用戶的是release版,倘若我們不用異常,用assert的話,固然是可以在release版也打開assert,但assert同樣也只能提供很有限的信息(文件和行號(hào),以及assert的表達(dá)式),很多時(shí)候這些信息是不足夠理解一個(gè)bug的(更不用說還得手動(dòng)截屏拷貝黏貼發(fā)送郵件才能匯報(bào)一個(gè)bug了),所以往往接下來(lái)還需要在開發(fā)人員自己的環(huán)境下試圖重現(xiàn)bug。這就不夠理想了。理想情況下,一個(gè)bug發(fā)生的時(shí)刻,程序應(yīng)該自己具備收集一切必要的信息的能力。那么對(duì)于一個(gè)bug來(lái)說,有哪些信息是至關(guān)重要的呢?

  • Error Message本身,例如“您的下標(biāo)越界啦!”少部分情況下,光是Error Message已經(jīng)足夠診斷。不過這往往是對(duì)于在開發(fā)的早期出現(xiàn)的一些簡(jiǎn)單bug,到中后期往往這類簡(jiǎn)單bug都被清除掉了,剩下的較為隱蔽的bug的診斷則需要多得多的信息。
  • Callstack。C++的異常由于性能的考慮,并不支持callstack。所以必須另想辦法。
  • 錯(cuò)誤發(fā)生地點(diǎn)的上下文變量的值:例如越界訪問,那么越界的下標(biāo)的值是多少,而被越界的容器的大小又是多少,等等。例如解析一段xml失敗了,那么這段xml是什么,當(dāng)前解析到哪兒,等等。例如調(diào)用Win32 API失敗了,那么Win32 Error Message是什么。
  • 錯(cuò)誤發(fā)生的環(huán)境:例如目標(biāo)進(jìn)程是什么。
  • 錯(cuò)誤發(fā)生之前用戶做了什么:對(duì)于輸入法來(lái)說,例如錯(cuò)誤發(fā)生之前的若干個(gè)鍵敲擊。
  • 如果程序能夠自動(dòng)把這些信息收集并打包起來(lái),發(fā)送給開發(fā)人員,那么就能夠?yàn)樵\斷提供極大的幫助(當(dāng)然,既便如此仍然還是會(huì)有難以診斷的bug)。而且這一切都要以不增加寫代碼過程中的開銷的方式來(lái)進(jìn)行,如果每次都要在代碼里面做一堆事情來(lái)收集這些信息,那煩都得煩死人了,沒有人會(huì)愿意用的。

    那么到底如何才能無(wú)代價(jià)地盡量收集充足的信息為診斷bug提供幫助呢?

    首先是callstack,有很多種方法可以給C++異常加上callstack,不過很多方法會(huì)帶來(lái)性能損失,而且用起來(lái)也不方便,例如在每個(gè)函數(shù)的入口處加上一小段代碼把函數(shù)名/文件/行號(hào)打印到某個(gè)地方,或者還有一些利用dbghelp.dll里面的StackWalk功能。我們使用的是沒有性能損失的簡(jiǎn)單方案:在拋C++異常之前先手動(dòng)MiniDumpWriteDump,在異常捕獲端把minidump發(fā)回來(lái),在開發(fā)人員收到minidump之后可以使用VS或windbg進(jìn)行調(diào)試(但前提是相應(yīng)的release版本必須開啟pdb)。可能這里你會(huì)擔(dān)心,minidump難道不是很耗時(shí)間的嘛?沒錯(cuò),但是既然程序已經(jīng)發(fā)生了異常,稍微多花一點(diǎn)時(shí)間也就無(wú)所謂了。我們對(duì)于“附帶minidump的異常”的使用原則是,只在那些真正“異常”的情況下拋出,換句話說,只在你認(rèn)為應(yīng)該使用的assert的地方用,這類錯(cuò)誤屬于critical error。另外我們還有不帶minidump的異常,例如網(wǎng)絡(luò)失敗,xml解析失敗等等“可以預(yù)見”的錯(cuò)誤,這類錯(cuò)誤發(fā)生的頻率較高,所以如果每次都minidump會(huì)拖慢程序,所以這種情況下我們只拋異常不做minidump。

    然后是Error Message,如何才能像assert那樣,在Error Message里面包含表達(dá)式和文件行號(hào)?

    最后,也是最重要的,如何能夠把上下文相關(guān)變量的值capture下來(lái),因?yàn)橐环矫鎟elease版本的minidump在調(diào)試的時(shí)候所看到的變量值未必正確,另一方面如果這個(gè)值在堆上(例如std::string的內(nèi)部buffer就在堆上),那就更看不著了。

    所有上面這些需求我們通過一個(gè)ENSURE宏來(lái)實(shí)現(xiàn),它的使用很簡(jiǎn)單:

    ENSURE(0 <= index && index < v.size())(index)(v.size());

    ENSURE宏在release版本中同樣生效,如果發(fā)現(xiàn)表達(dá)式求值失敗,就會(huì)拋出一個(gè)C++異常,并會(huì)在異常的.what()里面記錄類似如下的錯(cuò)誤信息:

    Failed: 0 <= index && index < v.size() File: xxx.cpp Line: 123 Context Variables:index = 12345v.size() = 100

    (如果你為stream重載了接收vector的operator <<,你甚至可以把vector的元素也打印到error message里頭)

    由于ENSURE拋出的是一個(gè)自定義異常類型ExceptionWithMinidump,這個(gè)異常有一個(gè)GetMinidumpPath()可以獲得拋出異常的時(shí)候記錄下來(lái)的minidump文件。

    ENSURE宏還有一個(gè)很方便的feature:在debug版本下,拋異常之前它會(huì)先assert,而assert的錯(cuò)誤消息正是上面這樣。Debug版本assert的好處是可以讓你有時(shí)間attach debugger,保證有完整的上下文。

    利用ENSURE,所有對(duì)Win32 API的調(diào)用所發(fā)生的錯(cuò)誤返回值就可以很方便地被轉(zhuǎn)化為異常拋出來(lái),例如:

    ENSURE_WIN32(SHGetKnownFolderPath(rfid, 0, NULL, &p) == S_OK);

    為了將LastError附在Error Message里面,我們額外定義了一個(gè)ENSURE_WIN32:

    #define ENSURE_WIN32(exp) ENSURE(exp)(GetLastErrorStr())

    其中GetLastErrorStr()會(huì)返回Win32 Last Error的錯(cuò)誤消息文本。

    而對(duì)于通過返回HRESULT來(lái)報(bào)錯(cuò)的一些Win32函數(shù),我們又定義了ENSURE_SUCCEEDED(hr):

    #define ENSURE_SUCCEEDED(hr) \if(SUCCEEDED(hr)) \ else ENSURE(SUCCEEDED(hr))(Win32ErrorMessage(hr))

    其中Win32ErrorMessage(hr)負(fù)責(zé)根據(jù)hr查到其錯(cuò)誤消息文本。

    ENSURE宏使得我們開發(fā)過程中對(duì)錯(cuò)誤的處理變得極其簡(jiǎn)單,任何地方你認(rèn)為需要assert的,用ENSURE就行了,一行簡(jiǎn)單的ENSURE,把bug相關(guān)的三大重要信息全部記錄在案,而且由于ENSURE是基于異常的,所以沒有辦法被程序忽略,也就不會(huì)導(dǎo)致難以調(diào)試的二階三階bug,此外異常不像錯(cuò)誤代碼需要手動(dòng)去傳遞,也就不會(huì)帶來(lái)為了錯(cuò)誤處理而造成的額外的開發(fā)成本(用錯(cuò)誤代碼來(lái)處理錯(cuò)誤的最大的開銷就是錯(cuò)誤代碼的手工檢查和層層傳遞)。

    ENSURE宏的實(shí)現(xiàn)并不復(fù)雜,打印文件行號(hào)和表達(dá)式文本的辦法和assert一樣,創(chuàng)建minidump的辦法(這里只討論win32)是在__try中RaiseException(EXCEPTION_BREAKPOINT…),在__except中得到EXCEPTION_POINTERS之后調(diào)用MiniDumpWriteDump寫dump文件。最tricky的部分是如何支持在后面capture任意多個(gè)局部變量(ENSURE(expr)(var1)(var2)(var3)…),并且對(duì)每個(gè)被capture的局部變量同時(shí)還得capture變量名(不僅是變量值)。而這個(gè)宏無(wú)限展開的技術(shù)也在大概十年前就有了,還是Andrei Alexandrescu寫的一篇DDJ文章:Enhanced Assertions?。神奇的是,我的CSDN博客當(dāng)年第一篇文章就是翻譯的它,如今十年后又在自己的項(xiàng)目中用到,真是有穿越的感覺,而且穿越的還不止這一個(gè),我們項(xiàng)目不用任何第三方庫(kù),包括boost也不用,這其實(shí)也沒有帶來(lái)什么不便,因?yàn)閎oost的大量有用的子庫(kù)已經(jīng)進(jìn)入了TR1,唯一的不便就是C++被廣為詬病的:沒有一個(gè)好的event實(shí)現(xiàn),boost.signal這種非常強(qiáng)大的工業(yè)級(jí)實(shí)現(xiàn)當(dāng)然是可以的,不過對(duì)于我們的項(xiàng)目來(lái)說boost.signal的許多feature根本用不上,屬于殺雞用牛刀了,因此我就自己寫了一個(gè)剛剛滿足我們項(xiàng)目的特定需求的event實(shí)現(xiàn)(使用tr1::function和lambda,這個(gè)signal的實(shí)現(xiàn)和使用都很簡(jiǎn)潔,可惜variadic templates沒有,不然還會(huì)更簡(jiǎn)潔一些)。我在03年寫boost源碼剖析系列的時(shí)候曾經(jīng)詳細(xì)剖析了boost.signal的實(shí)現(xiàn)技術(shù),想不到十年前關(guān)注的技術(shù)十年后還會(huì)在項(xiàng)目中用到。

    由于輸入法對(duì)錯(cuò)誤的容忍度較低,所以我們?cè)谒械某隹谔幎荚O(shè)置了兩重柵欄,第一重catch所有的C++異常,如果是ExceptionWithMinidump類型,則發(fā)送帶有dump的問題報(bào)告,如果是其他繼承自std::exception的異常類型,則僅發(fā)送包含.what()消息的問題報(bào)告,最后如果是catch(…)收到的那就沒辦法了,只能發(fā)送“unknown exception occurred”這種消息回來(lái)了。

    inline void ReportCxxException(std::exception_ptr ex_ptr) {try{std::rethrow_exception(ex_ptr);}catch(ExceptionWithMiniDump& ex){LaunchProblemReporter(…, ex.GetMiniDumpFilePath());}catch(std::exception& ex){LaunchProblemReporter(…, ex.what());}catch(...){LaunchProblemReporter("Unknown C++ Exception"));} }

    C++異常外面還加了一層負(fù)責(zé)捕獲Win32異常的,捕獲到unhandled win32 exception也會(huì)寫minidump并發(fā)回。

    考慮到輸入法應(yīng)該“能不崩潰就不崩潰”,所以對(duì)于C++異常而言,除了彈出問題報(bào)告程序之外,我們并不會(huì)阻止程序繼續(xù)執(zhí)行,這樣做有以下幾個(gè)原因:

  • 很多時(shí)候C++異常并不會(huì)使得程序進(jìn)入不可預(yù)測(cè)的狀態(tài),只要合理使用智能指針和ScopeGuard,該釋放的該回滾的操作都能被正確執(zhí)行。
  • 輸入法的引擎的每一個(gè)輸入session(從開始輸入到上詞)理論上是獨(dú)立的,如果session中間出現(xiàn)異常應(yīng)該允許引擎被reset到一個(gè)可知的好的狀態(tài)。
  • 輸入法內(nèi)核中有核心模塊也有非核心模塊,引擎屬于核心模塊,云候選詞、換膚、還有我們的創(chuàng)新feature:Rich Candidates(目前被譯為多媒體輸入,但其實(shí)沒有準(zhǔn)確表達(dá)出這個(gè)feature的含義,只不過第一批release的apps確實(shí)大多是輸入多媒體的,但我們接下來(lái)會(huì)陸續(xù)更新一系列的Rich Candidates Apps就不止是多媒體了)也屬于非核心模塊,非核心模塊即便出了錯(cuò)誤也不應(yīng)該影響內(nèi)核的工作。因此對(duì)于這些模塊而言我們都在其出口處設(shè)置了Error Boundary,捕獲一切異常以免影響整個(gè)內(nèi)核的運(yùn)作。
  • 另一方面,對(duì)于Native Language而言,除了語(yǔ)言級(jí)別的異常,總還會(huì)有Platform Specific的“硬”異常,例如最常見的Access Violation,當(dāng)然這種異常越少越好(我們的代碼基中鼓勵(lì)使用ENSURE來(lái)檢查各種pre-condition和post-condition,因?yàn)橐话銇?lái)說Access Violation不會(huì)是第一手錯(cuò)誤,它們幾乎總是由其他錯(cuò)誤導(dǎo)致的,而這個(gè)“其他錯(cuò)誤”往往可以用ENSURE來(lái)檢查,從而在它導(dǎo)致Access Violation之前就拋出語(yǔ)言級(jí)別的異常。舉一個(gè)簡(jiǎn)單的例子,還是vector的元素訪問,我們可以直接v[i],如果i越界,會(huì)Access Violation,那么這個(gè)Access Violation便是由之前的第一手錯(cuò)誤(i越界)所導(dǎo)致的二階異常了。而如果我們?cè)趘[i]之前先ENSURE(0 <= i && i < v.size())的話,就可以阻止“硬”異常的發(fā)生,轉(zhuǎn)而成為匯報(bào)一個(gè)語(yǔ)言級(jí)別的異常,語(yǔ)言級(jí)別的異常跟平臺(tái)相關(guān)的“硬”異常相比的好處在于:

  • 語(yǔ)言級(jí)別異常的信息更豐富,你可以capture相關(guān)的變量的值放在異常的錯(cuò)誤消息里面。
  • 語(yǔ)言級(jí)別的異常是“同步”的,一個(gè)寫的規(guī)范的程序可以保證在語(yǔ)言級(jí)別異常發(fā)生的情況下始終處于可知的狀態(tài)。C++的Stack Unwind機(jī)制可以確保一切善后工作得到執(zhí)行。相比之下當(dāng)平臺(tái)相關(guān)的“硬”異常發(fā)生的時(shí)候你既不會(huì)有機(jī)會(huì)清理資源回滾操作,也不能確保程序仍然處于可知的狀態(tài)。所以語(yǔ)言級(jí)別的異常允許你在模塊邊界上設(shè)定Error Boundary并且在非核心模塊失敗的時(shí)候仍然保持程序運(yùn)行,語(yǔ)言級(jí)別的異常也允許你在核心模塊,例如引擎的出口設(shè)置Error Boundary,并且在出錯(cuò)的情況下reset引擎到一個(gè)干凈的初始狀態(tài)。簡(jiǎn)言之,語(yǔ)言級(jí)別的異常讓程序更健壯。
  • 理想情況下,我們應(yīng)該、并且能夠通過ENSURE來(lái)避免幾乎所有“硬”異常的發(fā)生。但程序員也是人,只要是代碼就會(huì)有疏忽,萬(wàn)一真的發(fā)生了“硬”異常怎么辦?對(duì)于輸入法而言,即便出現(xiàn)了這種很遺憾的情況我們?nèi)匀徊幌M愕乃拗鞒绦虮罎?#xff0c;但另一方面,由于“硬”異常使得程序已經(jīng)處于不可知的狀態(tài),我們無(wú)法對(duì)程序以后的執(zhí)行作出任何的保障,所以當(dāng)我們的錯(cuò)誤邊界處捕獲這類異常的時(shí)候,我們會(huì)設(shè)置一個(gè)全局的flag,disable整個(gè)的輸入法內(nèi)核,從用戶的角度來(lái)看就是輸入法不工作了,但一來(lái)宿主程序沒有崩潰,二來(lái)你的所有鍵敲擊都會(huì)被直接被宿主程序響應(yīng),就像沒有打開輸入法的時(shí)候一樣。這樣一來(lái)即便在最壞的情況之下,宿主程序仍然有機(jī)會(huì)去保存數(shù)據(jù)并體面退出。

    所以,綜上所述,通過基于C++異常的ENSURE宏,我們實(shí)現(xiàn)了以下幾個(gè)目的:

  • 極其廉價(jià)的錯(cuò)誤檢查和匯報(bào)(和assert一樣廉價(jià),卻沒有assert的諸多缺陷):尤其是對(duì)于快速開發(fā)來(lái)說,既不可忽視錯(cuò)誤,又不想在錯(cuò)誤匯報(bào)和處理這種(非正事)上消耗太多的時(shí)間,這種時(shí)候ENSURE是完美的方案。
  • 豐富的錯(cuò)誤信息。
  • 不可忽視的錯(cuò)誤:編譯器會(huì)忠實(shí)負(fù)責(zé)stack unwind,不會(huì)讓一個(gè)錯(cuò)誤被藏著掖著,最后以二階三階錯(cuò)誤的方式表現(xiàn)出來(lái),給診斷造成麻煩。
  • 健壯性:看上去到處拋異常會(huì)讓人感覺程序不夠健壯,而實(shí)際上恰恰相反,如果程序真的有bug,那么一定會(huì)浮現(xiàn)出來(lái),即便你不用異常,也并沒有消除錯(cuò)誤本身,遲早錯(cuò)誤會(huì)以其他形式表現(xiàn)出來(lái),在程序的世界里,有錯(cuò)誤是永遠(yuǎn)藏不住的。而異常作為語(yǔ)言級(jí)別支持的錯(cuò)誤匯報(bào)和處理機(jī)制,擁有同步和自動(dòng)清理的特點(diǎn),支持模塊邊界的錯(cuò)誤屏障,支持在錯(cuò)誤發(fā)生的時(shí)候重置程序到干凈的狀態(tài),從而最大限度保證程序的正常運(yùn)行。如果不用異常而用error code,只要疏忽檢查一點(diǎn),遲早會(huì)導(dǎo)致“硬”異常,而一旦后者發(fā)生,基本剩下的也別指望程序還能正常工作了,能做得最負(fù)責(zé)任的事情就是別導(dǎo)致宿主崩潰。
  • 另一方面,如果使用error code而不用異常來(lái)匯報(bào)和處理錯(cuò)誤,當(dāng)然也是可以達(dá)到上這些目的,但會(huì)給開發(fā)帶來(lái)高昂的代價(jià),設(shè)想你需要把每個(gè)函數(shù)的返回值騰出來(lái)用作HRESULT,然后在每個(gè)函數(shù)返回的時(shí)候必須check其返回錯(cuò)誤,并且如果自己不處理必須勤勤懇懇地轉(zhuǎn)發(fā)給上層。所以對(duì)于error code來(lái)說,要想快就必須犧牲周密的檢查,要想周密的檢查就必須犧牲編碼時(shí)間來(lái)做“不相干”的事情(對(duì)于需要周密檢查的錯(cuò)誤敏感的應(yīng)用來(lái)說,最后會(huì)搞到代碼里面一眼望過去盡是各種if-else的返回值錯(cuò)誤檢查,而真正干活的代碼卻縮在不起眼的角落,看過win32代碼的同學(xué)應(yīng)該都會(huì)有這個(gè)體會(huì))。而只有使用異常和ENSURE,才真正實(shí)現(xiàn)了既幾乎不花任何額外時(shí)間、又不至于漏過任何一個(gè)第一手錯(cuò)誤的目的。

    最后簡(jiǎn)單提一下異常的性能問題,現(xiàn)代編譯器對(duì)于異常處理的實(shí)現(xiàn)已經(jīng)做到了在happy path上幾乎沒有開銷,對(duì)于絕大多數(shù)應(yīng)用層的程序來(lái)說,根本無(wú)需考慮異常所帶來(lái)的可忽視的開銷。在我們的對(duì)速度要求很敏感的輸入法程序中,做performance profiling的時(shí)候根本看不到異常帶來(lái)任何可見影響(除非你亂用異常,例如拿異常來(lái)取代正常的bool返回值,或者在loop里面拋接異常,等等)。具體的可以參考GoingNative2012@Channel9上的The Importance of Being Native的1小時(shí)06分處。

    C++11的其他特性的運(yùn)用

    資源管理和錯(cuò)誤處理是現(xiàn)代C++風(fēng)格最醒目的標(biāo)志,接下來(lái)再說一說C++11的其他特性在我們項(xiàng)目中的使用。

    首先還是lambda,lambda除了配合ON_SCOPE_EXIT使用威力無(wú)窮之外,還有一個(gè)巨大的好處,就是創(chuàng)建on-the-fly的tasks,交給另一個(gè)線程去執(zhí)行,或者創(chuàng)建一個(gè)delegate交給另一個(gè)類去調(diào)用(像C#的event那樣)。(當(dāng)然,lambda使得STL變得比原來(lái)易用十倍這個(gè)事情就不說了,相信大家都知道了),例如我們有一個(gè)BackgroundWorker類,這個(gè)類的對(duì)象在內(nèi)部維護(hù)一個(gè)線程,這個(gè)線程在內(nèi)部有一個(gè)message loop,不斷以Thread Message的形式接收別人委托它執(zhí)行的一段代碼,如果是委托的同步執(zhí)行的任務(wù),那么委托(調(diào)用)方便等在那里,直到任務(wù)被執(zhí)行完,如果執(zhí)行過程中出現(xiàn)任何錯(cuò)誤,會(huì)首先被BackgroundWorker捕獲,然后在調(diào)用方線程上重新拋出(利用C++11的std::exception_ptr、std::current_exception()以及std::rethrow_exception())。BackgroundWorker的使用方式很簡(jiǎn)單:

    bgWorker.Send([&] { .. /* do something */ });

    有了lambda,不僅Send的使用方式像上面這樣直觀,Send本身的實(shí)現(xiàn)也變得很優(yōu)雅:

    bool Send(std::function<void()> action) {HANDLE done = CreateEvent(NULL, TRUE, FALSE, NULL);std::exception_ptr pCxxException;unsigned int win32ExceptionCode = 0;EXCEPTION_POINTERS* win32ExceptionPointers = nullptr;std::function<void()> synchronousAction = [&]{ON_SCOPE_EXIT([&] {SetEvent(done);});AllExceptionsBoundary(action,[&](std::exception_ptr e){ pCxxException = e; },[&](unsigned int code, EXCEPTION_POINTERS* ep){ win32ExceptionCode = code;win32ExceptionPointers = ep; });};bool r = Post(synchronousAction);if(r){WaitForSingleObject(done, INFINITE);CloseHandle(done);// propagate error (if any) to the calling threadif(!(pCxxException == nullptr)){std::rethrow_exception(pCxxException);}if(win32ExceptionPointers){RaiseException(win32ExceptionCode, ..);}}return r; }

    這里我們先把外面?zhèn)鬟M(jìn)來(lái)的function wrap成一個(gè)新的lambda function,后者除了負(fù)責(zé)調(diào)用前者之外,還負(fù)責(zé)在調(diào)用完了之后flag一個(gè)event從而實(shí)現(xiàn)同步等待的目的,另外它還負(fù)責(zé)捕獲任務(wù)執(zhí)行中可能發(fā)生的錯(cuò)誤并保存下來(lái),留待后面在調(diào)用方線程上重新raise這個(gè)錯(cuò)誤。

    另外一個(gè)使用lambda的例子是:由于我們項(xiàng)目中需要解析XML的地方用的是MSXML,而MSXML很不幸是個(gè)COM組件,COM組件要求生存在特定的Apartment里面,而輸入法由于是被動(dòng)加載的dll,其主線程不是輸入法本身創(chuàng)建的,所以主線程到底屬于什么Apartment不由輸入法來(lái)控制,為了確保萬(wàn)無(wú)一失,我們便將MSXML host在上文提到的一個(gè)專屬的BackgroundWorker對(duì)象里面,由于BackgroundWorker內(nèi)部會(huì)維護(hù)一個(gè)線程,這個(gè)線程的apartment是由我們?nèi)珯?quán)控制的。為此我們給MSXML創(chuàng)建了一個(gè)wrapper類,這個(gè)類封裝了這些實(shí)現(xiàn)細(xì)節(jié),只提供一個(gè)簡(jiǎn)便的使用接口:

    XMLDom dom; dom.LoadXMLFile(xmlFilePath);dom.Visit([&](std::wstring const& elemName, IXMLDOMNode* elem) {if(elemHandlers.find(elemName) != elemHandlers.end()){elemHandlers[elemName](elem);} });

    基于上文提到的BackgroundWorker的輔助,這個(gè)wrapper類的實(shí)現(xiàn)也變得非常簡(jiǎn)單:

    void Visit(TNodeVisitor const& visitor) {bgWorker_.Send([&] {ENSURE(pXMLDom_ != NULL);IXMLDOMElement* root;ENSURE(pXMLDom_->get_documentElement(&root) == S_OK);InternalVisit(root, visitor);}); }

    所有對(duì)MSXML對(duì)象的操作都會(huì)被Send到host線程上去執(zhí)行。

    另一個(gè)很有用的feature就是static_assert,例如我們?cè)贓NSURE宏的定義里面就有一行:

    static_assert(std::is_same<decltype(expr), bool>::value, "ENSURE(expr) can only be used on bool expression");

    避免調(diào)ENSURE(expr)的時(shí)候expr不是bool類型,確給隱式轉(zhuǎn)換成了bool類型,從而出現(xiàn)很隱蔽的bug。

    至于C++11的Move Semantics給代碼帶來(lái)的變化則是潤(rùn)物細(xì)無(wú)聲的:你可以不用擔(dān)心返回vector, string等STL容易的性能問題了,代碼的可讀性會(huì)得到提升。

    最后,由于VS2010 SP1并沒有實(shí)現(xiàn)全部的C++11語(yǔ)言特性,所以我們也并沒有用上全部的特性,不過話說回來(lái),已經(jīng)被實(shí)現(xiàn)的特性已經(jīng)相當(dāng)有用了。

    代碼質(zhì)量

    在各種長(zhǎng)期和短期壓力之下寫代碼,當(dāng)然代碼質(zhì)量是重中之重,尤其是對(duì)于C++代碼,否則各種積累的技術(shù)債會(huì)越壓越重。對(duì)于創(chuàng)新項(xiàng)目而言,代碼基處于不停的演化當(dāng)中,一開始的時(shí)候什么都不是,就是一個(gè)最簡(jiǎn)單的骨架,然后逐漸出現(xiàn)一點(diǎn)prototype的樣子,隨著不斷的加進(jìn)新的feature,再不斷重構(gòu),抽取公共模塊,形成concept和abstraction,isolate接口,拆分模塊,最終prototype演變成product。關(guān)于代碼質(zhì)量的書很多,有一些寫得很好,例如《The Art of Readable Code》,《Clean Code》或者《Implementation Patterns》。這里沒有必要去重復(fù)這些書已經(jīng)講得非常好的技術(shù),只說說我認(rèn)為最重要的一些高層的指導(dǎo)性原則:

  • 持續(xù)重構(gòu):避免代碼質(zhì)量無(wú)限滑坡的辦法就是持續(xù)重構(gòu)。持續(xù)重構(gòu)是The Boy Scout Rule的一個(gè)推論。離開一段代碼的時(shí)候永遠(yuǎn)保持它比上次看到的時(shí)候更干凈。關(guān)于重構(gòu)的書夠多的了,細(xì)節(jié)的這里就不說了,值得注意的是,雖然重構(gòu)有一些通用的手法,但具體怎么重構(gòu)很多時(shí)候是一個(gè)領(lǐng)域相關(guān)的問題,取決于你在寫什么應(yīng)用,有些時(shí)候,重構(gòu)就是重設(shè)計(jì)。例如我們的代碼基當(dāng)中曾經(jīng)有一個(gè)tricky的設(shè)計(jì),因?yàn)橄喈?dāng)tricky,導(dǎo)致在后來(lái)的一次代碼改動(dòng)中產(chǎn)生了一個(gè)很隱蔽的regression,這使得我們重新思考這個(gè)設(shè)計(jì)的實(shí)現(xiàn),并最終決定換成另一個(gè)(很遺憾仍然還是tricky的)實(shí)現(xiàn),后者雖然仍然tricky(總會(huì)有不得已必須tricky的地方),但是卻有一個(gè)好處:即便以后代碼改動(dòng)的過程中又涉及到了這塊代碼并且又導(dǎo)致了regression,那么至少所導(dǎo)致的regression將不再會(huì)是隱蔽的,而是會(huì)很明顯。
  • KISS:KISS是個(gè)被說爛了的原則,不過由于”Simple”這個(gè)詞的定義很主觀,所以KISS并不是一個(gè)很具有實(shí)踐指導(dǎo)意義的原則。我認(rèn)為下面兩個(gè)原則要遠(yuǎn)遠(yuǎn)有用得多: 1) YAGNI:You Ain’t Gonna Need It。不做不必要的實(shí)現(xiàn),例如不做不必要的泛化,你的目的是寫應(yīng)用,不是寫通用庫(kù)。尤其是在C++里面,要想寫通用庫(kù)往往會(huì)觸及到這門語(yǔ)言最黑暗的部分,是個(gè)時(shí)間黑洞,而且由于語(yǔ)言的不完善往往會(huì)導(dǎo)致不完備的實(shí)現(xiàn),出現(xiàn)使用上的陷阱。2) 代碼不應(yīng)該是沒有明顯的bug,而應(yīng)該是明顯沒有bug:這是一條很具有指導(dǎo)意義的原則,你的代碼是否一眼看上去就明白什么意思,就確定沒有bug?例如Haskell著名的quicksort就屬于明顯沒有bug。為了達(dá)到這個(gè)目的,你的代碼需要滿足很多要求:良好的命名(傳達(dá)意圖),良好的抽象,良好的結(jié)構(gòu),簡(jiǎn)單的實(shí)現(xiàn),等等。最后,KISS原則不僅適用于實(shí)現(xiàn)層面,在設(shè)計(jì)上KISS則更加重要,因?yàn)樵O(shè)計(jì)是決策的第一環(huán),一個(gè)設(shè)計(jì)可能需要三四百行代碼,而另一個(gè)設(shè)計(jì)可能只需要三四十行代碼,我們就曾遇到過這樣的情況。一個(gè)糟糕的設(shè)計(jì)不僅制造大量的代碼和bug(代碼當(dāng)然是越少越好,代碼越少bug就越少),成為后期維護(hù)的負(fù)擔(dān),侵入式的設(shè)計(jì)還會(huì)增加模塊間的粘合度,導(dǎo)致被這個(gè)設(shè)計(jì)拖累的代碼像滾雪球一樣越來(lái)越多,所以code review之前更重要的還是要做design review,前面決策做錯(cuò)了后面會(huì)越錯(cuò)越離譜。
  • 解耦原則:這個(gè)就不多說了,都說爛了。不過具體怎么解耦很多時(shí)候還是個(gè)領(lǐng)域相關(guān)的問題。雖然有些通用范式可循。
  • Best Practice Principle:對(duì)于C++開發(fā)來(lái)說尤其重要,因?yàn)樵贑++里面,同一件事情往往有很多不同的(但同樣都有缺陷的)實(shí)現(xiàn),而實(shí)現(xiàn)的成本往往還不低,所以C++社群多年以來(lái)一直在積淀所謂的Best Practices,其中的一個(gè)子集就是Idioms(慣用法),由于C++的學(xué)習(xí)曲線較為陡峭,悶頭寫一堆(有缺陷)的實(shí)現(xiàn)的成本很高,所以在一頭扎進(jìn)去之前先大概了解有哪些Idioms以及各自適用的場(chǎng)景就變得很有必要。站在別人的肩膀上好過自己掉坑里。
  • 對(duì)了,這篇文章從頭到尾是用英庫(kù)拼音輸入法寫的。最后貼個(gè)圖:(http://pinyin.engkoo.com/)


    [我們?cè)谡腥薦?由于我們之前的star intern祁航同學(xué)離職去國(guó)外讀書了,所以再次尋找實(shí)習(xí)生一枚,參與英庫(kù)拼音輸入法client端的開發(fā),要求如下:

  • 扎實(shí)的win32系統(tǒng)底層知識(shí)。
  • 扎實(shí)的C++功底,對(duì)現(xiàn)代C++風(fēng)格有一定的認(rèn)識(shí)(了解C++11更好)。
  • 理解編寫干凈、可讀、高效的代碼的重要性。(最好讀過clean code或implementation patterns)
  • 對(duì)新技術(shù)有熱忱,有很強(qiáng)的學(xué)習(xí)能力;善于溝通,喜歡討論。
  • 有興趣的請(qǐng)發(fā)簡(jiǎn)歷至liuweipeng@outlook.com。此外,為了節(jié)省我們雙方的時(shí)間,我希望你在發(fā)簡(jiǎn)歷的同時(shí)回答以下兩個(gè)問題:

  • 簡(jiǎn)要介紹一下你在大學(xué)里面學(xué)習(xí)技術(shù)的歷程,例如看過那些書,經(jīng)常上那些地方查資料,(如果有)參加過哪些開源項(xiàng)目,(如果有)寫過哪些技術(shù)文章,等等。
  • 有針對(duì)性地對(duì)于上面的要求中提到的幾點(diǎn)做簡(jiǎn)要的介紹:例如對(duì)win32有哪些了解,C++方面的技術(shù)儲(chǔ)備,以及對(duì)高質(zhì)量代碼的認(rèn)識(shí),等等。
  • from:?http://mindhacks.cn/2012/08/27/modern-cpp-practices/

    總結(jié)

    以上是生活随笔為你收集整理的C++11(及现代C++风格)和快速迭代式开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    波多野结衣一区二区 | 久久人人爽人人片av | 免费a级毛片在线看 | 99 精品 在线 | 视频在线国产 | 久久久久久高潮国产精品视 | 在线视频中文字幕一区 | 国产又粗又猛又黄视频 | 国产精品自产拍在线观看桃花 | 国产精品 国产精品 | 欧美成人高清 | 色狠狠久久av五月综合 | 国产黄av| 午夜视频在线观看一区二区三区 | 在线视频国产区 | 91视频午夜 | 免费av试看 | 精品久久网 | 黄色成人免费电影 | 免费在线色视频 | 免费看片成年人 | 日韩a在线看| 最近中文字幕国语免费av | 色综合色综合色综合 | 日韩精品中文字幕av | 精品久久久成人 | 狠狠色香婷婷久久亚洲精品 | 国产黄色大片免费看 | 中文字幕中文字幕在线中文字幕三区 | 欧美精品亚洲二区 | 日韩精品视频一二三 | 91精品伦理 | 91一区一区三区 | 九热精品 | 免费毛片aaaaaa| 国产高清在线视频 | av成人动漫在线观看 | 韩国三级在线一区 | 日韩一区二区免费在线观看 | 日韩激情在线视频 | 国产一在线精品一区在线观看 | 999久久久久久 | 国产99久久久欧美黑人 | 日韩欧美一区二区在线 | 亚洲精品美女视频 | 五月婷婷丁香综合 | 在线天堂日本 | 国产免费三级在线观看 | 国产精品久久久久久久久久直播 | www.狠狠色| 免费a一级| 国产网站av| 四虎影视成人永久免费观看亚洲欧美 | 91麻豆精品国产91久久久久久 | 成人欧美亚洲 | 开心激情五月婷婷 | 精品乱码一区二区三四区 | 69av视频在线观看 | 女人久久久久 | 中文字幕av播放 | 久久超碰网 | 国语精品免费视频 | 一级免费观看 | av高清免费在线 | 久久国产精品精品国产色婷婷 | 国产精品美女久久久网av | 免费黄色网址网站 | 久久黄色免费视频 | 国产成人一区二区三区免费看 | 免费亚洲视频 | 久久久久久久免费看 | 久久99国产精品自在自在app | 国产免费av一区二区三区 | 亚洲精品在线免费播放 | 2024av | 91精品国产99久久久久久久 | 中文字幕在线一二 | 日本久久电影网 | 不卡精品视频 | 深夜免费福利在线 | 午夜视频导航 | 日韩在线播放av | 久久激情视频网 | 国内精品久久久久久久97牛牛 | 欧美黄色特级片 | 国产精品久久久久久久免费观看 | 久久精品a | 国产丝袜制服在线 | 精品美女在线视频 | 在线免费视频你懂的 | 中文字幕在线网 | 97碰碰碰 | 成人超碰97 | 日韩在线高清 | 99精品免费网 | 婷婷亚洲五月 | 天天插综合 | 99re亚洲国产精品 | 久久久香蕉视频 | 日本久久影视 | 亚洲国产成人高清精品 | 500部大龄熟乱视频 欧美日本三级 | 日韩成人精品一区二区 | 天天操欧美| 97超碰在线久草超碰在线观看 | 九九久久成人 | av成人在线观看 | 久久久免费在线观看 | 国产在线观看xxx | 国产成人av电影在线观看 | 中文字幕高清av | 99精品久久只有精品 | 天天操天天射天天插 | 久久精品一二区 | 91日韩精品| 中文字幕五区 | 波多野结衣视频一区二区三区 | 亚洲天堂自拍视频 | 99精品一区| 日本一区二区三区免费观看 | 91精品国产乱码在线观看 | 中文字幕乱码电影 | 国产精品手机在线播放 | 91丨九色丨国产在线观看 | 九九免费在线观看视频 | 久久深爱网 | 伊人六月| 国产高清 不卡 | 五月天婷婷在线视频 | 99超碰在线播放 | 黄色免费观看网址 | 欧美性爽爽 | 午夜黄色大片 | 国产精品va在线观看入 | 久久久久久久久影院 | 丁香导航 | 免费色婷婷 | 精品国产伦一区二区三区观看说明 | 亚洲高清在线精品 | 五月开心激情网 | 亚洲精品99久久久久久 | 国产精品九九久久99视频 | 超碰免费成人 | 四虎在线免费观看视频 | 久久精品香蕉视频 | 国产免费亚洲 | 91精品国产成人www | 国产中文字幕视频 | 国产精品一区欧美 | 69视频在线| 色资源二区在线视频 | 天天av资源 | 国产视频亚洲 | 西西444www大胆无视频 | 亚洲国产日韩欧美在线 | 日韩欧美在线观看一区 | 久久综合成人网 | 免费看黄色91 | 日韩中文字幕免费电影 | 日韩在线观看视频一区二区三区 | 91资源在线免费观看 | 狠狠干网站 | 欧美二区在线播放 | 久久久精品影视 | 色五月成人 | 在线观看成人小视频 | 国产免费a | 国产伦精品一区二区三区免费 | 97超碰在线视 | 亚洲日本在线一区 | 欧美一区在线观看视频 | 欧美一级片免费在线观看 | 碰天天操天天 | 五月开心综合 | 久久久久久久久久影院 | 天海翼一区二区三区免费 | 日本不卡123 | 91丨九色丨高潮丰满 | 91精品成人 | 精品国产乱码一区二区三区在线 | 超碰国产在线播放 | 六月色婷婷 | 免费视频在线观看网站 | 国产a级片免费观看 | 日韩中字在线 | 中文字幕在线观看第一区 | 97超碰在线免费 | 亚洲午夜久久久久久久久久久 | 欧美日韩一区二区免费在线观看 | 探花视频在线版播放免费观看 | 国产探花视频在线播放 | 91久久在线观看 | 人人cao| 麻豆传媒精品 | 色婷婷激情五月 | 亚洲狠狠操| 国产精品99久久久精品免费观看 | 国产精品18毛片一区二区 | 午夜精品视频免费在线观看 | 免费观看全黄做爰大片国产 | 精品久久综合 | 一级特黄av | 亚洲精品国产精品国产 | 久久精品这里热有精品 | 久草热久草视频 | 欧美日韩精品免费观看视频 | 久久激情视频 久久 | 久久精品激情 | 久久精品中文字幕一区二区三区 | 久久这里只有精品视频99 | 最新av电影网址 | 国产日韩中文字幕 | 在线视频电影 | 99精品色 | 久草av在线播放 | 中文字幕中文字幕在线中文字幕三区 | 国产成人三级 | 免费网站v | 粉嫩av一区二区三区入口 | 国产精品av免费观看 | 国内久久久 | 最新av在线播放 | 中文字幕一区二区三区视频 | 高清av网 | 狠狠操电影网 | 亚洲精品99久久久久中文字幕 | 国产精品免费久久久久 | 国产黄免费看 | 香蕉久久国产 | 四虎影视8848aamm | 免费在线观看污 | 在线亚洲成人 | 狠狠躁夜夜a产精品视频 | 国产破处视频在线播放 | 久久99国产精品免费网站 | 亚洲精品一区二区久 | 9ⅰ精品久久久久久久久中文字幕 | 精品久久久免费视频 | 国产精品五月天 | 国产真实精品久久二三区 | 最近的中文字幕大全免费版 | 欧美精品一区二区在线播放 | 精品在线免费视频 | 国内精品视频在线 | 国产专区日韩专区 | 丁香婷婷综合色啪 | 日韩在线视频一区二区三区 | 中文在线最新版天堂 | 色偷偷88欧美精品久久久 | 欧美日韩久 | 成片人卡1卡2卡3手机免费看 | 亚洲干视频在线观看 | 国产色在线视频 | 午夜婷婷在线观看 | 色资源在线观看 | 欧美少妇xxxxxx | 一区二区三区四区五区六区 | 免费看黄色小说的网站 | 久久99久久99精品中文字幕 | 久久成电影| 欧美日韩免费视频 | av中文电影 | 国产午夜在线观看视频 | 黄色在线网站噜噜噜 | 在线国产视频观看 | 久久久久这里只有精品 | 一区二区三区四区在线 | 成人午夜久久 | 天天干国产| 中文字幕在线视频一区 | 久久久综合色 | 亚洲国产一区在线观看 | 久久午夜国产精品 | www.99热精品| 97电影手机| 日韩av电影中文字幕在线观看 | 国产99一区 | 日日日爽爽爽 | 国产免费亚洲 | 亚洲激情在线播放 | www免费看 | 日韩成人一级大片 | 国产亚州精品视频 | 久久免费99精品久久久久久 | 91看片在线免费观看 | 麻豆视频免费看 | 亚洲国产精品女人久久久 | av久久在线 | 91九色免费视频 | 精品视频在线视频 | 日本在线观看视频一区 | 欧美日本国产在线观看 | av一区二区三区在线观看 | 国产精品久久麻豆 | 99视频精品在线 | 久久国产美女视频 | 欧洲高潮三级做爰 | avove黑丝| 免费合欢视频成人app | 99中文字幕在线观看 | 国产精品成人一区二区三区吃奶 | 国产一区在线视频 | 日韩黄色一级电影 | 五月天天色| 久久艹影院| 亚洲日韩欧美一区二区在线 | 国产成人久久久久 | 成人久久久久 | 日韩美女一级片 | 91网址在线看 | 韩国三级一区 | 免费在线国产精品 | 精品国产日本 | 一区二区不卡 | 天天拍天天色 | 在线看成人av | a特级毛片 | 四虎最新域名 | 99在线观看| 久操免费视频 | 91网址在线 | 另类五月激情 | 91资源在线播放 | 亚洲爱爱视频 | 黄色一级在线观看 | 国产在线高清视频 | 亚洲精品美女久久久久 | 国产麻豆精品在线观看 | 国产精品理论在线观看 | 少妇av片 | 成人国产一区 | 亚洲综合在线观看视频 | 欧美黑人猛交 | 色九九在线 | 一区二区三区日韩在线观看 | 成人理论在线观看 | 久久久久久久久久久免费视频 | 欧美精品久久久久久 | 91亚洲网 | 丁香婷婷久久 | 综合久久久久 | 久久69av | 91视频链接 | 国产黄色片免费在线观看 | 成人毛片在线视频 | 天天操天天曰 | 成人久久久久久久久 | 91亚洲视频在线观看 | 在线亚洲人成电影网站色www | 人人澡人人澡人人 | 国产成人三级一区二区在线观看一 | 永久免费毛片 | 欧美日韩免费在线视频 | 在线性视频日韩欧美 | 久久久蜜桃一区二区 | 久久香蕉国产精品麻豆粉嫩av | 精品国产电影一区 | 色五月成人 | 婷婷六月综合网 | 91九色视频 | 伊人永久| 久久天天躁狠狠躁亚洲综合公司 | 在线色资源 | 欧美精品三级在线观看 | www.福利视频 | 久爱综合 | 视频国产精品 | 婷婷在线精品视频 | 在线国产99 | 在线观看免费成人av | 日韩在线观看视频一区二区三区 | 国产伦精品一区二区三区在线 | 九九九九色| 一级欧美日韩 | 日韩黄在线观看 | 操操操av| 国产999精品| 久久精品国产一区二区三区 | 欧美aaa级片 | 丁香花中文在线免费观看 | 久久爱www.| 91福利影院在线观看 | 中文字幕在线观看国产 | 成人av资源网站 | 欧日韩在线视频 | 亚洲无线视频 | 国产精品刺激对白麻豆99 | 久久影院午夜论 | 超碰在线国产 | 日韩二区三区在线 | 亚洲涩涩色 | 精品在线观看视频 | 国产一二区在线观看 | 欧美另类视频 | 日韩免费成人av | 999成人 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 久久超碰免费 | 在线观看日韩中文字幕 | 国产福利av在线 | 国产日韩欧美在线观看 | 欧美一区三区四区 | 亚洲精选国产 | 欧美少妇xxx | 久久综合免费视频 | 99在线免费视频观看 | 免费av在| 免费黄色av. | 特级毛片网站 | 欧美精品一区二区蜜臀亚洲 | 最新av网址在线观看 | 999久久久久久久久久久 | 日韩二区在线观看 | 91色综合 | 国产精品黄色av | 欧美色操 | 久久久久久片 | 一级黄色电影网站 | 婷婷丁香花 | 国产精品久久免费看 | 日韩免费区 | 91成人精品一区在线播放 | 亚洲专区欧美专区 | 欧美国产日韩一区二区 | 91资源在线播放 | 国产原创在线 | 97碰碰碰| 人人草人人草 | 久草免费在线观看 | 九九免费在线观看 | 在线99| 99re久久资源最新地址 | 国产 成人 久久 | 能在线观看的日韩av | 免费在线观看av网站 | 成人免费 在线播放 | 麻花豆传媒mv在线观看网站 | 久草视频免费播放 | 久艹在线免费观看 | 最近免费在线观看 | 国产精品专区h在线观看 | 国产一级片观看 | 久久综合久久综合九色 | 成人午夜在线电影 | 中文字幕在线观看播放 | 国产高清无av久久 | 色综合久久88色综合天天免费 | 婷婷久久综合九色综合 | 国产不卡在线观看视频 | 久久首页| 91视频网址入口 | 国产精品麻豆一区二区三区 | www色av| 日韩一区二区三区免费视频 | 欧洲色吧 | 男女免费视频观看 | japanesexxxhd奶水| 精品v亚洲v欧美v高清v | 免费在线观看污 | 91九色视频观看 | 久草在线中文888 | 日本美女xx| 成人国产精品电影 | 国产成人精品在线播放 | 国产中文字幕一区二区 | 在线亚洲成人 | .国产精品成人自产拍在线观看6 | 手机看片| 色综合 久久精品 | 99精品视频网站 | 久久99久久99精品免观看粉嫩 | 91av美女| 日韩,中文字幕 | 亚洲国产成人在线观看 | 天天躁日日躁狠狠躁av麻豆 | 天天弄天天操 | 毛片的网址| 精品久久国产一区 | 久久精品视频免费 | 中文av一区二区 | 日韩久久影院 | 中文字幕日韩免费视频 | 久久国产露脸精品国产 | 91字幕 | 成人av片免费观看app下载 | 午夜在线资源 | 婷婷激情欧美 | 欧美国产三区 | 在线观看岛国 | 国产裸体视频网站 | 日韩视频免费播放 | 久久免费视频一区 | 久久免费视频网站 | 在线观看中文字幕dvd播放 | 国产在线看| 麻豆传媒精品 | 国产精品久久精品 | 国产精品福利无圣光在线一区 | 国产日韩欧美在线 | 亚洲精品在线观看的 | 一区二区不卡视频在线观看 | 国产精品初高中精品久久 | 一级一级一片免费 | 久久免费福利视频 | 日韩va亚洲va欧美va久久 | 亚洲精品xxxx | 久久免费精品一区二区三区 | 国产九九九九九 | 色综合天天综合网国产成人网 | 欧美一区日韩精品 | 一本一道波多野毛片中文在线 | 色综合国产 | 国产成人精品电影久久久 | 日韩欧美高清视频在线观看 | 99久久毛片 | 久久久久黄| 亚洲国产影院av久久久久 | 奇米导航| 成人精品999 | 在线看免费 | 91在线视频播放 | 国产亚洲成av人片在线观看桃 | 五月开心色 | 91在线免费观看网站 | 亚洲毛片一区二区三区 | 国产午夜免费视频 | 国产在线看 | 天天躁天天躁天天躁婷 | 国产在线精品视频 | 欧美日韩另类在线 | av免费在线播放 | 国产成人福利在线 | 三级av片 | 日韩欧美电影在线 | 亚洲第一av在线 | 色婷婷色 | 亚洲精品在线观 | 国产精品一区二区久久精品爱涩 | 美女网站色免费 | 久久久久久久18 | 欧美日在线 | 国产精品va在线播放 | 国产中文字幕第一页 | 天天操天天射天天插 | 午夜99| 在线免费观看羞羞视频 | 中文av日韩 | 91精品视屏 | 亚洲精品中文字幕视频 | 久久成人精品电影 | 91精品国产综合久久久久久久 | 91亚洲精品国偷拍 | 日韩精品你懂的 | 亚洲精品99久久久久中文字幕 | 欧美精品一区二区蜜臀亚洲 | 久久一精品 | 狠狠色狠狠综合久久 | 国产精品毛片一区二区三区 | 亚洲干| 国色天香永久免费 | 日韩av影视 | 久久r精品 | 国产精品理论在线观看 | 中文字幕免费高 | 欧美精品网站 | 激情五月在线视频 | 一区二区三区免费播放 | 成人黄色电影在线 | 色视频一区 | 免费日韩 精品中文字幕视频在线 | 热久精品 | 亚洲 欧美 日韩 综合 | 草在线| 国产一级淫片免费看 | 99精品国产一区二区三区麻豆 | 免费视频97 | 欧美韩国日本在线 | 久久tv| 日韩中文久久 | 亚洲六月丁香色婷婷综合久久 | 五月天综合激情网 | 丁香综合 | 久久婷婷一区二区三区 | 手机在线欧美 | 男女激情麻豆 | 国产精品欧美激情在线观看 | 999在线视频| 国产精品久久久视频 | 日本性xxx | 911精品美国片911久久久 | 三级免费黄色 | 免费69视频 | 伊人婷婷色 | 国产精品一区二区久久 | 色综合久久久久 | 伊人色**天天综合婷婷 | 久久综合欧美精品亚洲一区 | 国产在线精品二区 | 久久精品精品电影网 | 四虎影视成人永久免费观看亚洲欧美 | 成年人在线免费视频观看 | 国产精品一区二 | 91麻豆网 | 亚洲最大成人免费网站 | 日本aaaa级毛片在线看 | 88av视频| 欧美一级片免费播放 | 免费又黄又爽 | 亚洲免费小视频 | 91中文在线| 免费在线观看黄色网 | 久要激情网 | 国产免费小视频 | 天堂av最新网址 | 一级成人免费 | 91精选 | 中文字幕中文字幕在线中文字幕三区 | 久久夜夜夜 | 日本精品在线看 | 天天操天天操天天操天天操天天操天天操 | 国产打女人屁股调教97 | 超级碰碰免费视频 | 狠狠色狠狠色合久久伊人 | 欧美aa一级 | 国产午夜三级一区二区三桃花影视 | 狠狠干夜夜操 | 97精品国产91久久久久久 | 精品国产一区二区在线 | 久久精品欧美一区 | 中文字幕免费看 | 亚洲精品国产欧美在线观看 | 国产成在线观看免费视频 | 91| 亚洲国产精品500在线观看 | 日本午夜免费福利视频 | 中文字幕a在线 | 天天做日日做天天爽视频免费 | 亚洲激情小视频 | 成人av免费 | 日韩电影一区二区在线 | 国产黄色视 | 香蕉蜜桃视频 | 欧美性高跟鞋xxxxhd | 午夜精品久久久久久久99无限制 | 四川妇女搡bbbb搡bbbb搡 | 日韩精品欧美视频 | 精品色999 | 免费看的黄色片 | 国产人成精品一区二区三 | 狠狠久久综合 | 天天干夜夜爽 | 99精品亚洲 | 久久婷婷综合激情 | 狠狠干网 | 91亚洲欧美 | 亚洲视频综合 | 激情五月婷婷综合 | 337p日本大胆噜噜噜噜 | 久草在线观看视频免费 | 日本精品久久久一区二区三区 | 精品国产网址 | 天天操综合 | 亚洲精品国产精品乱码在线观看 | 视频91在线| 九九热久久免费视频 | 欧美日韩亚洲第一页 | 日韩伦理一区二区三区av在线 | 国产小视频在线播放 | 黄色的片子 | 成人一级电影在线观看 | 国产精品每日更新 | www五月| 国产午夜精品久久久久久久久久 | 97精品超碰一区二区三区 | 国产九九九视频 | 久久综合狠狠综合久久激情 | 亚洲精品黄色在线观看 | 国产视频中文字幕在线观看 | 日韩视频一区二区 | 久久99在线视频 | 91精品国产一区 | 国产精品一区二区视频 | 国产福利中文字幕 | 亚洲成年人免费网站 | 在线v片免费观看视频 | 一本一本久久a久久 | 五月婷婷综合在线观看 | 久久成人一区 | 久久久久久久久国产 | 国产一级大片免费看 | a极黄色片| 国产成人中文字幕 | 亚洲色图27p | av电影一区| 久久综合爱 | 激情欧美丁香 | 国内精品久久久久久久久久久久 | 亚洲欧美国内爽妇网 | 精品久久久久久久久亚洲 | 97在线观看免费视频 | 国产精品嫩草69影院 | a黄色影院| 福利视频一区二区 | 成人av在线影视 | 国产黄在线看 | 中文字幕av在线不卡 | 日韩在线视频免费播放 | 一级黄色av | 国产福利在线免费观看 | 久久视频在线视频 | 日韩欧美精品在线 | 狠狠干免费 | 青青草在久久免费久久免费 | 全久久久久久久久久久电影 | 日日操日日操 | 免费看日韩片 | 国产精品久久久久久久久免费看 | 天天操天天操天天 | 亚洲精品视频 | 中文字幕的 | 色婷婷视频 | 日韩高清一二区 | 婷婷伊人综合亚洲综合网 | 欧美狠狠色| 人人搞人人干 | 97免费在线观看 | 久草视频在线资源站 | 免费在线观看不卡av | 色吧av色av| 天天干天天操天天搞 | wwxxxx日本| 国产精品永久久久久久久www | 国产拍揄自揄精品视频麻豆 | 91av视频免费在线观看 | 丁香六月综合网 | 午夜精品久久久久久久久久 | 久久在线 | 97视频在线看 | 国产一区在线观看免费 | 激情综合啪啪 | 精品国产乱码久久久久 | www.五月婷 | 综合激情| 国内精品久久影院 | 久草视频在线免费 | 99久精品视频 | 一区二区三区精品在线视频 | 日本三级香港三级人妇99 | 久久久久久激情 | 91亚洲精品久久久中文字幕 | 丰满少妇对白在线偷拍 | 天天干天天操天天做 | 免费三级黄色 | 又色又爽又黄高潮的免费视频 | 国产精品久久久久久久久久直播 | 天天操天天爽天天干 | 欧美日韩精品电影 | 在线观看的av | 99久久精品国产亚洲 | 午夜久草 | 久久免费试看 | 一级α片 | 国产一级大片免费看 | 精品视频资源站 | 国内精自线一二区永久 | 久久激情综合 | 成年人在线免费看视频 | 五月综合色婷婷 | 中文字幕日本在线观看 | 久久男人视频 | 91精品视频免费在线观看 | 五月婷婷欧美视频 | 国产97在线播放 | 国产精品99久久久久久人免费 | 亚洲国产精品成人精品 | 欧美a级片网站 | 天堂av在线免费观看 | 中文字幕在线视频一区二区三区 | 伊人五月天.com | 正在播放五月婷婷狠狠干 | 日日夜夜网站 | 久久久精品免费看 | 五月激情五月激情 | 亚洲精品视频第一页 | 日本女人b | 二区三区毛片 | 天天干夜夜想 | 日韩精品亚洲专区在线观看 | 中文不卡视频 | 蜜臀av性久久久久av蜜臀妖精 | 欧美日韩国产一二 | 在线亚洲午夜片av大片 | 天天综合网入口 | 成人精品亚洲 | 欧美日韩国产欧美 | 免费观看黄色12片一级视频 | 成人午夜片av在线看 | 日本久久久久 | 久久久久一区二区三区四区 | 五月天综合激情网 | 97色资源 | 国产免费久久精品 | 天天做天天爱天天爽综合网 | 中文字幕视频一区 | 久久精品视频在线观看 | 久久精品一区二区三区国产主播 | 东方av免费在线观看 | 天天干,天天射,天天操,天天摸 | 精品久久福利 | 国产一区二区电影在线观看 | 成人动漫视频在线 | 狠狠躁夜夜av| 中文字幕成人一区 | 午夜私人影院 | 久久尤物电影视频在线观看 | 欧美性生活免费看 | 91视频免费播放 | 丁香五月缴情综合网 | 精品人人人| 亚洲www天堂com | 日韩精品在线看 | 成人av直播 | 亚洲国产精品va在线看黑人动漫 | 夜夜躁日日躁狠狠久久av | 国内精品在线看 | 亚洲欧美日韩国产一区二区三区 | 美女视频黄免费 | 国产一线在线 | 九九热免费在线观看 | 日日夜夜骑 | 亚洲精品国产区 | 欧美色一色 | 月丁香婷婷 | 国产精品视频全国免费观看 | 亚洲午夜精品一区二区三区电影院 | 日本亚洲国产 | 日免费视频 | 国产又粗又猛又色又黄网站 | 久久久久久久久久久久久久av | 黄色av一区二区三区 | 亚洲一区二区三区91 | 在线日韩亚洲 | 日韩一级成人av | www婷婷 | 91免费网址 | 久久成人毛片 | 91成人在线观看喷潮 | 亚洲黄色免费在线 | 久久精品女人毛片国产 | 男女视频91 | 欧美国产高清 | 天天天天天天天天操 | 99国产精品久久久久久久久久 | 国产精品久久久久久一区二区三区 | 色噜噜噜 | 又色又爽又激情的59视频 | 欧美九九九 | 久久久久久久久久久免费 | 日韩高清精品一区二区 | 九九热在线观看 | 国内久久精品 | 国产精品久久久久久久久久久久午夜 | www.天天色 | 99视频在线免费播放 | 色久天| 亚洲国产日韩欧美 | 美女视频黄是免费的 | 久久视频6 | 日韩在线一区二区免费 | 精品国产一区二区三区不卡 | 天天草天天摸 | 欧美性一级观看 | 久久这里只有精品视频99 | 国产精品99久久久精品免费观看 | 日韩在线无 | 一区二区精品在线 | 国产亚洲字幕 | 欧美日韩一级在线 | 99免费在线播放99久久免费 | 九九热久久免费视频 | 黄色h在线观看 | 亚洲理论在线观看电影 | 天天射天天干天天爽 | 最新91在线视频 | 麻豆视频在线免费 | 伊人天天狠天天添日日拍 | 九九视频网 | 国产自偷自拍 | 国产精品久久久久久久久蜜臀 | 18女毛片| 五月婷婷六月丁香 | 国产男女无遮挡猛进猛出在线观看 | 国内精品久久久久影院一蜜桃 | 一二三区av| 免费91麻豆精品国产自产在线观看 | 亚洲综合五月天 | 97国产| 99久久精品免费看国产 | 日韩激情网 | 国产成人精品亚洲 | 在线观看视频国产 | 国产手机在线精品 | 波多野结衣电影一区二区三区 | 免费成人在线视频网站 | 免费视频久久 | 国产精品永久免费在线 | www.五月天色 | 91欧美精品 | 久久精品成人 | 国产成人在线观看免费 | 缴情综合网五月天 | 91精品国产91p65 | 夜夜操网站| 精品在线观看国产 | 日韩欧美一区二区在线 | 日韩三级中文字幕 | 欧美精品亚洲精品 | 99精品在线免费在线观看 | 五月天六月婷 | 日日干综合| 国产一级二级av | av综合av | 婷婷四房综合激情五月 | 五月婷婷,六月丁香 | 麻豆网站免费观看 | 超碰在线人人97 | 国产黄在线 | 8090yy亚洲精品久久 | 日韩精品亚洲专区在线观看 | 一区二区三区精品在线 | 在线观看 国产 | 精品视频免费在线 | 久久精品免视看 | 国产精品涩涩屋www在线观看 | 又黄又爽又色无遮挡免费 | 成人免费一区二区三区在线观看 | 国产精品 日韩 欧美 | 国产精品6 | 亚洲激情六月 | 亚洲欧美国产精品va在线观看 | 国产97超碰| 国产午夜小视频 | 午夜视频福利 | 操操操天天操 | av在线8 | 在线视频在线观看 | 亚洲精品乱码久久久久 | 亚洲九九爱 | 亚洲视频观看 | 日本h在线播放 | 日日干av| 免费观看www7722午夜电影 | 成人av手机在线 | 亚洲精品国产拍在线 | 欧美国产日韩一区二区三区 | 99爱视频在线观看 | 色天天中文 | 日日弄天天弄美女bbbb | 中文字幕av一区二区三区四区 | 我爱av激情网 | 久草在 | 在线观看一级 | 最新中文字幕在线资源 | 在线免费观看一区二区三区 | 91传媒免费观看 | 亚洲免费不卡 | 日韩久久片 | 伊人av综合 | 中文字幕在线观看一区二区三区 | 国产一级二级在线观看 | 亚洲综合视频在线 | 久久久久久看片 | 亚洲精品黄| 日韩av不卡在线播放 | 一区二区丝袜 | 91精品国产一区 | 91视频在线看 | 欧美在线一二区 | 亚洲欧洲一级 | 日韩精品视频免费专区在线播放 | 在线视频亚洲 | 日韩大片在线免费观看 | 欧美激情第十页 | 午夜视频在线观看网站 | 美女啪啪图片 | 97超碰超碰| 久久久99精品免费观看 | 免费一级日韩欧美性大片 | 狠狠干综合网 | 色瓜 | 97精品在线观看 | 国产精品人人做人人爽人人添 | 日韩视频三区 | 久久不卡免费视频 | 久久高清av | 国产精品手机播放 | 日韩理论影院 | 日韩大片在线看 |