Angular, 工程之美

asnowwolf發表於2018-06-07

如果只能用一個詞來概括 Angular 的優點,那我會選“工程化”;如果要換個文藝點的詞,我會說 “大巧若工,大道至簡”。這個誕生於一群 Google 工程師之手的框架,從一開始就打上了鮮明的“工程化”烙印。

什麼是工程化開發呢?它是和手工作坊式的開發相對而言的。主要的特徵是需要多人、多時段的分工與配合,而不是一個超級程式設計師寫完90%的程式碼,其他人只負責打下手。

但大中型系統面對的挑戰不是靠超級程式設計師就能解決的。如果一個系統的複雜性逐漸累積,遲早會讓它的維護成本越來越高,變成雞肋一般的存在。要解決複雜性的問題,首要辦法就是從架構層面上隔離系統的各個部分,讓它們彼此之間相互獨立,互不干擾,也就是說,分而治之,各個擊破。然而這並不容易。

分工協作

多人開發最大的挑戰就是如何分工協作。

在 Angular 中,用來支援分工協作的主要基礎設施是模組。

比如,如果我們不通過模組來給應用劃分出硬邊界,那麼 A 編寫的元件就可能會隨手引用到 B 編寫的元件,但 B 寫這個元件原本只是為了給自己用的,根本沒有考慮過複用問題。那麼 A 和 B 之間的工作就產生了意外耦合,而意外是工程化最大的敵人。實踐證明,各種形式的意外耦合往往會埋下地雷。

有了模組,B 就可以把這個元件先私藏起來,讓別人沒法用它,這樣 A 寫的元件就不會再無意間依賴 B 寫的元件了。如果將來多方達成共識,就可以收集多方需求,真正為了複用的目的而去開發一個新的元件,把原來的元件改寫成這個可複用元件再加一些適配程式碼。

現在,終於可以為每個模組都指定一個明確的“責任人”,而不再“九龍治水”了。同時,團隊分工也得到了優化,少量高手負責開發可複用模組和核心模組,而新手則負責開發那些“只需要對自己負責”的模組,無論他寫的程式碼質量怎樣,至少這些問題不會擴大化。讓每個人都人盡其才,這是老闆最喜歡的狀態了,也是你架構水平和管理水平的體現。

除了意外耦合的問題之外,多人開發的另一個挑戰就是契約。

手工作坊式的開發可能不需要顯式的契約,因為超級程式設計師“心懷宇宙”,瞭解整個系統的每一處關鍵點。但一旦人多了,這種方式的弊端就會顯露出來了,畢竟人類的心靈並不相通。這時候,就需要顯式地描述契約,並藉助工具來保障這些契約沒有被誤解。

在 Angular 中,用來應對契約問題的主要基礎設施是型別與測試。

Angular 藉助 TypeScript 來對型別提供支援。通過型別,Angular 可以對介面契約進行一定程度的表達,比如我只希望你給我傳一個數字而不希望是字串,那麼我就可以把這個引數定義為 number 型別,如果呼叫者傳了字串進來,那麼在編譯階段就能發現並阻止這個錯誤。我們都知道,發現錯誤越早,解決它的代價就越低。當然,TypeScript 的型別系統遠比這強大多了,而作為 TypeScript 從初生到成熟期間的主要實踐者,Angular 可謂把 TypeScript 的特性發揮到了極致。這塊的內容非常龐大,這裡就不展開講了。

不過,型別只能對契約進行一定程度的表達,但它無法表達“我要一個大於10小於1000的數字”之類的契約,這時候就需要不同層次的測試出手了。從最早的 AngularJS 開始,測試就一直是重中之重,更不用說重寫後的 Angular 了。不但 Angular 本身程式碼的測試覆蓋率很高,而且還對你寫自己的測試提供了全方位的支援。你可以對不依賴 Angular 的獨立函式/獨立類進行極其快捷輕便的測試,也可以對依賴 Angular 的服務等進行很容易的測試,還可以對元件進行 midway 測試,以驗證它是否正確的生成了 DOM 節點,而不必動用 E2E 測試等比較沉重的方式。同時,別忘了 Angular 中的服務、元件、管道等都是純類(POJO),只是附加在它們上面的註解讓它們分化成了不同的用途,卻並沒有改變它們作為 POJO 的本質,因此,你也可以把它們當做不依賴 Angular 的 POJO 進行極其快捷的測試。這麼多層次的測試支援,可以讓你完全不用擔心“能不能測”的問題,只需要擔心某種場景下哪種測試方式最合適的問題,而這其中的運用之妙存乎一心,就是你能發揮最大作用的地方。

多人開發中一個稍微容易但更加繁瑣的問題是程式碼風格。

一個團隊要想形成統一的程式碼風格,“寫得像一個人一樣”其實相當麻煩,特別是在團隊中還有人員流動的情況下。光靠制訂程式碼規範是沒用的,《規範.exe》勝於《規範.txt》。業界的常規實踐是靠 Check Style 工具,JS 的世界中通常叫 lint。但是 lint 不認識具體的框架,因此它能保障的只是與框架無關的程式碼規範,而對像 Angular 這樣的大型框架就無能為力了。

Angular 開發組想到了這一點,他們專門做了一個用於 Angular 風格檢查的工具,並且整合進了 Angular CLI 中。它所依據的規範就是 Angular 開發組提供的官方風格指南,有了這份風格指南,天南海北、中國外國、現在未來的 Angular 程式設計師就可以寫出風格大致相同的程式碼了。當然,你們團隊可以根據需要對風格檢查的規則進行調整,不過,如果你們不是非常資深的 Angular 團隊,建議還是按照預設值來做吧。

時間是系統最大的客戶,也是系統最大的敵人。

一個只為列印一次 Hello, world! 而寫的程式不需要任何工程化。但那些真正的系統勢必會隨著時間的推移而不斷演化,使用時間越長,說明這個系統越被認可,但另一方面,使用時間越長,這個系統腐化的可能性也越高,直到最後“殺死”這個系統。

Angular 如何抵抗歲月的侵蝕

首先,Angular 的設計原則是每個部件只關注一件事(關注點分離 SoC)。

從巨集觀上說,core 模組只負責提供依賴注入等基礎設施,forms 模組只提供表單支援,router 模組只管路由等等。這樣明確的劃分開,就讓 Angular 同時具有了大而全和小而美這兩種看似矛盾的特徵。小而美保證了系統可以只用儘可能少的 Angular 模組,從而避免其它模組發生更改時帶來的影響。大而全則保證了系統即使只用 Angular 自身提供的模組都能做很多事,讓第三方依賴所帶來的侵蝕也最小化。

從微觀上說,服務拆分出了那些具有全域性性的、要在多個元件之間共享的狀態和邏輯,但它完全不關心介面展現;元件只負責展現使用者介面,它自己的部分邏輯可以委託給服務;指令只負責對 DOM 元素或現有元件的能力進行簡單的擴充套件;管道只負責從模型資料到顯示資料的轉換。這樣分工明確之後,需求變更帶來的影響也被最小化了。

其次,Angular 的版本釋出策略在保持穩定和擁抱變化之間做出了較好的平衡。

事實上,這種版本釋出策略並不是 Angular 首創和獨有的,它叫做語義化版本(semver)。NodeJS 以及最新的 Java 等重量級選手都遵循著這種策略。

語義化版本的要點是:只在主版本號變更時引入不向後相容的改動,次版本號變更只增加能向後相容的新特性,修訂號變更只用能向後相容的方式修 bug。這裡的要點是“向後相容”只會出現在主版本號變更時。

但採用這種釋出策略對作者自身的工程化能力要求很高,一般的作者是不容易做到的,不過 Angular 開發組對這一點倒是信心十足,而且也一直在兌現著承諾。事實上,Angular 開發組所做的遠不止這些: 它的每一個主版本都會完全相容上一個主版本,只是會把不相容的部分標記出來給你一個警告,讓你有半年的時間可以從容修改它。同時,Angular CLI 中還提供了 update 命令,它可以幫助你自動把程式從上一個主版本升級到當前主版本,我曾用兩分鐘的時間把一個花了一個人月開發的 Angular 程式從 5.0 升級到了 6.0。

語義化版本帶來的好處是可以兼顧發展生態圈與追逐新技術。生態圈喜歡穩定,誰也不希望自己寫的庫過半年就沒法用了,就算真沒法用了,也不希望自己花大量時間遷移,卻只為了跟隨新版本。但不斷跟上前沿卻是一項技術能擁有長久生命力的根本保障。而真正的工程化,兩者都想要,一個也不能少。

最後,Angular 既老且新。

說它老,是因為 Angular 採用了大量有十幾年二十幾年歷史的成熟技術,比如依賴注入、介面、註解等。這些全都是經歷過歲月的重重考驗的最佳實踐,既然那麼多年都沒被侵蝕,可以預期,除非將來出現了革命性的變化,否則照樣拿它們沒辦法。難能可貴的是,Angular 從未給它們發明新的名詞進行包裝,要知道,前端圈最喜歡做的事可能就是發明新名詞了。

說它新,是因為 Angular 熱情擁抱其它上下游技術,並積極追隨標準。從最初選擇與少年時期的 TypeScript 合作,到跟 RxJS 的深度整合,再到對 PWA 的第一時間支援,再到對 Web Components 等標準草案的緊緊追隨,無不體現著 Angular 的開放和與時偕行的心態。

結語

工程化的本質目的就是支援團隊化開發並幫助系統抵抗時間的侵蝕,而 Angular 展現出的這些工程之美,讓我對 Angular 的未來充滿信心。

周雖舊邦,其命維新!

來源:https://juejin.im/post/5b18b7015188254fbb75687c?utm_medium=fe&utm_source=weixinqun

相關文章