青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆-341  評論-2670  文章-0  trackbacks-0

我一直以來對于exception的態度都是很明確的。首先exception是好的,否則就不會有絕大多數的語言都支持他了。其次,error code也沒什么問題,只是需要一個前提——你的語言得跟Haskell一樣有monad和comonad。你看Haskell就沒有exception,大家也寫的很開心。為什么呢?因為只要把返回帶error code結果的函數給做成一個monad/comonad,那么就可以用CPS變換把它變成exception了。所以說CPS作為跟goto同樣基本的控制流語句真是當之無愧呀,只是CPS是type rich的,goto是type poor的。

其實很多人對于exception的恐懼心理在于你不知道一個函數會拋什么exception出來,然后程序一crash你就傻逼了。對于server來講情況還好,出了問題只要殺掉快速重啟就行了,如今沒個replication和fault tolerance還有臉說你在寫后端(所以不知道那些做web的人究竟在反對什么)?這主要的問題還是在于client。只要client上面的東西還沒保存,那你一crash數據就完蛋了是不是——當然這只是你的想象啦,其實根本不是這樣子的。

我們的程序拋了一個access violation出來,和拋了其它exception出來,究竟有什么區別呢?access violation是一個很奇妙的東西,一旦拋了出來就告訴你你的程序沒救了,繼續執行下去說不定還會有破壞作用。特別是對于C/C++/Delphi這類語言來說,你不小心把錯誤的東西寫進了什么亂七八糟的指針里面去,那會兒什么事情都沒發生,結果程序跑著跑著就錯了。因為你那個算錯了得到的野指針,說不定是隔壁的不知道什么object的成員變量,說不定是heap里面的數據結構,或者說別的什么東西,就這么給你寫了。如果你寫了別的object的成員變量那封裝肯定就不管用了,這個類的不變量就給你破壞了。既然你的成員函數都是基于不變量來寫的,那這個時候出錯時必須的。如果你寫到了heap的數據結構那就更加呵呵呵了,說不定下次一new就崩了,而且你還不知道為什么。

出了access violation以外的exception基本是沒什么危害的,最嚴重的大概也就是網線被拔了,另一塊不是裝OS的硬盤突然壞了什么的這種反正你也沒辦法但是好歹還可以處理的事情。如果這些exception是你自己拋出來的那就更可靠了——那都是計劃內的。只要程序未來不會進入access violation的狀態,那證明你現在所能拿到的所有變量,還有指針指向的memory,基本上都還是靠譜的。出了你救不了的錯誤,至少你還可以吧數據安全的保存下來,然后讓自己重啟——就跟word一樣。但是你有可能會說,拿出了access violation怎么就不能保存數據了呢?因為這個時候內存都毀了,指不定你保存數據的代碼new點東西然后掛了,這基本上是沒準的。

所以無論你喜歡exception還是喜歡error code,你所希望達到的效果本質上就是避免程序未來會進入access violation的狀態。想做到這一點,方法也是很簡單粗暴的——只要你在函數里面把運行前該對函數做的檢查都查一遍就好了。這個無論你用exception還是用error code,寫起來都是一樣的。區別在于調用你的函數的那個人會怎么樣。那么我來舉個例子,譬如說你覺得STL的map實在是太傻比了,于是你自己寫了一個,然后有了一個這樣子的函數:

// exception版本
Symbol* SymbolMap::Lookup(const wstring& name);

// error code版本
int SymbolMap::Lookup(const wstring& name, Symbol*& result);

// 其實COM就是你們最喜歡的error code風格了,寫起來應該很開心才對呀,你們的雙重標準真嚴重
HRESULT ISymbolMap::Lookup(BSTR name, ISymbol** result);

于是拿到了Lookup函數之后,我們就要開始來完成一個任務了,譬如說拿兩個key得到兩個symbol然后組合出一個新的symbol。函數的錯誤處理邏輯是這樣的,如果key失敗了,因為業務的原因,我們要告訴函數外面說key不存在的。調用了一個ComposeSymbol的函數丟出什么IndexOutOfRangeException顯然是不合理的。但是合并的那一步,因為業務都在同一個領域內,所以suppose里面的異常外面是可以接受的。如果出現了計劃外的異常,那我們是處理不了的,只能丟給上面了,外面的代碼對于不認識的異常只需要報告任務失敗了就可以了。于是我們的函數就會這么寫:

Symbol* ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map)
{
    Symbol* sa=0;
    Symbol* sb=0;
    try
    {
        sa=map->Lookup(a);
        sa=map->Lookup(b);
    }
    catch(const IndexOutOfRangeException& ex)
    {
        throw SymbolKeyException(ex.GetIndex());
    }
    return CreatePairSymbol(sa, sb);
}

看起來還挺不錯。現在我們可以開始考慮error code的版本了。于是我們需要思考幾個問題。首先第一個就是Lookup失敗的時候要怎么報告?直接報告key的內容是不可能的,因為error code是個int。

題外話,error code當然可以是別的什么東西,如果需要返回豐富內容的錯誤的話,那怎樣都得是一個指針了,這個時候你們就會面臨下面的問題——這已經他媽不滿足誰構造誰釋放的原則了呀,而且我這個指針究竟直接返回出去外面理不理呢,如果只要有一個環節不理了,那內存豈不是泄露了?如果我要求把錯誤返回在參數里面的話,我每次調用函數都要創建出那么個結構來保存異常,不僅有if的復雜度,還有創建空間的復雜度,整個代碼都變成了屎。所以還是老老實實用int吧……

那我們要如何把key的信息給編碼在一個int里面呢?因為key要么是來自于a,要么是來自于b,所以其實我們就需要兩個code了。那Lookup的其他錯誤怎么辦呢?CreatePairSymbol的錯誤怎么辦呢?萬一Lookup除了ERROR_KEY_NOT_FOUND以外,或者是CreatePairSymbol的錯誤剛好跟a或者b的code重合了怎么辦?對于這個問題,我只能說:

要不你們team的人先開會討論一下最后記錄在文檔里面備查以免后面的人看了傻眼了……

好了,現在假設說會議取得了圓滿成功,會議雙方加深了互相的理解,促進了溝通,最后還寫了一個白皮書出來,有效的落實了對a和b的code的指導,于是我們終于可以寫出下面的代碼了:

#define SUCCESS 0 // global error code for success
#define ERROR_COMPOSE_SYMBOL_WRONG_A 1
#define ERROR_COMPOSE_SYMBOL_WRONG_B 2

int ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map, Symbol*& result)
{
    int code=SUCCESS;
    Symbol* sa=0;
    Symbol* sb=0;
    switch(code=map->Lookup(a, sa))
    {
    case SUCCESS:
        break;
    case ERROR_SYMBOL_MAP_KEY_NOT_FOUND:
        return ERROR_COMPOSE_SYMBOL_WRONG_A;
    default:
        return code;
    }
    switch(code=map->Lookup(b, sb))
    {
    case SUCCESS:
        break;
    case ERROR_SYMBOL_MAP_KEY_NOT_FOUND:
        return ERROR_COMPOSE_SYMBOL_WRONG_B;
    default:
        return code;
    }
    return CreatePairSymbol(sa, sb, result);
}

啊,好像太長,干脆我還是不負責任一點吧,反正代碼寫的好也漲不了工資,干脆不認識的錯誤都返回ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR好了,于是就可以把代碼變成下面這樣……都到這份上了不要叫自己程序員了,叫程序狗吧……

#define SUCCESS 0 // global error code for success
#define ERROR_COMPOSE_SYMBOL_WRONG_A 1
#define ERROR_COMPOSE_SYMBOL_WRONG_B 2
#define ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR 3

int ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map, Symbol*& result)
{
    Symbol* sa=0;
    Symbol* sb=0;
    if(map->Lookup(a, sa)!=SUCCESS)
        return ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
    if(map->Lookup(b, sb)!=SUCCESS)
        return ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
    if(CreatePairSymbol(sa, sb, result)!=SUCCESS)
        return ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
    return SUCCESS;
}

當然,如果大家都一樣不負責任的話,還是exception完爆error code:

Symbol* ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map)
{
    return CreatePairSymbol(map->Lookup(a), map->Lookup(b));
}

大部分人人只會用在當前條件下最容易寫的方法來設計軟件,而不是先設計出軟件然后再看看怎樣寫比較容易,這就是為什么我說,只要你一個月給程序員還給不到一狗半,還是老老實實在政策上落實exception吧。至少exception寫起來還不會讓人那么心煩,可以把程序寫得堅固一點。

好了,單線程下面至少你還可以爭吵說究竟exception好還是error code好,但是到了異步程序里面就完全不一樣了。現在的異步程序都很多,譬如說有良心的手機app啦,譬如說javascript啦,metro程序等等。一個try根本沒辦法跨線程使用所以一個這樣子的函數(下面開始用C#,C++11的future/promise我用的還不熟):

class Normal
{
    public string Do(string args);
}

最后就會變成這樣:

class Async
{
    // before .NET 4.0
    IAsyncResult BeginDo(string args, Action<IAsyncResult> continuation);
    string EndDo(IAsyncResult ar);

    // after .NET 4.0
    Task<string> DoAsync(string args);
}

當你使用BeginDo的時候,你可以在continuation里面調用EndDo,然后得到一個string,或者得到一個exception。但是因為EndDo的exception不是在BeginDo里面throw出來的,所以無論你EndDo返回string也好,返回Tuple<string, Exception>也好,對于BeginDo和EndDo的實現來說其實都一樣,沒有上文所說的exception和error code的區別。

不過.NET從BeginDo/EndDo到DoAsync經歷了一個巨大的進步。雖然形式上都一樣,但是由于C#并不像Haskell那樣可以完美的操作函數,C#還是面向對象做得更好,于是如果我們吧Task<T>看成下面的樣子,那其實兩種寫法是沒有區別的:

class Task<T>
{
    public IAsyncResult BeginRun(Action<IAsyncResult> continuation);
    public T EndRun(IAsyncResult ar);
}

不過如果還是用BeginRun/EndRun這種方法來調用的話,使用起來還是很不方便,而且也很難把更多的Task組合在一起。所以最后.NET給出的Task是下面這個樣子的(Comonad!):

class Task<T>
{
    public Task<U> ContinueWith<U>(Func<Task<T>, U> continuation);
}

盡管真實的Task<T>要比上面那個復雜得多,但是總的來說其實就是圍繞著基本簡單的函數建立起來的一大堆helper function。到這里C#終于把CPS變換在異步處理上的應用的這一部分給抽象出來了。在看CPS的效果之前,我們先來看一個同步函數:

void button1_Clicked(object sender, EventArgs e)
{
        // 假設我們有string Http.Download(string url);
        try
        {
                string a = Http.Download(url1);
                string b = Http.Download(url2);
                textBox1.Text=a+b;
        }
        catch(Exception ex)
        {
                textBox1.Text=ex.Message;
        }
}

這段代碼顯然是一個GUI里面的代碼。我們如果在一個GUI程序里面這么寫,就會把程序寫得跟QQ一樣卡了。所以實際上這么做是不對的。不過為了表達程序需要做的所有事情,就有了這么一個同步的版本。那么我們嘗試吧這個東西修改成異步的把!

void button2_Clicked(object sender, EventArgs e)
{
    // 假設我們有Task<string> Http.DownloadAsync(string url);
    // 需要MethodInvoker是因為,對textBox1.Text的修改只能在GUI線程里面做
    Http.DownloadAsync(url1).ContinueWith(ta=>new MethodInvoker(()=>
    {
        try
        {
            // 這個時候ta已經運行完了,所以對ta.Result的取值不會造成GUI線程等待IO。
            // 而且如果DownloadAsync內部出了錯,異常會在這里拋出來。
            string a=ta.Result;
            Http.DownloadAsync(url2).ContinueWith(tb=>new MethodInvoker(()=>
            {
                try
                {
                    string b=tb.Result;
                    textBox1.Text=a+b;
                }
                catch(Exception ex)
                {
                    textBox1.Text=ex.Message;
                }
            })));
        }
        catch(Exception ex)
        {
            textBox1.Text=ex.Message;
        }
    })));
}

我們發現,異步操作發生的異常,把優越的exception拉低到了丑陋的error code的同一個情況上面——我們需要不斷地對每一個操作重復同樣的錯誤處理過程!而且在這種地方我們連“不負責任”的選項都沒有了,如果你不try-catch(或者不檢查error code),那到時候程序就會發生一些莫名其妙的問題,在GUI那一層你什么事情都不知道,整個程序就變成了傻逼。

現在可以開始解釋一下什么是CPS變換了。CPS變換就是把所有g(f(x))都給改寫成f(x, r=>g(r))的過程。通俗一點講,CPS變換就是幫你把那個同步的button1_Click給改寫成異步的button2_Click的這個過程。盡管這么說可能不太嚴謹,因為button1_Click跟button2_Click所做的事情是不一樣的,一個會讓GUI卡成qq,另一個不會。但是我們討論CPS變換的時候,我們討論的是對代碼結構的變換,而不是別的什么東西。

現在就是激動人心的一步了。既然CPS可以把返回值變換成lambda表達式,那反過來我們也可以把所有的以這種形式存在的lambda表達式都改寫成返回值嘛。現在我們滾回去看一看button2_Click,會發現這個程序其實充滿了下面的pattern:

// lambda的參數名字故意起了跟前面的變量一樣的名字(previousTask)因為其實他們就是同一個東西
previousTask.ContinueWith(previousTask=>new MethodInvoker(()=>
{
    try
    {
        continuation(previousTask.Result);
    }
    catch(Exception ex)
    {
        textBox1.Text=ex.Message;
    }
})));

我們可以“發明”一個語法來代表這個過程。C#用的是await關鍵字,那我們也來用await關鍵字。假設說上面的代碼永遠等價于下面的這個代碼:

try
{
    var result=await previousTask;
    continuation(result);
}
catch(Exception ex)
{
    textBox1.Text=ex.Message;
}

兩段代碼的關系就跟i++;和i=i+1;一樣是可以互相替換的,只是不同的寫法而已。那我們就可以用相同的方法來把button2_Click給替換成下面的button3_Click了:

void button3_Click(object sender, EventArgs e)
{
    try
    {
        var a=await Http.DownloadAsync(url1);
        try
        {
            var b=await Http.DownloadAsync(url2);
            textBox1.Text=a+b;
        }
        catch(Exception ex)
        {
            textBox1.Text=ex.Message;
        }
    }
    catch(Exception ex)
    {
        textBox1.Text=ex.Message;
    }
}

聰明的讀者立刻就想到了,兩個try其實是重復的,那為什么不把他們合并成一個呢!當然我想告訴大家的是,異常是在不同的線程里面拋出來的,只是我們用CPS變換把代碼“改寫”成這種形式而已。理論上兩個try是不能合并的。但是!我們的C#編譯器君是很聰明的。正所謂語言的抽象高級了一點,那么編譯器對你的代碼也就理解得更多了一點。如果編譯器發現你在try里面寫了兩個await,馬上就明白了過來他需要幫你復制catch的部分——或者說他可以幫你自動的復制catch的部分,那情況就完全不同了,最后就可以寫成:

// C#要求函數前面要加一個async來允許你在函數內使用await
// 當然同時你的函數也就返回Task而不是void了
// 不過沒關系,C#的event也可以接受一個標記了async的函數,盡管返回值不一樣
// 設計語言這種事情就是牽一發而動全身呀,加個await連event都要改
async void button4_Click(object sender, EventArgs e)
{
    try
    {
        string a=await Http.DownloadAsync(url1);
        string b=await Http.DownloadAsync(url2);
        textBox1.Text=a+b;
    }
    catch(Exception ex)
    {
        textBox1.Text=ex.Message;
    }
}

把兩個await換成回調已經讓我們寫的夠辛苦了,那么如果我們把await寫在了循環里面,事情就不那么簡單了。CPS需要把循環翻譯成遞歸,那你就得把lambda表達時拿出來寫成一個普通的函數——這樣他就可以有名字了——然后才能遞歸(寫出一個用于CPS的Y-combinator是一件很困難的事情,盡管并沒有比Y-combinator本身困難多少)。這個例子就復雜到爆炸了,我在這里就不演示了。

總而言之,C#因為有了CPS變換(await),就可以把button4_Click幫你寫成button3_Click然后再幫你寫成button2_Click,最后把整個函數變成異步和回調的形式(真正的做法要更聰明一點,大家可以反編譯去看)在異步回調的寫法里面,exception和error code其實是一樣的。但是CPS+exception和CPS+error code就跟單線程下面的exception和error code一樣,有著重大的區別。這就是為什么文章一開始會說,我只會在帶CPS變換的語言(Haskell/F#/etc)里面使用error code。

在這類語言里面利用相同的技巧,就可以不是異步的東西也用CPS包裝起來,譬如說monadic parser combinator。至于你要選擇monad還是comonad,基本上就是取決于你要自動提供錯誤處理還是要手動提供錯誤處理。像上面的Task.ContinueWith,是要求你手動提供錯誤處理的(因為你catch了之后可以干別的事情,Task無法自動替你選擇最好的措施),所以他就把Task.ContinueWith寫成了comonad的那個樣子。

寫到這里,不禁要同情寫前端的那幫javascript和自以為可以寫后端的node.js愛好者們,你們因為小小的eval的問題,不用老趙的windjs(windjs給javascript加上了await但是它不是一個altjs所以得顯式調用eval),是一個多大的損失……

posted on 2013-06-09 23:01 陳梓瀚(vczh) 閱讀(14014) 評論(8)  編輯 收藏 引用 所屬分類: 啟示

評論:
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-06-09 23:40 | sorra
C#的這個特性挺棒的!  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-06-09 23:55 | DiryBoy
跪  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-06-10 12:15 | jagd
Haskell 不僅有 Control.Exception 而且用起來特麻煩, 有時還非用不可 (例如判斷 EOF)  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-06-10 20:38 | 陳梓瀚(vczh)
@jagd
haskell寫帶副作用的程序是很痛苦的  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-06-12 02:36 | BYVois
C#裏面的await獲取異步異常跟我的Continuation.js中對同步異步異常統一用try..catch處理完全一樣。  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-06-12 06:28 | 陳梓瀚(vczh)
@BYVois
當然了,因為函數式只有這種處理方法……不過C#的做法不是直接的CPS手法,他是把整個代碼編譯成了一個狀態機,減少了很多call來call去的消耗,產生的代碼也就少了很多。  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code[未登錄] 2013-11-10 03:25 | patz
問個問題,在這段代碼里:

void button2_Clicked(object sender, EventArgs e)
{
Http.DownloadAsync(url1).ContinueWith(ta=>new MethodInvoker(()=>
{
try
{
// 這個時候ta已經運行完了,所以對ta.Result的取值不會造成GUI線程等待IO。
// 而且如果DownloadAsync內部出了錯,異常會在這里拋出來。
string a=ta.Result;

這里的ContinueWith應該是在別的線程里去執行,所以即使ta沒有運行完,ta.Result也不回造成UI線程hang。(當然ta肯定運行完了)

是這樣吧?  回復  更多評論
  
# re: 如何設計一門語言(六)&mdash;&mdash;exception和error code 2013-11-10 05:49 | 陳梓瀚(vczh)
@patz
不管執行完沒有,ta.Result都會進一個鎖等結果好了再返回。所以如果你調用Result的時候已經算好了,那也是瞬間獲得鎖然后返回。不過await編譯出來的代碼在訪問Result的時候都保證已經執行完了。  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产欧美日韩在线| 午夜精品久久久久影视| 欧美成人午夜视频| 国产精品高潮久久| 欧美黄色影院| 亚洲国产一区在线观看| 你懂的网址国产 欧美| 国产一区二区av| 久久黄色小说| 另类图片国产| 亚洲第一主播视频| 麻豆精品国产91久久久久久| 欧美日韩精品一区二区在线播放 | 久久亚洲捆绑美女| 日韩视频免费在线| 香蕉亚洲视频| 亚洲男同1069视频| 国产精品欧美久久| 99综合精品| 99av国产精品欲麻豆| 久久久夜夜夜| 久久久久久尹人网香蕉| 欧美三级电影一区| 亚洲美女毛片| 在线亚洲美日韩| 欧美大片一区二区| 日韩一区二区精品视频| 亚洲精品一区二区三区在线观看| 香蕉成人伊视频在线观看 | 亚洲欧美成aⅴ人在线观看| 一区二区精品国产| 国产精品成人v| 亚洲深夜影院| 欧美在线中文字幕| 国产一区二区精品久久99| 亚洲专区一区| 蜜桃av噜噜一区| 亚洲美女电影在线| 国产精品国产福利国产秒拍 | 欧美多人爱爱视频网站| 亚洲国产精品免费| 欧美日韩国产色综合一二三四| 亚洲国产精品成人va在线观看| 亚洲国产精品久久久久婷婷884| 久久激情一区| 1024成人网色www| 欧美激情中文不卡| 欧美在线亚洲| 一区二区三区视频在线看| 亚洲国产成人tv| 日韩一区二区精品| 亚洲愉拍自拍另类高清精品| 欧美在线一二三四区| 老司机午夜精品视频| 欧美激情在线免费观看| 久久综合激情| 久久综合九色综合久99| 久久中文在线| 免费短视频成人日韩| 欧美日韩岛国| 欧美xx69| 国产精品v欧美精品v日韩| 欧美精品在线观看播放| 欧美日本免费一区二区三区| 欧美国产高潮xxxx1819| 欧美日本免费一区二区三区| 欧美日韩国产一区精品一区| 欧美精品大片| 欧美揉bbbbb揉bbbbb| 国产精品视频久久久| 国产精品久久久久秋霞鲁丝| 久久免费视频一区| 国产精品99免费看 | 亚洲国产精品成人综合| 日韩视频中文| 久久精品中文字幕一区二区三区| 欧美激情一区二区三级高清视频| 亚洲一区二区欧美| 激情综合色丁香一区二区| 亚洲宅男天堂在线观看无病毒| 精品99一区二区三区| 国产精品免费看片| 欧美精品一区二区三区蜜桃| 免费看成人av| 欧美精品三级日韩久久| 欧美国产日本在线| 欧美精品一线| 欧美日韩一区成人| 国产欧美精品在线播放| 欧美色中文字幕| 国产丝袜美腿一区二区三区| 国产夜色精品一区二区av| 国产欧美一区二区视频| 国产一区二区日韩精品| 亚洲福利视频免费观看| 亚洲视频精品在线| 老**午夜毛片一区二区三区| 久久男人av资源网站| 欧美黄色网络| 亚洲一区二区三区中文字幕| 欧美在线视频全部完| 伊人激情综合| 亚洲精品四区| 免费视频亚洲| 欧美 亚欧 日韩视频在线| 国产在线不卡视频| 欧美亚洲一区二区三区| 中文在线资源观看网站视频免费不卡| 欧美激情精品| 性色av一区二区三区| 亚洲在线观看视频网站| 国产一区二区三区四区老人| 久久精品国产77777蜜臀| 99re国产精品| 亚洲欧美偷拍卡通变态| 亚洲自拍啪啪| 免费成人av在线| 一区二区三区免费观看| 免费高清在线一区| 一区二区视频免费完整版观看| 亚洲一区二区精品| 国产精品久久久久久久久搜平片 | 午夜性色一区二区三区免费视频| 免播放器亚洲| 久久av免费一区| 亚洲主播在线| 亚洲人线精品午夜| 91久久在线播放| 久久综合激情| 欧美一区二区三区在线| 亚洲一区二区三区免费在线观看| 亚洲人成人一区二区在线观看 | 免费成人小视频| 久久久在线视频| 久久国产福利国产秒拍| 香蕉成人伊视频在线观看| 亚洲一区二区三区777| 亚洲视频1区| 亚洲一区二区黄色| 亚洲综合视频1区| 午夜国产精品视频免费体验区| 亚洲天堂成人在线观看| 亚洲天天影视| 亚洲女优在线| 欧美一区视频| 久久久久久久一区| 媚黑女一区二区| 欧美激情国产高清| 欧美日韩亚洲视频一区| 国产精品护士白丝一区av| 国产精品久久久久毛片软件 | 欧美激情一区二区三区在线视频 | 久久aⅴ国产欧美74aaa| 久久久国产精品亚洲一区 | 一区二区三区四区五区在线| 亚洲天天影视| 久久精品91久久香蕉加勒比 | 亚洲欧美国产高清| 午夜一级在线看亚洲| 久久精品视频免费播放| 欧美xxx在线观看| 欧美视频网站| 国产一区二区丝袜高跟鞋图片| 影音先锋日韩精品| 亚洲免费av观看| 亚洲欧美国产日韩天堂区| 久久久水蜜桃av免费网站| 欧美激情第8页| 在线亚洲+欧美+日本专区| 午夜精品福利视频| 欧美成年人在线观看| 国产精品你懂得| 亚洲国产美女精品久久久久∴| 亚洲少妇中出一区| 久久这里有精品15一区二区三区| 亚洲黄网站在线观看| 亚洲欧美日本国产专区一区| 久久一区精品| 国产精品国产三级国产专播精品人 | 欧美日韩国产电影| 国产综合激情| 一区二区av在线| 91久久精品日日躁夜夜躁国产| 久久精品亚洲热| 亚洲国产精品小视频| 免费成人高清在线视频| 国产精品青草久久| 亚洲精品在线观| 久久网站热最新地址| 久久久亚洲国产美女国产盗摄| 中日韩美女免费视频网址在线观看| 久久久噜噜噜久久狠狠50岁| 亚洲三级电影在线观看| 久久久精品久久久久| 国产精品美女黄网| 99国产精品视频免费观看| 久久亚洲一区二区三区四区| 亚洲视频在线一区| 欧美精品乱人伦久久久久久 | 国产亚洲精品aa|