重構原則(續)

      網友投稿 1057 2025-03-31

      (續)


      ##2.6 重構、架構和YAGNI

      重構極大地改變了人們考慮軟件架構的方式。在我的職業生涯早期,我被告知:在任何人開始寫代碼之前,必須先完成軟件的設計和架構。一旦代碼寫出來,架構就固定了,只會因為程序員的草率對待而逐漸腐敗。

      重構改變了這種觀點。有了重構技術,即便是已經在生產環境中運行了多年的軟件,我們也有能力大幅度修改其架構。正如本書的副標題所指出的,重構可以改善既有代碼的設計。但我在前面也提到了,修改遺留代碼經常很有挑戰,尤其當遺留代碼缺乏恰當的測試時。

      重構對架構最大的影響在于,通過重構,我們能得到一個設計良好的代碼庫,使其能夠優雅地應對不斷變化的需求。“在編碼之前先完成架構”這種做法最大的問題在于,它假設了軟件的需求可以預先充分理解。但經驗顯示,這個假設很多時候甚至可以說大多數時候是不切實際的。只有真正使用了軟件、看到了軟件對工作的影響,人們才會想明白自己到底需要什么,這樣的例子不勝枚舉。

      應對未來變化的辦法之一,就是在軟件里植入靈活性機制。在編寫一個函數時,我會考慮它是否有更通用的用途。為了應對我預期的應用場景,我預測可以給這個函數加上十多個參數。這些參數就是靈活性機制——跟大多數“機制”一樣,它不是免費午餐。把所有這些參數都加上的話,函數在當前的使用場景下就會非常復雜。另外,如果我少考慮了一個參數,已經加上的這一堆參數會使新添參數更麻煩。而且我經常會把靈活性機制弄錯——可能是未來的需求變更并非以我期望的方式發生,也可能我對機制的設計不好。考慮到所有這些因素,很多時候這些靈活性機制反而拖慢了我響應變化的速度。

      有了重構技術,我就可以采取不同的策略。與其猜測未來需要哪些靈活性、需要什么機制來提供靈活性,我更愿意只根據當前的需求來構造軟件,同時把軟件的設計質量做得很高。隨著對用戶需求的理解加深,我會對架構進行重構,使其能夠應對新的需要。如果一種靈活性機制不會增加復雜度(比如添加幾個命名良好的小函數),我可以很開心地引入它;但如果一種靈活性會增加軟件復雜度,就必須先證明自己值得被引入。如果不同的調用者不會傳入不同的參數值,那么就不要添加這個參數。當真的需要添加這個參數時,運用函數參數化(310)也很容易。要判斷是否應該為未來的變化添加靈活性,我會評估“如果以后再重構有多困難”,只有當未來重構會很困難時,我才考慮現在就添加靈活性機制。我發現這是一個很有用的決策方法。

      這種設計方法有很多名字:簡單設計、增量式設計或者YAGNI[mf-yagni]——“你不會需要它”(you aren?t going to need it)的縮寫。YAGNI并不是“不做架構性思考”的意思,不過確實有人以這種欠考慮的方式做事。我把YAGNI視為將架構、設計與開發過程融合的一種工作方式,這種工作方式必須有重構作為基礎才可靠。

      采用YAGNI并不表示完全不用預先考慮架構。總有一些時候,如果缺少預先的思考,重構會難以開展。但兩者之間的平衡點已經發生了很大的改變:如今我更傾向于等一等,待到對問題理解更充分,再來著手解決。演進式架構[Ford et al.]是一門仍在不斷發展的學科,架構師們在不斷探索有用的模式和實踐,充分發揮迭代式架構決策的能力。

      ##2.7 重構與軟件開發過程

      讀完前面“重構的挑戰”一節,你大概已經有這個印象:重構是否有效,與團隊采用的其他軟件開發實踐緊密相關。重構起初是作為極限編程(XP)[mf-xp]的一部分被人們采用的,XP本身就融合了一組不太常見而又彼此關聯的實踐,例如持續集成、自測試代碼以及重構(后兩者融匯成了測試驅動開發)。

      極限編程是最早的敏捷軟件開發方法[mf-nm]之一。在一段歷史時期,極限編程引領了敏捷的崛起。如今已經有很多項目使用敏捷方法,甚至敏捷的思維已經被視為主流,但實際上大部分“敏捷”項目只是徒有其名。要真正以敏捷的方式運作項目,團隊成員必須在重構上有能力、有熱情,他們采用的開發過程必須與常規的、持續的重構相匹配。

      重構的第一塊基石是自測試代碼。我應該有一套自動化的測試,我可以頻繁地運行它們,并且我有信心:如果我在編程過程中犯了任何錯誤,會有測試失敗。這塊基石如此重要,我會專門用一章篇幅來討論它。

      如果一支團隊想要重構,那么每個團隊成員都需要掌握重構技能,能在需要時開展重構,而不會干擾其他人的工作。這也是我鼓勵持續集成的原因:有了CI,每個成員的重構都能快速分享給其他同事,不會發生這邊在調用一個接口那邊卻已把這個接口刪掉的情況;如果一次重構會影響別人的工作,我們很快就會知道。自測試的代碼也是持續集成的關鍵環節,所以這三大實踐——自測試代碼、持續集成、重構——彼此之間有著很強的協同效應。

      有這三大實踐在手,我們就能運用前一節介紹的YAGNI設計方法。重構和YAGNI交相呼應、彼此增效,重構(及其前置實踐)是YAGNI的基礎,YAGNI又讓重構更易于開展:比起一個塞滿了想當然的靈活性的系統,當然是修改一個簡單的系統要容易得多。在這些實踐之間找到合適的平衡點,你就能進入良性循環,你的代碼既牢固可靠又能快速響應變化的需求。

      有這三大核心實踐打下的基礎,才談得上運用敏捷思想的其他部分。持續交付確保軟件始終處于可發布的狀態,很多互聯網團隊能做到一天多次發布,靠的正是持續交付的威力。即便我們不需要如此頻繁的發布,持續集成也能幫我們降低風險,并使我們做到根據業務需要隨時安排發布,而不受技術的局限。有了可靠的技術根基,我們能夠極大地壓縮“從好點子到生產代碼”的周期時間,從而更好地服務客戶。這些技術實踐也會增加軟件的可靠性,減少耗費在bug上的時間。

      這一切說起來似乎很簡單,但實際做起來毫不容易。不管采用什么方法,軟件開發都是一件復雜而微妙的事,涉及人與人之間、人與機器之間的復雜交互。我在這里描述的方法已經被證明可以應對這些復雜性,但——就跟其他所有方法一樣——對使用者的實踐和技能有要求。

      ##2.8 重構與性能

      關于重構,有一個常被提出的問題:它對程序的性能將造成怎樣的影響?為了讓軟件易于理解,我常會做出一些使程序運行變慢的修改。這是一個重要的問題。我并不贊成為了提高設計的純潔性而忽視性能,把希望寄托于更快的硬件身上也絕非正道。已經有很多軟件因為速度太慢而被用戶拒絕,日益提高的機器速度也只不過略微放寬了速度方面的限制而已。但是,換個角度說,雖然重構可能使軟件運行更慢,但它也使軟件的性能優化更容易。除了對性能有嚴格要求的實時系統,其他任何情況下“編寫快速軟件”的秘密就是:先寫出可調優的軟件,然后調優它以求獲得足夠的速度。

      我看過3種編寫快速軟件的方法。其中最嚴格的是時間預算法,這通常只用于性能要求極高的實時系統。如果使用這種方法,分解你的設計時就要做好預算,給每個組件預先分配一定資源,包括時間和空間占用。每個組件絕對不能超出自己的預算,就算擁有組件之間調度預配時間的機制也不行。這種方法高度重視性能,對于心律調節器一類的系統是必需的,因為在這樣的系統中遲來的數據就是錯誤的數據。但對其他系統(例如我經常開發的企業信息系統)而言,如此追求高性能就有點兒過分了。

      第二種方法是持續關注法。這種方法要求任何程序員在任何時間做任何事時,都要設法保持系統的高性能。這種方式很常見,感覺上很有吸引力,但通常不會起太大作用。任何修改如果是為了提高性能,通常會使程序難以維護,繼而減緩開發速度。如果最終得到的軟件的確更快了,那么這點損失尚有所值,可惜通常事與愿違,因為性能改善一旦被分散到程序各個角落,每次改善都只不過是從對程序行為的一個狹隘視角出發而已,而且常常伴隨著對編譯器、運行時環境和硬件行為的誤解。

      { 勞而無獲}

      克萊斯勒綜合薪資系統的支付過程太慢了。雖然我們的開發還沒結束,這個問題卻已經開始困擾我們,因為它已經拖累了測試速度。

      Kent Beck、Martin Fowler和我決定解決這個問題。等待大伙兒會合的時間里,憑著對這個系統的全盤了解,我開始推測:到底是什么讓系統變慢了?我想到數種可能,然后和伙伴們談了幾種可能的修改方案。最后,我們就“如何讓這個系統運行更快”,提出了一些真正的好點子。

      然后,我們拿Kent的工具度量了系統性能。我一開始所想的可能性竟然全都不是問題肇因。我們發現:系統把一半時間用來創建“日期”實例(instance)。更有趣的是,所有這些實例都有相同的幾個值。

      于是我們觀察日期對象的創建邏輯,發現有機會將它優化。這些日期對象在創建時都經過了一個字符串轉換過程,然而這里并沒有任何外部數據輸入。之所以使用字符串轉換方式,完全只是因為代碼寫起來簡單。好,也許我們可以優化它。

      然后,我們觀察這些日期對象是如何被使用的。我們發現,很多日期對象都被用來產生“日期區間”實例——由一個起始日期和一個結束日期組成的對象。仔細追蹤下去,我們發現絕大多數日期區間是空的!

      處理日期區間時我們遵循這樣一個規則:如果結束日期在起始日期之前,這個日期區間就該是空的。這是一條很好的規則,完全符合這個類的需要。采用此規則后不久,我們意識到,創建一個“起始日期在結束日期之后”的日期區間,仍然不算是清晰的代碼,于是我們把這個行為提煉成一個工廠函數,由它專門創建“空的日期區間”。

      我們做了上述修改,使代碼更加清晰,也意外得到了一個驚喜:可以創建一個固定不變的“空日期區間”對象,并讓上述調整后的工廠函數始終返回該對象,而不再每次都創建新對象。這一修改把系統速度提升了幾乎一倍,足以讓測試速度達到可接受的程度。這只花了我們大約五分鐘。

      我和團隊成員(Kent和Martin謝絕參加)認真推測過:我們了若指掌的這個程序中可能有什么錯誤?我們甚至憑空做了些改進設計,卻沒有先對系統的真實情況進行度量。

      我們完全錯了。除了一場很有趣的交談,我們什么好事都沒做。

      教訓是:哪怕你完全了解系統,也請實際度量它的性能,不要臆測。臆測會讓你學到一些東西,但十有八九你是錯的。

      {--:}——Ron Jeffries

      關于性能,一件很有趣的事情是:如果你對大多數程序進行分析,就會發現它把大半時間都耗費在一小半代碼身上。如果你一視同仁地優化所有代碼,90%的優化工作都是白費勁的,因為被你優化的代碼大多很少被執行。你花時間做優化是為了讓程序運行更快,但如果因為缺乏對程序的清楚認識而花費時間,那些時間就都被浪費掉了。

      第三種性能提升法就是利用上述的90%統計數據。采用這種方法時,我編寫構造良好的程序,不對性能投以特別的關注,直至進入性能優化階段——那通常是在開發后期。一旦進入該階段,我再遵循特定的流程來調優程序性能。

      在性能優化階段,我首先應該用一個度量工具來監控程序的運行,讓它告訴我程序中哪些地方大量消耗時間和空間。這樣我就可以找出性能熱點所在的一小段代碼。然后我應該集中關注這些性能熱點,并使用持續關注法中的優化手段來優化它們。由于把注意力都集中在熱點上,較少的工作量便可顯現較好的成果。即便如此,我還是必須保持謹慎。和重構一樣,我會小幅度進行修改。每走一步都需要編譯、測試,再次度量。如果沒能提高性能,就應該撤銷此次修改。我會繼續這個“發現熱點,去除熱點”的過程,直到獲得客戶滿意的性能為止。

      一個構造良好的程序可從兩方面幫助這一優化方式。首先,它讓我有比較充裕的時間進行性能調整,因為有構造良好的代碼在手,我能夠更快速地添加功能,也就有更多時間用在性能問題上(準確的度量則保證我把這些時間投在恰當地點)。其次,面對構造良好的程序,我在進行性能分析時便有較細的粒度。度量工具會把我帶入范圍較小的代碼段中,而性能的調整也比較容易些。由于代碼更加清晰,因此我能夠更好地理解自己的選擇,更清楚哪種調整起關鍵作用。

      我發現重構可以幫助我寫出更快的軟件。短期看來,重構的確可能使軟件變慢,但它使優化階段的軟件性能調優更容易,最終還是會得到好的效果。

      ##2.9 重構起源何處

      我曾經努力想找出“重構”(refactoring)一詞的真正起源,但最終失敗了。優秀程序員肯定至少會花一些時間來清理自己的代碼。這么做是因為,他們知道整潔的代碼比雜亂無章的代碼更容易修改,而且他們知道自己幾乎無法一開始就寫出整潔的代碼。

      重構不止如此。本書中我把重構看作整個軟件開發過程的一個關鍵環節。最早認識重構重要性的兩個人是Ward Cunningham和Kent Beck,他們早在20世紀80年代就開始使用Smalltalk,那是一個特別適合重構的環境。Smalltalk是一個十分動態的環境,用它可以很快寫出功能豐富的軟件。Smalltalk的“編譯-鏈接-執行”周期非常短,因此很容易快速修改代碼——要知道,當時很多編程環境做一次編譯就需要整晚時間。它支持面向對象,也有強大的工具,最大限度地將修改的影響隱藏于定義良好的接口背后。Ward和Kent努力探索出一套適合這類環境的軟件開發過程(如今,Kent把這種風格叫作極限編程)。他們意識到:重構對于提高生產力非常重要。從那時起他們就一直在工作中運用重構技術,在正式的軟件項目中使用它,并不斷精煉重構的過程。

      Ward和Kent的思想對Smalltalk社區產生了極大影響,重構概念也成為Smalltalk文化中的一個重要元素。Smalltalk社區的另一位領袖是Ralph Johnson,伊利諾伊大學厄巴納-香檳分校教授,著名的GoF[gof]之一。Ralph最大的興趣之一就是開發軟件框架。他揭示了重構有助于靈活高效框架的開發。

      Bill Opdyke是Ralph的博士研究生,對框架也很感興趣。他看到了重構的潛在價值,并看到重構應用于Smalltalk之外的其他語言的可能性。他的技術背景是電話交換系統的開發。在這種系統中,大量的復雜情況與日俱增,而且非常難以修改。Bill的博士研究就是從工具構筑者的角度來看待重構。Bill對C++的框架開發中用得上的重構手法特別感興趣。他也研究了極有必要的“語義保持的重構” (semantics-preserving refactoring),并闡明了如何證明這些重構是語義保持的,以及如何用工具實現重構。Bill的博士論文[Opdyke]是重構領域中第一部豐碩的研究成果。

      我還記得1992年OOPSLA大會上見到Bill的情景。我們坐在一間咖啡廳里,Bill跟我談起他的研究成果,我還記得自己當時的想法:“有趣,但并非真的那么重要。”唉,我完全錯了。

      John Brant和Don Roberts將“重構工具”的構想發揚光大,開發了一個名為Refactoring Browser (重構瀏覽器)的重構工具。這是第一個自動化的重構工具,多虧Smalltalk提供了適合重構的編程環境。

      那么,我呢?我一直有清理代碼的傾向,但從來沒有想到這會如此重要。后來我和Kent一起做一個項目,看到他使用重構手法,也看到重構對開發效能和質量帶來的影響。這份體驗讓我相信:重構是一門非常重要的技術。但是,在重構的學習和推廣過程中我遇到了挫折,因為我拿不出任何一本書給程序員看,也沒有任何一位專家打算寫這樣一本書。所以,在這些專家的幫助下,我寫下了這本書的第1版。

      幸運的是,重構的概念被行業廣泛接受了。本書第1版銷量不錯,“重構”一詞也走進了大多數程序員的詞匯庫。更多的重構工具涌現出來,尤其是在Java世界里。重構的流行也帶來了負面效應:很多人隨意地使用“重構”這個詞,而他們真正做的卻是不嚴謹的結構調整。盡管如此,重構終歸成了一項主流的軟件開發實踐。

      ##2.10 自動化重構

      過去10年中,重構領域最大的變化可能就是出現了一批支持自動化重構的工具。如果我想給一個Java的方法改名,在IntelliJ IDEA或者Eclipse這樣的開發環境中,我只需要從菜單里點選對應的選項,工具會幫我完成整個重構過程,而且我通常都可以相信,工具完成的重構是可靠的,所以用不著運行測試套件。

      第一個自動化重構工具是Smalltalk的Refactoring Browser,由John Brandt和Don Roberts開發。在21世紀初,Java世界的自動化重構工具如雨后春筍般涌現。在JetBrains的IntelliJ IDEA集成開發環境(IDE)中,自動化重構是最亮眼的特性之一。IBM也緊隨其后,在VisualAge的Java版中也提供了重構工具。VisualAge的影響力有限,不過其中很多能力后來被Eclipse繼承,包括對重構的支持。

      重構也進入了C#世界,起初是通過JetBrains的Resharper,這是一個Visual Studio插件。后來Visual Studio團隊直接在IDE里提供了一些重構能力。

      如今的編輯器和開發工具中常能找到一些對重構的支持,不過真實的重構能力各有高低。重構能力的差異既有工具的原因,也受限于不同語言對自動化重構的支持程度。在這里,我不打算分析各種工具的能力,不過談談重構工具背后的原則還是有點兒意思的。

      一種粗糙的自動化重構方式是文本操作,比如用查找/替換的方式給函數改名,或者完成提煉變量(119)所需的簡單結構調整。這種方法太粗糙了,做完之后必須重新運行測試,否則不能信任。但這可以是一個便捷的起步。在用Emacs編程時,沒有那些更完善的重構支持,我也會用類似的文本操作宏來加速重構。

      要支持體面的重構,工具只操作代碼文本是不行的,必須操作代碼的語法樹,這樣才能更可靠地保持代碼行為。所以,今天的大多數重構功能都依附于強大的IDE,因為這些IDE原本就在語法樹上實現了代碼導航、靜態檢查等功能,自然也可以用于重構。不僅能處理文本,還能處理語法樹,這是IDE相比于文本編輯器更先進的地方。

      重構工具不僅需要理解和修改語法樹,還要知道如何把修改后的代碼寫回編輯器視圖。總而言之,實現一個體面的自動化重構手法,是一個很有挑戰的編程任務。盡管我一直開心地使用重構工具,對它們背后的實現卻知之甚少。

      在靜態類型語言中,很多重構手法會更加安全。假設我想做一次簡單的函數改名(124):在Salesman類和Server類中都有一個叫作addClient的函數,當然兩者各有其用途。我想對Salesman中的addClient函數改名,Server類中的函數則保持不變。如果不是靜態類型,工具很難識別調用addClient的地方到底是在使用哪個類的函數。Smalltalk的Refactoring Browser會列出所有調用點,我需要手工決定修改哪些調用點。這個重構是不安全的,我必須重新運行所有測試。這樣的工具仍然有用,但在Java中的函數改名(124)重構則可以是完全安全、完全自動的,因為在靜態類型的幫助下,工具可以識別函數所屬的類,所以它只會修改應該修改的那些函數調用點,對此我可以完全放心。

      一些重構工具走得更遠。如果我給一個變量改名,工具會提醒我修改使用了舊名字的注釋。如果我使用提煉函數(106),工具會找出與新函數體重復的代碼片段,建議代之以對新函數的調用。在編程時可以使用如此強大的重構功能,這就是為什么我們要使用一個體面的IDE,而不是固執于熟悉的文本編輯器。我個人很喜歡用Emacs,但在使用Java時,我更愿意用IntelliJ IDEA或者Eclipse,很大程度上就是為了獲得重構支持。

      盡管這些強大的重構工具有著魔法般的能力,可以安全地重構代碼,但還是會有閃失出現。通過反射進行的調用(例如Java中的Method.invoke)會迷惑不夠成熟的重構工具,但比較成熟的工具則可以很好地應對。所以,即便是最安全的重構,也應該經常運行測試套件,以確保沒有什么東西在不經意間被破壞。我經常會間雜進行自動重構和手動重構,所以運行測試的頻度是足夠的。

      能借助語法樹來分析和重構程序代碼,這是IDE與普通文本編輯器相比具有的一大優勢。但很多程序員又喜歡用得順手的文本編輯器的靈活性,希望魚與熊掌兼得。語言服務器(Language Server)是一種正在引起關注的新技術:用軟件生成語法樹,給文本編輯器提供API。語言服務器可以支持多種文本編輯器,并且為強大的代碼分析和重構操作提供了命令。

      ##2.11 延展閱讀

      在第2章就開始談延展閱讀,這似乎有點兒奇怪。不過,有大量關于重構的材料已經超出了本書的范圍,早些讓讀者知道這些材料的存在也是件好事。

      本書的第1版教很多人學會了重構,不過我的關注點是組織一本重構的參考書,而不是帶領讀者走過學習過程。如果你需要一本面向入門者的教材,我推薦Bill Wake的《重構手冊》[Wake],其中包含了很多有用的重構練習。

      重構的原則(續)

      很多重構的先行者同時也活躍于軟件模式社區。Josh Kerievsky在《重構與模式》[Kerievsky]一書中緊密連接了這兩個世界。他審視了影響巨大的GoF[gof]書中一些最有價值的模式,并展示了如何通過重構使代碼向這些模式的方向演化。

      本書聚焦討論通用編程語言中的重構技巧。還有一些專門領域的重構,例如已經引起關注的《數據庫重構》[Ambler & Sadalage](由Scott Ambler和Pramod Sadalage所著)和《重構HTML》[Harold](由Elliotte Rusty Harold所著)。

      盡管標題中沒有“重構”二字,Michael Feathers的《修改代碼的藝術》[Feathers]也不得不提。這本書主要討論如何在缺乏測試覆蓋的老舊代碼庫上開展重構。

      本書(及其前一版)對讀者的編程語言背景沒有要求。也有人寫專門針對特定語言的重構書籍。我的兩位前同事Jay Fields和Shane Harvey就撰寫了Ruby版的《重構》[Fields et al.]。

      在本書的Web版和重構網站(refactoring.com)[ref.com]上都可以找到更多相關材料的更新。

      本文轉載自異步社區

      軟件開發

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何在wps表格中設置超鏈接(wps怎么設置超鏈接到表格)
      下一篇:jira可以項目管理嗎
      相關文章
      亚洲AV无码成人网站久久精品大| 久久亚洲私人国产精品| 亚洲精品V欧洲精品V日韩精品| 国产亚洲玖玖玖在线观看| 亚洲一区中文字幕久久| 亚洲AV无码国产精品色午友在线| 狠狠亚洲狠狠欧洲2019| 亚洲精品A在线观看| 亚洲精品tv久久久久久久久久| 伊人久久五月丁香综合中文亚洲 | 亚洲日本va午夜中文字幕久久| 无码欧精品亚洲日韩一区夜夜嗨 | 亚洲国产精品自产在线播放| 伊在人亚洲香蕉精品区麻豆| 九月婷婷亚洲综合在线| 亚洲av无码不卡私人影院| 亚洲成a人片在线观看久| 亚洲精品国产高清不卡在线 | 亚洲人成在线播放| 国内精品久久久久影院亚洲| 亚洲av无码一区二区三区人妖| 亚洲AV成人无码网天堂| 亚洲JIZZJIZZ中国少妇中文| 亚洲熟女乱综合一区二区| 久久久久亚洲AV成人网| 亚洲色精品vr一区二区三区| 亚洲高清国产AV拍精品青青草原| 久久久久亚洲精品影视| 亚洲视频网站在线观看| 亚洲jjzzjjzz在线播放| 亚洲日韩精品国产3区| MM1313亚洲精品无码久久| 久久精品熟女亚洲av麻豆| 亚洲成年看片在线观看| 国产亚洲精品AA片在线观看不加载 | 亚洲伊人tv综合网色| 亚洲免费在线视频观看| 亚洲综合在线一区二区三区| 国产亚洲精品AAAA片APP| 亚洲情a成黄在线观看| 国产成人麻豆亚洲综合无码精品|