網頁制作poluoluo文章簡介:web前端的弱架構導致的代碼污染.
就要開始新的項目了,全站的前端架構也要開始構思,得益於之前做雅虎關系時的一些或成功或失敗的實踐總結,還是應當從架構的層面著手,去解決一些前端團隊開發的問題。如今市面上有著各種各樣的js庫和框架,但庫和框架還是有很大區別的,首先,“庫”是widget的一個集合,特點是上手容易使用簡便,參照例子只大概只需要引用一個script標簽到頁面中,再加上一些簡單的類似a.start(config)的啟動代碼就可以了,而框架則是網站的樹狀結構的抽象,包括模塊關系,功能之間的依賴,入口的先後順序,以及性能的hack等等,幸運的是阿裡還是很明智的選擇了yui作為其前端架構的底層,只是由於yui2強大豐富的widget庫,使得多數人認為yui2更是一個庫而非框架,而yui2的yuiloader也僅僅是一個可選的模塊,這樣就更加淡化了yui2在框架層面靈活強大的表現力,個人認為如果網站沒有以yuiloader搭建起來的模塊樹做支撐,yui2的作用無疑是一把牛刀只用作殺雞而已。在yui3中,這種情況有了根本改變,框架層面的模塊化代替了簡單的庫,這對於團隊開發的大型網站是很有啟發的。
當然,在做前端開發的時候,使用loader必然要多寫幾行代碼,多幾次封裝,又麻煩又不方便,這樣做到底好在什麼地方呢。這個話題大概說來話長,這裡只從網站規模和項目特點的方面來作簡單說明:
像taobao這種大型的網站,前端開發占有不小的比重,這類網站一般會按照大類劃分成不同的產品,每個產品相當於一個獨立完整的網站,只有整體樣式風格和組件會和主戰保持一致。
那麼從web前端角度講,這類網站大致包含四部分內容:
1),網站下轄站點所需的全局變量和函數,包括所有子站共通的部分
2),子站外框(公共頭尾)
3),網頁主體內容
4),組件
根據三者的特點,各自的重用情況為:
1),全站的全局變量和函數:幾乎每個頁面都要用到
2),子站外框:幾乎每個子站的網頁都保持一致
3),網頁的主體內容,不可重用
4),組件:重用率高,但是被有選擇的調用,只在必要的時候才會引用組件
從需求的角度上講,這類網站的特點是更新頻繁,從工程的角度講,代碼的維護成本大於甚至遠遠大於代碼開發成本。也就是說項目隨著時間的推移,修改功能的項目數量要遠遠大於新功能的開發,到項目的中後期,工程師的大部分時間不是寫新代碼,而是花大量的經歷去讀舊代碼,或者進行性能優化,或者針對新需求進行改進。不管是性能改進還是需求改進,都將增加代碼的熵,代碼污染也會越來越嚴重,維護成本也會急劇變大,這讓項目後期開發工程師苦不堪言,有一部分原因是項目前期文檔不全,缺少寫代碼的必要的約束和約定,再者是注釋不夠及時和規范,導致代碼可讀性差,三是工程師水平參差不齊,有些人在修改代碼過程中會無意識的破壞原有代碼的結構,四是時間壓力導致代碼質量下降,五是框架結構的不合理。
完善文檔和規范、增加新人培訓力度可以以解決一部分問題,但這往往和制度和執行力度有關。從項目生命周期來看,項目分為疊代和快速開發,快速開發是開發時間短,不需要和其他項目並行,及時開發及時測試及時打包上線,和其他項目發生沖突的可能性很小,簡單迭代的開發模式是基於分支和主干的多線程的並行開發。在此基礎上的前端開發也是保持和後段開發同步的迭代。迭代的優點是項目並行和減少沖突的發生。從後端開發的角度看,經常多人開發同一個頁面,只是每個工程師負責的模塊不同而已,一般情況下頁面中只有include的入口,然後調用各自工程師的代碼,這樣在頁面中的代碼污染就比較低,但是隨著頁面復雜度的增加,後段代碼會越來越多的直接出現在頁面當中,比如使用更多的echo代替模版,html代碼中嵌套更多的if判斷和for循環。這樣會增加代碼污染的程度。從前端開發來看,也會經常出現多人合作開發同一個頁面,也是每人負責不同的功能模塊,這裡有兩種情況,1是多人同時開發不同的應用模塊,兩者沒有依賴關系,2是多人分別開發框架和應用,兩者之間有依賴關系,應用依賴框架。而兩者之間的耦合應當僅僅以入口調用作為接口,因此前端框架應當考慮到這種情況,即多人開發的前端功能代碼應當盡可能地分離。
在js代碼的寫法上,自然推薦將代碼都封裝到頁面的js文件中,在page中只留出入口。然而實際情況並非如此,在缺少框架約束的情況下,多數人不會選擇去在讀懂之前的爛代碼的基礎上再添加自己的代碼,通常會直接用最簡單粗暴的用script標簽引一個js,緊跟著開一段script代碼塊寫啟動入口,甚至於連script標簽都爛的寫,直接在原先代碼塊中直接開辟一段區域自己加代碼。這種做法應當如何看待呢?從庫的角度講,庫中給了很多模塊的實例,使用模塊的方式大都也是手寫一個script標簽引一個js文件,然後再頁面中添加啟動入口。那麼當多人開發同一個頁面中的不同功能的時候就會這樣做:
首先,a在頁面中實現了一個功能,大致是這個樣子:
<script src="base.js" /><!--庫的種子-->
<script src="a.js" />
<script>
a.start();
</script>
b也實現了另外一個功能,大致是這個樣子:
<script src="base.js" /><!--庫的種子-->
<script src="b.js" />
<script>
b.start();
</script>
b在coding的時候多數情況不會去review頁面代碼,或者說review了頁面的爛代碼也看不懂,再或者說兩個人並行開發,並不知曉對方的實現。這樣就會導致base.js的引用重復。
另外一種情況是,如果一個例子比較復雜,需要引用多個js文件,比如:
<script src="base.js" /><!--庫的種子-->
<script src="a.js" />
<script src="b.js" />
<script>
m.start();
</script>
那麼a.js和b.js的引用順序則需要特別注意,但在實際開發中,並不能保證每個人都能細心的將js文件的順序排好,如果排亂了順序則需要比較多的調試成本。所以由此看出,純粹基於“庫”模式的開發會帶來很多隱患,這裡只提到了開發過程中常遇到的問題,另外,這種手寫js標簽引用js文件的方式也是缺乏語義並犧牲了性能(每個script src占用一個http請求?),同時,inline script block散步在頁面的各個角落,盡管每個script block都被匿名函數閉包起來,開發速度很快,但這種代碼毫無結構性可言,對於其他人也是晦澀難懂,等到項目中後期的維護成本會很高,所以說換作誰都不願意去接手維護一個如此coding的項目。因此,可以認為這種coding方式所導致的代碼污染會大大降低生產率(這裡的生產率包括開發和維護兩部分)和頁面性能。
而yui3強制使用Ylaoder,從結構上一定程度的保證代碼質量和可讀性。按照yui3的思路,項目的前期應當花大精力去做模塊的依賴,在此基礎上開發出豐富的widget,使用過程中指需要load模塊名字,而不必去關系模塊需要依賴那些js,以及這些js的先後順序是什麼。另外,通過簡單hack,loader可以將yui3以及所有項目js都combo在一起(taobao assetcdn現在不提供這種功能,不過很快就會有了),在多的模塊都合並到一個js http請求中,外加一個種子,一個頁面的js http請求頂多只有兩個。
那麼是否可以說,象taobao這種復雜多變(需求變更頻繁)的大網站就可以用yui3來架構,以避免深度的代碼污染了呢?非也,yui3在設計上並不能滿足類似taobao的這種門戶。上文提到了網站的四個組成部分,全局、外框、主內容區和組件,他們之間的關系應當是什麼樣的呢?顯然,頁面rander的時候,應當首先加載全局變量,然後初始化load頁面所屬於的子站的公共頭和公共尾,之後再執行頁面邏輯,因此網站三個層次的邏輯關系很明確,所以三者之間必然會發生耦合,框架則需要將這三個層次之間的耦合降到最低,因為,實際情況可能是由很多人同時開發框架功能、模塊、頁面主邏輯和底層庫的維護,因此就需要每個部分的負責團隊的代碼相互隔離,以保證並行開發中不發生沖突,鑒於此,純粹基於yui3的代碼結構有三種:
1,通過YUI().use()的嵌套將層次之間的邏輯關系表達清楚,比如,a的代碼屬於框架,b的代碼屬於應用,c的代碼屬於子應用:
YUI().use(*,function(){
//a的代碼
YUI().use(*,function(){
//b的代碼
YUI().use(*,function(){
//c的代碼
});
});
});
三個閉包的嵌套,這段代碼層次感比較清晰,把a的代碼看作框架,b的代碼看作應用,c的代碼看作子應用,調用關系一目了然,即框架執行完畢後加載並執行應用的代碼,應用的代碼加載並執行完後執行子應用的代碼。
但三段代碼非常緊密的耦合在一塊,在團隊並行開發中將會經常發生代碼沖突。如果代碼一次編寫不再修改則這種寫法並無大礙,往往代碼需要經過反復修改,因此這種顯示依賴的編碼風格無法避免代碼污染的發生。而且最可能b部分的代碼是污染最嚴重的,因為應用可能包含多個子應用,比如產品的列表頁御覽頁和詳情頁可能都包含相同的右側廣告位,這樣b的代碼中就可能包含有平級的多個YUI().use()。
另外一點,每個YUI().use相當於一個loader,每次loader的加載都會將本閉包內需要的模塊所對應的js文件加載進頁面中,這樣如果三次loader的加載有重復的情況則無法去重,造成的不必要的http請求將會增加一倍以上,嚴重影響性能。
2,一個閉包,各自入口
YUI().use(/*a,b,c的modules*/,function(){
a.start();
b.start();
c.start();
});
這種寫法比較精煉,調用順序一目了然,同樣,若代碼一次編寫不再修改則這是一種不錯的寫法,考慮到代碼的頻繁修改,這種方法仍有三個缺點,1,a,b,c都各自需要引用各自的modules,各自的模塊是作為參數寫在use的非倒數第一個參數中的,誰也不清楚哪些是a的模塊,哪些是b的模塊,哪些是c的模塊,這種歧異代碼是多人協作項目中必須要避免的。2,a,b,c的代碼之間看不出依賴關系,而實際上,框架、應用和子應用之間的調用應當是按順序,而在代碼維護過程中,a,b,c的調用順序是可能被修改的。3,三個人寫的代碼仍然在一起,不敢保證每次修改都會將模塊封裝致頁面中,一旦有人開了先例圖省事將模塊代碼直接寫在了這個閉包中,以後的修改將會一發不可收拾,導致每次需求變更都會更加污染代碼。這種情況在雅虎關系的開發中頻繁發生,並最終導致代碼極其臃腫,後期即便開發一個類似tabnew的小功能都要耗費半個月時間。
3,各自閉包,互不干擾
YUI.use(*,function(Y){
//a的代碼
});
YUI.use(*,function(Y){
//b的代碼
});
YUI.use(*,function(Y){
//c的代碼
});
三個人寫的代碼完全分開,不存在相互干擾的問題,再者,即便某個人的代碼寫的亂七八糟,也不會影響其它人的代碼的健康。但缺點有2個,1,象上一節說的代碼實際上有依賴關系,這裡看不出依賴關系,2,同樣的性能問題。
因此如上所述諸多因為框架約束不足帶來的代碼污染和維護成本增高的現象,應當通過更加強壯的框架來解決。目前正在開發中的cubee框架將會很大程度解決上述問題的發生。
嗯?什麼是cubee,cubee有什麼用?首先可以確定,cubee是框架,非庫,初衷是解決的是團隊項目中的代碼污染和降低高維護成本,關於cubee的設計詳情以後再分享吧。