成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從對 Vue 中 mixin 的批評,到對模塊間依賴關系的探討

開發 前端
編程框架日新月異,工具平臺推陳出新。但有意思的是,代碼的壞味道不會因為你使用工具的時髦而自行消散,團隊成員的編程水平也不會隨著工具的進化而水漲船高。

編程框架日新月異,工具平臺推陳出新。但有意思的是,代碼的壞味道不會因為你使用工具的時髦而自行消散,團隊成員的編程水平也不會隨著工具的進化而水漲船高。

工具從來都不是你寫出好代碼的決定因素,相反它可能是最無關緊要的條件之一,但偏偏又給大部分人救命稻草一般的錯覺。它更類似于催化劑,能助你的代碼一臂之力,也能加速它的滅亡。

mixin 就是很好的一個例子。

mixin 語法回顧

如果你對 Vue 中的 mixin 語法還不甚了解,我用一句話和一個例子就能將它簡單概括:mixin 是一種組件間的代碼共享機制,允許你將代碼封裝為一個獨立模塊,將其用于在多個組件之間共享。

假設 Toolbar 和 Card 組件在實例化這個組件時都需要傳遞 title、subTitle 屬性,那么你就可以考慮將這兩個屬性封裝到一個公共的例如名為 ComminMixin 的模塊里,然后將這個模塊“插入”到有需求的組件中。

首先定義 CommonMixin 模塊代碼:

 

  1. export default { 
  2.   props: { 
  3.     title: string, 
  4.     subTitle: string, 
  5.   } 

接著在 Toolbar 和 Card 組件中對其進行引用即可:

 

  1. import CommonMixin from '../mixins/commin-mixin.js' 
  2. export default { 
  3.   mixins: [CommonMixin] 

這種方式同你直接在 Toolbar 中定義 title 和 subTitle 的屬性并無不同:

 

  1. import CommonMixin from '../mixins/commin-mixin.js' 
  2. export default { 
  3.   props: { 
  4.     title: string, 
  5.     subTitle: string 
  6.   } 

mixin 機制存在什么樣的問題?早在 2016 年的時候 React 官方就發布過一篇名為 Mixins Considered Harmful 的文章,其中詳細敘述了 mixin 機制下會引起的幾類問題,比如命名沖突、比如引起滾雪球般的復雜性等等。這里我只提及我認為危害最大的一點:隱式依賴。并借此引出我們下一節的話題。

隱式依賴

正如上一節代碼所示,mixin 模塊內的代碼和組件內的代碼是等價的,如果你知道 mixin 中存在一個名為 hello 的方法,你完全和可以在組件中以 this.hello() 的形式無差別的對其進行調用。

并且這種等價還是雙向的,雖然 mixin 模塊在當初被定義時并不知道將來會有哪些組件引用它的,但如果當下你十分確定某個消費它的組件中注定存在 world 方法,你就可以在 mixin 模塊中調用 this.world() 。這種關系還能延申至 mixin 之間,無論是平行關系還是嵌套關系下的 mixin 模塊(mixin 模塊可以繼續引用其他的 mixin 模塊),它們之間都可以互相訪問變量和方法。

在這里你是不是已經嗅到了危險的味道?

看上去方便至極了!可一旦需要對代碼進行維護時,問題就暴露了,哪怕你只是想理解某個極小的代碼片段。

假設某個同事在組件中偶遇了 hello 方法,想給它新增一個參數來實現更多的功能——這看上去不起眼的小事在實際操作起來卻比登天還難。因為他根本不知道該方法是在哪個 mixin 模塊中被定義的,他所知道的只有方法屬于 this,于是他不得不翻看每一個 mixin 的定義。

退一步說即使他在某個 mixin 中找到了該方法的定義,又會遇到另一個難題:不敢修改這個函數的簽名。雖然他可以知道有多少個組件文件引用了這個 mixin 模塊,但是他不知道有多少處直接或者間接的調用了這個方法。也就是說這一次修改會造成的后果和帶來的影響難以評估。

所有這些問題的根源在于組件與 mixin 間,mixin 與 mixin 間的依賴是隱式的。也就是說當 A 模塊依賴 B 函數時,這種關系既不是通過顯示的聲明(比如 import 語句或者依賴注入的方式)取得,也不是通過公共約定(例如 windows 對象上存在 getComputedStyle 方法)確定下來的。

這種關系也讓 IDE 武功盡廢,我發現解決這個問題的最后方式竟然是 Ctrl + C(復制),Ctrl + V(粘貼),Ctrl + Shift + F(全局查找),Ctrl + H(文本替換)。

隱式依賴不僅會對腳本代碼帶來負面影響,對樣式代碼也會。flex 布局是一個正面的例子:如果你想控制父容器內孩子元素的布局,只需要修改父容器上 flex 有關的屬性即可,你不依賴孩子元素的 DOM 結構,更不依賴孩子元素上的樣式;而一個反模式的例子是 text-overflow: ellipsis 屬性,單一的該樣式屬性是不足以自動省略容器內的文字,容器還需要滿足 1) 寬度必須是 px 像素為單位 2) 元素必須擁有 overflow:hidden 和 white-space:nowrap 樣式。而 text-overflow 屬性本身并沒有告知我們還需要這些“配套設施”。

最終帶來的局面剛好符合 Uncle Bob Martin 在他的 The principles of OOD 一些列文章中談到過的糟糕設計(Bad Design)的幾個特征,比如

  • 僵化(Ridigity):代碼難以修改,因為改動會影響到的地方太多
  • 脆弱(Fragility):當你做出修改時,系統中預期之外的地方會遭到破壞
  • 難以修改(Immobility):代碼很難被復用,因為它與當前系統中的功能耦合在了一起

前兩條在上面解釋過了,很好理解。至于最后一條特征,mixin 不僅似乎沒有違背,還執行的非常好不是嗎?這就涉及到我們下一節要聊的內容

Defactoring

這里暫停一下,我們似乎陷入到了一種窘境當中:我們都承認 mixin 是極其強大靈活的,它將代碼的復用發揮到了極致。但現在看了恰恰是著這種靈活性給我們的代碼帶來了災難。我們應該如何理解這種矛盾?

我們要回答的第一個問題是:這種靈活性真的是我們想要的嗎?

Reginald Braithwaite 在13 年寫過一篇很有意思的技術文章 Defactoring,注意不是重構的那個單詞 Refctoring。

什么是 defactoring? 簡而言之如果我們將把大單體代碼拆分為細粒度碎片代碼過程稱之為 factoring 的話,那么 defactoring 代指的就是相反將代碼碎片拼裝起來的過程。

為什么我們會需要 defactoring? 因為靈活性帶來的并不總是好處,它會給我們帶來認知上的困惱,你總是需要將不同的碎片碎片拼湊起來之后才能理解整幅圖的原貌;模棱兩可的代碼總是會讓你摸不清它的意圖;更不要說代碼的復雜性了。

你可能會問如果萬一呢?有時“靈活性”的背后是我們對于未來的恐懼:我們可能需要支持 A 功能或者支持 B 功能,但事實上你不需要提前實現這些可能性,讓你的代碼有能力應對這些可能性即可。

所以說恰當的 defactoring 是有必要的。

第二點我們需要考慮到人的因素。我很喜歡 Coding Horror 提出的 Falling Into The Pit of Success 的理論,引用原文中的話說就是:

  • a well-designed system makes it easy to do the right things and annoying (but not impossible) to do the wrong things.

在站在項目和團隊的角度上考慮代碼的可維護性時尤其如此。

除此之外代碼應該是易于修改,并且是很容易修改正確的。比如 TypeScript 相比 JavaScript 就是,但很明顯 mixin 并不是。

一段代碼從你編寫完畢之日起,它的命運就再也不掌握在你手中了。他人很可能會領悟不到你設計某個屬性的意圖,你精心設計的一段優化性能的代碼很容易就被破壞掉。所以我們需要適應度函數,需要有測試。

在實際的工作中 mixin 大部分被濫用了。你可能會定義一個名為 ComponentCommonMixin 的模塊,初衷是用于存儲和所有組件關聯的通用屬性。但后續的開發人員并不曉得你的初衷是什么,導致他們在規劃接下來的公共屬性時會無腦的往這個模塊里添加,讓它變得臃腫不堪——“噢,因為它是公共的”。

表面上看它分離了公共屬性代碼和組件專屬代碼,但實際上在 mixin 模塊內部剛好是緊耦合這種反模式的最佳體現。這種狀態下的 mixin 根本毫無“單一職責(Single Responsibility )”可言,在一個模塊內部既可能包含了和樣式有關的屬性,也可能包含了和權限有關的行為,涉及對任何一塊業務的需求變更都會導致模塊被“打開”進行重新修改,這也有違開放封閉原則(Open-closed )。

普適的 mixin 模式

令人欣慰的是這種 mixin 中的隱式依賴問題是 Vue 框架下的特例。

提起代碼的復用我們首先想到的是繼承,但繼承不是萬能的:繼承打破了父類的封裝;繼承要求子類覆寫方法時與父類兼容;多數語言不支持多繼承。一言以蔽之繼承機制對類的抽象設計能力要求很高,劣質的抽象比沒有抽象更難維護。

在這些限制之下,組合模式似乎是一類不錯的選擇,而 mixin 就是組合的一種實現方式。這里我們直接參考 TypeScript 2.2 RC 官方技術博客 中的一個例子,來說明 mixin 是如何實現的。簡單來說分為下面四個步驟:

  • takes a constructor
  • declares a class that extends that constructor
  • adds members to that new class
  • and returns the class itself.

這里我們嘗試實現一個 Timestamped mixin,它會在需要拓展的類上添加一個 timestamp 屬性:

 

  1. type Constructor<T = {}> = new (...args: any[]) => T; 
  2.  
  3. function Timestamped<TBase extends Constructor>(Base: TBase) { 
  4.   return class extends Base { 
  5.     timestamp = Date.now(); 
  6.   }; 

首先 Constructor 是一類用于描述構造函數簽名的類型,它支持傳入泛型 T, T 代表著構造函數實例化后返回的結果類型。它的作用不大,主要為了在下面的方法中承接基類而已。

Timestamped 方法接收一個基類作為參數,這個基類必須要符合上面定義的構造函數簽名,它必須是能“構造出點什么東西的”。在函數的實現中,它用一個匿名類來繼承這個基類,并且在匿名類上新增一個 timestamp 屬性之后返回出去。

使用的效果怎樣呢?我們以一個 Point 類為例,看看如何對它進行拓展

 

  1. class Point { 
  2.     x: number; 
  3.     y: number; 
  4.     constructor(x: number, y: number) { 
  5.         this.x = x; 
  6.         this.y = y; 
  7.     } 
  8.  
  9. const TimestampedPoint = Timestamped(Point); 
  10.  
  11. const p = new TimestampedPoint(10, 10); 
  12. p.x + p.y; 
  13. p.timestamp.getMilliseconds(); 

Point 自身并沒有定義 timestamp 屬性,但是通過 Timestamped 方法拓展之后,在依舊保留自身行為的同時,又新增了 timestamp 屬性。

這種模式可以無限的嵌套下去,例如我們還可以添加 draw、color 等 mixin,同時對 Point 類進行拓展:

  1. const NewPoint = draw(color(Timestamped(Point))) 

這種模式看起來是不是非常眼熟?就是 React 中的高階組件,你一定使用過 withRouter 或者是 connect 方法來對組件進行封裝。

但為什么這種模式下似乎就不存在隱式依賴中提及的問題?因為除了移除模塊當中的“隱式”元素之外,我們還間接的調整了模塊間的依賴方向。

在下圖 Vue 的 mixin 模式中,組件 A 和 B 看似在以單向的方式引用 mixin 模塊 B,但實際上因為隱式依賴(上圖中灰色虛線所示)的關系,模塊和組件間的依賴關系是并無統一方向可言,甚至可以是循環依賴的。

而下圖在 TypeScript 的 mixin 模式中,draw 函數中的匿名類對傳遞給它函數的類一無所知,它只管往在匿名類中添加自己的屬性和行為即可,并且匿名類都是相互獨立的。這樣就保證了模塊之間的依賴是單向的。注意上述的箭頭雖然表達的是“依賴”關系,但它并非是 UML 中的依賴,它既沒有調用依賴模塊的方法,也沒有將依賴模塊作為自己的成員變量

當然,如果你“足夠有信心”的話,你還是可以強行調用傳入的基類上的方法,只不過如果你真的打算這么做的話,你可能需要通過接口或者類型將基類約束起來,給出方法的簽名來保證它是存在的。

模塊間的依賴方向是另一個我們需要關心但可能會被忽略的一點,因為它會影響到我們的調整模塊代碼的難度。Uncle Bob Martin 在《整潔架構》一書中提出了「組件依賴原則」(Stable Dependencies Principle)。他認為在軟件開發中的軟件設計不可能是靜態的,它注定是需要被調整的,并且不同組件模塊調整的頻率并不相同。因此,一個注定需要被改動的組件不應該依賴那些難以被撼動組件,否則它自己也會變得難以修改。

例如對于下圖中的 Y 模塊而言,它依賴額外的三個模塊。以至于這三個模塊中任意一個模塊的變更都會給它帶來影響,這會導致它變得極不穩定。

隱式依賴的其他體現

隱式依賴另一個極富爭議的例子就是服務定位(Service Locator)模式。

服務定位模式在大多數時候被認為是反模式。在前端領域中可以實現但很少被用到。

什么是服務定位模式?假設你在某個類的方法中需要調用某個依賴的方法,你可以在方法中通過 Locator “臨時”找到這個依賴:

 

  1. class  MyClass { 
  2.   public void MyMethod() { 
  3.     var dep = Locator.resolve(IDep)(); 
  4.     dep.DoSomething(); 
  5.   } 

它能工作沒有錯,但我們還存在另一種實現方式,我們可以通過創建實例時的構造函數傳入依賴,也可以通過依賴注入傳入依賴:

 

  1. class  MyClass { 
  2.   public MyClass(IDep dep) {} 
  3.   public void MyMethod() { 
  4.     dep.DoSomething(); 
  5.   } 

在使用服務定位模式實現的前提下,你想創建一個實例并且調用它的方法很可能會失?。?/p>

 

  1. var myClass = new MyClass(); 
  2. myClass.MyMethod(); 

因為服務定位模式的問題在于它的依賴被隱藏起來了,你無法一眼看穿它對 IDep 的依賴,所以你也就可能不會在項目中引入對應的 Locator 以及 IDep。哪怕你完整收集了它的所有依賴,你還需要額外的引入 Locator 模塊,可它與你真正需要的業務功能并無太大關系。如果能在構造函數中進行顯式的聲明,那這些問題都能夠得到避免。

結束語

我當然同意 mixin 是中性的,所有事故的背后本質上都是人的問題。但如果我們承認“人”是我們在軟件活動中永遠也無法消除的不穩定因素的話,那就要面對 mixin 會比其他機制更讓我們的軟件岌岌可危的這個風險。這個時候我們沒有理由視而不見了。

責任編輯:未麗燕 來源: 知乎
相關推薦

2010-08-24 09:47:05

LINQ to SQL

2023-08-10 15:01:28

5G物聯網

2019-09-23 11:07:00

PythonRedis軟件

2020-06-08 10:17:37

數據分析冠狀病毒機器學習

2010-08-06 10:34:27

ODB2系統性能優化

2023-09-07 18:32:46

人工智能網絡

2021-03-10 09:33:51

技術研發管理

2009-06-04 10:34:19

Hibernate一對一對多關系配置

2019-04-11 15:45:08

ReactMixin前端

2009-09-14 13:25:08

LINQ多方面探討

2020-09-22 20:00:30

微服務架構設計

2010-06-17 17:36:30

2015-10-22 10:08:17

iOSATS適配

2024-09-20 05:46:00

2024-09-11 16:49:55

2021-09-15 09:31:39

前端開發工具

2016-10-26 19:53:05

2022-07-25 17:00:16

元宇宙金融購物

2022-07-01 11:08:54

首席信息官CIOIT領導者

2009-06-18 14:22:06

Hibernate多對Hibernate
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品久久久久久久7电影 | 国产欧美精品一区二区色综合 | 国产婷婷色一区二区三区 | 91社区视频| 成年视频在线观看福利资源 | 亚洲欧美一区二区三区国产精品 | 亚洲视频1区 | 欧美精品在线播放 | 91在线观看视频 | 欧美网站一区二区 | 五月天综合影院 | 成人在线免费观看av | 人人九九精 | 国产精品夜夜春夜夜爽久久电影 | 久久久美女 | 免费国产一区 | 日韩a| 成年人网站在线观看视频 | 国产精品久久久久久婷婷天堂 | 自拍第1页 | 91成人免费观看 | 欧美亚洲另类在线 | 视频精品一区二区三区 | 国产精品久久精品 | 91精品国产综合久久久久 | 国产一区二区三区免费 | 久久97精品| 国产精品国产三级国产aⅴ中文 | 日日干日日操 | 国产农村一级国产农村 | 一区二区精品 | 欧美综合一区二区 | 日美女逼逼 | 欧美一区二区三区视频在线观看 | 精品欧美视频 | 成人精品 | 亚洲精品在线免费观看视频 | 韩国av电影网 | 亚洲超碰在线观看 | 男人久久天堂 | 在线观看深夜视频 |