前言
編寫代碼,實質(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)容無需滾動鼠標進行翻頁。
少些代碼:每寫一行都需要維護惜索;不需要的功能特笋,砍掉,不需要的代碼巾兆,刪掉