生鮮 B2B 技術平臺的前端團隊該如何搭建

前端早早聊發表於2019-03-26

此文寫於 1 年前,轉載至此,大家可以加 Scott 微信: codingdream 成為朋友圈的朋友,聊南聊北,哈哈哈。


![圖片描述][1] 線下越重,線上需要越輕,這個輕指的是輕便輕巧和簡潔易用,通過前面幾章小菜技術與產品歷史介紹,我們瞭解到 B2B 生鮮領域線上下是如此之重,那麼在交易場景線上化的過程中,端的移動化就勢在必行,試想一下,讓菜市場攤位老闆人手一臺筆記本點開網頁選購支付,讓採購銷售抱著電腦去拜訪客戶,一邊聊蔬菜行情,一邊開啟筆記本進行記錄,有沒有一種回到世紀初的感覺。

產品的移動化,這將是我們展開這篇文章的背景,我們會先了解小菜的產品託管在哪些端上,然後感受這些端帶來的挑戰,最後是我們面對這些挑戰所採取的策略,以及整個小菜前端團隊歷練後的技術成長和沉澱,和我們對於自己的一個評估和對未來的展望,本文將採用最通俗易懂的方式陳述,會略有繁瑣,但力求對技術新人也足夠友好。

一、小菜大前端的端有哪些

小菜早期圍繞著蔬菜銷地以客戶集單批發的模式摸爬滾打幾年,從上游的蔬菜供應商到下游批發市場的攤位老闆,在這個長長的鏈路中,我們誕生了這樣幾款線上產品來服務於不同的人群和場景,之前文章中也有介紹,這裡再彙總一下,共 7 款 App:

  • 宋小菜 服務於銷地批發老闆的下單工具
  • 宋小福 服務於小菜內部銷售團隊的 CRM 銷售管理與客戶管理工具
  • 宋小倉 連線司機-物流-採購-銷售的蔬菜在途位置監控工具
  • 採祕 服務於小菜內部採購團隊的蔬菜品類採購工具
  • 麥大蔬 服務於上游蔬菜供應商的大宗農產品交易平臺
  • 宋大倉 服務於上游囤貨配資的進出庫管理平臺
  • 行情寶 服務於產銷兩地的行情采集和預測工具

前 6 款 App 都是基於 ReactNative 開發的 iOS/Android App,最後一個是微信小程式,它們涵蓋了公司幾乎所有的協同場景和工作流,其他涉及稽核、資料觀測和過程管理的部分,則會進入到我們 PC 端產品中,也就是:

  • ERP 後臺管理系統

生鮮的 toB 場景,角色眾多,鏈路冗長,這種延伸到產地農民,延伸到小 B 交易的管理系統一定會角色雜,許可權多,操作重,業務複雜度所帶來的頁面複雜度不是一般的小系統可比擬。

到目前為止,我們已經看到小菜的 7 個移動端 App,以及一個複雜的後臺管理系統,這些都跟前端工程師息息相關,除了這些,還有 2 個重要的內部產品,就是:

  • 大表哥 資料包表系統
  • 大瓜子 市調模板配置系統

其中大表哥(諧音:搭 Excel 表格)由前端工程師獨立研發和維護的資料包表系統,單拎出來這個系統,是因為在 B2B 公司,尤其涉及到供應鏈的長鏈路場景中,真實業務資料的及時反饋對於每一個執行團隊都至關重要,沒有這些資料抓手,就失去了多維度資料觀測,都很難快速的做出正確的運營決策和業務調整,甚至很難發現業務中出現的漏洞和問題,比如不正常的非自助下單(也就是銷售幫忙下單)的比例。

關於報表系統後文還有介紹,我們再為前端增加一個服務的產品場景,就是微信生態內產品,比如公眾號或者小程式,它的技術棧和執行環境跟原生 App 和 PC 都不同,雖然小程式可以帶來更多的業務可能性,也會對前端帶來更大的挑戰。

我們把這些端合併一下,小菜前端要服務的端或場景:

  • 移動端(iOS/Android App/小程式)
  • PC 端(ERP)
  • 工具端(大表哥資料包表)

端上全部開花,這也應了我之前在掘金 JTalk 上小菜對於長鏈路流通交易分享的一個觀點:鏈路足夠長,每個節點上都可以長出產品。那這些端產品都是與業務有強關聯的,還有更多技術基建的和服務於團隊內的產品,比如:

  • 大伯伯(諧音打包包) 實現 App 選倉庫選分支選環境配置的自主打包與推包系統
  • 大表姐(來自飢餓遊戲,寓意開工沒有回頭箭) 實現 6 款 App 解包差分後下發熱更新包的釋出系統
  • 姑奶奶 線上異常彙集分析與與 Bug 定級指派系統
  • 大舅子 向下呼叫微服務介面向上提供 GraphQL 查詢能力的資料聚合服務
  • RGB 使用者使用 App 的 PV/UV,以及業務資料監控相關的視覺化平臺
  • 110 解決端異常收集與報警需求
  • 堂哥工作臺 團隊記錄資源分配與 redmine 同步的自動化週報系統
  • ITms 解決內部 App 安裝測試的配置生成和預裝服務
  • ...

這些是服務於團隊內部的工具鏈,全部由小菜前端自行維護。到這裡我們發現,在小菜這樣一家創業公司內,前端要服務的端和場景的確較多,但這些產品和工具的背後,整個前端組也就 10 個人而已(我們當然也求才若渴),但是人雖少,效率不能自我妥協,所以我們能服務到這些端,也正是基於端的多樣性和數量,我們稱自己:宋小菜大前端。

先上小菜端上若干產品和工具的技術棧圖,幫助大家理解我們的技術理念:

image.png | left | 747x545

二、多端帶來的挑戰

1. 【物理現狀】移動端的碎片化

古典網際網路時代,因為要相容 IE678 而痛苦不堪,Hack 黑魔法經驗基本代表前端水平,如今網際網路早已移動化,我們理想中的移動端開發,看上去是可以大膽使用新語法特性,只需要做好尺寸相容就好了,但事實並非如此,不僅在移動端的瀏覽器不是如此,在移動端開發 RN App 也是如此,這是我們某一款 App 一段時間內,所收集上來的手機廠商分佈:

image.png | left | 747x493

可以發現 Android 的碎片化非常嚴重,每一個廠商下面有不同時期推出的不同型號的手機,這些手機有著不同版本的作業系統,不同的解析度和用電策略,不同的後臺程式管理方式和使用者許可權,要讓一款 App 在哪怕頭部 40% 的手機上相容,都是一件艱難的事情,這個客觀物理現狀疊加下面的社群現狀,App 質量保證這件事情會變得雪上加霜。

2. 【社群現狀】技術框架的不穩定性

回到本文的開頭,我們在長鏈路的 B2B 生鮮場景中,為了更快更輕,開發出 7 款 App,而且將來隨著業務場景的擴充會誕生更多獨立 App 甚至是集大成的 App,所以技術選型不太可能選擇原生的 Java/Object-C 開發,尤其對於創業公司,6 款 App 得需要多少名原生開發工程師才能搞定,高頻繁重的業務變化又怎樣靠堆人來保證?

想清楚這些,一開始我們就調研 ReactNative,並最終全部從原生切換到了 RN,通過跑過來的這 3 年來看,使用 RN 為公司節約了大量的人力成本同時,也儘可能的滿足到幾乎所有的需要快速迭代的業務場景,又快又輕,成為宋小菜大前端團隊做事的一個典型特徵。

但換一個角度看,就是帶來的問題,又快又輕的背後是 RN 版本的飛速迭代,截止到目前,也就是 2018 年 6 月份,RN 還沒有推出一個官方的正式的長期維護的穩定版本,什麼意思?就是 RN 目前依然處在不穩定的研發週期內,我們依然站在刀尖起舞,用不穩定的 RN 版本試圖開發穩定的應用,三年走過來,我們在 RN 的框架裡,多少次面對舊版本侷限性和新版本不穩定性都進退不得,舊版本的 Bug 可能會在新版本中修復,新版本引進則會來新版本自己的問題。

除了 RN 自身版本,還有第二個問題,圍繞著 RN 有很多業界優秀的元件,但這些社群元件甚至官方元件,都不一定能及時跟進最新的 RN 版本,同時還能相容到較老的 RN 版本,所以 RN 升級導致的元件不相容性,會引發你 Fork 修改元件的衝動,但這樣會帶來額外的開發成本和版本維護成本,取捨會成為版本升降的終極問題。

在國內開發,還有第三個問題,就是中文文件缺乏,社群資源匱乏,參考文獻陳舊,可拿來主義的開源工程方案甚至社群線上線下會議分享都很缺乏,一個不小心就會踩坑,這就是 RN 社群的現狀,我們在刀尖浪花上獨步,App 選型背後的技術棧穩定性則成為懸在頭上的一把鍘刀,你不知道什麼時候會咔嚓一聲。

3. 【人才現狀】人員能力的長短不齊

我們知道有一個詞叫做主觀能動性,表示沒有條件創造條件也可以上,這個詞的主體就是人,聊完移動端裝置現狀和社群現狀後,我們來聊聊人的問題。RN 在國內真正開始普及使用,是從 2015 年開始,也就意味著,到 2018 年,一個 RN 工程師也就只有 3 年的工作經驗,而 RN 的 “Learn once, write anywhere” 也刺激著一切 Care 人員開支, Care 產品研發投入價效比的公司紛紛跳水研究 RN,爭搶 RN 人才,RN 是前端中的移動前端,前端有多搶手,那麼 RN 工程師就比它還要搶手。

這導致基本上 RN 工程師,很難靠外部招聘,只能靠內部培養,這也是小菜前端的成長曆程,我們有 2 名資深 RN 工程師,一個是從服務端 Java,一個是從原生 Android 開發轉過來的。如果 RN 人手不足,產品支援的力度和速度就一定會遇到瓶頸,這就是我們曾經面臨的問題,就是人才現狀,外招數量不足,內培速度有限,RN 工程師的數量和能力就時不時成為公司業務擴張的瓶頸。

4. 【公司現狀】高密集業務的交付質量

作為工程師,我們有很強的自尊心和不容挑戰的程式碼潔癖,但在一個創業公司裡面,甚至大公司的一個創業團隊裡面,我們需要對接一些關鍵的業務節點,衝刺一些特定的時間視窗,並且要及時響應多變的業務,和業務背後多變的產品形態,這都會帶來非常密集的需求佇列。

這些密集的需求佇列對我們的程式碼實現質量有非常高的挑戰,一個元件用 5 分鐘思考如何抽象和用 50 分鐘思考,實現後的穩定性、相容性都是不同的,如何保證產品按期交付上線,會是擺在我們面前一個非常關鍵的命題,而這個難題之外,還有一個更難的命題等著我們,那就是如何保證交付不延期的同時,還能保證交付質量。

要知道,如果一個專案程式碼趕的太毛糙,後期維護起來的成本會是巨大的,甚至只能用更高的成本重構重寫。本質上,再次重構就一定是公司在為早期的猛衝買單,為這些技術債買單,如何不去買單或者如何用最小的成本買單,這跟我們早期的業務密集程度,交付週期,質量把控有很大的關係。

綜上,移動端碎片化所帶來的相容難度,RN 框架的侷限性,版本間差異帶來的不穩定性,技術社群資源的匱乏和前端團隊技術能力掣肘,再疊加上高密度的業務排期,讓前端開發這個本來很酷的事情,變得晴雨不定。

這些避不開的現實,是繞不過去的坎兒,是搭建團隊必須搞定的基礎,我們想要把 B2B 生鮮的線上線下場景通過端產品關聯起來,想要通過前端團隊的使用者側輸出從而讓這些產品落地,就必須面對這些現實挑戰,而應對這些挑戰,首先必須搞清楚有哪些挑戰,搞清楚挑戰以後,我們就會認識到,首當其衝的事情,是去搭建 B2B 生鮮公司的前端技術棧和人才梯隊,現在我們進入到本文的重點。

三、如何應對井噴的挑戰

1. 前端梯隊如何搭建

創業公司的技術團隊,本質上就是人和事,用合適的人搞定特定的事,人才的瓶頸就是這家公司產品落地速度和上線質量的瓶頸,因此人是第一位的,對於前端團隊來說,如何一步步形成有綜合戰鬥力的團隊,取決於搭建什麼層次的前端梯隊,如果所有人一視同仁,培養同樣的能力棧,發揮同樣的興趣向,跟進同樣的業務線,那麼這個梯隊的扁平就會帶來致命的團隊瓶頸:能力可複製但不能互補,能力可遞進但很難跨越,不能互補和很難跨越會導致團隊內的技術路線過於單一,技術思維趨於固化,至於技術儲備的豐富性和技術溝通帶來的碰撞就更有限,最終導致人做事越來越機械化,甚至失去最初的技術初心。

那麼小菜前端到底如何搭建,還是要從公司的人員、業務和技術現狀出發,由於端的碎片化和技術框架的不穩定性,就必須在質量保障上投入巨大的人力保證產品可用,而人才能力侷限性和數量的匱乏,就跟產品的質量保證成為了天然的矛盾,不可協調,程式碼攛太快,線上天天都是 Bug,程式碼攛太慢,產品節奏跟不上,至於跟工程師天天宣講要小心小心再小心,能起到的作用也不大,因為工程師本身的能力也是參差不齊的,所以就必須把團隊先拆成兩部分,一部分做基建支援,一部分做業務支援,基建支援的同學研發整個團隊的工具腳手架、抽象和打磨團隊的基礎通用元件、長期維護專案的通用架構,這些投入都會反哺到業務支援的同學,業務的同學可以放心大膽的基於基建的成果做上層業務開發,穩定的工程基礎有了保障,上層的業務程式碼做質量保障難度就大大降低了。

除了分出來人做基建,做業務,還需要有核心的技術骨幹,做技術前瞻性的研究,為團隊 3 個月後,半年後,甚至 1 年後的技術方向,做必要的調研、測試和實驗性開發,因為對於刀耕火種的早期技術團隊,從原始人到邁向外太空跨空間作戰,這中間還差著很多個關鍵的技術迭代節點,這些關鍵的技術迭代節點,一部分是靠外招技術專家和資深的工程師來輸血發力,還有一大部分是需要靠團隊內部長期的積累沉澱,也就是人才內部培養。

我們總結一下:

  • 基建的同學負責輸出工具系統、基礎元件、流程規範,保證內部效率最大化和質量的有效保障
  • 架構的同學負責攻克技術底層難點,調研先進技術,升級團隊技術架構,沉澱技術方案,鎖定和推進團隊未來技術方向
  • 業務的同學負責產品跟進,高頻使用基建產品,並通過反饋來優化團隊的技術基礎設施,同時基於業務來抽象更多的基建需求

基建、架構、業務這三個角色並不是相互獨立,而是互有重合各有側重,一個業務的同學,可能也同時在負責基建的事情,一個基建的同學,可能也同時在參與架構的設計,在小菜就有同學以架構和基建為主,業務也時不時的參與開發,架構和基建必須依託於業務場景來做,不能脫離了場景,不然會輸出畸形的難以落地的技術方案。

上面是人員的分工,還有三個重要的保障,這裡不做引申,只列舉一下:

  • 團隊人員的興趣棧、能力棧和業務要儘量匹配
  • 團隊人員的階段性目標、長期規劃要跟進公司的職業晉升路線和能力模型
  • 團隊要有持續性的內部技術互動分享和對外的技術理念、方法方案分享

小菜的前端是大前端,對人的要求是:一專多精多能,至少在某個領域內朝著專家方向走,同時要慢慢精通多項技能,最後是具備多個特定技術棧的開發能力,比如 ReactNative,在小菜就是一個必須具備的開發能力,不要求每一個同學都成為 RN 專家或者精通,但要具備業務開發的能力,通俗點描述,就是能用 RN 開發業務產品。

最後一點,就是資源流轉,架構的同學,基建的同學和業務的同學的梯次關係是從下到上,越下越接近技術本質,越上越接近業務結果,越向下需要越好的技術實力,越向上需要越好的業務理解能力,這兩個能力都是核心能力,需要讓團隊成員沿著梯隊關係慢慢流動起來,業務中技術能力好的同學可以有機會沉下來做做基建,長期埋頭基建的同學可以有機會上去做做業務,業務理解不錯技術沉澱又好的同學可以繼續沉下去參與架構,這樣團隊內部的同學都可以有多樣性的技術場景和業務場景,一旦有同學請假、陷在別的業務不能抽調,馬上就有同學可以補位進來開發,不會影響到產品上線節點。

關於團隊如何搭建,目前小菜是走到了這個算是 v1.0 的階段,未來還有更多挑戰,也會帶來更多的基於公司現狀的新調整,無論如何變遷,方法論我們先沉下來:

  • 人才梯隊要有層次:基礎架構、基建和業務上層等
  • 人才成長要有規劃:興趣棧、能力棧和公司關係
  • 人才能力要有擴充套件:單人能力和互補後的團隊能力

以人為過程,以事為結果,人事之間要有動態的機制形成互惠互補的關係,只有這樣,團隊才會初心不變,激情常在。

2. 如何做技術選型

技術選型是一個行業老話題了,方法論也有很多,在小菜我們遵循的是:技術方向性預研大踏步,業務基建型開發小碎步,前者儘可能激進,後者儘可能保守,比如 資料包表系統,我們激進的採用 GraphQL 來解決 SQL => 頁面 Dom 的鏈路問題,在宋小福 App 上面,我們就求穩的採用 v0.48 的 ReactNative 版本,而不是用當時較新的 v50+ 版本。

在做技術選型之前,還有一些比較重要的基礎性問題需要搞定,那就是團隊技術動作的一致性,這個一致主要包含兩點:

  • 程式碼規範共用一套
  • 倉庫合作方式共用一套

這兩點如果不一致,會給技術選型後的落地帶來內耗成本,千萬不可大意。

再回到技術選型本身,拋開激進保守的大踏步和小碎步,我們需要回到技術本質和工程師的本質來看待如何選的命題,技術的本質是效率,工程師的本質是興趣,如果這一套技術選型不能帶來效率,如果工程師普遍不感興趣,那麼通常這一個選型我們不會採納,我覺得這一個主觀一些的標準,大家可以參考,但這裡面也要權衡好歷史包袱、維護成本,上手難度等這些客觀現實,如果一個新技術會帶來革命性的效率提升,那麼即使有上手難度和維護成本,我們也會果斷入坑,比如 GraphQL 對於資料包表對於解放前後端有大幅度的提升,我們會果斷入坑大力推行,如果一個技術對於團隊是錦上添花,那麼我們會慎重選用,比如 TypeScript,可以給工程穩定性帶來了較大的保障,但我們只選擇在熱更新這種 RN SDK 和 Server 端的去整合,而不是一下子推廣到整個團隊專案中鋪開用,這裡面就會考慮到實際得到的好處,以及歷史包袱和上手難度,反覆權衡後並沒有帶來更大的價值,所以這兩類場景的推行和不大力推行,就又不會太依賴於工程師的喜好興趣。

那麼我們技術選型後的結果是如何呢?

文章最開始的那張圖,裡面就是我們的技術棧,這裡再做一下總結:

  • 工具類:強依賴 [Node][2],多而雜的其他技術,如:[MongoDB/Redis][3]/MySQL/Shell/Python
  • 業務類:強依賴 [React/ReactNative][4],適度整合其他技術,如: Redux/GraphQL/Apollo
  • 框架類:除了 [React 全家桶][5]會謹慎選擇,[Node 端框架][6]則相對寬鬆:[Koa][7]/Thinkjs/Eggjs

這些相對求穩,不求穩的部分,如小程式開發,我們會使用 mpvue,也會用原生,還會整合進去 GraphQL,同時一些涉及到資料爬取和視訊影象識別,我們也會整合 Python/C++/TenserFLow 等等,不過這些往往是前瞻性的技術嘗試,會讓團隊的同學適當分配精力持續研究。

3. RN 的 App 工程如何架構

小菜的主要產品型別,尤其是對外的產品,主要是 RN App,而且數量較多,那麼 RN 專案的合理架構就變得尤其重要,我們這裡探討下小菜前端在 RN App 上面的沉澱,涉及到原生層面的技術細節太多,這裡暫不做討論。

首先,我們在構建 RN App 工程時需要關注這幾個關鍵要素:

  • 配置管理
  • 靜態檔案管理
  • 網路請求
  • 元件管理
  • 路由管理
  • 資料快取
  • App 的熱更新
  • 資料蒐集

__配置管理__是指可以靈活合理的管理 App 的內部環境,主要包括:

  • App 本身的一些配置
  • 所使用三方外掛的配置

我們在構建工程時儘量將所有的配置抽象統一放置在一個地方,這樣便於查詢和修改,但是由於大多數配置都統一放在同一個地方,那麼就難免有部分檔案要使用某個配置時其引用路徑比較長,比如:

import { pluginAConfig } from '../../../../../config'
複製程式碼

這樣就造成了閱讀性很差且程式碼不美觀,因此我們可以使用 Facebook 的 fbjs 模組提供的一個功能providesModule :

//config.js
/**
 * config for all
 * @providesModule config 
 * 使用 providesModule 將 config 暴露出去
 **/
import pluginAConfig from './plugin_a_config'

export default {
    pluginAConfig
}

// 然後在其他檔案中呼叫
// A.js
import { pluginAConfig } from 'config'
複製程式碼

這樣就能很方便地在 App 的任意一處使用 config 了,但是我們要避免濫用 providesMoudle ,因為使用了 providesMoudle 進行宣告的模組的原始碼,想要在編輯器中使用跳轉到定義的方式去檢視比較困難,不利於團隊多人合作。

__靜態資源__泛指會被多次呼叫的圖片或 icon,我們一般在 RN 使用圖片時是直接引用的:

import { Image } from 'react-native'

render(){
  return (
    <Image source={{uri: './logo.png'}} />
  )
}
複製程式碼

當圖片需要在多處使用時,我們可能會將這些可能會被反覆使用的圖片統一管理到 assets 資料夾中,統一管理和使用,但是當需要使用圖片資源的檔案巢狀較深時,引用圖片就變得麻煩:

render(){
  return (
    <Image source={{uri: '../../../../assets/logo.png'}} />
  )
}
複製程式碼

這個問題與配置管理的問題一樣,可以首先將圖片資源按照型別進行分類,比如 assets 資料夾下有 button/icon/img/splash/svg 等,每一個型別的結構如下:

- icon/
 - asset/
 - index.js
複製程式碼

其中 asset 資料夾儲存我們的圖片資源,在 index.js 中對圖片進行引用並暴露為模組:

// index.js
export default {
   IconAlarmClockOrange: require('./asset/icon_alarm_clock_orange.png'),
   IconAvatarBlue: require('./asset/icon_avatar_blue.png'),
   IconArrowLeftBlue: require('./asset/icon_arrow_left_blue.png'),
   IconArrowUpGreen: require('./asset/icon_arrow_up_green.png')
}
複製程式碼

然後再在 assets 資料夾下編輯 index.js ,將所有的圖片資源作為 assets 模組暴露出去,為了避免和其他模組衝突你可以修改模組名為 xxAssets

// assets/index.js
/**
 * @providesModule myAssets
 **/
 import Splash from './splash'
 import Icon from './icon'
 import Img from './img'
 import Btn from './button'
 import Svg from './svg'

 export {
   Splash,
   Icon,
   Img,
   Btn,
   Svg
 }

// A.js
import { Icon } from 'myAssets'

render(){
  return (
    <Image source={Icon.IconAlarmClockOrange} />
  )
}
複製程式碼

這樣,我們就能很方便地將分散在專案各處的圖片資源統一到一個地方進行管理了,使用起來也非常方便。

__網路請求__這塊,react-native 使用 whatwg-fetch,我們也可以選在其他的三方包如 axios 來做網路請求,但有我們在開發中遇到過一個問題,那就是我們明明已經在程式碼裡已經修改了 cookie, 但是每次請求可能還是會帶上之前的 cookie 從而造成一些困擾,所以這裡推薦一個實用的元件 Networking :

import { NativeModules } from 'react-native'
const { Networking } = NativeModules

// 手動清除已快取 Cookie,這樣就能解決上述的問題了
Networking.clearCookies(callBack)

複製程式碼

當然,Networking 的功能不止於此,還有很多其他有趣的功能可以發掘,可以直接用它來包裝自己的網路請求工具,還支援 abort ,可以參考 原始碼 來具體把玩。

使用 RN 開發 App 本身效率就比較高,如果想要繼續進階就要考慮元件化開發,一旦涉及到元件化開發,就不可避免地會涉及到元件管理的問題,這裡的__元件管理__比較寬泛,它實際上應該指的是:

  • 元件規範
  • 元件型別劃分
  • 元件開發標準

元件規範指的是 UI 設計規範,我們可以與設計同學交流規定好一套特定的規範,然後將通用的樣式屬性(如主題顏色,按鈕輪廓,返回按鍵,Tab 基礎樣式等)定義出來,便於所有的元件開發者在開發時使用,而不是開發者各自為政在開發時重複寫樣式檔案,這裡推薦一個比較好用的用於樣式定義的三方外掛 react-native-extended-stylesheet ,我們可以使用這個外掛定義我們的通用屬性:

// mystyle
import { PixelRatio, Dimensions } from 'react-native'
import EStyleSheet from 'react-native-extended-stylesheet'

const { width, height } = Dimensions.get('window')

const globals = {
  /** build color **/
  $Primary: '#aa66ff',
  $Secondary: '#77aa33',
  $slimLine: 1 / PixelRatio.get(),
  /** dimensions **/
  $windowWidth: width,
  $windowHeight: height
}

EStyleSheet.build(globals)

module.exports = {
  ...EStyleSheet,
  create: styleObject => EStyleSheet.create(styleObject),
  build: (obj) => {
    if (!obj) {
      return
    }
    EStyleSheet.build(_.assign(obj, globals))
  }
}

// view.js
import MyStyleSheet from 'mystyle'

const s = MyStyleSheet.create({
  container: {
    backgroundColor: '$Secondary',
    width: '$windowWidth'
  }
})

render....
複製程式碼

這樣,我們就能在開發的任意外掛或者 App 中直接使用這些基礎屬性,當某些屬性需要修改時只需要更新 mystyle 元件即可,還可以衍生出主題切換等功能,使得開發更加靈活。

關於元件型別我們會拋開三方元件以及原生元件,因為一旦涉及到這兩者,需要寫的東西就太多了,我們將元件按使用範圍分為通用元件和業務元件兩大類。

首先什麼是業務元件?即我們在開發某個業務產品常用到的元件,這個元件繫結了與業務相關的一些特殊屬性,除了這個業務開發以外,其他地方都不適用,但是在開發這個業務時多個頁面會頻繁地使用到,所以我們有必要將其抽象出來,方便使用。

什麼是通用元件?即可以在 App 範圍內使用甚至於跨 App 使用的元件,這裡可以對這個類別進行細分,我們將能跨 App 使用的元件上傳到了自己的搭建的私有 npm 倉庫,方便我們的 App 開發者使用,同時,具有 App 自己特色的元件則放到工程中統一管理,同樣適用 providesModules 暴露出去。

制定一整套元件開發標準的是很重要的,因為很多元件開發可能是多人維護的,有一套既定的規範就可以降低維護成本,元件使用的說明文件的完善也同樣重要。

開發 App 就不可避免地會遇到如何管理頁面以及處理頁面跳轉等問題,也就是__路由管理__問題,自從 Facebook 取消了 RN 本身自帶的 Navigator 以後,許多依賴於這個元件的開發者不得不將目光投向百花齊放的社群三方元件,FB 隨後推薦大家使用的是 react-community 推出的 react-navigation ,現在這個路由元件已經獨立出來了。我們在開發時就是使用的這個元件作為路由管理元件,只不過是在其基礎上做了一些定製 ,使得使用更加簡單,部分跳轉動作更加符合我們的產品場景,推薦大家使用這個元件。當然,除去這個元件還有很多其他的元件可供選擇:

路由管理作為整個 App 的骨架,它是這幾個部分中最重要的一部分,合理地定製和使用路由管理可以極大地簡化我們的開發複雜度。

一般情況下需要快取的資料基本上就可能是我們會在 App 很多地方都會使用到的全域性資料,如使用者資訊,App 設定(非應用層面的設定)等,RN 提供一個 AsyncStorage 儲存引擎,通常的使用方式是對這個資料引擎進行包裝後暴露出符合我們要求的讀寫介面。這裡推薦另外一種使用方式:

既然需要快取的資料可能是會在 App 很多地方使用到的全域性資料,那麼我們可以將這些全域性資料使用 redux 來進行管理,而利器 redux-persist 則能讓我們很優雅地讀寫我們的快取資料。

同時,如果對 react-navigation 進行合理的定製,接管其路由管理,那麼我們還能實現儲存使用者退出 App 之前最後瀏覽的頁面的狀態,使用者在下次開啟 App 依然可以從之前瀏覽的地方繼續使用 App,當然,這個功能要謹慎使用!

App 的版本更新,RN 除了傳統的 App 更新外還有一個熱更新的可選項(傳統 App 更新也有熱更新,其原理就不太一樣了),社群大多數人都推薦使用 codepush 來進行熱更新,至於其後端解決方案 貌似已經有了一個 code-push-server ,我們是使用自己的熱更新方案,其原理就是在不更新原生程式碼的基礎上更新 JS 程式碼和靜態資原始檔。

蒐集的 App 使用資料(包括異常資料)並對此分析,根據分析來定位問題是保證 App 質量的有效手段之一。你可以選擇自己搭建一套資料蒐集服務,包括客戶端 SDK 和服務端蒐集服務,或者選擇市場上已有的工具,目前較為成熟的收據蒐集工具比較多,如友盟,mixpanel, countly 等等,在此不作贅述。

總結一下,一個 RN App 架構應該要保證 App 的執行穩定以及開發的便捷。執行穩定這一方面,除了從 JS 層面(如單元測試,JS 錯誤上報等)保證之外,很大程度上還要依賴於原生層面的處理,所以團隊裡面要有同學的精力可以投在原生研究上面,至於開發便捷,我們儘量將複雜重要或者簡單繁瑣的操作在構建工程時就做掉,這樣也可以大幅度提高我們的開發效率,降低開發者之間的合作溝通成本。

4. 效率協同工具如何打造

效率協同往往不分家,效率寬泛一點,就是又快又好,協同寬泛一點,就是順滑無內耗,而且效率協同在不同的場景下,一定有不同的表現,所以效率協同一定要具體到某一個場景才有意義,比如:

我們要釋出 6 款 RN App 中的若干款,在一週內的若干天釋出,由若干人自行打包測試自行釋出,那麼這裡面就有巨大的協同問題,同時還有一些效率問題,如果一個同學進來改了 3 行介面呼叫程式碼,他至少要有這幾個階段:

  • 開發階段本機切新分支除錯
  • 測試階段打一個連線測試環境的包測試有效性
  • 測試完再打一個連線正式環境的本地包測正確性
  • 最後再打一個連線正式環境的用來熱更新的包進行釋出

那麼多人之間都各自來做這個釋出,就會出現一些釋出衝突的協同問題,如果把釋出許可權全回收到某一個人,協同貌似能解決,但是會帶來效率問題,大家要讓這個釋出人頻繁打包,或者打好的包,反覆傳給釋出人,釋出人的時間線就被他人的開發程式給打斷了,變成了一個打包員,關於這個我專門做了一張圖:

image.png | left | 747x523

這裡面的一個圓點,就代表一個編譯後的包,比如 A 打出來的不需要 Debug 的連線正式環境的需要熱更新的 iOS 的 ipa 包,那麼 A 的這個包,跟 B 打出來的不需要 Debug 的連線正式環境的需要熱更新的 iOS 的 ipa 包,即便是在同一個倉庫的同一個分支,也不能保證 100% 一模一樣的包,原因在於,這些本地打的包,還會受到 Node/NPM 版本(語義化),XCode 版本,原生熱更新版本控制等等因素影響,導致這個包自身很容易出問題,甚至是一些人肉引發的分支和人工上傳等等的影響,也會導致這個包釋出出問題,舉一個我們真實發生過的故障,A 打完包,把包檔案釘釘傳給 B,B 在釋出的時候,選擇本地檔案時候選錯了一個老版本直接釋出上線,導致線上部分使用者直接版本回退,我們後來不得已採用緊急回滾,才把影響範圍控制住。

上面大篇幅的介紹打包的這個場景,是小菜前端早期非常痛苦的一個場景,協同方式和規範無論我們如何三令五申總是避免不了人肉的問題,一旦出問題,就是大問題大故障,那麼這時候,就必須投入基建的力量來打造一款或者一套流程工具,通過工具一勞永逸的解決這些主要的協同問題,把瑣碎人肉的事情交給機器去做,機器比人做的快,也比人做的好。

我們來總結一下,團隊跑一段時間,一定會擠壓一些問題,這些問題不可以視而不見,也不可以拿業務支援第一這樣的藉口來無限期推遲解決問題,而是時不時評估一下,有沒有可能通過系統和工具,來約束一些行為,來取代一些人肉工作,進而可以一勞永逸的解決掉一些問題,一旦決定去解決了,那麼如何打造協同工具就變得順水推舟了,因為工程師最擅長的乾的事情之一,就是造輪子造工具。

小菜前端造了哪些輪子哪些工具呢,文章最開始就已經列出來了,這裡再陳述和解釋一下:

  • 大伯伯(諧音打包包) 解決人肉打包帶來的協同問題
  • 大表姐(代號 Laurence - 來自飢餓遊戲) 解決人肉釋出、人工維護版本,問題追溯定位的效率問題
  • 姑奶奶 解決去多個三方平臺檢視異常日誌和 Bug 反饋指派的人肉效率問題
  • 大舅子 解決前後端耦合在 Restful/Mock/冗餘的介面 的合作效率問題
  • RGB 解決純數字報表分析頁面訪問資料和使用者行為的效率問題
  • 堂哥工作臺 解決組員與 Leader 日報週報難回憶帶來的書寫效率和後期回顧問題
  • 小菜圖書館 解決小菜書架上面借書還書靠人肉記錄維護的效率問題
  • ...

其中的大舅子這個單獨拿出來說一下,現在前後端常見的合作方式是基於 restful API 的介面合作,前後端經過一輪介面評審,後端再為前端寫 Mock 資料,可能還會加上一個 Proxy 服務,最終前端本地的頁面上,做 Mock 環境、測試環境和正式環境的切換,這種方式最大的問題有 2 個:

  • 前端比較依賴於後端的介面定義,後端為前端 Mock 做完後,前端才方便的進行頁面中的資料替換和邏輯判斷,有等待成本
  • 前端複雜多變的頁面會影響到後端的介面複雜度和體積,頁面上的欄位增減,都會反映到介面的欄位增減,介面本身變得不穩定,會帶來很多隱患點,比如介面體積越來越臃腫,或者介面有多個版本,一旦介面文件更新沒跟上,會導致後期的同一個介面的不同版本之間,增加呼叫出錯概率等等

當然業界也有各種各樣規避這些問題的策略,可能是文件建設,可能是流程約束,小菜早期,哪怕到現在,也是在使用這種方式合作的,直到現在我們有了大舅子,前後端合作的方式開始進化,大舅子系統架構圖如下:

image.png | left | 747x429

大舅子目前的架構是放到閘道器下面,閘道器層做一些鑑權和安全的處理,向下把一個 GraphQL 的請求轉發給大舅子,大舅子上面根據這個 Query Type 對應的 Resolver 去呼叫下層的服務介面,下層可能是另外一個 GraphQL 服務,也可能是微服務,也可能是資料庫,相容度很高,無論是哪一種,大舅子的角色扮演就是配置和聚合:配置客戶端上頁面對應的資料型別,巢狀關係和資料結構,向下連線和聚合不同的資料來源。

內部的開發正式環境關係圖如下:

image.png | left | 747x636

這個事情並不新鮮,多年前,[Nodejs][8] 就在扮演資料聚合層的角色,把多個 API 聚合成一個 API,或者打散一些 API,聚合成新的 API,但本質上依然是向客戶端提供 API,這種 API 依然是面向頁面,可以看做是頁面驅動的 API,大舅子因為整個建模基礎是 GraphQL,所以頁面和資料結合的權利,交給了客戶端自己去做,它需要什麼資料,就在客戶端宣告什麼資料結構,帶來的好處很多,這裡列舉兩條我認為有價值的:

  • 前端可以不再侷限於介面評審階段,可以繼續往前提到資料庫/表結構評審階段進入開發流程,在表結構評審期就能拿到欄位定義與含義,從而再大舅子上提前定義前端自己的 Type 和 Resolver
  • 後端不再耽誤自己的時間為前端提供 Mock,也不再受頁面 API 的約束,可以下沉精力專心做下層業務領域能努力的建設,下沉的領域能力多大,那麼端上可使用和組合的能力就有多大

從此,塵歸塵,土歸土,前後因為頁面資料控制權的分離而解耦,也因為資料能力的回收而同時貼近業務,前端也被倒逼去了解業務,不再僅僅是介面和產品互動驅動,現在大舅子還在早期的迭代階段,關於它的好處和優化的空間還非常非常大,今天不做深入討論,我們來總結一下:

小菜前端已經從工具基建中受益,因為工具帶來了協同和效率的優化只是結果之一,最重要的收穫還有兩個:

  • 解放了小菜前端團隊以及技術團隊的人肉時間,可以有精力做其他事情
  • 通過基建工具研發培養了小菜前端分析問題和解決問題的能力,同時沉澱了一些不錯的技術方案

那麼小菜的成長和沉澱,我們接下來就可以來總結一下了。

四、技術成長和沉澱

技術成長就是工程師的能力變化,我在 4 月份給大家做了一個 10 個月前後的能力評估,這 10 個月,是小菜前端 3 年來基建密度和團隊內調整最大的幾個月,也是團隊整體戰鬥力提升最大的幾個月,本文的所有分析、策略和實際的解決辦法,也都是在這幾個月裡面進行實施的,挑了幾個同學,挑了幾個主要的能力維度,我們感受下他們的技術成長,白色的 * 代表 10 個月之前的能力值,2 顆星代表可以熟練的開發,三顆星代表基本精通或擅長,四顆星是比較精通。

image.png | left | 747x343

可以看到每個人都有不同程度不同層面的成長,有的全面開花,有的某些領域內快速積累,也要同學技術成長不多,但是協作能力工程能力有很大提升,其實還少了一個維度,就是參與業務拿到的結果或者說業務能力,圖上放不下了,稍後會做分析和補充,我們再來看下這些同學做的事情:

image.png | left | 747x420

如果仔細比照一下,我們很容易得出三個結論:

  • 整個技術團隊綜合技術能力有大幅度的提升
  • 承擔職責越多的同學能力成長越多越快(如組員 A)
  • 承擔繁重基建和工具開發的同學比承擔業務開發的同學技術成長更多(如組員 E)

業務能力沒有放到圖上,這是要補充的第四個結論

  • 承擔業務越多的同學,專案管理能力/溝通能力/業務理解能力也越好

以上是人才成長,那麼沉澱下來的內容一共是三部分:

  • 通用的工具技術解決方案
  • 通用的技術模組和業務元件庫
  • 團隊整體的問題解決套路(分析解決問題的思維方式)

通用的技術解決方案可以不斷的快速複用,比如我們宋小福用新架構前後調整優化有 1 個月,把這同一套架構放到麥大蔬上 2 周就夠了,再次遷移到新專案宋大倉裡面只需要 1 周就搞定了。

通用的技術模組和業務元件庫,則是我們的元件三步走策略,首先是某個業務產品線下面的元件模板,比如 篩選元件或者列表元件,能在這個業務場景下的產品形態中通用,如果它可以跨產品線,那麼就會躍升為 App 內通用元件,如果它還能繼續抽象具有可重用性,那麼就可以躍升為跨 App 的通用業務元件,比如熱更新元件,地理位置定位元件,登入元件,異常提示彈窗元件等等。

團隊整體的問題解決套路,這個是我們最大的收穫,再直白一點,就是如何更快更好更有創造性的做事,這種思維方式,解決問題的套路,本質上是可以在團隊內不斷傳承的,無論我們後面遇到什麼樣的業務和團隊問題,我們用這一套場景-技術-長短期投入產出比評估的路子,都能用較輕的方式把問題解決掉,這個對於我們培養新人有很大的幫助。

五、對未來的展望

小菜三年走過來,前端團隊從早期的技術和人員不穩定,到現在趨於穩定,站在公司的角度,最大的收穫就是培養和磨鍊了一批有創業熱情,有擔當勇氣,有技術底蘊的一群人,這一群人抱團在一起,可以在所謂大前端這個框框內玩出更多的花樣,支援到更多的業務場景。

站在今天看明天,雖然有很多東西對我們來說依然是未知的,但我們不再像過去一樣臨場發怯,手忙腳亂,取而代之的是無論多大多難的業務型別,我們都可以坐下來利用這幫人的智慧匯聚出一個最優選擇,胸有成竹的去做技術探索和工程嘗試,在跟公司一起成長變大的過程中,小菜前端也一定會沉澱出來更有實踐價值,更有效率的技術方案,而這些就是我們將來可推廣複用的寶貴技術資產,還有我們這群人,可以開心有趣有挑戰性的 Coding,想進一步瞭解我們團隊的可以 移步這裡

相關文章