前言
隱藏細節,暴露抽象。
作為一名有追求的工程師,我們希望代碼能夠在版本迭代中逐漸優化而不是劣化;同時也會學習掌握更多的技巧和工具,去更好的設計、實現和組織代碼。偶然看到一個apple工程師的分享,于是加上一些自己的經驗和感受,做一些總結。
正文
一、代碼組織
1、使用group
作為一名iOS工程師,Xcode應該是最熟悉的工具之一。舊版本的Xcode在新建一個目錄時,只會作為創建一個引用,不會同時在相同的路徑下去創建目錄。新版本Xcode創建目錄的時候都是以group的形式去創建,會在同級路徑下去創建對應的目錄。
比如下圖,在創建New Group的時候,就會同樣在Audio的目錄下去創建一個New Group的目錄。如果項目的代碼是很久以前的Xcode創建的,最好檢查一遍目錄,使得Xcode的工程文件目錄和實際的文件目錄結構保持一致;如果項目是新Xcode創建則盡量在Xcode中創建group。
2、拆分大文件
如果項目有使用storyboard,則可以把較大的storyboard文件,通過引用的方式拆分成多個storyboard。這樣能提升打開時的速度,也能使得多人協同開發時減少沖突的產生。
但是我經歷過的項目都沒有使用storyboard,大文件的矛盾更多是產生在.m文件,以一個我們項目中的文件為例:
這個2000行的.m文件并不是一蹴而就,而是隨著十幾個版本的迭代,邏輯不斷增加,慢慢變大的文件。這也是我們常說的歷史技術債務。技術債務產生的原因多種多樣,可能是最開始的時候沒有很好的框架設計,也可能是實現過程中有不規范的現象,又或者是多人協作開發導致的代碼膨脹。當發現問題之后,就需要去償還這個技術債務。
.m文件拆分首先需要把業務的核心邏輯梳理出來,抽象出來該模塊的狀態信息、關鍵參數,將外部業務在.m內添加的邏輯改為依賴.m提供的狀態,而狀態可以通過通知、消息等方式拋出去;
核心但是又內聚的邏輯可以使用xxLogic去封裝,然后.m文件直接依賴該xxLogic;也可以將其聚合到.m文件的一個Category之中;
經過這番處理,.m文件能得到極大瘦身,而梳理完內外部依賴之后,后續再新增邏輯也不用去查看.m文件,而是依賴下面的.h文件。
3、重視Xcode的提示
保持Xcode工程設置是最新的,使用Xcode自帶的性能優化。當Xcode彈出下面這個框的提示時,如果沒有特殊訴求,apple工程師推薦點擊Perform Changes
按鈕。
在編譯的過程中,Xcode給出的warning可能在線上運行時就是一個Bug。建議在debug開發階段,打開Treat Warnings as Errors
選項;追本溯源,找到問題的根本原因,解決每一個編譯期間的warning。
如果是已知問題,暫無解決方案,為了避免阻塞編譯運行,可以使用xcode指令去忽略。(具體的warning類型可以在Xcode的Issue Navigator查看,快捷鍵是command+5)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// ingore code
#pragma clang diagnostic pop
4、去掉無用的代碼
我們有時會提交一部分被注釋代碼,理由可能是代碼現在不需要但下個版本可能會用到,臨時注釋一下,反正不影響運行。但是設想一下,如果團隊里面每個人都有這個習慣,那么項目中是否會存在很多無用的代碼?并且這個代碼可能永遠也不會有用武之地。
所以,果斷地刪除那些無用代碼吧,即使真的有需要用到的時候,也可以通過代碼的版本控制工具去找到那些歷史代碼。
二、代碼管理
版本控制系統已經成為開發的必備工具之一。曾經svn也是版本管理的高效工具,Windows系統中的小烏龜(TortoiseSVN)非常好用。但是隨著git的出現,svn已經被逐漸淘汰。
1、提交獨立
一個復雜功能往往由多個需求點組成,開發過程也可能持續數天時間。可以把需求的提交拆分成多次,盡量使得單次提交獨立,Xcode可以看到每一行代碼的提交備注信息。 換位思考,我們希望從git的commit信息里面,看到這段代碼的緣由。
點一下右邊對應信息,選擇show commit,還可以看到對應commit的具體內容。
一個人可以記住昨天為什么寫這段代碼,但很難記住一段數月乃至數年前的代碼為何出現。
2、分支管理
為了保持開發階段的便利,提供alpha分支,作為日常開發的合入分支;為了保證外網代碼的可查,提供beta分支,作為版本發布的打包分支;當版本發布之后,還需要打tag記錄對應版本,比如說release_1.0.0.10。
日常的需求開發(feature分支)、問題修復(bug分支)都是在非主干分支進行開發,最終再合入alpha分支。合入的要求根據團隊實際情況,可以是分支驗收完成再合入,也可以合入后統一驗收。
3、Code Review
Code Review(代碼審查,后面簡稱CR)是發生在分支合入的情況,是成熟開發團隊必不可少的環節。CR有助于團隊代碼風格的統一,包括函數命名、變量命名、代碼組織風格等。同時,CR要求代碼具備一定的可讀性,也要求單次提交不過包括過多改動。
三、文檔
1、必要的注釋
好的代碼一目了然,能清晰描述邏輯,不需要注釋來輔助描述。但是一段特殊邏輯,需要有注釋來描述為何存在,以方便在改動之后去回歸影響點。
比如說一段經典的dispatch_after 1秒的邏輯,這1秒可能是為了避免某些異常case,也可能是產品側的需求要求。
2、對外方法的描述
平時的開發過程,除了注意變量和方法的命名要具有含義,對外提供方法的注釋可以清晰描述需要的參數。比如說下面的一個方法:
在Xcode中選擇對應的方法,按下快捷鍵option+?就可以看到該方法的描述,以及各個參數的要求。如果方法還沒添加描述,則按下option+command+?自動生成待補充的描述。
3、文檔積累
隨著業務的發展,項目中代碼不可避免的會快速膨脹,直接閱讀代碼會非常吃力。此時就需要有文檔來輔助了解各個模塊的情況。
文檔應當避免對具體邏輯細節的贅述,更是和從整體的設計和考慮的因素出發,描述該模塊是如何運行起來。同時在設計的過程,也應該基于之前的技術方案設計。
培養團隊的寫文檔習慣,每個版本前期組織技術方案評審,由負責較為復雜需求的工程師準備一份技術方案的設計文檔,可以達到事半功倍的效果。
四、便捷工具
大家提到Xcode的分析工具,第一反應往往是Instrucment中的工具集。但是實際開發中還有一些便捷工具。
1、Network Link Conditioner
模擬弱網絡環境,以前是在手機的設置-開發者-Network Link Conditioner可以去設置,現在真機連接之后可以在Xcode中按下command+shift+1,選擇對應的設備就可以選擇具體的網絡環境。
2、 Address Sanitizer
Address Sanitizer是內存錯誤檢測工具,通過malloc/free增加標記實現。
比如說下面這一段代碼,buf指針創建了1024內存,再手動釋放,然后再去訪問buf指針的元素。這段代碼編譯時正常,在運行時不一定會崩潰 ,有可能就會演化成一個偶現bug,難以定位。
在使用Address Sanitizer工具的時候,運行到130行時就會報錯:Use of dealloccated memory
。
打開方式是在scheme選項中,勾選Address Sanitizer。
3、Thread Sanitizer
Thread Sanitizer是線程錯誤檢測工具,可以檢測到一些多線程數據訪問的錯誤,比如說下面的代碼。
sTestNum是靜態全局變量,創建了多個線程去操作該變量,會觸發Data Race
。
打開方式是在scheme選項中,勾選Thread Sanitizer。
Thread Sanitizer關注的是數據的多線程訪問,通過記錄內存的訪問來實現,并不能定位到多線程的crash問題,比如下面這個crash:
4、Main Thread Checker
Main Thread Checker是多線程操作UI檢查工具,UI操作只能在主線程執行,如果在子線操作則會觸發警告。
打開方式是在scheme選項中,勾選Main Thread Checker。
5、Debug Gauges
在debug運行程序的時候,Debug Gauge能快捷地查看CPU、Memory、Disk、Network信息。
打開方式是Xcode按下command+7。
五、開發建議
1、最小依賴原則
一段邏輯的運行,往往需要外部的變量輸入。有時候為了便捷開發,函數調用時候不會傳遞參數,而是通過全局變量、self指針等直接去獲取需要的數據。但是這樣會導致代碼邏輯紊亂。在編碼的時候,非常建議使用最小依賴原則:盡可能少的使用外部依賴。
以函數為例,一個xx邏輯處理的方法應該只依賴函數參數。這樣函數的輸入輸出是固定的,即使函數放到其他地方,只要保證函數的輸入不變,則邏輯的輸出是不變的。
同理,除了函數還有view、model等等,盡可能少的去依賴外部數據、外部模塊,則該處邏輯更加獨立,更容易實現可以直接復用的view、model等等。
2、組件化&模塊化
實現功能的時候,應盡可能去除耦合;特定功能組成的庫就是組件,寫新功能代碼盡可能要往組件方向實現;而模塊化指的是根據業務形態,把代碼按照功能、業務進行聚合,相當于組合了各種組件和業務邏輯的庫。
模塊化和組件化等一個重要特點就是Pod化,將這些特定、獨立的功能代碼和業務代碼從主工程中剝離,抽象出來業務需要的接口,再重新通過pod依賴引入主工程。在這個過程,不單單是把代碼轉移到Pod庫,還需要做一些業務的解耦和依賴抽象。
好處也是顯而易見:
開發上,模塊化后各個業務相對獨立,能夠更加專注自己業務邏輯,即使業務出錯影響面也比較可控;
效率上,模塊化后可以做二進制組件,加快編譯速度;
管理上,組件owner的意識更強,方便添加數據監控;
架構上,強迫面向接口編程,避免大量耦合的膠水代碼。
總結
本文部分參考自 WWDC2019,結合一些工作經驗,做了更適合自己的闡述。
自己也梳理了接下來一段時間的技術優化方向:
日常業務迭代,通過CR保證新增代碼風格統一;
復雜業務需求,需要做技術方案評審,集思廣益;
已有歷史債務,小模塊微整實現,大業務走專項重構,注意人力投入、業務影響和收益評估。