前言
编写代码,实质是在梳理逻辑,为了完善整个逻辑流程,我们借用编程语言的变量、函数、流程控制、循环、注释、方法等串接起来,完善一套系统的逻辑。
为了完善这套逻辑,我们借助了许多工具:设计方法、架构设计、项目组织等。
意识到没有,代码的好坏一定程度上可以从逻辑层面评判。
- 符合逻辑,不一定是最优的代码
- 不符合逻辑,一定不是好的代码
逻辑的串接靠的是编程语言的变量、函数、流程控制、循环、注释等。
一、 规范
绝大多数的人,不会从零完整的完成一个复杂的项目,大多是团队共同合作,完成一个大的项目。
这个时候,假如你是中途参与进来。你在实现逻辑的时候,你是照着自己的逻辑来还是依照团队的风格来。
比如项目组织,命名等...
按照团队的命名风格来
1.1 编程语言的规范
每门编程语言,都存在一定的规范,比如 Python 采用的下划线的变量命令规则,Go 则采用驼峰式的变量命令规则等。
Effective Dart: 代码风格
Kotlin 编码规范
另外,Dart语言默认80个字符换行,在团队中需要设置
100
个字符换行。
二、 命名
给变量,函数,方法命名时易于理解
- 专业的单词:使用领域内的单词
- 避免空泛的名字
- 具体的名字
- 变量名带上更多细节
- 不使用令人误解的名字
- 布尔值命名
2.1 领域内单词
天星银行的领域单词分为几大类
类型 | 不常见的单词 |
---|---|
account(账户) | biometric(生物识别)、faceID(面容识别)、touchID(指纹识别) |
deposit(存款) | timeDeposit(定期)、currentDeposit (活期)、 |
kyc(开户) | employment(职业) 、questionnaire(调查问卷)、liveness(活体检测)、document(证件) |
loan(贷款) | anti frau(反欺诈)、partial repay(部分还款)、credit(信用) |
personal(个人) | language(语言)、fileMaintain(资料维护)、promotion(促销) |
security(安全) | softToken(安全令牌)、remain(提醒) |
transfer(转账) | fps(转数快)、tupta(分享)、 |
2.2 避免空泛的名字
变量的命名一般要赋予一定的意义,极少情况下可以使用没有什么意义的单词。比如最常见的:
var i int
for (i=0;i<10;i++){
fmt.Println(i)
}
这种没什么意义的单词,一般适用于局部作用域。
2.3 具体的名字
完成什么任务就使用什么单词。一般变量使用名词居多,函数使用动词开头居多。
String toString() => json.encode(toJson());
/// 设备硬件支持的生物识别类型 (指纹,面容)
static Future hardwareDetectedBiometric() async {}
int pages = 0;
String userName = "";
2.4 带上更多细节
一般命名不建议过长,也不建议过短,最长三个单词的长度吧
如何带上更多的细节。
- 尝试使用后缀
- 尝试使用单位
- 尝试指向具体的细节
比如:
String currentFlowName;
VoidCallback onFinishLoad,
List otpTypes;
final BiometricType openedBiometricType;
几组对仗的后缀:
- max/min
- first/last
- begin/end
...
ServerCanStart() 不如 CanListenOnPort()
2.5 不使用令人误解的词
比如:Filter 在数据库操作中容易使用这个单词,这个单词没有带上更多的细节,实质上在使用的过程中,还是需要查看编写的SQL 语句等才能知道具体的过滤细节。整体思考多了几步。不易让人理解。
建议多读几遍自己命名的单词
2.6 布尔值
提到布尔值,因为就存在两种结果。所有,一般使用是否这样意思的词。
通常使用 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(恢复) |
make | launch(发动)、create(创建)、begin(开始)、open(打开) |
start | create、set up(建立)、build(建立)、generate(发送)、compose(构成)、add(添加)、new(新) |
2.8 带单位的值
static const int resultTimeoutInSecs = 10;
- 使用专用的单词 - 例如:不用 Get ,使用 Fetch、Download 。由上下文决定
- 避免使用空泛的单词 - tmp 和 retval 。除非有特定的理由
3 设计
好的源代码应当“看上去养眼”。如何使用好的留白,对齐及顺序来让你的代码变得更易读。
- 使用一致的布局,让读者很快习惯这种风格
- 让相似的代码看上去相似
- 把相关的代码分组,形成代码块
这里以设计的四个规范类比代码的组织。
3.1 对齐
编程语言为什么强调缩进?难道不是为了阅读代码的人更容易看懂代码吗?写代码的人更容易组织代码吗?仅仅是设计者为了好玩?
static Future 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);
」
- 放眼望去,确实知道实现什么任务
- 风格统一
- 整整齐齐
3.2 重复、亲密、比较、审美
最应该避免的其实就是写重复的代码,一般的做法往往是提炼,将重复的抽象出一个函数之类的。这里的重复,是风格的统一。
- 重复的代码使用方法去提炼
- 选择一个有意义的顺序,始终一致地使用它
- 把声明按照块组织起来
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';
...
}
可以结合比较下 student.go 和 teacher.go
这样的组织方式,讲道理,并不太会给阅读代码的人带来太多的认知负担。
3.3 留白
设计领域页面的设计,并不强调内容越多越好,恰当的在页面上留有空白,使整体设计有呼吸感。
那编程如何实现留白?
- 恰当的换行,使相似的内容更紧凑
- 提取,使用方法来组织不规范的东西
- 代码分段
假如你一个函数需要写 100 多行,不好意思,我可能建议你,不要这么做。
- 拆分,逻辑梳理、提取方法
- 尽量维持最长 30~50行左右(这样使屏幕能装载下,一次就能完成的阅读整个函数的逻辑)
static Future 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 什么时候需要注释
- 关键点
- 缺陷点
- 常量
- 全局注释
- 总结性注释
关键点:
有些时候,仅仅靠之前的“表面工作” 已经不能完全能够满足让人易于理解。这个时候需要在关键点添加注释。
缺陷点:
是的,承认自己的代码写的不是最优的,仅仅只是实现,还存在更优的办法,所以需要在有缺点的地方加上注释。
// TODO: move to remote config
static const String customerServicePhoneNumber = '9288321';
/// TODO: 更好的方案是:现有的图形上做动画,而不是重置动画。
@override
void didUpdateWidget(TotalAssetsWidget oldWidget) {
super.didUpdateWidget(oldWidget);
...
}
常量:
给常量注释,赋予了更多的意义。
///
/// Area codes
///
static const String areaCodeHK = '852';
全局注释:
一般在文件开头,表明文件内代码完成的任务。
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 条件参数的顺序
涉及流程控制的话,一般涉及条件判断,条件判断语句中的参数的顺序。
if (number < 10) {} // A
if (10 > number) {} // B
if (receivedNumber < expectedNumber) // C
if (expectNumber > receivedNumber) // D
通常我们会选择 A,C,
那么应该准从什么样的尊则?
左边倾向于变量,右边倾向于常量;
5.2 if...else 语句块的顺序
可以参照下面的下面准则:
- 先判断正向逻辑的,再判断负向逻辑
- 先处理简单
- 先处理有趣的或者可疑的
void _listenScrollPosition() {
if (widget.bottomButtonText == null) return;
...
}
5.2 避免使用三目运算符
三目运算符一定程度上能够精简代码,减少代码的行数,但是却存在另外一个缺点,即:不容易理解(虽然大学教材总会考这类题目,判断执行的顺序和结果)
只在简单场景下使用三目运算符。
5.4 函数什么时候返回
经常我们编写函数的时候,喜欢声明一个变量用来存储结果,到所有的逻辑结束后返回这个变量作为函数的返回值。
- 可以提前进行函数返回值,多几个 return, 没关系
- 最好函数都要有返回值,Golang 里建议至少返回一个 错误信息
5.5 减少多层级的嵌套
层级的增多,增加了认知的负担。而且容易出现不容易发现的 bug。
如何减少嵌套:
- 提前函数返回
- 在循坏内使用 continue
5.6 表达式
建议使用短表达式
if createParam.Data.ShopType == RegionEntrances {}
// 感觉表达式长了,怎么做:
var shopTypeEqual = createParam.Data.ShopType == RegionEntrances
if shopTypeEqual {}
6. 重新组织代码,持续迭代
有下面几条准则:
不相干的任务,提取出来
一次只专注干一件事
梳理逻辑时,如果你能使用自然语言表述出来,对你写出逻辑清晰的代码很有帮助
单函数行数不宜过长 30 ~ 50 为佳。再一个评判方法是,查看函数的内容无需滚动鼠标进行翻页。
少些代码:每写一行都需要维护;不需要的功能,砍掉,不需要的代码,删掉