接手一個不合格的業務線代碼,我是如何去維護以及重構的

項目背景

IM聊天功能做爲整個電商功能的補充和重要支撐,相信不少的電商App都會集成這麼一個功能,可是大多數公司的IM功能相信都是集成的融雲或者環信的SDK。html

可是相信做爲電商的有力支撐,IM的消息對於各個公司來講都有不一樣的業務需求,也就是說普通的圖片、文字、紅包甚至語音這種經常使用的消息類型並不能有力支撐起一個IM的業務。咱們公司的IM業務功能正式在這種背景下,徹底從前端到後端都是徹底由本身人設計、開發以及維護的。前端

我是在17年年中左右正式接手了我司的IM業務功能,剛開始是原由於解決線上的一個bug,因而開始梳理了一下代碼邏輯,因而。。。懵逼了很久很久很久。 雖然IM的代碼已經在線上跑了好久了,在我開始解決bug以前貌似有大概有大半年將近一年的的時間沒有人來維護,可是從架構設計上、業務代碼實現等點來看,質量十分堪憂。ios

梳理代碼

我司IM的架構設計大概是在幾年之前,長鏈接協議使用的是WebSocket,業務邏輯有一些複雜,目測從數據邏輯到業務邏輯再到UI邏輯,代碼量可能會接近5w這個量級,因此說一開始就一行一行的看代碼邏輯顯然是不太理智的。數據庫

梳理第一步

第一步的主要目的是熟悉代碼的主要脈絡,因而我開始有序的梳理沿着數據流向梳理主幹,要點以下:後端

  • 分析各個數據模型(model)。
  • 整理各個HTTP請求的API。
  • 整理並備註各個Notification的Key,並標記使用場景。
  • 整理各個Delegate回調函數的使用場景。
  • 整理並備註各個枚舉值的含義以及使用場景。

由於代碼歷史久遠且開發維護人員換了好幾茬以後,代碼邏輯比較混亂,剛開始梳理的時候大部分時間都花在了備註各類代碼上。緩存

梳理第二步

第一步以後,我其實已經對於IM的架構邏輯開始有了一個初步的比較寬泛的瞭解。第二步的的主要目的是整理在使用的主要的幾個組件:性能優化

  • 數據庫的初始化建立以及使用邏輯。
  • HTTP代碼的初始化建立以及使用邏輯。
  • 長鏈接代碼的初始化建立以及使用邏輯,特別是與服務端溝通和保活的部分。
  • 針對以上基礎控件的二次封裝控件的整理。

梳理了上面幾個以後,我陸續整理了以下:網絡

  • 數據庫的表結構設計、初始化建立以及銷燬等邏輯。
  • 瞭解HTTP的接口,進一步瞭解了基礎的業務設計。
  • 經過和服務端的同事溝通,明確了當前的長鏈接協議下,兩端是如何保活、溝通數據以及溝通狀態的
  • 各個API的使用場景以及使用邏輯。

梳理第三步

上面兩步,基本上把最核心的工具整理完成,下面就開始將代碼邏輯串起來,整理業務邏輯:數據結構

  • 羣聊的收發消息邏輯。
  • 私聊的收發消息邏輯。
  • 登陸、退出登陸、更換帳號登陸以及綁定帳號以後的業務邏輯。
  • 自後臺喚醒以後的業務邏輯。
  • 收到推送消息以後的業務邏輯。

整理好了以上以後,我利用流程圖工具ProcessOn建立了大概有10張左右的流程表,數據流向和業務邏輯一清二楚。架構

特別是聊天的數據邏輯,從HTTP請求、長鏈接推送以及數據庫操做,無所不包。可是圖整理完以後,對於主要流程幾乎是掌握的比較清楚了,即便回頭有忘記的,回頭查看錶以後就會一清二楚,不只便於我熟悉邏輯,對之後的維護也頗有益處。

維護

遇到的困境

雖然代碼質量堪憂,可是代碼中的bug還算比較少,因此在解決線上bug這個問題上,沒有遇到過多的麻煩。可是其中也有幾個bug十分麻煩,找了很久才找到問題的緣由。

其中有一個,找緣由大概找了一週多的時間,最後定位問題在咱們的帳號系統上。由於歷史緣由,咱們的IM業務和其餘業務是兩套帳號體系,中間經歷過一次帳號變動,可是因爲某些奇葩的緣由(有部分業務經歷了hack),致使部分用戶的帳號沒有成功的過渡帳號變動,致使了一些問題。

表象緣由是代碼邏輯混亂,bug緣由複雜,沒法復現並難以定位問題。 但更深的緣由是時間久遠,咱們對當時的代碼設計以及業務邏輯變動毫無記錄,致使沒法快速定位到問題的緣由。

思考

對於一個邏輯複雜的業務,首先要考慮的是易拓展、易維護和模塊組件間的高度解耦。

對於一個成熟的業務來講,我認爲易於維護性更爲重要,由於業務已經進入成熟階段的話就意味着功能增長的概率比較低,那麼對於線上bug修復和小修小補的優化就是主要工做。 我司的IM就是這樣的,你們看梳理的代碼的第一步應該能看出來,我耗費了大量的時間去作各類備註,然而,這寫備註應該在開發階段就應該寫好了。包括對於以前兩套帳號系統存在的緣由,包括變動一次帳號的緣由,是否須要有一個詳細的記錄?

那麼對於這種這種有助於後期維護的記錄,我認爲咱們須要對重要的業務變動以及業務設計要有一個詳細的記錄,不管是本身做爲記錄仍是爲後面接手的同窗作個參考,我認爲都是極爲重要的。

重構

思索需求

通過最開始的瞭解和一段時間維護,我發現遇到的最大麻煩是數據邏輯、業務邏輯、和UI操做邏輯混到了一塊兒,簡直能夠說是牽一髮而動全身。特別是數據邏輯十分混亂,由於數據邏輯的混亂,致使我對於後面業務邏輯的變動十分費力,測試成本也是指數級上漲,另外UI邏輯雜亂,適配iPhone X的時候也遇到了一些小麻煩。

如今的架構設計的緣由是什麼?這樣設計業務需求是否合理?是否有優化的空間?

分析現狀與判斷將來

不管是客戶端仍是服務端,亦或是兩端數據交互上,IM業務的架構設計自己就存在不少問題。由於時間短暫,不太可能一次性解決全部的問題,特別是對於服務端來講,一次性的大規模重構可能性極低。

對於如今來講:

  • UI重構是首當其衝的, 在保證現有邏輯的基礎上,從新設計UI層的邏輯結構,保證代碼的複用性和可擴展性,爲了未來有可能的業務升級留足空間。
  • 梳理基礎組件,好比HTTP、長鏈接協議和數據庫,還有其餘的一些工具類,經過封裝成組件和組件引用來是他們能從業務邏輯中獨立出來。便於獨立維護、升級甚至替換
  • 將原有的數據操做邏輯從UI邏輯中完整抽離出來,須要達到向下對基礎組件要有封裝和控制,向上對業務邏輯要有承接,並且依然要作到耦合度儘可能的低。
  • 由於IM部分,我司的兩個App這部分代碼有90%的代碼重合,須要考慮兩端通用的問題。

架構設計

  • 工廠模式
  • 瘦Model
  • 去model化的Cell
  • 項目優先,分離核心業務模塊組成pod
  • 考慮業務變動可能性,儘量向上保持API穩定性
  • KVO進行反向傳值

着手動工

UI層重構

UI層的重構是最早開始的,不管架構怎麼變,UI層都是直接面向用戶的,直接承載了公司的業務功能實現,因此爲了靈活適應業務的升級或變化,給用戶一個好用流暢的入口,UI層設計上要儘量的靈活。耦合儘量的小,流暢性上要有保證。

重構思路相對簡單,重寫View和Controller,去除冗餘複雜的UI邏輯代碼,規範並統一第三方框架使用,封裝公用組件,隔離膠水代碼,設計靈活的UI結構。

由於IM系統的最主要UI仍然是TableView,因此針對TableView的各類優化就是重中之重,我着重說一下我對於複雜Cell類型的設計方案。

一、共有的組件有不少,好比時間、頭像、背景氣泡等等,因此說子類繼承父類是最基礎的方案。
二、棄置Autolayout的UI書寫方式,徹底用Frame來寫UI佈局。Cell高度以及內部UI組件的佈局和位置,經過異步計算並緩存爲LayoutModel,經過這種方式下降計算的重複耗時操做。
三、有的消息類型只是負責展現,可是有的確有相對複雜的業務邏輯,可是爲了防止Cell代碼的膨脹,採用了瘦Cell的方式分離邏輯,力求使Cell儘可能只負責UI的承載和展現,增長helper層處理相關邏輯。
四、由於雖然是相同的一個數據,可是呈現的方式會存才差別化,因此採用瘦Model的形式,經過建立Helper對取到的原始數據進行相對應的加工,直接提供給業務邏輯處理好的數據。在AFNetworking給的Demo中,是一個典型的胖Model的例子,倒不是說他的例子很差,只是隨着業務邏輯的複雜以及生數據和熟數據的差別愈來愈大的時候,胖Mode的代碼量會幾何級數般的膨脹,因此仍是要因地制宜,具體狀況具體分析。
五、利用Factory模式分離出關於複雜Cell類型的判斷,包括初始化、賦值等。
六、使用KVO取代delegate進行反向傳值,用以減小代碼耦合。
複製代碼

關於如何保證UI性能,可參考個人另外一篇Blog,iOS 性能優化的探索.

最終結果,第一個完整case的UI層Controller代碼,從3000行直接縮減到了1200行,Controller中沒有複雜的多方數據處理邏輯,複雜的邏輯判斷。只做爲UI展現以及接口調用,徹底剝離了數據邏輯的處理,全部的處理邏輯由下面的數據邏輯層處理。

數據邏輯層重構

我再分析了業務需求並設計了架構以後,決定重構以自下而上的順序來進行,因而第一部分就是對於數據邏輯層的重構。步驟以下: 此部分,分爲如下三層結構:

一、業務數據邏輯層
二、適配器層(adapter)
三、基礎組件服務層(server)
複製代碼
一、業務數據邏輯層

這部分的主要做用是直接受到UI層的調用,負責長鏈接以及短鏈接的創建,數據庫的初始化操做等。 向上直接承接UI邏輯和業務邏輯,是高度面向業務封裝的接口。好比在發送照片消息的時候,只須要將調用API傳入Image對象,其餘的流程好比說是上傳資源以及組成message對象等,則不須要上一層調用和考慮。

因此這一層儘量的會很薄,不會有特別多的邏輯代碼。

二、適配器層(adapter)

這部分的主要工做是承上啓下,承接上一層的面向業務的封裝,調用下一層基礎組件服務層的接口,能夠說絕大多數的接口封裝都集中在這一層。由於咱們有些業務的長鏈接和短鏈接的使用上不是很合理,因此我將長短鏈接都封裝到了一個網絡服務的類中,此後假如長短鏈接的業務產生了變化,但仍能夠保持向上的接口穩定性。

舉個例子說,當推送來一條消息以後,是經過長鏈接,可是須要收到數據以後在進行AFN的操做完成消息體完整數據的獲取,以後要存入數據庫而且將是否讀取狀態設置爲NO,當用戶讀取當前消息以後,將這部分消息還要更新爲已讀狀態。

這部分操做涉及到了全部的組件的操做,可是反饋到最上面一層的時候,大概只是新的消息,而且是完整的消息,而後再刷新UI。因此說,這一層的業務量比較大,幾乎是要按照各類標準操做,完整的處理好全部組件的接口。

三、基礎組件服務層(server)

這部分基本能夠說是基於IM自己的業務特色,對於基礎組件的調用封裝。 包括:

一、數據庫部分,對於IM消息的數據結構,封裝的對於數據庫的建立,以及增刪改查等接口。以及基於業務的一些接口,例如一次性設置當前聊天的全部消息爲已讀狀態等接口。
二、AFN部分,這部分相對來講就很簡單了,基本上是依賴於AFN封裝的接口,好比獲取當前User的詳細信息等。
三、長鏈接部分,包括對於長鏈接協議的建立鏈接、斷連以及心跳超時上報等操做,也包括了發送消息和收到消息回調等底層操做。
複製代碼

拆分以後的Manager層代碼量所見到原來的40%左右,因而更名爲Session層。

基礎組件層的重構以及封裝

這部分由於屬於公用的基礎組件,因此相對來講只是基礎組件的好比說AFN以及數據庫(FMDB)是整個App的組成,因此沒什麼其餘的操做,只是單純作了一層邏輯上分層。

可是對於長鏈接咱們作了一些定製,好比:

一、增長了重連的邏輯機制。
二、增長建立鏈接以及斷掉鏈接時候各類狀態的判斷等。
複製代碼

主要任務仍是集中在對於協議庫自己的邏輯補充和健壯性優化等。

其餘操做

一、建立枚舉文件,擴展標準化的枚舉變量。
二、合併以及分割Model,隨着業務的擴展,原來的Model設計已經不符合當下的業務發展,根據如今固定的業務,從新設計了Model的集成關係,對於分化嚴重的也作了從新分割。
三、分離並封裝了膠水代碼到一個大的工具類,便於調用和調試。
複製代碼

走過的彎路

一、過分思考代碼解耦合而忽略了業務邏輯複雜性,錯將組件化各組件的解耦合的邏輯應用在了原本就是高耦合的MVC架構上。嘗試使用去model化的Cell,可是實際操做環節發現增長了大量的邏輯判斷,無形中將Model本該處理的業務邏輯轉接到Controller和View上,表面上看上去API簡潔到家,可是上手代碼量並不算小,不利於維護。

二、在一開始採用了MVCS的設計重構UI層,簡化Controller中對於Model的處理,在Store中進行了主動和被動網絡邏輯、本地數據庫調用等。但事實上最後經過封裝統一入口的方式將獲取數據的邏輯所有從UI邏輯層剝離開,下沉到了數據邏輯層,對於UI層來講只須要考慮的是進行了調用獲取數據的API操做或者是被動受到了新的數據,不須要考慮數據來自於服務端、Cache仍是本地數據庫,也不須要考慮後面的邏輯。

三、對於UI層和下一層的數據溝通,雖然採用了KVO的方式回調,下降了耦合性,可是仍然存在參數複雜的狀況下,傳遞過多的Key的狀況,致使解析稍顯困難和複雜。

四、Cell的繼承,看上去是一個很直觀的設計,可是隨着重構代碼量的增長以及業務變化發現繼承過程當中會存在不少問題,經過面向協議等方式或許能夠解決繼承中的問題。

總結以及思考

架構設計的時候,必定要預判用戶的使用習慣,判斷將來的業務導向,儘量的下降代碼侵入性和耦合性。對於性能產生的影響的地方,經過以上幾點來設計架構。 架構設計分層要清晰,API設計要儘量簡潔,避免暴露過多的接口和參數,避免模塊之間的緊耦合,UI設計要儘量靈活。 重構前,須要思考切入點,是從上值下、從下至上,仍是模塊化抽離。

已從這家公司離職,部分邏輯全憑記憶整理,若是有疏漏或錯誤,還請你們海涵。

Refrence

相關文章
相關標籤/搜索