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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

我们精通那么多技术,为何还是做不好一个项目?

發(fā)布時(shí)間:2023/12/15 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 我们精通那么多技术,为何还是做不好一个项目? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

編寫高質(zhì)量可維護(hù)的代碼既是程序員的基本修養(yǎng),也是能決定項(xiàng)目成敗的關(guān)鍵因素,本文試圖總結(jié)出問(wèn)題項(xiàng)目普遍存在的共性問(wèn)題并給出相應(yīng)的解決方案。

1. 程序員的宿命?

程序員的職業(yè)生涯中難免遇到爛項(xiàng)目,有些項(xiàng)目是你加入時(shí)已經(jīng)爛了,有些是自己從頭開始親手做成了爛項(xiàng)目,有些是從里到外的爛,有些是表面光鮮等你深入進(jìn)去發(fā)現(xiàn)是個(gè)“焦油坑”,有些是此時(shí)還沒爛但是已經(jīng)出現(xiàn)問(wèn)題征兆走在了腐爛的路上。

國(guó)內(nèi)基本上是這樣,國(guó)外情況我了解不多,不過(guò)從英文社區(qū)和技術(shù)媒體上老外同行的抱怨程度看,應(yīng)該是差不多的,雖然整體素質(zhì)可能更高,但是也因更久的信息化而積累了更多問(wèn)題。畢竟“焦油坑、Shit_Mountain 屎山”這些舶來(lái)的術(shù)語(yǔ)不是無(wú)緣無(wú)故被發(fā)明出來(lái)的。

Any way,這大概就是我們這個(gè)行業(yè)的宿命——要么改行,要么就是與爛項(xiàng)目爛代碼長(zhǎng)相伴。就像宇宙的“熵增加定律”一樣:

孤立系統(tǒng)的一切自發(fā)過(guò)程均向著令其狀態(tài)更無(wú)序的方向發(fā)展,如果要使系統(tǒng)恢復(fù)到原先的有序狀態(tài)是不可能的,除非外界對(duì)它做功。

面對(duì)這宿命的陰影,有些人認(rèn)命了麻木了,逐漸對(duì)這個(gè)行業(yè)失去熱情。

那些不認(rèn)命的選擇與之抗?fàn)?#xff0c;但是地上并沒有路,當(dāng)年軟件危機(jī)的陰云也從未真正散去,人月神話仍然是神話,于是人們做出了各自不同的判斷和嘗試:

  • 掀桌子另起爐灶派:

    • 很多人把項(xiàng)目做爛的原因歸咎于項(xiàng)目前期的基礎(chǔ)沒打好、需求不穩(wěn)定一路打補(bǔ)丁、前面的架構(gòu)師和程序員留下的爛攤子難以收拾。

    • 他們要么沒有信心去收拾爛攤子,要么覺得這是費(fèi)力不討好,于是要放棄掉項(xiàng)目,寄希望于出現(xiàn)一個(gè)機(jī)會(huì)能重頭再來(lái)。

    • 但是他們對(duì)于如何避免重蹈覆轍、做出另一個(gè)爛項(xiàng)目是沒有把握也沒有深入思考的,只是盲目樂(lè)觀的認(rèn)為自己比前任更高明。

  • 激進(jìn)改革派:

    • 這個(gè)派別把原因歸結(jié)于爛項(xiàng)目當(dāng)初沒有采用正確的編程語(yǔ)言、最新最強(qiáng)大的技術(shù)棧或工具。

    • 他們中一部分人也想著有機(jī)會(huì)另起爐灶,用時(shí)下最流行最熱門的技術(shù)棧(spring boot、springcloud、redis、nosql、docker、vue)。

    • 或者即便不另起爐灶,也認(rèn)為現(xiàn)有技術(shù)棧太過(guò)時(shí)無(wú)法容忍了(其實(shí)可能并不算過(guò)時(shí)),不用微服務(wù)不用分布式就不能接受,于是激進(jìn)的引入新技術(shù)棧,魯莽的對(duì)項(xiàng)目做大手術(shù)。

    • 這種對(duì)剛剛流行還不成熟技術(shù)的盲目跟風(fēng)、技術(shù)選型不慎重的情況非常普遍,今天在他們眼中落伍的技術(shù)棧,其實(shí)也不過(guò)是幾年前另一批人趕的時(shí)髦。

    • 我不反對(duì)技術(shù)上的追新,但是同樣的,這里的問(wèn)題是:他們對(duì)于大手術(shù)的風(fēng)險(xiǎn)和副作用,對(duì)如何避免重蹈覆轍用新技術(shù)架構(gòu)做出另一個(gè)爛項(xiàng)目,沒有把握也沒有深入思考的,只是盲目樂(lè)觀的認(rèn)為新技術(shù)能帶來(lái)成功。

    • 也沒人能阻止這種簡(jiǎn)歷驅(qū)動(dòng)的技術(shù)選型浮躁風(fēng)氣,畢竟花的是公司的資源,用新東西顯得自己很有追求,失敗了也不影響簡(jiǎn)歷美化,簡(jiǎn)歷上只會(huì)增加一段項(xiàng)目履歷和幾種精通技能,不會(huì)提到又做爛了一個(gè)項(xiàng)目,名利雙收穩(wěn)賺不賠。

  • 保守改良派:

    • 還有一類人他們不愿輕易放棄這個(gè)有問(wèn)題但仍在創(chuàng)造效益的項(xiàng)目,因?yàn)樗麄兛吹搅隧?xiàng)目仍然有維護(hù)的價(jià)值,也看到了另起爐灶的難度(萬(wàn)事開頭難,其實(shí)項(xiàng)目的冷啟動(dòng)存在很多外部制約因素)、大手術(shù)對(duì)業(yè)務(wù)造成影響的代價(jià)、系統(tǒng)遷移的難度和風(fēng)險(xiǎn)。

    • 同時(shí)他們嘗試用溫和漸進(jìn)的方式逐步改善項(xiàng)目質(zhì)量,采用一系列工程實(shí)踐(主要包括重構(gòu)熱點(diǎn)代碼、補(bǔ)自動(dòng)化測(cè)試、補(bǔ)文檔)來(lái)清理“技術(shù)債”,消除制約項(xiàng)目開發(fā)效率和交付質(zhì)量的瓶頸。

如果把一個(gè)問(wèn)題項(xiàng)目比作病入膏肓的病人,那么這三種做法分別相當(dāng)于是放棄治療、截肢手術(shù)、保守治療。

2. 一個(gè) 35+ 程序員的反思

年輕時(shí)候我也是掀桌子派和激進(jìn)派的,新工程新框架大開大合,一路走來(lái)經(jīng)驗(yàn)值技能樹蹭蹭的漲,跳槽加薪好不快活。

但是近幾年隨著年齡增長(zhǎng),一方面新東西學(xué)不動(dòng)了,另一方面對(duì)經(jīng)歷過(guò)的項(xiàng)目反思的多了觀念逐漸改變了。

對(duì)我觸動(dòng)最大的一件事是那個(gè)我在 2016 年初開始從零搭建起的項(xiàng)目,在我 2018 年底離開的時(shí)候(僅從代碼質(zhì)量角度)已經(jīng)讓我很不滿意了。只是,這一次沒有任何借口了:

  • 從技術(shù)選型到架構(gòu)設(shè)計(jì)到代碼規(guī)范,都是我自己做的,團(tuán)隊(duì)不大,也是我自己組建和一手帶出來(lái)的;

  • 最開始的半年進(jìn)展非常順利,用著我最趁手的技術(shù)和工具一路狂奔,年底前替換掉了之前采購(gòu)的那個(gè)垃圾產(chǎn)品(對(duì)的,有個(gè)前任在業(yè)務(wù)上做參照也算是個(gè)很大的有利因素);

  • 做的過(guò)程我也算是全力以赴,用盡畢生所學(xué)——前面 13 年工作的經(jīng)驗(yàn)值和走過(guò)的彎路、教訓(xùn),使得公司只用其它同類公司同類項(xiàng)目 20% 的資源就把平臺(tái)做起來(lái)了;

  • 如果說(shuō)多快好省是最高境界,那么當(dāng)時(shí)的我算是做到了多、快、省——交付的功能非常豐富且貼近業(yè)務(wù)需求、開發(fā)節(jié)奏快速、對(duì)公司開發(fā)資源很節(jié)省;

  • 但是現(xiàn)在看來(lái),“好”就遠(yuǎn)遠(yuǎn)沒有達(dá)到了,到了項(xiàng)目中期,簡(jiǎn)單優(yōu)先級(jí)高的需求都已經(jīng)做完了,公司業(yè)務(wù)上出現(xiàn)了新的挑戰(zhàn)——接入另一個(gè)核心系統(tǒng)以及外部平臺(tái),真正的考驗(yàn)來(lái)了。

  • 那個(gè)改造工程影響面比較大,需要對(duì)我們的系統(tǒng)做大面積修改,最麻煩的是這意味著從一個(gè)簡(jiǎn)單的單體系統(tǒng)變成了一個(gè)分布式的系統(tǒng),而且業(yè)務(wù)涉及資金交易,可靠性要求較高,是難上加難。

  • 于是問(wèn)題開始出現(xiàn)了:我之前架構(gòu)的優(yōu)點(diǎn)——簡(jiǎn)單直接——這個(gè)時(shí)候不再是優(yōu)點(diǎn)了,簡(jiǎn)單直接的架構(gòu)在業(yè)務(wù)環(huán)境、技術(shù)環(huán)境都簡(jiǎn)單的情況下可以做到多快好省,但是當(dāng)業(yè)務(wù)、技術(shù)環(huán)境都陡然復(fù)雜起來(lái)時(shí),就不行了;

  • 具體的表現(xiàn)就是:架構(gòu)和代碼層面的結(jié)構(gòu)都快速的變得復(fù)雜、混亂起來(lái)了——熵急劇增加;

  • 后面的事情就一發(fā)不可收拾:代碼改起來(lái)越來(lái)越吃力、測(cè)試問(wèn)題變多、生產(chǎn)環(huán)境故障和問(wèn)題變多、于是消耗在排查測(cè)試問(wèn)題生產(chǎn)問(wèn)題和修復(fù)數(shù)據(jù)方面的精力急劇增加、出現(xiàn)惡性循環(huán)。。。

  • 到了這個(gè)境地,項(xiàng)目就算是做爛了!一個(gè)我從頭開始做起的沒有任何借口的失敗!

于是我意識(shí)到一個(gè)非常淺顯的道理:擁有一張空白的畫卷、一支最高級(jí)的畫筆、一間專業(yè)的畫室,無(wú)法保證你可以畫出美麗的畫卷。如果你不善于畫畫,那么一切都是空想和意淫。

然后我變成了一個(gè)“保守改良派”,因?yàn)槲乙庾R(shí)到掀桌子和激進(jìn)的改革都是不負(fù)責(zé)任的,說(shuō)不好聽的那樣其實(shí)是掩耳盜鈴、逃避困難,人不可能逃避一輩子,你總要面對(duì)。

即便掀了桌子另起爐灶了,你還是需要找到一種辦法把這個(gè)新的爐灶燒好,因?yàn)殡S著項(xiàng)目發(fā)展之前的老問(wèn)題還是會(huì)一個(gè)一個(gè)冒出來(lái),還是需要面對(duì)現(xiàn)實(shí)、不逃避、找辦法。

面對(duì)問(wèn)題不僅有助于你把當(dāng)前項(xiàng)目做好,也同樣有助于將來(lái)有新的項(xiàng)目時(shí)更好的把握住機(jī)會(huì)。

無(wú)論是職業(yè)生涯還是自然年齡,人到了這個(gè)階段都開始喜歡回顧和總結(jié),也變得比過(guò)去更在乎項(xiàng)目、產(chǎn)品乃至公司的商業(yè)成敗。

軟件開發(fā)作為一種商業(yè)活動(dòng),判斷其成敗的依據(jù)應(yīng)該是:能否以可接受的成本、可預(yù)期的時(shí)間節(jié)奏、穩(wěn)定的質(zhì)量水平、持續(xù)交付滿足業(yè)務(wù)需要的功能市場(chǎng)需要的產(chǎn)品。

其實(shí)就是項(xiàng)目管理四要素——成本、進(jìn)度、范圍、質(zhì)量,傳統(tǒng)項(xiàng)目管理理論認(rèn)為這四要素彼此制約難以兼得,項(xiàng)目管理的藝術(shù)在于四要素的平衡取舍。

關(guān)于軟件工程和項(xiàng)目管理的理論和著作已經(jīng)很多很成熟,這里我從程序員的視角提出一個(gè)新的觀點(diǎn)——質(zhì)量不可妥協(xié):

  • 質(zhì)量要素不是一個(gè)可以被犧牲和妥協(xié)的要素——犧牲質(zhì)量會(huì)導(dǎo)致其它三要素全都受損,反之同理,追求質(zhì)量會(huì)讓你在其它三個(gè)方面同時(shí)受益。

  • 在保持一個(gè)質(zhì)量水平的前提下,成本、進(jìn)度、范圍三要素確確實(shí)實(shí)是互相制約關(guān)系——典型的比如犧牲成本(加班加點(diǎn))來(lái)加快進(jìn)度交付急需的功能。

  • 正如著名的“破窗效應(yīng)”所啟示的那樣:任何一種不良現(xiàn)象的存在,都在傳遞著一種信息,這種信息會(huì)導(dǎo)致不良現(xiàn)象的無(wú)限擴(kuò)展,同時(shí)必須高度警覺那些看起來(lái)是偶然的、個(gè)別的、輕微的“過(guò)錯(cuò)”,如果對(duì)這種行為不聞不問(wèn)、熟視無(wú)睹、反應(yīng)遲鈍或糾正不力,就會(huì)縱容更多的人“去打爛更多的窗戶玻璃”,就極有可能演變成“千里之堤,潰于蟻穴”的惡果——質(zhì)量不佳的代碼之于一個(gè)項(xiàng)目,正如一扇破了的窗之于一幢建筑、一個(gè)螞蟻巢之于一座大堤。

  • 好消息是,只要把質(zhì)量提上去項(xiàng)目就會(huì)逐漸走上健康的軌道,其它三個(gè)方面也都會(huì)改善。管好了質(zhì)量,你就很大程度上把握住了項(xiàng)目成敗的關(guān)鍵因素。

  • 壞消息是,項(xiàng)目的質(zhì)量很容易失控,現(xiàn)實(shí)中質(zhì)量不佳、越做越臃腫混亂的項(xiàng)目比比皆是,質(zhì)量改善越做越好的案例聞所未聞,以至于人們將其視為如同物理學(xué)中“熵增加定律”一樣的必然規(guī)律了。

  • 當(dāng)然任何事情都有一個(gè)度的問(wèn)題,當(dāng)質(zhì)量低于某個(gè)水平時(shí)才會(huì)導(dǎo)致其它三要素同時(shí)受損。反之當(dāng)質(zhì)量高到某個(gè)水平以后,繼續(xù)追求質(zhì)量不僅得不到明顯收益,而且也會(huì)損害其它三要素——邊際效用遞減定律。

  • 這個(gè)度需要你為自己去評(píng)估和測(cè)量,如果目前的質(zhì)量水平還在兩者之間,那么就應(yīng)該重點(diǎn)改進(jìn)項(xiàng)目質(zhì)量。當(dāng)然,現(xiàn)實(shí)世界中很少看到哪個(gè)項(xiàng)目質(zhì)量高到了不需要重視的程度。

3. 項(xiàng)目走向衰敗的最常見誘因——代碼質(zhì)量不佳

一個(gè)項(xiàng)目的衰敗一如一個(gè)人健康狀況的惡化,當(dāng)然可能有多種多樣的原因——比如需求失控、業(yè)務(wù)調(diào)整、人員變動(dòng)流失。但是作為我們技術(shù)人,如果能做好自己分內(nèi)的工作——編寫出可維護(hù)的代碼、減少技術(shù)債利息成本、交付一個(gè)健壯靈活的應(yīng)用架構(gòu),那也絕對(duì)是功德無(wú)量的。

雖然很難估算出這究竟能挽救多少項(xiàng)目,但是在我十多年職業(yè)生涯中,經(jīng)歷的和近距離觀察的幾十個(gè)項(xiàng)目,確實(shí)看到了大量的項(xiàng)目正是由于代碼質(zhì)量不佳導(dǎo)致的失敗和遺憾,同時(shí)我也發(fā)現(xiàn)其實(shí)失敗項(xiàng)目的很多問(wèn)題、癥結(jié)也確確實(shí)實(shí)都可以歸因到項(xiàng)目代碼的混亂和質(zhì)量低下,比如一個(gè)常見的項(xiàng)目腐爛惡性循環(huán):代碼亂》bug 多》排查問(wèn)題耗時(shí)》復(fù)用度低》加班 996》士氣低落……

所謂“千里之堤,毀于蟻穴”,代碼問(wèn)題就是蟻穴。

接下來(lái),讓我們從項(xiàng)目管理聚焦到項(xiàng)目代碼質(zhì)量這個(gè)相對(duì)小的領(lǐng)域來(lái)深入剖析。編寫高質(zhì)量可維護(hù)的代碼是程序員的基本修養(yǎng),本文試圖在代碼層面找到一些失敗項(xiàng)目中普遍存在的癥結(jié)問(wèn)題,同時(shí)基于個(gè)人十幾年開發(fā)經(jīng)驗(yàn)總結(jié)出的一些設(shè)計(jì)模式作為藥方分享出來(lái)。

關(guān)于代碼質(zhì)量的話題其實(shí)很難通過(guò)一篇文章闡述明白,甚至需要一本書的篇幅,里面涉及到的很多概念關(guān)注點(diǎn)之間存在復(fù)雜微妙關(guān)系。

推薦《設(shè)計(jì)模式之美》的第二章節(jié)《從哪些維度評(píng)判代碼質(zhì)量的好壞?如何具備寫出高質(zhì)量代碼的能力?》,這是我看到的關(guān)于代碼質(zhì)量主題最精彩深刻的論述。

4. 一個(gè)失敗項(xiàng)目復(fù)盤

先貼幾張代碼截圖,看一下這個(gè)重病纏身的項(xiàng)目的病灶和癥狀:

  • 這是該項(xiàng)目中一個(gè)最核心、最復(fù)雜也是最經(jīng)常要被改動(dòng)的 class,代碼行數(shù) 4881;

  • 結(jié)果就是冗長(zhǎng)的 API 列表(列表需要滾動(dòng) 4 屏才能到底,公有私有 API 180 個(gè));

  • 還是那個(gè) Class,頭部的 import 延綿到了 139 行,去掉第一行 package 聲明和少量空行總共 import 引入了 130 個(gè) class!

  • 還是那個(gè)坑爹的組件,從 156 行開始到 235 行聲明了 Spring 依賴注入的組件 40 個(gè)!

這里先不去分析這個(gè)類的問(wèn)題,只是初步展示一下病情嚴(yán)重程度。

我相信這應(yīng)該不算是特別糟糕的情況,比這個(gè)嚴(yán)重的項(xiàng)目俯拾皆是,但是這也應(yīng)該足夠拿來(lái)暴露問(wèn)題、剖析成因了。

4.1 癥結(jié) 1:組件粒度過(guò)大、API 泛濫

分層的理念早已深入人心,尤其是業(yè)務(wù)邏輯層的獨(dú)立,徹底杜絕了之前(不分層的年代)業(yè)務(wù)邏輯與展現(xiàn)邏輯、持久化邏輯等混雜的問(wèn)題。

但是好景不長(zhǎng),隨著業(yè)務(wù)的復(fù)雜和變更,在業(yè)務(wù)邏輯層的復(fù)雜性也急劇增加,成為了新的開發(fā)效率瓶頸, 問(wèn)題就出在了業(yè)務(wù)邏輯組件的劃分方式——按領(lǐng)域模型劃分業(yè)務(wù)邏輯組件:

  • 業(yè)界關(guān)于如何設(shè)計(jì)業(yè)務(wù)邏輯層 并沒有標(biāo)準(zhǔn)和最佳實(shí)踐,絕大多數(shù)項(xiàng)目(我自己經(jīng)歷過(guò)的項(xiàng)目以及我有機(jī)會(huì)深入了解的項(xiàng)目)中大家都是想當(dāng)然的按照業(yè)務(wù)領(lǐng)域?qū)ο髞?lái)設(shè)計(jì);

  • 例如:領(lǐng)域?qū)嶓w對(duì)象有 Account、Order、Delivery、Campaign。于是業(yè)務(wù)邏輯層就設(shè)計(jì)出 AccountService、OrderService、DeliveryService、CampaignService

  • 這種做法在項(xiàng)目簡(jiǎn)單是沒什么問(wèn)題,事實(shí)上項(xiàng)目簡(jiǎn)單時(shí) 你隨便怎么設(shè)計(jì)都問(wèn)題不大。

  • 但是當(dāng)項(xiàng)目變大和復(fù)雜以后,就會(huì)出現(xiàn)問(wèn)題了:

    • 組件臃腫:Service 組件的個(gè)數(shù)跟領(lǐng)域?qū)嶓w對(duì)象個(gè)數(shù)基本相當(dāng),必然造成個(gè)別 Service 組件變得非常臃腫——API 非常多,代碼行數(shù)達(dá)到幾千行;

    • 職責(zé)模糊:業(yè)務(wù)邏輯往往跨多個(gè)領(lǐng)域?qū)嶓w,無(wú)論放在哪個(gè) Service 都不合適,同樣的,要找一個(gè)功能的實(shí)現(xiàn)邏輯也無(wú)法確定在哪個(gè) Service 中;

    • 代碼重復(fù) or 邏輯糾纏的兩難選擇:當(dāng)遇到一個(gè)業(yè)務(wù)邏輯,其中的某個(gè)環(huán)節(jié)在另一個(gè)業(yè)務(wù)邏輯 API 中已經(jīng)實(shí)現(xiàn),這時(shí)如果不想忍受重復(fù)實(shí)現(xiàn)和代碼,就只能去調(diào)用那個(gè) API。但這樣就造成了業(yè)務(wù)邏輯組件之間的耦合與依賴,這種耦合與依賴很快會(huì)擴(kuò)散——新的 API 又會(huì)被其它業(yè)務(wù)邏輯依賴,最終形成蜘蛛網(wǎng)一樣的復(fù)雜依賴甚至循環(huán)依賴;

    • 復(fù)用代碼、減少重復(fù)雖然是好的,但是復(fù)雜耦合依賴的害處也很大——趕走一只狼引來(lái)了一只虎。兩杯毒酒給你選!

前面截圖的那個(gè)問(wèn)題組件 ContractService 就是一個(gè)典型案例,這樣的組件往往是熱點(diǎn)代碼以及整個(gè)項(xiàng)目的開發(fā)效率的瓶頸。

4.2 藥方 1:倒金字塔結(jié)構(gòu)——業(yè)務(wù)邏輯組件職責(zé)單一、禁止層內(nèi)依賴

問(wèn)題根源的反面其實(shí)就藏著解決方案,只是需要我們有意識(shí)的去改變習(xí)慣、遵循新的設(shè)計(jì)風(fēng)格,而不是憑直覺去設(shè)計(jì):

  • 業(yè)務(wù)邏輯層應(yīng)該被設(shè)計(jì)成一個(gè)個(gè)功能非常單一的小組件,所謂小是指 API 數(shù)量少、代碼行數(shù)少;

  • 由于職責(zé)單一因此必然組件數(shù)量多,每一個(gè)組件對(duì)應(yīng)一個(gè)很具體的業(yè)務(wù)功能點(diǎn)(或者幾個(gè)相近的);

  • 復(fù)用(調(diào)用、依賴)只應(yīng)該發(fā)生在相鄰的兩層之間——上層調(diào)用下層的 API 來(lái)實(shí)現(xiàn)對(duì)下層功能的復(fù)用;

  • 于是系統(tǒng)架構(gòu)就自然呈現(xiàn)出倒立的金字塔形狀:越接近頂層的業(yè)務(wù)場(chǎng)景組件數(shù)量越多,越往下層的復(fù)用性高,于是組件數(shù)量越少。

4.3 癥結(jié) 2:低內(nèi)聚、高耦合

經(jīng)典面向?qū)ο罄碚摳嬖V我們,好的代碼結(jié)構(gòu)應(yīng)該是“高內(nèi)聚、低耦合”的:

  • 高內(nèi)聚:組件本身應(yīng)該盡可能的包含其所實(shí)現(xiàn)功能的所有重要信息和細(xì)節(jié),以便讓維護(hù)者無(wú)需跳轉(zhuǎn)到其它多個(gè)地方去了解必要的知識(shí)。

  • 低耦合:組件之間的互相依賴和了解盡可能少,以便在一個(gè)組件需要改動(dòng)時(shí)其它組件不受影響。

其實(shí)這兩者就是一體兩面,做到了高內(nèi)聚基本也就做到了低耦合,相反如果內(nèi)聚度很低,勢(shì)必存在大量高耦合的組件。

我觀察發(fā)現(xiàn),很低項(xiàng)目都存在低內(nèi)聚、高耦合的問(wèn)題。根本原因在于很多程序員,甚至是很多經(jīng)驗(yàn)豐富的程序員也缺少這方面的意識(shí)——對(duì)概念不甚清楚、對(duì)危害沒有認(rèn)識(shí)、對(duì)如何避免更是無(wú)從談起。

很多人從一開始就憑直覺寫程序,有了一定經(jīng)驗(yàn)以后一般能認(rèn)識(shí)到重復(fù)代碼的危害,對(duì)復(fù)用性有很強(qiáng)的認(rèn)識(shí),于是就會(huì)掉進(jìn)一個(gè)陷阱——盲目追求復(fù)用,結(jié)果破壞了內(nèi)聚性。

  • 業(yè)界關(guān)于“復(fù)用性”的認(rèn)識(shí)存在一個(gè)誤區(qū)——認(rèn)為包括業(yè)務(wù)邏輯組件在內(nèi)的任何層面的組件都應(yīng)該追求最大限度的可復(fù)用性;

  • 復(fù)用當(dāng)然是好的,但那應(yīng)該有個(gè)前提條件:不增加系統(tǒng)復(fù)雜度的情況下的復(fù)用,才是好的。

  • 什么樣的復(fù)用會(huì)增加系統(tǒng)復(fù)雜性、是不好的呢?前面提到的,一個(gè)業(yè)務(wù)邏輯 API 被另一個(gè)業(yè)務(wù)邏輯 API 復(fù)用——就是不好的:

    • 損害了穩(wěn)定性:因?yàn)闃I(yè)務(wù)邏輯本身是跟現(xiàn)實(shí)世界的業(yè)務(wù)掛鉤的,而業(yè)務(wù)會(huì)發(fā)生變化;當(dāng)你復(fù)用一個(gè)會(huì)發(fā)生變化的 API,相當(dāng)于在沙子上建高樓——地基是松動(dòng)的;

    • 增加了復(fù)雜性:這樣的依賴還造成代碼可讀性降低——在一個(gè)本就復(fù)雜的業(yè)務(wù)邏輯代碼中,包含了對(duì)另一個(gè)復(fù)雜業(yè)務(wù)邏輯的調(diào)用,復(fù)雜度會(huì)急劇增加,而且會(huì)不斷泛濫和傳遞;

    • 內(nèi)聚性被破壞:由于業(yè)務(wù)邏輯被打散在了多個(gè)組件的方法內(nèi),變得支離破碎,無(wú)法在一個(gè)地方看清整體邏輯脈絡(luò)和實(shí)現(xiàn)步驟——內(nèi)聚性被破壞,同時(shí)也意味著,這個(gè)調(diào)用鏈條上涉及的所有組件之間存在高耦合。

4.4 藥方 2:復(fù)用的兩種正確姿勢(shì)——打造自己的 lib 和 framework

軟件架構(gòu)中有兩種東西來(lái)實(shí)現(xiàn)復(fù)用——lib 和 framework,

  • lib 庫(kù)是供你(應(yīng)用程序)調(diào)用的,它幫你實(shí)現(xiàn)特定的能力(比如日志、數(shù)據(jù)庫(kù)驅(qū)動(dòng)、json 序列化、日期計(jì)算、http 請(qǐng)求)。

  • framework 框架是供你擴(kuò)展的,它本身就是半個(gè)應(yīng)用程序,定義好了組件劃分和交互機(jī)制,你需要按照其規(guī)則擴(kuò)展出特定的實(shí)現(xiàn)并綁定集成到其中,來(lái)完成一個(gè)應(yīng)用程序。

  • lib 就是組合方式的復(fù)用,framework 則是繼承式的復(fù)用,繼承的 Java 關(guān)鍵字是 extends,所以本質(zhì)上是擴(kuò)展。

  • 過(guò)去有個(gè)說(shuō)法:“組合優(yōu)于繼承,能用組合解決的問(wèn)題盡量不要繼承”。我不同意這個(gè)說(shuō)法,這容易誤導(dǎo)初學(xué)者以為組合優(yōu)于繼承,其實(shí)繼承才是面向?qū)ο笞顝?qiáng)大的地方,當(dāng)然任何東西都不能亂用。

  • 典型的繼承亂用就是為了獲得父類的某個(gè) API 而去繼承,繼承一定是為了擴(kuò)展,而不是為了直接獲得一個(gè)能力,獲得能力應(yīng)該調(diào)用 lib,父類不應(yīng)該去實(shí)現(xiàn)具體功能,那是 lib 該做的事。

  • 也不應(yīng)該為了使用 lib 而去繼承 lib 中的 Class。lib 就是用來(lái)被組合被調(diào)用的,framework 就是用來(lái)被繼承、擴(kuò)展的。

  • 再展開一下:lib 既可以是第三方的(log4j、httpclient、fastjson),也可是你自己工程的(比如你的持久層 Dao、你的 utils);

  • framework 同理,既可以是第三方的(springmvc、jpa、springsecurity),也可以是你項(xiàng)目?jī)?nèi)封裝的面向具體業(yè)務(wù)領(lǐng)域的(比如 report、excel 導(dǎo)出、paging 或任何可復(fù)用的算法、流程)。

  • 從這個(gè)意義上說(shuō),一個(gè)項(xiàng)目中的代碼其實(shí)只有 3 種:自定義的 lib class、自定義的 framework 相關(guān) class、擴(kuò)展第三方或自定義 framework 的組件 class。

  • 再擴(kuò)展一下:相對(duì)于過(guò)去,現(xiàn)在我們已經(jīng)有了足夠多的第三方 lib 和 framework 來(lái)復(fù)用,來(lái)幫助項(xiàng)目節(jié)省大量代碼,開發(fā)工作似乎變成了索然無(wú)味、沒技術(shù)含量的 CRUD。但是對(duì)于業(yè)務(wù)非常復(fù)雜的項(xiàng)目,則需要有經(jīng)驗(yàn)、有抽象思維、懂設(shè)計(jì)模式的人,去設(shè)計(jì)面向業(yè)務(wù)的 framework 和面向業(yè)務(wù)的 lib,只有這樣才能交付可維護(hù)、可擴(kuò)展、可復(fù)用的軟件架構(gòu)——高質(zhì)量架構(gòu),幫助項(xiàng)目或產(chǎn)品取得成功。

4.5 癥結(jié) 3:抽象不夠、邏輯糾纏——High Level 業(yè)務(wù)邏輯和 Low Level 實(shí)現(xiàn)邏輯糾纏

當(dāng)我們說(shuō)“代碼中包含的業(yè)務(wù)邏輯”的時(shí)候,我們到底在說(shuō)什么?業(yè)界并沒有一個(gè)標(biāo)準(zhǔn),大家經(jīng)常講的 CRUD 增刪改查其實(shí)屬于更底層的數(shù)據(jù)訪問(wèn)邏輯。

我的觀點(diǎn)是:所謂代碼中的業(yè)務(wù)邏輯,是指這段代碼所表現(xiàn)出的所有輸入輸出規(guī)則、算法和行為,通常可以分為以下 5 類:

  • 輸入合法性校驗(yàn):

  • 業(yè)務(wù)規(guī)則校驗(yàn):典型的如檢查交易記錄狀態(tài)、金額、時(shí)限、權(quán)限等,通常包含數(shù)據(jù)庫(kù)或外部接口的查詢作為參考;

  • 數(shù)據(jù)持久化行為:數(shù)據(jù)庫(kù)、緩存、文件、日志等任何形式的數(shù)據(jù)寫入行為;

  • 外部接口調(diào)用行為;

  • 輸出/返回值準(zhǔn)備。

當(dāng)然具體到某一個(gè)組件實(shí)例,可能不會(huì)包括上述全部 5 類業(yè)務(wù)邏輯,但是也可能每一類業(yè)務(wù)邏輯存在多個(gè)。

單這樣看你可能覺得并不是特別復(fù)雜,但是現(xiàn)實(shí)中上述 5 類業(yè)務(wù)邏輯中的每一個(gè)通常還包含著一到多個(gè)底層實(shí)現(xiàn)邏輯,如 CRUD 數(shù)據(jù)訪問(wèn)邏輯或第三方 API 的調(diào)用。

例如輸入合法性校驗(yàn),通常需要查詢對(duì)應(yīng)記錄是否存在,外部接口調(diào)用前通常需要查詢相關(guān)記錄以獲得調(diào)用接口需要的參數(shù),調(diào)用接口后還需要根據(jù)結(jié)果更新相關(guān)記錄狀態(tài)。

顯然這里存在兩個(gè) Level 的邏輯——High Level 的與業(yè)務(wù)需求對(duì)應(yīng)且關(guān)聯(lián)緊密的邏輯、Low Level 的實(shí)現(xiàn)邏輯。

如果對(duì)兩個(gè) Level 的邏輯不加以區(qū)分、混為一談,代碼質(zhì)量立刻就會(huì)遭到嚴(yán)重?fù)p害:

  • 可讀性變差:兩個(gè)維度的復(fù)雜性——業(yè)務(wù)復(fù)雜性和底層實(shí)現(xiàn)的技術(shù)復(fù)雜性——被摻雜在了一起,復(fù)雜度 1+1>2 劇增,給其他人閱讀代碼增加很大負(fù)擔(dān);

  • 可維護(hù)性差:可維護(hù)性通常指排查和解決問(wèn)題所需花費(fèi)的代價(jià)高低,當(dāng)兩個(gè) level 的邏輯糾纏在一起,會(huì)使排查問(wèn)題變的更困難,修復(fù)問(wèn)題時(shí)也更容易出錯(cuò);

  • 可擴(kuò)展性無(wú)從談起:擴(kuò)展性通常指為系統(tǒng)增加一個(gè)特性所需花費(fèi)的代價(jià)高低,代價(jià)越高擴(kuò)展性越差;與排查修復(fù)問(wèn)題類似,邏輯糾纏顯然也會(huì)使添加新特性變得困難、一不小心就破壞了已有功能。

下面這段代碼就是一個(gè)典型案例——High Level 的邏輯流程(參數(shù)獲取、反序列化、參數(shù)校驗(yàn)、緩存寫入、數(shù)據(jù)庫(kù)持久化、更新相關(guān)交易記錄)完全淹沒在了 Low Level 的實(shí)現(xiàn)邏輯(字符串比較、Json 反序列化、redis 操作、dao 操作以及前后各種瑣碎的參數(shù)準(zhǔn)備和返回值處理)。下一節(jié)我會(huì)針對(duì)這段問(wèn)題代碼給出重構(gòu)方案。

@Override public void updateFromMQ(String compress) {try {JSONObject object = JSON.parseObject(compress);if (StringUtils.isBlank(object.getString("type")) || StringUtils.isBlank(object.getString("mobile")) || StringUtils.isBlank(object.getString("data"))){throw new AppException("MQ返回參數(shù)異常");}logger.info(object.getString("mobile")+"<<<<<<<<<獲取來(lái)自MQ的授權(quán)數(shù)據(jù)>>>>>>>>>"+object.getString("type"));Map map = new HashMap();map.put("type",CrawlingTaskType.get(object.getInteger("type")));map.put("mobile", object.getString("mobile"));List<CrawlingTask> list = baseDAO.find("from crt c where c.phoneNumber=:mobile and c.taskType=:type", map);redisClientTemplate.set(object.getString("mobile") + "_" + object.getString("type"),CompressUtil.compress( object.getString("data")));redisClientTemplate.expire(object.getString("mobile") + "_" + object.getString("type"), 2*24*60*60);//保存成功 存入redis 保存48小時(shí)CrawlingTask crawlingTask = null;// providType:(0:新顏,1XX支付寶,2:ZZ淘寶,3:TT淘寶)if (CollectionUtils.isNotEmpty(list)){crawlingTask = list.get(0);crawlingTask.setJsonStr(object.getString("data"));}else{//新增crawlingTask = new CrawlingTask(UUID.randomUUID().toString(), object.getString("data"),object.getString("mobile"), CrawlingTaskType.get(object.getInteger("type")));crawlingTask.setNeedUpdate(true);}baseDAO.saveOrUpdate(crawlingTask);//保存芝麻分到xyzif ("3".equals(object.getString("type"))){String data = object.getString("data");Integer zmf = JSON.parseObject(data).getJSONObject("taobao_user_info").getInteger("zm_score");Map param = new HashMap();param.put("phoneNumber", object.getString("mobile"));List<Dperson> list1 = personBaseDaoI.find("from xyz where phoneNumber=:phoneNumber", param);if (list1 !=null){for (Dperson dperson:list1){dperson.setZmScore(zmf);personBaseDaoI.saveOrUpdate(dperson);AppFlowUtil.updateAppUserInfo(dperson.getToken(),null,null,zmf);//查詢多租戶表 身份認(rèn)證、淘寶認(rèn)證 為0 置為1}}}} catch (Exception e) {logger.error("更新my MQ授權(quán)信息失敗", e);throw new AppException(e.getMessage(),e);} }

4.6 藥方 3:控制邏輯分離——業(yè)務(wù)模板 Pattern of NestedBusinessTemplate

解決“邏輯糾纏”最關(guān)鍵是要找到一種隔離機(jī)制,把兩個(gè) Level 的邏輯分開——控制邏輯分離,分離的好處很多:

  • 根據(jù)經(jīng)驗(yàn),當(dāng)我們著手維護(hù)一段代碼時(shí),一定是想先弄清楚它的整體流程、算法和行為,而不是一上來(lái)就去研究它的細(xì)枝末節(jié);

  • 控制邏輯分離后,只需要去看 High Level 部分就能了解到上述內(nèi)容,閱讀代碼的負(fù)擔(dān)大幅度降低,代碼可讀性顯著增強(qiáng);

  • 讀懂代碼是后續(xù)一切維護(hù)、重構(gòu)工作的前提,而且一份代碼被讀的次數(shù)遠(yuǎn)遠(yuǎn)高于被修改的次數(shù)(高一個(gè)數(shù)量級(jí)),因此代碼對(duì)人的可讀性再怎么強(qiáng)調(diào)都不為過(guò),可讀性增強(qiáng)可以大幅度提高系統(tǒng)可維護(hù)性,也是重構(gòu)的最主要目標(biāo)。

  • 同時(shí),根據(jù)我的經(jīng)驗(yàn),High Level 業(yè)務(wù)邏輯的變更往往比 Low Level 實(shí)現(xiàn)邏輯變更要來(lái)的頻繁,畢竟前者跟業(yè)務(wù)直接對(duì)應(yīng)。當(dāng)然不同類型項(xiàng)目情況不一樣,另外它們發(fā)生變更的時(shí)間點(diǎn)往往也不同;

  • 在這樣的背景下,控制邏輯分離的好處就更明顯了:每次維護(hù)、擴(kuò)充系統(tǒng)功能只需改動(dòng)一個(gè) Levle 的代碼,另一個(gè) Level 不受影響或影響很小,這會(huì)大幅降低修改成本和風(fēng)險(xiǎn)。

我在總結(jié)過(guò)去多個(gè)項(xiàng)目中的教訓(xùn)和經(jīng)驗(yàn)后,總結(jié)出了一項(xiàng)最佳實(shí)踐或者說(shuō)是設(shè)計(jì)模式——業(yè)務(wù)模板 Pattern of NestedBusinessTemplat,可以非常簡(jiǎn)單、有效的分離兩類邏輯,先看代碼:

public class XyzService {abstract class AbsUpdateFromMQ {public final void doProcess(String jsonStr) {try {JSONObject json = doParseAndValidate(jsonStr);cache2Redis(json);saveJsonStr2CrawingTask(json);updateZmScore4Dperson(json);} catch (Exception e) {logger.error("更新my MQ授權(quán)信息失敗", e);throw new AppException(e.getMessage(), e);}}protected abstract void updateZmScore4Dperson(JSONObject json);protected abstract void saveJsonStr2CrawingTask(JSONObject json);protected abstract void cache2Redis(JSONObject json);protected abstract JSONObject doParseAndValidate(String json) throws AppException; } @SuppressWarnings({ "unchecked", "rawtypes" }) public void processAuthResultDataCallback(String compress) {new AbsUpdateFromMQ() { @Override protected void updateZmScore4Dperson(JSONObject json) {//保存芝麻分到xyzif ("3".equals(json.getString("type"))){String data = json.getString("data");Integer zmf = JSON.parseObject(data).getJSONObject("taobao_user_info").getInteger("zm_score");Map param = new HashMap();param.put("phoneNumber", json.getString("mobile"));List<Dperson> list1 = personBaseDaoI.find("from xyz where phoneNumber=:phoneNumber", param);if (list1 !=null){for (Dperson dperson:list1){dperson.setZmScore(zmf);personBaseDaoI.saveOrUpdate(dperson);AppFlowUtil.updateAppUserInfo(dperson.getToken(),null,null,zmf);}}} }@Override protected void saveJsonStr2CrawingTask(JSONObject json) {Map map = new HashMap();map.put("type",CrawlingTaskType.get(json.getInteger("type")));map.put("mobile", json.getString("mobile"));List<CrawlingTask> list = baseDAO.find("from crt c where c.phoneNumber=:mobile and c.taskType=:type", map);CrawlingTask crawlingTask = null;// providType:(0:xx,1yy支付寶,2:zz淘寶,3:tt淘寶)if (CollectionUtils.isNotEmpty(list)){crawlingTask = list.get(0);crawlingTask.setJsonStr(json.getString("data"));}else{//新增crawlingTask = new CrawlingTask(UUID.randomUUID().toString(), json.getString("data"),json.getString("mobile"), CrawlingTaskType.get(json.getInteger("type")));crawlingTask.setNeedUpdate(true);}baseDAO.saveOrUpdate(crawlingTask); }@Override protected void cache2Redis(JSONObject json) {redisClientTemplate.set(json.getString("mobile") + "_" + json.getString("type"),CompressUtil.compress( json.getString("data")));redisClientTemplate.expire(json.getString("mobile") + "_" + json.getString("type"), 2*24*60*60); }@Override protected JSONObject doParseAndValidate(String json) throws AppException {JSONObject object = JSON.parseObject(json);if (StringUtils.isBlank(object.getString("type")) || StringUtils.isBlank(object.getString("mobile")) || StringUtils.isBlank(object.getString("data"))){throw new AppException("MQ返回參數(shù)異常");}logger.info(object.getString("mobile")+"<<<<<<<<<獲取來(lái)自MQ的授權(quán)數(shù)據(jù)>>>>>>>>>"+object.getString("type"));return object;}}.doProcess(compress); }

如果你熟悉經(jīng)典的 GOF23 種設(shè)計(jì)模式,很容易發(fā)現(xiàn)上面的代碼示例其實(shí)就是 Template Method 設(shè)計(jì)模式的運(yùn)用,沒什么新鮮的。

沒錯(cuò),我這個(gè)方案沒有提出和創(chuàng)造任何新東西,我只是在實(shí)踐中偶然發(fā)現(xiàn) Template Method 設(shè)計(jì)模式真的非常適合解決廣泛存在的邏輯糾纏問(wèn)題,而且也發(fā)現(xiàn)很少有程序員能主動(dòng)運(yùn)用這個(gè)設(shè)計(jì)模式;一部分原因可能是意識(shí)到“邏輯糾纏”問(wèn)題的人本就不多,同時(shí)熟悉這個(gè)設(shè)計(jì)模式并能自如運(yùn)用的人也不算多,兩者的交集自然就是少得可憐;不管是什么原因,結(jié)果就是這個(gè)問(wèn)題廣泛存在成了通病。

我看到一部分對(duì)代碼質(zhì)量有追求的程序員 他們的解決辦法是通過(guò)"結(jié)構(gòu)化編程"和“模塊化編程”:

  • 把 Low Level 邏輯提取成 private function,被 High Level 代碼所在的 function 直接調(diào)用;

    • 問(wèn)題 1 硬連接不靈活:首先,這樣雖然起到了一定的隔離效果,但是兩個(gè) level 之間是靜態(tài)的硬關(guān)聯(lián),Low Level 無(wú)法被簡(jiǎn)單的替換,替換時(shí)還是需要修改和影響到 High Level 部分;

    • 問(wèn)題 2 組件內(nèi)可見性造成混亂:提取出來(lái)的 private function 在當(dāng)前組件內(nèi)是全局可見的——對(duì)其它無(wú)關(guān)的 High Level function 也是可見的,各個(gè)模塊之間仍然存在邏輯糾纏。這在很多項(xiàng)目中的熱點(diǎn)代碼中很常見,問(wèn)題也很突出:試想一個(gè)包含幾十個(gè) API 的組件,每個(gè) API 的 function 存在一兩個(gè)關(guān)聯(lián)的 private function,那這個(gè)組件內(nèi)部的混亂程度、維護(hù)難度是難以承受的。

  • 把 Low Level 邏輯抽取到新的組件中,供 High Level 代碼所在的組件依賴和調(diào)用;更有經(jīng)驗(yàn)的程序員可能會(huì)增加一層接口并且借助 Spring 依賴注入;

    • 問(wèn)題 1 API 泛濫:提取出新的組件似乎避免了“結(jié)構(gòu)化編程”的局限性,但是帶來(lái)了新的問(wèn)題——API 泛濫:因?yàn)榻M件之間調(diào)用只能走 public 方法,而這個(gè) API 其實(shí)沒有太多復(fù)用機(jī)會(huì)根本沒必要做成 public 這種最高可見性。

    • 問(wèn)題 2 同層組件依賴失控:組件和 API 泛濫后必然導(dǎo)致組件之間互相依賴成為常態(tài),慢慢變得失控以后最終變成所有組件都依賴其它大部分組件,甚至出現(xiàn)循環(huán)依賴;比如那個(gè)擁有 130 個(gè) import 和 40 個(gè) Spring 依賴組件的 ContractService。

下面介紹一下 Template Method 設(shè)計(jì)模式的運(yùn)用,簡(jiǎn)單歸納就是:

  • High Level邏輯封裝在抽象父類AbsUpdateFromMQ的一個(gè)final function中,形成一個(gè)業(yè)務(wù)邏輯的模板;

  • final function保證了其中邏輯不會(huì)被子類有意或無(wú)意的篡改破壞,因此其中封裝的一定是業(yè)務(wù)邏輯中那些相對(duì)固定不變的東西。至于那些可變的部分以及暫時(shí)不確定的部分,以abstract protected function形式預(yù)留擴(kuò)展點(diǎn);

  • 子類(一個(gè)匿名內(nèi)部類)像“做填空題”一樣填充,模板實(shí)現(xiàn)Low Level邏輯——實(shí)現(xiàn)那些protected function擴(kuò)展點(diǎn);由于擴(kuò)展點(diǎn)在父類中是abstract的,因此編譯器會(huì)提醒子類的程序員該擴(kuò)展什么。

那么它是如何避免上面兩個(gè)方案的 4 個(gè)局限性的:

  • Low Level 需要修改或替換時(shí),只需從父類擴(kuò)展出一個(gè)新的子類,父類全然不知無(wú)需任何改動(dòng);

  • 無(wú)論是父類還是子類,其中的 function 對(duì)外層的 XyzService 組件都是不可見的,即便是父類中的 public function 也不可見,因?yàn)橹挥谐钟蓄惖膶?shí)例對(duì)象才能訪問(wèn)到其中的 function;

  • 無(wú)論是父類還是子類,它們都是作為 XyzService 的內(nèi)部類存在的,不會(huì)增加新的 java 類文件更不會(huì)增加大量無(wú)意義的 API(API 只有在被項(xiàng)目?jī)?nèi)復(fù)用或發(fā)布出去供外部使用才有意義,只有唯一的調(diào)用者的 API 是沒有必要的);

  • 組件依賴失控的問(wèn)題當(dāng)然也就不存在了。

SpringFramework 等框架型的開源項(xiàng)目中,其實(shí)早已大量使用 Template Method 設(shè)計(jì)模式,這本該給我們這些應(yīng)用開發(fā)程序員帶來(lái)啟發(fā)和示范,但是很可惜業(yè)界沒有注意到和充分發(fā)揮它的價(jià)值。

NestedBusinessTemplat 模式就是對(duì)其充分和積極的應(yīng)用,前面一節(jié)提到過(guò)的復(fù)用的兩種正確姿勢(shì)——打造自己的 lib 和 framework,其實(shí) NestedBusinessTemplat 就是項(xiàng)目自身的 framework。

4.7 癥結(jié) 4:無(wú)處不在的 if else 牛皮癬

無(wú)論你的編程啟蒙語(yǔ)言是什么,最早學(xué)會(huì)的邏輯控制語(yǔ)句一定是 if else,但是不幸的是它在你開始真正的編程工作以后,會(huì)變成一個(gè)損害項(xiàng)目質(zhì)量的壞習(xí)慣。

幾乎所有的項(xiàng)目都存在 if else 泛濫的問(wèn)題,但是卻沒有引起足夠重視警惕,甚至被很多程序員認(rèn)為是正常現(xiàn)象。

首先我來(lái)解釋一下為什么 if else 這個(gè)看上去人畜無(wú)害的東西是有害的、是需要嚴(yán)格管控的:

  • if else if ...else 以及類似的 switch 控制語(yǔ)句,本質(zhì)上是一種 hard coding 硬編碼行為,如果你同意“magic number 魔法數(shù)字”是一種錯(cuò)誤的編程習(xí)慣,那么同理,if else 也是錯(cuò)誤的 hard coding 編程風(fēng)格;

  • hard coding 的問(wèn)題在于當(dāng)需求發(fā)生改變時(shí),需要到處去修改,很容易遺漏和出錯(cuò);

  • 以一段代碼為例來(lái)具體分析:

if ("3".equals(object.getString("type"))){String data = object.getString("data");Integer zmf = JSON.parseObject(data).getJSONObject("taobao_user_info").getInteger("zm_score");Map param = new HashMap();param.put("phoneNumber", object.getString("mobile"));List<Dperson> list1 = personBaseDaoI.find("from xyz where phoneNumber=:phoneNumber", param);if (list1 !=null){for (Dperson dperson:list1){dperson.setZmScore(zmf);personBaseDaoI.saveOrUpdate(dperson);AppFlowUtil.updateAppUserInfo(dperson.getToken(),null,null,zmf);}} }
  • if ("3".equals(object.getString("type")))

    • 顯然這里的"3"是一個(gè) magic number,沒人知道 3 是什么含義,只能推測(cè);

    • 但是僅僅將“3”重構(gòu)成常量 ABC_XYZ 并不會(huì)改善多少,因?yàn)?if (ABC_XYZ.equals(object.getString("type"))) 仍然是面向過(guò)程的編程風(fēng)格,無(wú)法擴(kuò)展;

    • 到處被引用的常量 ABC_XYZ 并沒有比到處被 hard coding 的 magic number 好多少,只不過(guò)有了含義而已;

    • 把常量升級(jí)成 Enum 枚舉類型呢,也沒有好多少,當(dāng)需要判斷的類型增加了或判斷的規(guī)則改變了,還是需要到處修改——Shotgun Surgery(霰彈式修改)

  • 并非所有的 if else 都有害,比如上面示例中的 if (list1 !=null) { 就是無(wú)害的,沒有必要去消除,也沒有消除它的可行性。判斷是否有害的依據(jù):

    • 如果 if 判斷的變量狀態(tài)只有兩種可能性(比如 boolean、比如 null 判斷)時(shí),是無(wú)傷大雅的;

    • 反之,如果 if 判斷的變量存在多種狀態(tài),而且將來(lái)可能會(huì)增加新的狀態(tài),那么這就是個(gè)問(wèn)題;

    • switch 判斷語(yǔ)句無(wú)疑是有害的,因?yàn)槭褂?switch 的地方往往存在很多種狀態(tài)。

4.8 藥方 4:充血枚舉類型——Rich Enum Type

正如前面分析呈現(xiàn)的那樣,對(duì)于代碼中廣泛存在的狀態(tài)、類型 if 條件判斷,僅僅把被比較的值重構(gòu)成常量或 enum 枚舉類型并沒有太大改善——使用者仍然直接依賴具體的枚舉值或常量,而不是依賴一個(gè)抽象。

于是解決方案就自然浮出水面了:在 enum 枚舉類型基礎(chǔ)上進(jìn)一步抽象封裝,得到一個(gè)所謂的“充血”的枚舉類型,代碼說(shuō)話:

  • 實(shí)現(xiàn)多種系統(tǒng)通知機(jī)制,傳統(tǒng)做法:

enum NOTIFY_TYPE { email,sms,wechat; } //先定義一個(gè)enum——一個(gè)只定義了值不包含任何行為的“貧血”的枚舉類型if(type==NOTIFY_TYPE.email){ //if判斷類型 調(diào)用不同通知機(jī)制的實(shí)現(xiàn) 。。。 }else if (type=NOTIFY_TYPE.sms){。。。 }else{。。。 }
  • 實(shí)現(xiàn)多種系統(tǒng)通知方式,充血枚舉類型——Rich Enum Type 模式:

enum NOTIFY_TYPE { //1、定義一個(gè)包含通知實(shí)現(xiàn)機(jī)制的“充血”的枚舉類型email("郵件",NotifyMechanismInterface.byEmail()),sms("短信",NotifyMechanismInterface.bySms()),wechat("微信",NotifyMechanismInterface.byWechat()); String memo;NotifyMechanismInterface notifyMechanism;private NOTIFY_TYPE(String memo,NotifyMechanismInterface notifyMechanism){//2、私有構(gòu)造函數(shù),用于初始化枚舉值this.memo=memo;this.notifyMechanism=notifyMechanism;}//getters ... } public interface NotifyMechanismInterface{ //3、定義通知機(jī)制的接口或抽象父類public boolean doNotify(String msg);public static NotifyMechanismInterface byEmail(){//3.1 返回一個(gè)定義了郵件通知機(jī)制的策的實(shí)現(xiàn)——一個(gè)匿名內(nèi)部類實(shí)例return new NotifyMechanismInterface(){public boolean doNotify(String msg){.......}};}public static NotifyMechanismInterface bySms(){//3.2 定義短信通知機(jī)制的實(shí)現(xiàn)策略return new NotifyMechanismInterface(){public boolean doNotify(String msg){.......}};} public static NotifyMechanismInterface byWechat(){//3.3 定義微信通知機(jī)制的實(shí)現(xiàn)策略return new NotifyMechanismInterface(){public boolean doNotify(String msg){.......}};} }//4、使用場(chǎng)景 NOTIFY_TYPE.valueof(type).getNotifyMechanism().doNotify(msg);
  • 充血枚舉類型——Rich Enum Type 模式的優(yōu)勢(shì):

    • 不難發(fā)現(xiàn),這其實(shí)就是 enum 枚舉類型和 Strategy Pattern 策略模式的巧妙結(jié)合運(yùn)用,

    • 當(dāng)需要增加新的通知方式時(shí),只需在枚舉類 NOTIFY_TYPE 增加一個(gè)值,同時(shí)在策略接口 NotifyMechanismInterface 中增加一個(gè) by 方法返回對(duì)應(yīng)的策略實(shí)現(xiàn);

    • 當(dāng)需要修改某個(gè)通知機(jī)制的實(shí)現(xiàn)細(xì)節(jié),只需修改 NotifyMechanismInterface 中對(duì)應(yīng)的策略實(shí)現(xiàn);

    • 無(wú)論新增還是修改通知機(jī)制,調(diào)用方完全不受影響,仍然是 NOTIFY_TYPE.valueof(type).getNotifyMechanism().doNotify(msg);

  • 與傳統(tǒng) Strategy Pattern 策略模式的比較優(yōu)勢(shì):常見的策略模式也能消滅 if else 判斷,但是實(shí)現(xiàn)起來(lái)比較麻煩,需要開發(fā)更多的 class 和代碼量:

    • 每個(gè)策略實(shí)現(xiàn)需單獨(dú)定義成一個(gè) class;

    • 還需要一個(gè) Context 類來(lái)做初始化——用 Map 把類型與對(duì)應(yīng)的策略實(shí)現(xiàn)做映射;

    • 使用時(shí)從 Context 獲取具體的策略;

  • Rich Enum Type 的進(jìn)一步的充血:

    • 上面的例子中的枚舉類型包含了行為,因此已經(jīng)算作充血模型了,但是還可以為其進(jìn)一步充血;

    • 例如有些場(chǎng)景下,只是要對(duì)枚舉值做個(gè)簡(jiǎn)單的計(jì)算獲得某種 flag 標(biāo)記,那就沒必要把計(jì)算邏輯抽象成 NotifyMechanismInterface 那樣的接口,殺雞用了牛刀;

    • 這是就可以在枚舉類型中增加 static function 封裝簡(jiǎn)單的計(jì)算邏輯;

  • 策略實(shí)現(xiàn)的進(jìn)一步抽象:

    • 當(dāng)各個(gè)策略實(shí)現(xiàn)(byEmail bySms byWechat)存在共性部分、重復(fù)邏輯時(shí),可以將其抽取成一個(gè)抽象父類;

    • 然后就像前一章節(jié)——業(yè)務(wù)模板 Pattern of NestedBusinessTemplate 那樣,在各個(gè)子類之間實(shí)現(xiàn)優(yōu)雅的邏輯分離和復(fù)用。

5. 重構(gòu)前的火力偵察:為你的項(xiàng)目編制一套代碼庫(kù)目錄/索引——CODEX

以上就是我總結(jié)出的最常見也最影響代碼質(zhì)量的 4 個(gè)問(wèn)題及其解決方案:

  • 職責(zé)單一、小顆粒度、高內(nèi)聚、低耦合的業(yè)務(wù)邏輯層組件——倒金字塔結(jié)構(gòu);

  • 打造項(xiàng)目自身的 lib 層和 framework——正確的復(fù)用姿勢(shì);

  • 業(yè)務(wù)模板 Pattern of NestedBusinessTemplate——控制邏輯分離;

  • 充血的枚舉類型 Rich Enum Type——消滅硬編碼風(fēng)格的 if else 條件判斷;

接下來(lái)就是如何動(dòng)手去針對(duì)這 4 個(gè)方面進(jìn)行重構(gòu)了,但是事情還沒有那么簡(jiǎn)單。

上面所有的內(nèi)容雖然來(lái)自實(shí)踐經(jīng)驗(yàn),但是要應(yīng)用到你的具體項(xiàng)目,還需要一個(gè)步驟——火力偵察——弄清楚你要重構(gòu)的那個(gè)模塊的邏輯脈絡(luò)、算法以致實(shí)現(xiàn)細(xì)節(jié),否則貿(mào)然動(dòng)手,很容易遺漏關(guān)鍵細(xì)節(jié)造成風(fēng)險(xiǎn),重構(gòu)的效率更難以保證,陷入進(jìn)退兩難的尷尬境地。

我 2019 年一整年經(jīng)歷了 3 個(gè)代碼十分混亂的項(xiàng)目,最大的收獲就是摸索出了一個(gè)梳理爛代碼的最佳實(shí)踐——CODEX:

  • 在閱讀代碼過(guò)程中,在關(guān)鍵位置添加結(jié)構(gòu)化的注釋,形如://CODEX ProjectA 1 體檢預(yù)約流程 1 預(yù)約服務(wù) API 入口

  • 所謂結(jié)構(gòu)化注釋,就是在注釋內(nèi)容中通過(guò)規(guī)范命名的編號(hào)前綴、分隔符等來(lái)體現(xiàn)出其所對(duì)應(yīng)的項(xiàng)目、模塊、流程步驟等信息,類似文本編輯中的標(biāo)題 1、2、3;

  • 然后設(shè)置 IDE 工具識(shí)別這種特殊的注釋,以便結(jié)構(gòu)化的顯示。Eclipse 的 Tasks 顯示效果類似下圖;

  • 這個(gè)結(jié)構(gòu)化視圖,本質(zhì)上相對(duì)于是代碼庫(kù)的索引、目錄,不同于 javadoc 文檔,CODEX 具有更清晰的邏輯層次和更強(qiáng)的代碼查找便利性,在 Eclipse Tasks 中點(diǎn)擊就能跳轉(zhuǎn)到對(duì)應(yīng)的代碼行;

  • 這些結(jié)構(gòu)化注釋隨著代碼一起提交后就實(shí)現(xiàn)了團(tuán)隊(duì)共享;

  • 這樣的一份精確無(wú)誤、共享的、活的源代碼索引,無(wú)疑會(huì)對(duì)整個(gè)團(tuán)隊(duì)的開發(fā)維護(hù)工作產(chǎn)生巨大助力。

  • 進(jìn)一步的,如果在 CODEX 中添加 Markdown 關(guān)鍵字,甚至可以將導(dǎo)出的 CODEX 簡(jiǎn)單加工后,變成一張業(yè)務(wù)邏輯的 Sequence 序列圖,如下所示。

6. 總結(jié)陳詞——不要辜負(fù)這個(gè)程序員最好的時(shí)代

毫無(wú)疑問(wèn)這是程序員最好的時(shí)代,互聯(lián)網(wǎng)浪潮已經(jīng)席卷了世界每個(gè)角落,各行各業(yè)正在越來(lái)越多的依賴 IT。過(guò)去只有軟件公司、互聯(lián)網(wǎng)公司和銀行業(yè)會(huì)雇傭程序員,隨著云計(jì)算的普及、產(chǎn)業(yè)互聯(lián)網(wǎng)和互聯(lián)網(wǎng)+興起,已經(jīng)有越來(lái)越多的傳統(tǒng)企業(yè)開始雇傭程序員搭建 IT 系統(tǒng)來(lái)支撐業(yè)務(wù)運(yùn)營(yíng)。

資本的推動(dòng) IT 需求的旺盛,使得程序員成了稀缺人才,各大招聘平臺(tái)上,程序員的崗位數(shù)量和薪資水平長(zhǎng)期名列前茅。

但是我們這個(gè)群體的整體表現(xiàn)怎么樣呢,捫心自問(wèn),我覺得很難令人滿意,我所經(jīng)歷過(guò)的以及近距離觀察到的項(xiàng)目,鮮有能夠稱得上成功的。這里的成功不是商業(yè)上的成功,僅限于作為一個(gè)軟件項(xiàng)目和工程是否能夠以可接受的成本和質(zhì)量長(zhǎng)期穩(wěn)定的交付。

商業(yè)的短期成功與否,很多時(shí)候與項(xiàng)目工程的成功與否沒有必然聯(lián)系,一個(gè)商業(yè)上很成功的項(xiàng)目可能在工程上做的并不好,只是通過(guò)巨量的資金資源投入換來(lái)的暫時(shí)成功而已。

歸根結(jié)底,我們程序員群體需要為自己的聲譽(yù)負(fù)責(zé),長(zhǎng)期來(lái)看也終究會(huì)為自己的聲譽(yù)獲益或受損。

我認(rèn)為程序員最大的聲譽(yù)、最重要的職業(yè)素養(yǎng),就是通過(guò)寫出高質(zhì)量的代碼做好一個(gè)個(gè)項(xiàng)目、產(chǎn)品,來(lái)幫助團(tuán)隊(duì)、幫助公司、幫助組織創(chuàng)造價(jià)值、增加成功的機(jī)會(huì)。

希望本文分享的經(jīng)驗(yàn)和方法能夠?qū)Υ擞兴鶐椭?#xff01;

往期推薦

程序猿的專屬口頭禪!

又漲了!2021 年 5 月程序員工資統(tǒng)計(jì)新鮮出爐~

王炸!這個(gè)項(xiàng)目把100多個(gè)知名網(wǎng)站都克隆出來(lái)了!

阿里旗下的29個(gè)開源項(xiàng)目

項(xiàng)目中Dao,Service,Controller,Util,Model是什么意思,為什么劃分?

ELK+FileBeat+Kafka分布式系統(tǒng)搭建圖文教程

Java線程池必備知識(shí)點(diǎn):工作流程、常見參數(shù)、調(diào)優(yōu)、監(jiān)控

點(diǎn)個(gè)再走唄 “在看”

總結(jié)

以上是生活随笔為你收集整理的我们精通那么多技术,为何还是做不好一个项目?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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

亚洲欧洲精品视频 | 国产日韩欧美网站 | 亚洲少妇激情 | 欧美成人中文字幕 | 四虎影视成人永久免费观看视频 | 在线观看视频你懂 | 久久综合久久久 | 国产精品不卡在线 | 欧美久久久久久 | 国产精品免费久久久久影院仙踪林 | 国产麻豆传媒 | 色欧美综合| 日本中文字幕免费观看 | 国产精品精品国产色婷婷 | 97精品国产一二三产区 | 天天舔天天搞 | 国产黄色片在线 | 国产色妞影院wwwxxx | 久久久久久久久免费 | 九九热在线免费观看 | 亚洲国产精品小视频 | 精品99视频 | 国产精品久久久一区二区三区网站 | 国产黄免费在线观看 | 国产区免费在线 | 正在播放国产一区 | 久久免费国产精品1 | 人人爽人人爽人人片 | 国产电影黄色av | 日韩免费高清在线观看 | 99精品区| 超碰免费成人 | 国产亚洲精品成人av久久影院 | 精品在线观看视频 | 国产精品久久久久久吹潮天美传媒 | 经典三级一区 | 久久无码av一区二区三区电影网 | 成人免费视频观看 | 色综合久久88色综合天天 | 精品在线观看一区二区 | 亚洲精品在线电影 | 日本最新一区二区三区 | 亚洲黄色在线 | 在线观看视频h | 热久久国产精品 | 色视频网址 | 免费看一及片 | 在线黄色国产 | 国产无套视频 | 欧美日韩久久一区 | 中文在线8新资源库 | 久久伊人国产精品 | 成人av在线影视 | 狠狠色狠狠色综合日日92 | 狠狠操综合网 | 久久综合婷婷 | 91成人免费在线 | 国产一级做a爱片久久毛片a | 亚洲精品一区二区精华 | 三级av网站 | 91视频 - x99av| 伊人影院av | 人人澡人人添人人爽一区二区 | 精品久久久网 | 婷婷色中文网 | 国产福利电影网址 | 国产精品国产三级国产不产一地 | 国产精品一区二区你懂的 | 久久精品99国产精品日本 | 国产精品av电影 | 国产亚洲精品久久久久动 | 欧美日韩国产在线一区 | 久久成人资源 | 国产视频精品网 | www色综合 | 欧洲激情综合 | 国产在线小视频 | 久久激情五月婷婷 | 亚洲欧美精品一区二区 | 免费在线播放视频 | www.色的 | 亚洲 综合 国产 精品 | 成人av电影免费观看 | 天天操综合网站 | 中文在线免费看视频 | jizzjizzjizz亚洲 | 亚洲精品国产第一综合99久久 | 麻豆国产视频下载 | 天天干天天操天天 | 夜夜视频资源 | 日韩美女久久 | 国产精品免费一区二区 | 国产精品一二 | 九九热精品视频在线观看 | 黄色一级免费电影 | 国内精品视频免费 | 成人资源站 | 久草视频在线免费 | 少妇按摩av| 成人试看120秒 | 久久er99热精品一区二区三区 | 久久免费视频在线观看 | 国产中文字幕视频在线观看 | 91av视频免费观看 | 国产精品日韩在线观看 | 亚洲欧洲精品一区二区精品久久久 | 欧美国产日韩一区二区三区 | 午夜丁香视频在线观看 | 亚洲一二三区精品 | 国内精品视频久久 | 精品国产a | 免费在线成人av | 久久在线观看 | 国产黄色播放 | 日韩一区二区三免费高清在线观看 | 特级黄录像视频 | 中文字幕在线国产 | 又大又硬又黄又爽视频在线观看 | 久操视频在线播放 | 日本爽妇网 | 精品久久久久久久久久久久久 | 久久久久久久久网站 | 91香蕉视频黄色 | 亚洲成人中文在线 | 亚洲精品在线播放视频 | 香蕉久草 | 2017狠狠干 | 国产成人综| 亚洲午夜av | 精品99在线观看 | 看片网站黄 | 四虎成人av | 久久99国产精品二区护士 | 久久久久激情电影 | 蜜臀一区二区三区精品免费视频 | 午夜精品999 | 国产99爱 | 91网免费看| 欧美另类网站 | 久草国产在线 | 9992tv成人免费看片 | 欧美日韩三区二区 | 久久99精品久久久久久秒播蜜臀 | 日韩精品一区二区免费 | 激情五月网站 | 亚洲精品一区二区网址 | 操操操av | 日日夜夜天天人人 | 国产精品成久久久久 | 狠狠操天天射 | 一区二区三区在线观看免费视频 | 91香蕉视频在线下载 | 在线日韩中文 | 欧洲色吧 | 亚洲伦理一区 | av久久久久久 | 摸阴视频| 国产在线v| 国产在线高清视频 | 国产第一福利网 | 欧美一二区视频 | 欧美日韩免费视频 | 亚洲狠狠操 | 日韩欧美精品免费 | 青青草国产精品视频 | 黄色影院在线免费观看 | 国产精品一区二区久久国产 | 开心激情婷婷 | 97视频在线| www四虎影院 | 深夜福利视频一区二区 | 国产喷水在线 | 天天操天天曰 | 日韩免费高清在线 | 99综合久久 | 人人插人人艹 | 精品人人爽| 成人在线播放av | 免费福利视频网站 | 久草免费资源 | 久久综合九色综合久99 | 热久精品 | 香蕉免费 | 久久久免费看视频 | 狠狠狠综合 | 在线91观看 | 二区在线播放 | 97av在线| 日韩在线观看视频网站 | 久免费视频 | 久久在线免费观看 | 久久久久99精品国产片 | 超碰在线资源 | 欧美精品久久久久久 | 中文字幕免费高清av | 中日韩在线 | 国产69精品久久久久99尤 | 99热官网| 美女精品 | 国产亚洲情侣一区二区无 | 午夜久久视频 | 成人资源在线播放 | av丝袜在线| 天天艹天天操 | 国产精品激情偷乱一区二区∴ | 麻豆免费观看视频 | 成人免费观看完整版电影 | 国产精品18videosex性欧美 | 欧美一区二区三区免费观看 | 日本超碰在线 | 日韩区在线观看 | 久久精品久久精品久久39 | 人人干干人人 | 亚洲专区路线二 | 国产精品久久久久久久久久白浆 | 免费视频区 | 亚洲成人黄色在线观看 | 日韩av手机在线观看 | 免费a视频在线 | 久章草在线观看 | 久久1电影院 | 亚洲一级片| 欧美99精品| 亚洲精品中文在线 | 日韩视频一区二区三区在线播放免费观看 | 国产成人一区二区三区在线观看 | 亚洲精品中文字幕视频 | 久久躁日日躁aaaaxxxx | 超碰电影在线观看 | 午夜精品一区二区三区视频免费看 | 成人黄色在线 | 久久精品一区八戒影视 | 国产视频日韩 | 免费av小说 | 丰满少妇在线 | 成人高清在线观看 | 91精品在线观看入口 | 午夜av剧场 | 91资源在线免费观看 | 欧美国产日韩一区二区三区 | 狠狠久久伊人 | 美女在线观看av | 久久免费播放视频 | 97电影在线| 久久综合成人网 | 久久久久久久久黄色 | av免费网站 | 国产成人精品一二三区 | 四虎影视成人永久免费观看亚洲欧美 | 国产三级在线播放 | 色婷五月天 | 97超碰.com| 中文字幕 国产专区 | www四虎影院 | 四虎在线视频 | 久久另类视频 | 人人射人人爱 | 日韩在线观看你懂得 | 国产精品正在播放 | 国产一级片免费观看 | 精品视频在线免费 | 久久久久免费精品视频 | 国产高清视频免费在线观看 | 日韩av不卡在线 | 天天操天天射天天爽 | 欧美91精品久久久久国产性生爱 | 亚洲免费观看在线视频 | 欧美一区二区三区激情视频 | 天天干天天干天天操 | av青草| 欧美另类sm图片 | 免费看的黄色录像 | 欧美日韩成人 | 日韩色区 | 欧美美女视频在线观看 | 91少妇精拍在线播放 | 国产精品a级 | 国模视频一区二区 | 91精品天码美女少妇 | 嫩小bbbb摸bbb摸bbb | 一区二区三区免费在线观看视频 | 日韩动漫免费观看高清完整版在线观看 | 成人av一二三区 | 免费视频资源 | 综合色中文| 五月婷香 | 一区二区三区在线观看中文字幕 | 麻豆久久久久 | 99看视频在线观看 | 欧美黄污视频 | 免费高清在线观看成人 | 青青河边草免费直播 | 免费影视大全推荐 | 夜夜骑日日操 | 久久久久北条麻妃免费看 | 激情丁香在线 | 一区二区三区动漫 | 亚洲精品9 | 久草综合视频 | 亚洲热久久 | 欧美91在线 | 久久成年人网站 | 一级黄色片在线 | 中文字幕资源在线观看 | 精品一区欧美 | a久久免费视频 | av中文字幕不卡 | 欧美性色综合网 | 九九久久婷婷 | 欧美aaaxxxx做受视频 | 天天综合91| 亚洲精品自在在线观看 | 国产一线在线 | 久久久久久欧美二区电影网 | 久草综合在线观看 | 99精品免费久久久久久日本 | 91视频久久久久 | 免费影视大全推荐 | 久久综合综合久久综合 | 91av资源网| 97精品一区二区三区 | 久久av影视| 亚洲成人精品av | 日韩精品免费在线视频 | 在线成人国产 | av丝袜在线| 欧美成人h版电影 | 美女视频黄在线 | av短片在线 | 五月婷婷丁香综合 | 欧美一级电影免费观看 | 国产小视频在线 | 天天夜操 | 黄色小网站在线观看 | 亚欧洲精品视频在线观看 | 五月天色丁香 | 天天综合网 天天 | 91视频在线免费看 | 亚洲 av网站 | 美女网站在线观看 | 国产精品99久久免费黑人 | 精品一区二区在线免费观看 | 日日天天av| 成人啊 v | 国产一级大片免费看 | 亚洲第一区在线观看 | 中文字幕 成人 | 97电影手机 | 深夜免费小视频 | 亚洲一二三在线 | 精品在线免费视频 | 日韩综合精品 | 视频一区二区视频 | 国产一区二区高清视频 | 97成人免费 | 91激情| 日本久久精品 | 波多野结衣久久精品 | 国产99久久九九精品免费 | 国产中文字幕视频在线观看 | 超碰免费公开 | 五月天久久精品 | 国产成人99av超碰超爽 | 亚洲精品中文字幕视频 | 最近中文字幕免费大全 | 欧洲精品视频一区二区 | 亚洲桃花综合 | 亚洲人成在线观看 | 在线看免费 | 免费视频成人 | 2019中文 | 成 人 黄 色 片 在线播放 | 亚洲黄色一级电影 | 久草男人天堂 | 色婷婷免费视频 | 亚洲精品美女久久久久 | 91av在线免费播放 | 中文字幕在线播放一区二区 | 日日夜夜人人精品 | 久草在线资源观看 | 夜夜躁天天躁很躁波 | 亚洲国产97在线精品一区 | 国产成人一区二区精品非洲 | 国内精品一区二区 | 99视频+国产日韩欧美 | 欧美美女一级片 | 亚洲国产精品小视频 | 天天干天天操天天爱 | 最新av网址在线观看 | 欧美日韩中文字幕视频 | 久久综合九色 | 亚洲精品乱码久久久久v最新版 | 日本视频精品 | 黄色在线观看网站 | 激情久久伊人 | 久久人人爽人人爽 | 91中文字幕视频 | 久久视频一区 | 日本久久久久久久久久 | 黄色av电影 | 在线观看91av | 1024手机基地在线观看 | 亚洲久久视频 | 国产精品福利久久久 | 成人在线免费av | 97香蕉久久超级碰碰高清版 | 美女黄视频免费 | 国产一级在线视频 | 在线观看视频免费播放 | 在线观看中文字幕av | 999久久久| 狠狠操精品 | 欧美日韩高清在线一区 | www.com在线观看 | 色综合久久久网 | 国产成人精品一区二 | 99产精品成人啪免费网站 | 日韩理论在线视频 | 国产在线不卡 | 天天天操操操 | 免费午夜网站 | 国产精品原创视频 | 成人午夜电影在线 | 久99久在线视频 | 欧美日韩精品久久久 | 日韩视频免费观看高清完整版在线 | 亚洲精品无 | 超碰资源在线 | 天天干天天干天天射 | 欧美精品免费一区二区 | 人人看看人人 | 久久中文精品视频 | 免费a网 | 亚洲日本中文字幕在线观看 | 日韩欧美一区二区三区在线观看 | 在线精品一区二区 | 日韩av一区二区在线 | 黄色在线免费观看网址 | 日韩av影片在线观看 | 亚洲91中文字幕无线码三区 | 91在线看视频免费 | 青青草国产成人99久久 | 人人爽人人爽人人爽学生一级 | 亚洲精品女人久久久 | 日本精品中文字幕 | 狠狠色丁香久久婷婷综 | 色婷婷六月天 | 免费在线观看视频a | 欧美精品国产综合久久 | 国产成人三级在线播放 | 成人性生交视频 | 久久久久久免费网 | 黄色大全视频 | 久久国产免费视频 | 精品亚洲网| 久草视频在线播放 | 成人免费共享视频 | 日韩av手机在线看 | 免费在线观看毛片网站 | 国内丰满少妇猛烈精品播 | 久久这里精品视频 | 日韩在线观看网址 | 成人午夜电影在线 | 久久精品99国产精品亚洲最刺激 | 国产精品嫩草影院9 | 波多野结衣在线中文字幕 | 最新精品国产 | 一区二区三区免费在线播放 | 又色又爽的网站 | 91福利社在线观看 | 成人资源在线观看 | 天堂av观看| 成人a级黄色片 | 波多野结衣电影一区二区三区 | 在线亚洲成人 | 中文字幕乱码日本亚洲一区二区 | 五月婷婷激情五月 | 啪啪免费视频网站 | 国产黄色资源 | av短片在线 | 在线免费观看国产黄色 | 国产欧美综合在线观看 | 欧美日韩国产在线 | 久久国产福利 | 中文字幕中文字幕 | 一级片免费视频 | 免费看成人a | 欧美婷婷色 | av经典在线| 中文字幕一区av | 久久久久久久久久影院 | 免费男女羞羞的视频网站中文字幕 | 欧美va电影| 96精品高清视频在线观看软件特色 | 激情小说网站亚洲综合网 | 一区二区三区在线免费播放 | 国产精品久久电影网 | 免费观看一区二区 | 成年人在线免费视频观看 | 久久av影视 | 青青河边草观看完整版高清 | 日韩va亚洲va欧美va久久 | 97成人在线观看视频 | 91九色成人蝌蚪首页 | 91探花视频 | 一区二区三区电影 | 福利视频导航网址 | 91亚洲国产成人久久精品网站 | 天天色天天射天天干 | 亚洲高清久久久 | 免费视频资源 | 91免费试看 | 91视频首页 | 91高清视频在线 | 久久久久久久久久久久亚洲 | 亚洲黄色激情小说 | 成人毛片a | 日韩精品在线视频 | 黄网站色成年免费观看 | 在线免费观看不卡av | 日韩免费电影 | 国产一区二区综合 | av福利在线看 | 亚洲乱亚洲乱亚洲 | 欧美日韩在线观看视频 | av在线在线| 亚洲午夜剧场 | 精品久久综合 | 久久国产精品久久精品 | 免费进去里的视频 | 中文字幕乱偷在线 | 日韩免费一区 | 国产精品一区免费在线观看 | 蜜桃传媒一区二区 | 久久成人高清 | 有码中文字幕 | a国产精品 | 91桃色国产在线播放 | 精品免费一区 | 综合国产在线 | 亚洲久草在线视频 | 在线观看福利网站 | 美女亚洲精品 | 国产原创在线 | 欧美成人a在线 | 欧美精品天堂 | 欧美一级乱黄 | 久久久国产一区 | 日韩中文字幕国产精品 | 伊人六月 | www.国产毛片| 91成人短视频在线观看 | 久久99深爱久久99精品 | 国产精品成人av电影 | 成人日批视频 | 国产特级毛片aaaaaaa高清 | 日韩免费一区 | 免费看片日韩 | 亚洲视频在线免费看 | 五月综合| 黄色的网站在线 | 91免费高清 | 天干啦夜天干天干在线线 | 国产精成人品免费观看 | 国产第一页精品 | 久草爱视频 | 久久这里只有精品9 | 亚洲一级黄色av | 久草视频观看 | 精品亚洲成人 | 欧美色婷婷 | 99爱这里只有精品 | av成人免费在线看 | 国产成人精品一二三区 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 亚洲专区在线播放 | 久久久精品亚洲 | 一区三区视频在线观看 | 国产精品久久久久影院 | 色综合天天视频在线观看 | 精品久久一二三区 | 欧美日韩3p | 91色亚洲 | 岛国大片免费视频 | 2019中文字幕网站 | 国产一级二级在线播放 | 国产精品白浆 | 视频国产一区二区三区 | 亚洲激情综合网 | 国产伦理久久精品久久久久_ | 91九色精品 | 国产精品原创av片国产免费 | 97在线视频免费观看 | 最近的中文字幕大全免费版 | 91精品欧美| 91麻豆精品国产91久久久更新时间 | 中文字幕乱偷在线 | 久久呀| 色香蕉在线视频 | 久久丝袜视频 | 另类老妇性bbwbbw高清 | 久久色视频 | 色瓜| 免费黄色激情视频 | 久久毛片网 | 九九九在线观看视频 | 日韩欧美精品一区二区 | 亚洲精品久久久久久中文传媒 | 日韩黄色免费看 | 精品久久久久久一区二区里番 | 成人国产精品一区二区 | 国产一级特黄毛片在线毛片 | 免费日韩 精品中文字幕视频在线 | 日韩在线免费视频观看 | 亚洲视频六区 | 久久男人中文字幕资源站 | 天天爱综合| 色综合久久久久综合 | 欧美人交a欧美精品 | 激情图片久久 | 就操操久久 | 婷久久| 久久久精品综合 | 丁香六月天婷婷 | 久久国产色 | 美女视频一区 | 日韩特黄一级欧美毛片特黄 | 国产一区视频在线观看免费 | 久草久草在线 | 麻豆国产精品va在线观看不卡 | 欧美 日韩 成人 | 婷婷色婷婷 | 久久免费视频4 | 一区中文字幕电影 | 天天操天天操天天操天天操天天操天天操 | 成人国产精品入口 | 国产精品丝袜久久久久久久不卡 | 国产主播大尺度精品福利免费 | 国产高清在线看 | 亚洲精品国产自产拍在线观看 | 国产一区视频在线播放 | 日韩高清一区二区 | 三级在线国产 | 久久这里只有精品9 | 精品久久久免费 | 中文字幕av在线免费 | 亚洲成aⅴ人片久久青草影院 | 欧美福利网址 | 久草国产在线观看 | 久久国产经典视频 | 探花视频免费在线观看 | 免费福利片2019潦草影视午夜 | 青青草国产精品 | 亚洲精品国产精品国自产在线 | 亚洲精品久 | 在线免费黄色 | 国产精品麻豆三级一区视频 | 午夜精品一区二区国产 | 99热精品久久 | 国产成人a v电影 | 色婷婷丁香 | 成人久久精品视频 | 国产精品久久久久久久久久妇女 | 亚洲欧美日韩一级 | 国产免费影院 | 丁香花在线观看免费完整版视频 | 97国产精品 | 国产91全国探花系列在线播放 | 天天射天天 | 国产精选在线观看 | 中文av影院 | 超碰人人超 | 在线观看视频免费播放 | 日韩三级中文字幕 | 精品国产一区二区三区久久久蜜臀 | 啪啪精品| 久久免费国产精品1 | 成人一区电影 | 日韩高清不卡在线 | 超碰com| 国产精品免费在线视频 | 国产 欧美 日产久久 | 日韩大片在线播放 | 日本精品久久久久中文字幕5 | 在线免费av播放 | 香蕉久久久久久久 | 在线观看91久久久久久 | 国产成人精品一区二区三区福利 | 国产精品一区二区62 | 国产香蕉av | www.av在线.com | 美女视频黄免费网站 | 国产午夜精品一区 | 欧美乱码精品一区二区 | 中文电影网| 中文资源在线官网 | 亚洲视屏 | 中文字幕在线观看网址 | 深夜免费福利视频 | 91免费看黄 | 成人精品福利 | 色综合久久久久久久 | 免费在线观看av电影 | a√国产免费a | 毛片www| 在线视频中文字幕一区 | 久久免费国产精品1 | 四虎影视4hu4虎成人 | 综合色在线| 2021av在线| 日韩午夜av | 日本精品久久久久 | 国产99久久久国产 | 国产精品第一 | 亚洲欧美怡红院 | 日韩av一区二区三区在线观看 | 成片视频在线观看 | 国产精品av在线 | 激情喷水 | 99免在线观看免费视频高清 | 欧美国产日韩一区二区 | 91精品国产自产在线观看永久 | 亚洲欧美日本一区二区三区 | 久久激情视频网 | 久久小视频 | 在线观看91精品视频 | 久久精品www人人爽人人 | 一区二区精品在线观看 | 天堂av在线网 | 久久综合中文色婷婷 | 亚洲视频久久久 | 中文字幕在线国产 | 国产夫妻自拍av | 国产一区观看 | 91成人在线观看喷潮 | 日韩xxx视频 | 久久国产精品久久精品 | 超碰免费观看 | 91桃花视频 | av电影免费在线播放 | 久久久久久久久久久免费av | 福利视频一二区 | www色网站 | 国产亚洲精品日韩在线tv黄 | 国产精品久久99综合免费观看尤物 | 久久人人97超碰精品888 | 成人毛片久久 | 日韩在线视频免费观看 | 国产精品久久久久久久7电影 | 91中文视频 | 操久| 天天摸夜夜添 | 久久久在线观看 | 亚洲精品高清一区二区三区四区 | 久久久久久久久久久久久影院 | 免费高清在线观看成人 | 夜夜操夜夜干 | 成人久久毛片 | 免费网址在线播放 | 日韩一区在线播放 | 播五月综合 | 亚洲网站在线看 | 国产精品成人国产乱一区 | 超碰97在线资源 | 狠狠干天天操 | 国内精品久久久久久 | 日韩高清在线一区二区 | 婷婷亚洲最大 | 欧美日韩中文另类 | 四虎小视频 | 97韩国电影| 激情久久伊人 | 99视频在线观看免费 | 亚洲一区黄色 | 亚洲国产免费av | 日韩精品在线一区 | 成人在线视频免费看 | 中文字幕在线看视频国产中文版 | 五月婷婷在线播放 | 免费观看v片在线观看 | 久久久久久久18 | 在线视频精品 | 麻豆91网站 | www日韩在线 | 日韩欧美xxx | 狠狠色综合网站久久久久久久 | 伊人久久av | 久久婷婷五月综合色丁香 | 在线观看成人福利 | 天天爱天天干天天爽 | 97狠狠干| 免费看三级黄色片 | 六月婷操| 夜夜爽www | 91福利国产在线观看 | 人人看人人草 | 人人干人人模 | 色综合久久精品 | 国产精品麻豆三级一区视频 | ,久久福利影视 | 伊人五月在线 | 国产一区视频在线 | 亚欧洲精品视频在线观看 | 国产免费xvideos视频入口 | 四虎最新入口 | 狠狠色噜噜狠狠狠合久 | 国产精品久久人 | 精品视频在线看 | 国产在线观看,日本 | 国产二区免费视频 | 美女视频是黄的免费观看 | 91成人在线看 | 美女视频黄频大全免费 | 色网站免费在线看 | 婷婷综合国产 | 亚洲欧美视频一区二区三区 | 亚洲国产精品久久 | av在线看网站 | 成人天堂网 | 欧美视频国产视频 | 91精品视频在线看 | av网站播放 | 五月天综合色激情 | 亚洲精品国产第一综合99久久 | 午夜av免费看 | 91久久国产露脸精品国产闺蜜 | 91亚洲精品久久久久图片蜜桃 | 在线视频欧美亚洲 | 97碰碰精品嫩模在线播放 | 国产亚洲激情视频在线 | 中国黄色一级大片 | 美女网站视频免费都是黄 | 亚洲欧美日本国产 | av在线电影免费观看 | 亚洲精品久久久久久久不卡四虎 | 91九色网站| 91传媒免费在线观看 | 97免费在线视频 | 在线播放日韩av | 午夜视频在线观看一区二区三区 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 亚洲精品在线观看免费 | 日韩试看 | 亚洲国产一区二区精品专区 | 精品视频www | 一本到视频在线观看 | 99视频精品免费观看, | 亚洲理论在线观看 | 欧美三级高清 | 在线免费三级 | 五月香婷 | 国产免费a| 久久久99国产精品免费 | 亚洲a在线观看 | 最近中文字幕mv | 香蕉视频国产在线 | 日韩精品黄 | 九九久久精品视频 | 综合激情 | 97在线观看免费观看 | 亚洲资源视频 | 亚洲视频精选 | 国产精品videossex国产高清 | 久久久999精品视频 国产美女免费观看 | 国产精品久久久久久久久费观看 | www.神马久久 | 西西www4444大胆视频 | 天天操一操| 国产成人精品三级 | 美女网站在线免费观看 | 在线三级av | 亚洲国产黄色 | 久久免费的视频 | 久久好看| 国产小视频在线 | 免费男女羞羞的视频网站中文字幕 | 国产伦理精品一区二区 | 五月婷丁香 | 一区二区三区高清在线 | 免费三级a| 国产一级做a爱片久久毛片a | 亚洲va欧美va人人爽春色影视 | 午夜91在线 | 亚洲综合色站 | 国产一区二区免费看 | 天天激情| 欧美精品国产综合久久 | 久久99精品久久久久婷婷 | 国产精品福利在线 | 天天色天天操天天爽 | 五月婷综合 | 午夜影院三级 | 午夜视频在线观看一区二区三区 | av在线免费观看黄 | 久久99精品久久久久蜜臀 | 日韩av影视在线观看 | 亚洲 欧美 精品 | 毛片基地黄久久久久久天堂 | 98久久| 网址你懂的在线观看 | 国产区精品视频 | 久久视频一区 | 国产精品入口久久 | av电影中文 | 国产aa精品 | 日韩一区二区三区视频在线 | 天天操天天添 | 国产精品久久久区三区天天噜 | 国产传媒一区在线 | 美女啪啪图片 | 91香蕉国产在线观看软件 | 亚洲精品在线观 | 亚洲激情一区二区三区 | 97超碰免费在线观看 | 精品久久久一区二区 | 欧美精品久久久久久久久久丰满 | 国产一区二区视频在线播放 | 国产视频 久久久 | www.色五月.com| 五月色丁香 | 久久久久美女 | 久久精品牌麻豆国产大山 | 五月婷婷综合在线视频 | 国产专区视频在线 | 精品一二区 | 日日夜夜天天干 | 日韩激情久久 | 欧美午夜久久 | 丁香婷婷激情国产高清秒播 | 91九色最新 | 97在线视频免费播放 | 国产人成在线视频 | 免费亚洲黄色 | 久久综合婷婷 | 色综合久 | 日韩av片免费在线观看 | 97视频在线免费观看 | 欧美在线视频二区 | 97超碰.com| 五月开心综合 | 日韩免费av片 | 久久国产系列 | 在线性视频日韩欧美 | 日韩精品久久一区二区三区 | 国产亚洲在线观看 | 日韩欧美成 | 亚洲精品xxxx| 手机色在线| 手机看片国产日韩 | 99久久精品免费看国产四区 | 超级碰碰碰免费视频 | 日批视频在线播放 | 国产精品粉嫩 | 香蕉久草 | av在线看片 | 久久免费国产精品1 | 91精品视频一区二区三区 | 91在线免费观看国产 | 免费一级特黄录像 | 久久精品国产精品亚洲 | 亚洲日本韩国一区二区 | 国产精品久久久久久久久久不蜜月 | 午夜精品久久久 | 波多野结衣视频在线 | 精品久久久久久国产偷窥 | 婷婷中文字幕综合 | 中文字幕第一页在线视频 | 精品久久美女 | 人人干人人超 | 18久久久久 | 久久久免费 | 精品女同一区二区三区在线观看 | 一本一本久久a久久精品综合妖精 | 性色av一区二区三区在线观看 | 五月综合婷 | 国产福利午夜 | 99在线观看视频 | 韩国三级av在线 | 亚洲精品乱码久久久久久 | www四虎影院| 久久99视频免费观看 | 国产精品亚 | 免费在线色视频 | 98超碰在线| 深爱婷婷 | 国产精品久久99 | 在线草 | 国内小视频在线观看 | 免费看污在线观看 | 国产精品夜夜夜一区二区三区尤 | 午夜婷婷综合 | www黄色大片 | 最近免费在线观看 | 手机成人在线电影 | 国产午夜三级一区二区三桃花影视 | 色噜噜在线观看 | av在线电影播放 | 99久久精品免费看国产四区 |