彎道超車!後端程式設計師的Angular快速指南

ThoughtWorks發表於2016-09-02

開篇寄語 —— 彎道超車,為時未晚

前端領域如火如荼,工資水平也水漲船高。作為後端程式設計師的你,羨慕嗎?但羨慕是沒用的,更別提嫉妒恨了。古人曰:與其臨淵羨魚,不如退而結網。

接下來,我不但要教你結網,還要教你後端程式設計師彎道超車的祕訣。我將對前端領域的概念進行簡要說明,並儘量用後端領域的概念來作類比,受到筆者個人背景的限制,可能會更多使用Java世界的概念來進行類比,不過.net等世界也大同小異。

筆者就是從後端程式設計師轉型而來,深知阻撓後端程式設計師轉型的那些攔路虎。比如:陌生的工具鏈、陌生的語言、龐雜的W3C標準、不一樣的設計風格、不一樣的編碼風格等等,不過最大的攔路虎是……框架太多!每套框架都號稱自己有一系列優點,讓人眼花繚亂,無從抉擇。

事實上,對於前端程式設計師來說,在這些框架之間進行選擇確實很困難,因為有時候前端程式設計師會過於沉迷細節。比如,他/她可能在50毫秒和100毫秒的響應時間之間舉棋不定,可能會為了實現細節上的優點,而影響專案管理和可維護性。最嚴重的問題可能還在於:誤判了前端需求的增長速度和工程化開發的重要性,而這正是後端程式設計師們彎道超車的好時機。

細說從頭

1-origin

以2004年Gmail的推出為起點,前端領域已經呈現出爆炸性增長的趨勢,經過十多年的成長,它是否即將進入停滯期?我只能說“Too young, too naive!”。在Web初興的時候,同樣有編寫傳統桌面應用的程式設計師覺得它會很快成長到停滯期,不過結果大家都已經看到了。在我看來,並不是什麼“前端程式設計技術”橫空出世了,而是它的需求一直都存在,卻由於技術條件不足而被壓抑著 —— 直到Web技術成熟到能把它釋放出來,這才導致了前端橫空出世並急速膨脹的假象。

回憶一下,在“前端”的概念誕生之前,我們是怎樣實現一個Web應用的?我們會先在伺服器上合成一段HTML,把它發回給瀏覽器;之後,幾乎任何操作都會向伺服器傳送一個請求,伺服器再渲染一個完整的新頁面發回來。

跳出習慣性思維,反思一下:這是自然的嗎?我們為什麼會需要如此複雜的過程?這其實是被迫做出的妥協:在從Netscape誕生開始的很長時間內,瀏覽器中的JS都是一個“玩具語言(這是JS之父說的)”:語法繁雜、坑多、直譯器效能低下、無模組化機制、無成熟的工具鏈,無成熟的第三方庫等等,JS程式設計師(如果當時有的話)也毫無懸念的佔據了鄙視鏈的底端。

面對JS這樣一位“豬隊友”,程式設計師們還能怎麼辦?只能求助於萬能的服務端語言了:它幾乎不會受到瀏覽器的制約,可以自由使用所需的一切程式設計資源。幸運的是,Web技術的標準化工作在這個過程中得以蹣跚前行,而JS的標準化工作也在三大瀏覽器巨頭的博弈中艱難的前進著。

Chrome,特別是V8引擎的誕生,終於結束了JS直譯器的效能問題,更重要的是,基於V8引擎,誕生了偉大的NodeJS。NodeJS就是前端世界的JRE或.net CLR。它主要有三大貢獻:

  1. 讓JS語言“入侵”了後端世界和桌面世界。

    這在前端開發的襁褓期有效擴大了JS語言的適用範圍,積累了大量第三方庫,很多第三方庫只要在合適的工具支援下也能在前端領域正常使用。

  2. 為前端開發提供了工具鏈。

    如今的前端世界,其工具鏈的複雜度和完善度已經逐漸逼近後端世界,比如:類似於gradle的構建工具gulp、grunt,類似於maven的包管理工具npm,類似於junit的測試框架karma等等,它們無一例外,都是基於NodeJS的。

  3. 提供了標準化的模組化方案CommonJS。

    在NodeJS誕生之前,模組化一直是JS世界的短板,雖然也有不少相互競爭的JS模組化方案,卻都沒能一統江湖,這主要是因為當時的很多前端應用都過於簡單,對模組化並沒有迫切需求。而隨著NodeJS入侵到後端世界和桌面世界,模組化成了不得不做的事情,於是NodeJS內建的CommonJS就成為了事實性的標準。JS的最新版本ES6中內建的模組化機制就是類似於CommonJS的。

與此同時,在另一條戰線上,還有一些技術在平行推進,那就是前端DOM庫與前端框架。在2006年,一個名叫jQuery的DOM庫橫空出世,它封裝了複雜的、特定於瀏覽器的DOM操縱類API,讓程式設計師可以不必處理一些繁瑣的細節差異,從而簡化了瀏覽器中的DOM程式設計。

在那個時代,雖然尚未正式提出“前端”的概念,不過已經出現了不少事實上的前端程式。但這些前端程式相對於如今包羅永珍的前端還是過於原始了,很多前端程式碼都只是嵌入在後端頁面中的龍套。

不過,這些程式就像最早爬到岸上的魚一樣,帶人們發現了一個新世界,對前端程式的需求也隨之井噴。什麼是“前端程式”呢?在我看來主要有如下幾個特徵:

  1. 客戶端渲染

    與傳統上藉助後端生成新頁面的方式不同,前端程式藉助瀏覽器的API來呈現內容(也就是“渲染”)並處理使用者動作,在這個過程中,並不需要藉助服務端的運算能力,也不需要網路。

  2. 單頁面

    客戶端渲染技術衍生出的一個主要特徵是單頁面應用。因為不需要再由伺服器發回新頁面,所以前端程式在理論上就具備了獨自渲染內容並全權處理使用者互動的能力,只在必要時,才會通過Web API尋求伺服器的幫助。

  3. 實時反饋

    客戶端渲染技術衍生出的另一個主要特徵是實時反饋。在傳統的應用中,除非內嵌JS程式碼,否則任何反饋都需要由服務端程式碼生成併發回,而且程式設計相對複雜。這導致很少有程式能夠給出實時反饋,即使做到了實時反饋的,也會因為網路延遲等問題而損害使用者體驗,而專業的前端程式則可以藉助客戶端運算輕鬆實現實時反饋。

隨著技術的進步,前端終於具備了擺脫“石器時代”的條件,於是,前端的時代終於要開始了。

前端時代!

2-competition

以jQuery為代表的DOM庫在使用中逐漸暴露出了很多缺點,特別是混雜邏輯程式碼和操縱DOM的程式碼導致難以維護。於是一大批新的前端MV*框架悄然出現了。

框架不同於庫:庫是一組被動式的程式碼,如果你不呼叫它,它就什麼都不做;而框架不同,框架提供了啟動、事件處理等各種通用性程式碼,你按照框架規約寫自己的程式碼,並把它“告訴”框架,框架會在合適的時機用合適的方式呼叫它們。

確實,這沒什麼新鮮的,你早就用過Spring或asp.net了,不是嗎?從這一點上來說,前端框架與後端框架大同小異。不過,前端框架還是有自己的鮮明特色的:

  1. 它們是……用JS寫的。

    毫無疑問,JS儘管並不完善,但它目前是並且仍將是前端世界的霸主,我認為其中最重要的原因是:它是各大瀏覽器廠商利益的最大公約數。Google曾孵化了一個在瀏覽器和後端共用的語言Dart,不過現在連自己的瀏覽器都不打算直接支援它了。從技術上講,Dart無疑是相當先進的,但現實卻更加殘酷。

  2. 它們是弱型別的。

    受限於JS的能力,前端框架無法訪問執行時型別(就像Java或.net中的反射機制),也就無法像後端框架那樣大量藉助介面來定義擴充套件方式。因此,框架只能藉助一些複雜的技巧來達成目標。當然,後續的技術發展在一定程度上改變了這一點,那就是微軟的新語言TypeScript的誕生,我們稍後再展開這個話題。

  3. 它們是靈活的。

    得益於JS的動態特性和弱型別特性,前端框架也非常靈活,比如你可以把任意物件傳給呼叫者,只要這個物件有呼叫者所需的屬性或方法即可,而不用像Java那樣明確定義介面。靈活,是優點,也是缺點 —— 在小規模、需求穩定的程式中,它可以極大的提高開發速度,用過Ruby或者Python的程式設計師大概深有體會;但在大規模、需求頻繁變化的程式中,它將是BUG之源,用過Ruby或Python的程式設計師大概深有體會。

那些年,前端MV*庫的競爭,其激烈程度幾乎不下於各種語言的競爭。2009年,一個註定要名聲大噪的框架加入了這場前端MV*大戰,它叫Angular。

Hello, Angular!

3-angular-1

Angular的英文原意是“角”,也就是“銳角、直角”的“角”。它的主要開發者是Adobe Flex的開發者Misko以及很多來自Google的後端程式設計師,因此它有很多理念和概念來自於Flex和後端程式設計領域,如宣告式介面(Declarative UI)、服務(Service)、依賴注入(Dependency Injection)等,併為單元測試提供了優秀的支援。可以說,它天生就有後端基因,其設計目標也是處理像傳統後端一樣複雜的需求。幸運或者不幸的是,它仍然是一個前端框架。它具有高度的靈活性 —— 既可以寫得很規整,也可以寫得很爛。當然,在某種意義上,這不應該算作Angular的問題,而是JS的“原罪”。

這種情況意味著,如果有成熟的最佳實踐和優秀的開發規範,Angular程式可以寫得很漂亮:簡潔明瞭、模組清晰、分層明確、關注點分離。但在開發組意識到社群需要一份來自官方的開發規範之前,Angular 0.x和1.x版本的爛程式碼和壞習慣已經氾濫成災了。

幸運的是,Angular有一個繁榮、強大的社群,社群在行動。無論是英文社群還是中文社群,都出現了一些優秀的Angular工程師,他們總結出了一些經驗和教訓,並給出了自己的解決之道,全憑自己的力量與熱情在社群中傳播。如果你是一個後端程式設計師,會發現這些最佳實踐和開發規範似曾相識。沒錯,很多優秀的Angular工程師本來就是後端工程師出身。這並不奇怪,前端歲月尚淺,優秀的前端工程師當然會有很多是從優秀的後端工程師轉型而來的。

但這還不是根本原因。在有些人的思維中,前端和後端好像是兩個截然不同的世界。並非如此!程式設計之道本來就是互通的,並不存在前端的程式設計之道和後端的程式設計之道。主導這兩個開發領域的設計原則不外乎就是SOLID等少數幾個,無論是前端的程式設計規範還是後端的程式設計規範,都是對這些原則的例項化。

社群的努力,在一定程度上彌補了Angular早期版本的缺憾,但,這還不夠。我們需要一份官方的開發規範,甚至,一個更好的Angular。後者才是重點!

Hello, Angular 2!

優秀的框架特性、繁榮的社群、廣泛的應用,但都被ES5(JS的早期版本)這個豬隊友給拖累了,另一個豬隊友則是老版本瀏覽器 —— 特別是IE8及更低的版本。於是,就在Angular 1.x如日中天的時候,Angular開發組高調開始了新版本的開發工作,它就是Angular 2!這裡還有很多小插曲按下不表,等我有時間開雜談時再慢慢說。

Angular 2本身不再是用ES5寫成的,而是TypeScript,簡稱TS。TS是微軟開發的一個新語言,它是ES6的超集,這意味著,凡是有效的ES6程式碼都同樣是有效的TS程式碼;另一方面,ES6是ES5的超集,所以凡是有效的ES5程式碼也同樣是有效的TS程式碼。但是在ES6的基礎上,TS增加了可選的型別系統以及在未來ES8中才會出現的裝飾器等特性。

你想知道TS為什麼這麼牛?很簡單,因為他爸是 —— 不,不,不是李剛,他爸是Anders Hejlsberg,如果Java程式設計師沒聽說過他還情有可原,如果是.net程式設計師,那就自己去面壁思過吧 —— 他是微軟.net的總架構師,C#語言之父,更資深的程式設計師可能還用過Delphi,那是Anders的“長子”。一個人設計了三個流行的工業級語言,也真是夠了。

雖然TS已經誕生了很久,但卻一直沒有流行起來,這主要是因為它還缺少一個“殺手級應用”。現在,Angular 2來了!

在擺脫了一個豬隊友之後,Angular 2終於可以隨心所欲的展示自己的風采了,比如:基於型別的依賴注入、強型別的庫檔案、更加便捷的語法、標準化的模組化機制等等,無法一一列舉。

但還有另一個豬隊友在拖後腿,那就是老式瀏覽器,對,說的就是你 —— IE 8!Angular從1.3開始就徹底拋棄了它,2.x就更不用說了。有一陣子,曾經傳言Angular 2不支援IE 11以下所有版本的IE,不過幸好,Angular開發組終於對現實做出了妥協,否則這又會是一個重大的公關危機了。能與IE 8說再見,真好。不過,這也意味著,當你準備開始用Angular 2做專案的時候,務必先跟客戶或產品經理敲定不需要支援IE 8,否則還是老老實實用Angular 1.2吧。

Angular 2,後端之友

Introduction-to-website-building-using-Angular-2-740X296

在Angular 1中就從後端借鑑過很多概念,到Angular 2自然就更進一步了。這些概念對沒有做過後端開發的新前端來說會有一定的難度,不過對後端程式設計師來說這不過是小菜一碟。接下來我們就逐個講講。

服務與依賴注入

沒錯,它們跟後端的服務與依賴注入是同一個概念,只是在實現細節上略有不同:

後端的服務是一個單例,在Angular 2中同樣如此;

後端的服務是使用型別來注入的,在Angular 2中同樣如此,不過由於TS的限制,Angular 2中通常會根據類進行注入,而不是像傳統的後端程式那樣優先使用介面;

後端的依賴注入器是由框架提供的,Angular 2中同樣如此;

後端的依賴可以進行配置,Angular 2中同樣如此,不過它的配置方式更加靈活,它不需要單獨的配置檔案(該死的XML),而是直接用程式程式碼,這賦予了它額外的靈活性,卻幾乎沒有損失(這讓我想起了Grails)。

不過Angular 2的依賴注入體系比傳統的後端更加靈活,它是一棵由多個注入器組成的樹,這棵樹跟元件樹平行存在。你可以把區域性使用的服務放在中下層節點上,來限制它的作用範圍,減小耦合度;你可以預留一些佔位(Placeholder)服務,等待呼叫方實現它,以達到“用組合代替繼承”的效果(要了解詳情,請自行分析LocationStrategy的設計);可以在不同的層級上配置同一個類的不同依賴例項,這樣它就可以覆蓋掉上層的配置,在必要時臨時建立一個“獨立王國”。

可選強型別

強型別是很多Java程式設計師信心的保障,但同時也因為過於繁瑣而飽受抨擊。

現在,它隨著TS又來到了前端世界。不過不用害怕Java世界中的悲劇重演,因為TS中的強型別是“可選”強型別。這意味著你可以完全不定義變數、屬性、引數等的資料型別,TS編譯器也會照樣放行。當你需要快速建立一個原型時,這種特性會非常有用,因為你不用現在就做很多決策。但當有一天你的原型經歷了從產品經理到CEO的重重考驗,終於修成正果的時候,你會發現它“太爛”了。

這是好事,這說明你在開發過程中沒有浪費精力。但如果你想繼續像這樣把它發展成一個產品級應用,那就要悲劇了。因為程式碼中有太多隻有你自己知道的約定和隱式介面,但新過來和你進行合作開發的人是無法和你心靈相通的。用不了多久,本來就是一團麵條的程式碼就變成了一坨漿糊,然後你就開始了無止境的加班歲月。沒錯,“福兮禍之所依”,現實就是這麼殘酷。

為了走得更遠,你先得為程式碼中的變數、屬性、引數等標上資料型別、抽象出介面,並且基於它們建立相應的開發規範(最好能用持續整合(CI)工具進行保障)。有了這些,即使是兩個負情商的大老爺們兒也能輕鬆做到“心靈相通”了。

加完型別之後,你彷彿回到了自己所熟悉的後端領域。現在,你的地盤兒,你做主!

測試驅動開發

如果測試驅動開發還不是你的基本功,那可能說明你在後端開發方面還有短板。即使你不是想做全棧,而是想完全轉型成前端,也應該補習一下測試驅動開發的技能。因為未來的前端開發,即使在純邏輯類程式碼的複雜度上都可能會趕上後端。

在1.x的時代,Angular就以其優秀的“可測試性”而著稱了,Angular 2當然不會放棄這個傳統優勢。Angular 2的單元測試更加簡單,我還是直說吧:Angular 2中單元測試的方式更像後端。在Angular 1.x的時代,單元測試中不得不使用諸如$controller(如果你不懂,請忽略它)等框架內部API,而Angular 2測試框架的設計中完全封裝了它們,當你測試一個元件時,大部分時候幾乎就是在測試一個普通的類。

傳統的前端程式設計師可能不太容易理解測試驅動開發的思維方式,特別是對於沒有什麼後端經驗的資深前端。這也同樣是後端程式設計師實現彎道超車的好機會。隨著前端職責的加重,在前端程式碼中,會出現越來越多的複雜邏輯,這些複雜邏輯如果沒有測試驅動開發的保障,將被迫用“寫程式碼、切換到瀏覽器、介面上點點看看、切換回IDE”的低效迴圈進行開發。

更重要的是,它很容易誕生高度耦合、恰好能用的爛程式碼。但在測試驅動開發的保障下,可以先從最簡單的規約開始,逐步補充更多規約。在開發過程中,你只要不時瞥一眼IDE的測試控制檯就可以了。這樣不但開發起來更快,而且可以收穫良好的程式碼結構,因為容易測試的程式碼通常也都是鬆耦合的。

分工,1+1 > 2

後端程式設計師學習前端技術時,往往會為HTML/CSS等頭疼不已,這些都是相對陌生甚至完全陌生的領域,如果急於為團隊貢獻生產力,那麼請把這些“後背”交給你的隊友。

延續Angular的一貫傳統,Angular 2對團隊分工提供了卓越的支援,它通常會把一個介面分成模板(*.html*.jade)、樣式(*.css*.scss*.less*.styl)、元件(*.js*.ts)和元件單元測試(*.spec.ts*.spec.js)等幾個基本名(base name)相同的檔案,它們被放在獨立檔案中但能很好的相互協作。

當你的前端技能還在蹣跚學步的時候,請放心的寫下一些粗糙的HTML/CSS程式碼,比如用div搭建出醜陋但能與你所寫的元件順暢協作的html檔案。然後,提交它,等你的隊友幫你把它修成漂亮的產品級介面。同樣的,如果你的前端隊友還不太清楚該如何幹淨漂亮的從元件中抽取出服務,那麼你就可以放心的幫他/她修改元件程式碼,而不用擔心無意間破壞了模板和樣式。

一個團隊中,如果能有謙遜的super star當然好,但這種團隊是可遇而不可求的,更現實的期望是一個能相互信任、各盡所能的團隊。而Angular在設計時就充分考慮了團隊分工的需求,要想建設這樣一個團隊,毫無疑問,Angular將是你的首選平臺!

結束語 —— Angular 2,全棧養成計劃

5-team

好吧,我承認我可恥的做了一次標題黨。本文並非在煽動後端程式設計師去革前端程式設計師的命,而是希望無論是前端程式設計師還是後端程式設計師,都能成長為優秀的全棧程式設計師(是的,前端程式設計師如果理解了Angular 2中的這些概念也會更容易向後端發展)。全棧程式設計師由於能有效節省溝通成本(比如不用頻繁協商API)而被很多開發組織寄予厚望,但真正培養起來可沒那麼容易。

有一陣子,培養全棧程式設計師的期望被放在了Fullstack JavaScript上 —— 它既能寫前端程式又能寫後端程式還能寫桌面程式。不過事實證明,這種期望落空了。即使經過了大爆發,NodeJS在企業應用開發、大資料等領域的資源積累也遠遠不及Java、C#、Python,甚至將來還有被新崛起的Scala和Go超越的危險。

或許我們應該換一種思路了:全棧一定要用同一種語言寫前端和後端嗎?

並非如此。事實上,我們更應該看重的是程式設計模型、思維方式和協作模式等方面的複用,而語言層面只是細枝末節而已。所以,Java或C#,加上TS與Angular 2,給了培養全棧的新曙光。相似的概念模型、相似的思維方式、相似的協作模式,這才是全棧程式設計師真正的核心技能,與語言無關。

這些,才是Angular 2給專業開發團隊帶來的,最珍貴的禮物!

相關文章