編寫可讀藝術(shù)的代碼

前言

編寫代碼,實質(zhì)是在梳理邏輯闭专,為了完善整個邏輯流程奴潘,我們借用編程語言的變量、函數(shù)影钉、流程控制画髓、循環(huán)、注釋平委、方法等串接起來奈虾,完善一套系統(tǒng)的邏輯。

為了完善這套邏輯廉赔,我們借助了許多工具:設(shè)計方法肉微、架構(gòu)設(shè)計、項目組織等蜡塌。
意識到?jīng)]有碉纳,代碼的好壞一定程度上可以從邏輯層面評判。

  • 符合邏輯岗照,不一定是最優(yōu)的代碼
  • 不符合邏輯村象,一定不是好的代碼

邏輯的串接靠的是編程語言的變量笆环、函數(shù)攒至、流程控制厚者、循環(huán)、注釋等迫吐。

一库菲、 規(guī)范

絕大多數(shù)的人,不會從零完整的完成一個復(fù)雜的項目志膀,大多是團隊共同合作熙宇,完成一個大的項目。

這個時候溉浙,假如你是中途參與進來烫止。你在實現(xiàn)邏輯的時候,你是照著自己的邏輯來還是依照團隊的風(fēng)格來戳稽。

比如項目組織馆蠕,命名等...

按照團隊的命名風(fēng)格來

1.1 編程語言的規(guī)范

每門編程語言,都存在一定的規(guī)范惊奇,比如 Python 采用的下劃線的變量命令規(guī)則互躬,Go 則采用駝峰式的變量命令規(guī)則等。
Effective Dart: 代碼風(fēng)格
Kotlin 編碼規(guī)范

另外颂郎,Dart語言默認80個字符換行吼渡,在團隊中需要設(shè)置100個字符換行。

二乓序、 命名

給變量寺酪,函數(shù),方法命名時易于理解

  • 專業(yè)的單詞:使用領(lǐng)域內(nèi)的單詞
  • 避免空泛的名字
  • 具體的名字
  • 變量名帶上更多細節(jié)
  • 不使用令人誤解的名字
  • 布爾值命名

2.1 領(lǐng)域內(nèi)單詞

天星銀行的領(lǐng)域單詞分為幾大類

類型 不常見的單詞
account(賬戶) biometric(生物識別)替劈、faceID(面容識別)寄雀、touchID(指紋識別)
deposit(存款) timeDeposit(定期)、currentDeposit (活期)抬纸、
kyc(開戶) employment(職業(yè)) 咙俩、questionnaire(調(diào)查問卷)、liveness(活體檢測)湿故、document(證件)
loan(貸款) anti frau(反欺詐)阿趁、partial repay(部分還款)、credit(信用)
personal(個人) language(語言)坛猪、fileMaintain(資料維護)脖阵、promotion(促銷)
security(安全) softToken(安全令牌)、remain(提醒)
transfer(轉(zhuǎn)賬) fps(轉(zhuǎn)數(shù)快)墅茉、tupta(分享)命黔、

2.2 避免空泛的名字

變量的命名一般要賦予一定的意義呜呐,極少情況下可以使用沒有什么意義的單詞。比如最常見的:

var i int

for (i=0;i<10;i++){
    fmt.Println(i)
}

這種沒什么意義的單詞悍募,一般適用于局部作用域蘑辑。

2.3 具體的名字

完成什么任務(wù)就使用什么單詞。一般變量使用名詞居多坠宴,函數(shù)使用動詞開頭居多洋魂。

String toString() => json.encode(toJson());
 /// 設(shè)備硬件支持的生物識別類型 (指紋,面容)
static Future<BiometricType> hardwareDetectedBiometric() async {} 

int pages = 0;
String userName = "";

2.4 帶上更多細節(jié)

一般命名不建議過長喜鼓,也不建議過短副砍,最長三個單詞的長度吧

如何帶上更多的細節(jié)。

  • 嘗試使用后綴
  • 嘗試使用單位
  • 嘗試指向具體的細節(jié)

比如:

String currentFlowName;

VoidCallback onFinishLoad,

List<OtpType> otpTypes;

final BiometricType openedBiometricType;

幾組對仗的后綴:

  • max/min
  • first/last
  • begin/end
    ...
ServerCanStart() 不如 CanListenOnPort()

2.5 不使用令人誤解的詞

比如:Filter 在數(shù)據(jù)庫操作中容易使用這個單詞庄岖,這個單詞沒有帶上更多的細節(jié)豁翎,實質(zhì)上在使用的過程中,還是需要查看編寫的SQL 語句等才能知道具體的過濾細節(jié)隅忿。整體思考多了幾步心剥。不易讓人理解。

建議多讀幾遍自己命名的單詞

2.6 布爾值

提到布爾值硼控,因為就存在兩種結(jié)果刘陶。所有,一般使用是否這樣意思的詞牢撼。

通常使用 is,has,can,should這樣的詞匙隔,可以把布爾值變得更明確。

final bool canBack;
final bool backOnFinish;

// 是否有更多收支歷史
@JsonKey(name: 'hasMoreRecord', required: true)
final bool hasMoreRecord;

2.7 不建議使用的單詞

get熏版、read纷责、util 恰恰這幾個單詞,在寫代碼中最容易使用撼短。選擇替代方案再膳。

單詞 更多選擇
send deliver(傳送)、dispatch(派遣)曲横、announce(宣布)喂柒、distribute(分配)、route(路線)
find search(搜尋)禾嫉、extract(提仍纸堋)、locate(位于)熙参、recover(恢復(fù))
make launch(發(fā)動)艳吠、create(創(chuàng)建)、begin(開始)孽椰、open(打開)
start create昭娩、set up(建立)凛篙、build(建立)、generate(發(fā)送)栏渺、compose(構(gòu)成)呛梆、add(添加)、new(新)

2.8 帶單位的值

static const int resultTimeoutInSecs = 10;
  • 使用專用的單詞 - 例如:不用 Get 迈嘹,使用 Fetch削彬、Download 全庸。由上下文決定
  • 避免使用空泛的單詞 - tmp 和 retval 秀仲。除非有特定的理由

3 設(shè)計

好的源代碼應(yīng)當“看上去養(yǎng)眼”。如何使用好的留白壶笼,對齊及順序來讓你的代碼變得更易讀神僵。

  • 使用一致的布局,讓讀者很快習(xí)慣這種風(fēng)格
  • 讓相似的代碼看上去相似
  • 把相關(guān)的代碼分組覆劈,形成代碼塊

這里以設(shè)計的四個規(guī)范類比代碼的組織保礼。

3.1 對齊

編程語言為什么強調(diào)縮進?難道不是為了閱讀代碼的人更容易看懂代碼嗎责语?寫代碼的人更容易組織代碼嗎炮障?僅僅是設(shè)計者為了好玩?

static Future<void> start(FlutterDriverWrapper driver) async {
    driver.log('register test begin...');

    /// home page
    await driver.waitFor(KeyManager.homeBtnLogin);
    await driver.takeScreenshot();
    await driver.tap(KeyManager.homeBtnLogin);

    /// login page
    await driver.waitFor(KeyManager.loginBtnRegister);
    await driver.takeScreenshot();
    await driver.tap(KeyManager.loginBtnRegister);

    /// validate new username page
    await driver.enterText(KeyManager.validateNewUsernameInputUsername, TestConfig.username);
    await driver.takeScreenshot();
    await driver.tap(KeyManager.validateNewUsernameBtnNext);
」
  • 放眼望去坤候,確實知道實現(xiàn)什么任務(wù)
  • 風(fēng)格統(tǒng)一
  • 整整齊齊

3.2 重復(fù)胁赢、親密、比較白筹、審美

最應(yīng)該避免的其實就是寫重復(fù)的代碼智末,一般的做法往往是提煉,將重復(fù)的抽象出一個函數(shù)之類的徒河。這里的重復(fù)系馆,是風(fēng)格的統(tǒng)一。

  • 重復(fù)的代碼使用方法去提煉
  • 選擇一個有意義的順序顽照,始終一致地使用它
  • 把聲明按照塊組織起來
  static double get s1 => s(3);

   /// Loan pages
  static const String loanCreditPrepare = '/loanCreditPrepare';
  ...

  /// Coupon
  static const String couponList = '/couponList';
   ...

  /// Transfer pages
  static const String transferPrepare = '/transferPrepare';
   ...
}

可以結(jié)合比較下 student.go 和 teacher.go

這樣的組織方式由蘑,講道理,并不太會給閱讀代碼的人帶來太多的認知負擔代兵。

3.3 留白

設(shè)計領(lǐng)域頁面的設(shè)計尼酿,并不強調(diào)內(nèi)容越多越好,恰當?shù)脑陧撁嫔狭粲锌瞻咨萑耍拐w設(shè)計有呼吸感谓媒。

那編程如何實現(xiàn)留白?

  • 恰當?shù)膿Q行何乎,使相似的內(nèi)容更緊湊
  • 提取句惯,使用方法來組織不規(guī)范的東西
  • 代碼分段

假如你一個函數(shù)需要寫 100 多行土辩,不好意思,我可能建議你抢野,不要這么做拷淘。

  • 拆分,邏輯梳理指孤、提取方法
  • 盡量維持最長 30~50行左右(這樣使屏幕能裝載下启涯,一次就能完成的閱讀整個函數(shù)的邏輯)
static Future<void> start(FlutterDriverWrapper driver) async {
    driver.log('kyc test begin...');

    /// home page
    await driver.tap(KeyManager.notifyBtnOk);

    /// prepare page
    await driver.tap(KeyManager.bottomBtnRight);

    /// kyc terms page
    await driver.delayed(TestConfig.pageStartDelay);
    await driver.scrollIntoView(KeyManager.bottomBtnRight);
    await driver.tap(KeyManager.bottomBtnRight);
    ...

4 注釋

幫助閱讀代碼的人對代碼了解的和寫代碼的人一樣多

4.1 什么時候不需要注釋

  • 好的命名不需要注釋

4.2 什么時候需要注釋

  • 關(guān)鍵點
  • 缺陷點
  • 常量
  • 全局注釋
  • 總結(jié)性注釋

關(guān)鍵點:
有些時候,僅僅靠之前的“表面工作” 已經(jīng)不能完全能夠滿足讓人易于理解恃轩。這個時候需要在關(guān)鍵點添加注釋结洼。

缺陷點:
是的,承認自己的代碼寫的不是最優(yōu)的叉跛,僅僅只是實現(xiàn)松忍,還存在更優(yōu)的辦法,所以需要在有缺點的地方加上注釋筷厘。

  // TODO: move to remote config
  static const String customerServicePhoneNumber = '9288321';

  /// TODO: 更好的方案是:現(xiàn)有的圖形上做動畫鸣峭,而不是重置動畫。
  @override
  void didUpdateWidget(TotalAssetsWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    ...
  }

常量:
給常量注釋酥艳,賦予了更多的意義摊溶。

  ///
  /// Area codes
  ///
  static const String areaCodeHK = '852';

全局注釋:
一般在文件開頭,表明文件內(nèi)代碼完成的任務(wù)充石。

class TestConfig {
  TestConfig._();
  ///
  /// Environment variables are used to configure the tests.
  /// It's more convenient to modify the env than to change the code.
  ///
  /// They are just key value pairs like following:
  ///
  /// ------------------------------
  ///   username = "bob010"
  ///   password = "bob0100"
  ///
  ///   accountGroup = "login"
  ///   kycGroup = "login, kyc"
  /// ------------------------------
  ///
  /// Note: line 'accountGroup = "login"' means which tests should be run
  /// for [accountGroup] test group.
  ///
  static void loadEnv() {
    load();
    env.removeWhere((k, v) => Platform.environment.containsKey(k));
    env.forEach((k, v) => log('[ENV] $k = $v'));
  }

5 流程控制

5.1 條件參數(shù)的順序

涉及流程控制的話莫换,一般涉及條件判斷,條件判斷語句中的參數(shù)的順序赫冬。

if (number < 10) {} // A

if (10 > number) {} // B

if (receivedNumber < expectedNumber) // C

if (expectNumber > receivedNumber) // D

通常我們會選擇 A浓镜,C
那么應(yīng)該準從什么樣的尊則劲厌?

左邊傾向于變量膛薛,右邊傾向于常量;

5.2 if...else 語句塊的順序

可以參照下面的下面準則:

  • 先判斷正向邏輯的补鼻,再判斷負向邏輯
  • 先處理簡單
  • 先處理有趣的或者可疑的
  void _listenScrollPosition() {
    if (widget.bottomButtonText == null) return;
    ...
  }

5.2 避免使用三目運算符

三目運算符一定程度上能夠精簡代碼哄啄,減少代碼的行數(shù),但是卻存在另外一個缺點风范,即:不容易理解(雖然大學(xué)教材總會考這類題目咨跌,判斷執(zhí)行的順序和結(jié)果)

只在簡單場景下使用三目運算符。

5.4 函數(shù)什么時候返回

經(jīng)常我們編寫函數(shù)的時候硼婿,喜歡聲明一個變量用來存儲結(jié)果锌半,到所有的邏輯結(jié)束后返回這個變量作為函數(shù)的返回值。

  • 可以提前進行函數(shù)返回值寇漫,多幾個 return, 沒關(guān)系
  • 最好函數(shù)都要有返回值刊殉,Golang 里建議至少返回一個 錯誤信息

5.5 減少多層級的嵌套

層級的增多殉摔,增加了認知的負擔。而且容易出現(xiàn)不容易發(fā)現(xiàn)的 bug记焊。

如何減少嵌套:

  • 提前函數(shù)返回
  • 在循壞內(nèi)使用 continue

5.6 表達式

建議使用短表達式

if createParam.Data.ShopType == RegionEntrances {}
// 感覺表達式長了逸月,怎么做:

var shopTypeEqual = createParam.Data.ShopType == RegionEntrances

if shopTypeEqual {}

6. 重新組織代碼,持續(xù)迭代

有下面幾條準則:

不相干的任務(wù)遍膜,提取出來
一次只專注干一件事
梳理邏輯時碗硬,如果你能使用自然語言表述出來,對你寫出邏輯清晰的代碼很有幫助
單函數(shù)行數(shù)不宜過長 30 ~ 50 為佳瓢颅。再一個評判方法是恩尾,查看函數(shù)的內(nèi)容無需滾動鼠標進行翻頁。
少些代碼:每寫一行都需要維護惜索;不需要的功能特笋,砍掉,不需要的代碼巾兆,刪掉

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虎囚,隨后出現(xiàn)的幾起案子角塑,更是在濱河造成了極大的恐慌,老刑警劉巖淘讥,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圃伶,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒲列,警方通過查閱死者的電腦和手機窒朋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝗岖,“玉大人侥猩,你說我怎么就攤上這事〉钟” “怎么了欺劳?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铅鲤。 經(jīng)常有香客問我划提,道長,這世上最難降的妖魔是什么邢享? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任鹏往,我火速辦了婚禮,結(jié)果婚禮上骇塘,老公的妹妹穿的比我還像新娘伊履。我一直安慰自己袜漩,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布湾碎。 她就那樣靜靜地躺著宙攻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪介褥。 梳的紋絲不亂的頭發(fā)上座掘,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音柔滔,去河邊找鬼溢陪。 笑死,一個胖子當著我的面吹牛睛廊,可吹牛的內(nèi)容都是我干的形真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼超全,長吁一口氣:“原來是場噩夢啊……” “哼咆霜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嘶朱,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛾坯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疏遏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脉课,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年财异,在試婚紗的時候發(fā)現(xiàn)自己被綠了倘零。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡戳寸,死狀恐怖呈驶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庆揩,我是刑警寧澤俐东,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站订晌,受9級特大地震影響虏辫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锈拨,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一砌庄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦娄昆、人聲如沸佩微。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哺眯。三九已至,卻和暖如春扒俯,著一層夾襖步出監(jiān)牢的瞬間奶卓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工撼玄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留夺姑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓掌猛,卻偏偏與公主長得像盏浙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荔茬,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355