作者:閒魚技術——意境
緣起
代碼對研發團隊的重要性不言而喻。代碼規範作爲一個研發團隊的核心基因,怎樣在團隊中高效傳承是一個挑戰。Flutter做爲移動端持續火熱的新型框架,正吸引越來越多的研發同學進入這個領域。但是面對全新的技術框架和之前幾乎很少接觸的dart語言,怎樣寫好Flutter代碼困擾了無數Flutter初學者。文本就着重分享一下閒魚Flutter團隊在基礎代碼規範方面所做的一些實踐。
Flutter靜態代碼掃描
Native開發同學都知道Android/iOS都有配套的Linter檢查機制。開發者可以通過Linter檢查,統一代碼風格,檢查代碼中的明顯缺陷。Flutter同樣具備這樣的能力。通過Linter檢查機制,是統一代碼風格最簡單高效的方式。
Flutter Linter機制
Flutter Linter檢查的機制植根於Dart Linter檢查,同時做了大量的功能拓展。Flutter的Linter規則記錄在工程一級目錄的analysis_options.yaml文件中。大家可以通過對該文件的定製來獲得適合自己團隊的代碼規則。
Flutter Linter 規則都從屬於三個主要的集合:
-
ERROR
規則標識的是代碼中可能出現的錯誤
-
Style
規則標識的是代碼風格的問題
-
Pub
規則標識的是Flutter包管理相關的問題
需要注意的是,考慮到實際開發場景中的穩定性。調整Linter規則的時候需要關注規則對應的成熟度。目前主要的成熟度等級包括:
- Stable - 穩定
- Experimental - 實驗性規則,可能會不太穩定
- Deprecated - 不再推薦使用的規則,建議廢棄
常見的Linter規則集
-
effective_dart是dart語言配套的Linter 規則。規則來源於Effective Dart文檔。該文檔中有對Dart語言高效使用的各種規範。effective_dart 規則比較老,目前已經廢棄。
-
google內部dart開發規範集合。目前也已經廢棄。
-
是Flutter團隊推薦的Flutter相關規則集。推薦大家在Flutter apps, packages, and plugins中廣泛使用。本身是dart recommended 規則集合的拓展版本。該規範會影響到發佈到 pub.dev 中 package的得分。
-
Dart團隊推薦使用的規則集。核心包括兩個子集:core & recommended。
閒魚規範的取捨
在衆多Liner規則中,選擇符合團隊需要的規則是一個很大的挑戰。閒魚在做的過程中,遵循了一些基本原則。
表達簡潔
在語義等效的情況下,充分利用dart語言的各種高級語法來降低表達的複雜度。簡潔表達不僅能提升編碼效率,同時也可以提升代碼的閱讀效率。舉幾個典型例子:
- 去除冗餘new關鍵字【unnecessary_new】
閒魚代碼庫中有大量的代碼都是使用new關鍵字來進行類初始化的。這一方面是因爲閒魚切入Flutter比較早,有歷史包袱;另一方面也是因爲客戶端同學將其他語言(例如java)的編碼習慣帶到了Flutter。雖然要修改的地方有很多,但是爲了表達簡潔,我們依然堅定的加入這條規則。
- 高效null處理【prefer_conditional_assignment, prefer_null_aware_operators】
dart語法中有很多null處理的語法糖,能大幅提升判空的效率,提升代碼的健壯性。這裏包括?的判斷,??的判空,以及null safety。需要不斷引導團隊同學更多使用這樣的語法來提升效率,而不是繼續使用if else的冗雜表達。
減少歧義
代碼表達需要準確,同學之間的理解儘可能一致。減少代碼之外的“以爲”,增加字裏行間上的“確定”。典型列子如下:
- 顯式變量類型【always_specify_types】
是否要明確變量類型,團隊內部不同技術棧背景的同學有比較大的分歧。前端背景的同學傾向於通過推斷的方式進行變量命名,好處是表達簡潔。客戶端背景同學傾向於直接明確定義,好處是直觀,減少不必要的推斷。最終經過激烈討論,大家還是決定採用明確變量類型的方案。從實踐角度出發,直接明確變量類型雖然從表達上略冗雜,但是歧義確實更少。
- 變量/函數狀態堅持最小化表達 【annotate_overrides,prefer_const_declarations,prefer_final_fields】
一個變量或者方法如果重寫了,請明確加上override註解。一個變量如果確定是const變量,請加上const關鍵字。一個變量如果沒有再被賦值,請定義成final。堅持最小化表達,有如下幾點收益:
- 最小化變量狀態表達能精準刻畫變量狀態
最小化變量狀態是指用最嚴格的屬性描述變量。例如一個變量從之前的多處賦值,改爲一處賦值。那麼變量其實自帶了final的隱形屬性。最小化狀態表達要求必須加上final關鍵字,而不是推斷是final。因爲一旦加上final關鍵字,後續在賦值就會報錯。這能減少不必要的理解成本,最大限度避免變量狀態的隱形改變。 - 提升代碼性能
增加的final const關鍵字能讓編譯器做更多優化,提升代碼的整體性能。
風格一致
多人開發的團隊中,每個人都有自己的喜好。代碼管理應該避免出現“破窗效應”。一個人把代碼寫爛了,後面的同學照着寫都爛了。正所謂,無規矩不成方圓。基礎代碼的表達,不追求絕對的對與錯,追求的是在風格上的儘可能統一。這裏有太多典型例子:
- 命名規範大體遵循駝峯的方式【camel_case_types,non_constant_identifier_names,constant_identifier_names 】
- 控制流中儘量使用大括號【curly_braces_in_flow_control_structures】
- import順序按照先dart引用,再package引用,再相對引用方式分模塊,模塊內引用按字母表排序【directives_ordering】
- required 關鍵字標識的變量請排到前面 【always_put_required_named_parameters_first】
- flutter佈局中優先使用SizedBox而不是Container【sized_box_for_whitespace】
- flutter顏色定義使用8位16進制整數標識顏色值【use_full_hex_values_for_flutter_colors】
- flutter widget在構造函數中加入key參數【use_key_in_widget_constructors】
這裏的每一條都不具有非這樣不可的理由。但是在閒魚寫代碼就必須遵循這個規範。不爲別的就爲了風格的統一,爲了提升團隊同學業務輪轉的效率。
代碼質量
Linter檢查的另一個重要的目標是發現潛在的代碼缺陷。這對代碼管理來說就更爲重要了,因爲這直接關係到穩定性的大局。穩定性是開發人員的底線,再怎麼小心也不爲過。同樣舉幾個典型的例子:
- 避免給void賦值 【void_checks】
- 變量比較之前先判斷類型 【unrelated_type_equality_checks】
- 避免catch 空實現【empty_catches】
- 避免使用隱形類型傳遞 【avoid_shadowing_type_parameters】
- 避免await 非future對象 【await_only_futures】
- 集合的remove需要傳遞符合集合的類型的參數【list_remove_unrelated_type】
如果這些問題代碼帶到生產環境,輕者出現exception,重則功能不可用。防患於未然,在代碼最開始編寫階段,就及時處理這些問題,代碼才能更健壯。
維護成本
效率對一個團隊固然很重要,但是追求一時的效率,而不顧及代碼生命週期內整體的效率是非常短視的行爲。我們今天越發關注代碼在後期的維護成本。基於此,文檔&註釋的重要性正越發凸顯出來。一個好的註釋,能省下很多的答疑,很多的試錯,很多的猜疑。不斷引導大家寫並維護文檔及註釋,是閒魚代碼規範明確的目標。舉幾個明確的例子:
- Deprecated函數需要給出明確的註釋[provide_deprecation_message]
- 註釋中的變量引用,符合引用的約束[comment_references]
規則權威
閒魚團隊Liner規則的制定,不是一家之言。邀請了團隊所有同學參與到規則的制定過程。一個規則的權威性來源於所有執行者發自內心的認同。舉一個典型例子:
在討論標準格式化的時候,團隊同學集中討論了一個規則:
【- lines_longer_than_80_chars # 爭議規則,每行長度不超過80字符】
大家認同統一格式化代碼風格的重要性。大家爭議的焦點是:80字符在很多高分辨率顯示器上太短了。這會導致很多不必要的換行,降低代碼閱讀體驗。我們聽取了開發同學反饋,把該條規範去掉了。改爲統一android studio配置來執行。最終標準是12號字,一行最多160字符。
寫在最後
當然僅僅做到上面的部分,僅僅是萬里長征的第一步。基礎代碼規範這件事,我們並不追求規則的多,而是更在意規則實際的效率收益。規則制定的過程中,我們聽取了開發同學真實的反饋,刪除了很多並不必要的規則。簡單,高效,並不爲了規範而規範是我們核心的目標。當然最終確定的規則,要盡最大努力保證執行。目前我們正在積極建設更完善的CI體系【後續會在寫文章跟大家交流】,將規範規則化。通過持續快速檢測,實時給使用者反饋,降低規範落地的成本。代碼是研發團隊的核心資產,需要我們共同守護。
附錄
上面介紹的規則細節,如果有不清楚的,可以在下面的鏈接中找到更詳細的解釋。
閒魚核心Liner規則
附上閒魚的核心Liner規則,給大家一點參考。
命名
排序
格式化
評論
庫使用
NULL
字符串
集合
函數
成員變量
構造函數
異常處理
Mixin
類型
參數
質量
Core集合融合部分
Recommended 集合融合部分
Flutter 集合融合部分
其他規則融合部分