引言:什么是好代码
-
WHAT
-
编码规范:最基本的要求,风格一致,符合业界惯例
-
可维护性:自解释性好,易于理解,结构清晰
-
-
HOW
- 基本要求,自解释,设计&思考,两顶帽子的工作方法
-
WHY
- 【表象—> 本质】
- 自解释:Don’t make me think、内在逻辑清晰明了、像与源码作者面对面交流、有品位、有格调;愉悦感。
1、命名:带上有效的信息
HOW
-
避免“泛化”词
所有数据都可以叫做“Data”;PersonalInfo? ImageContent?
-
增加信息量
带上单位:delay->delayMS,size->sizeMB
包含必要的解释: days -> daysSinceLastUpdate
具化到什么程度??【上下文作用域小可以泛化一些,否则尽量增加信息量】
-
用词有效【多琢磨,确保每一个词有意义,精准性】
多余的词 convertToString() -->toString()
有歧义的词 results = filter(objects,“year <= 2016”),这究竟是保留(select) 还是排除(exclude),不清楚。
-
约定俗成
循环变量:i、j、k
表示范围:【begin,end) 【first,last】
-
符合语感
类名、变量名:名词或者动名词 MemoryCache,ShoppingCart
函数、方法名:动宾结构 MemoryCache::fillCache(); ShoppingCart:: removeItem()
-
No Surprise !
getXXX(),setXxx():只表达简单操作,无副作用【不要去后台弄数据库】
成对API,参数含义要一致:
insert(uint32_t signs[], int n) 和 remove(uint32_t signs[], int n)首先n不符合规范,另外n的意思竟然不对称,极容易产生线上bug
-
测试用例
or in doc string
测试对象、场景简述、预期结果
Test_<Function>_<Expect>_<Situation>
2、注释:不是万金油
好代码 > 坏代码 + 注释
通过改善代码本身的质量去掉不应该有的注释
//Check to see if the employee is eligible for full benefits
if((emoloyee.flags & HOURLY_FLAG) && (employee.age > 65)) {
...
}
if(emolyee.isEligibleForFullBenefits()) {
}
什么是好注释??
- Dont repeat the codes! 【没有注释也能看懂】
//Account的类声明
class Account {
public:
//构造函数
Account();
// 设置Profit数值
void SetProfit(double profit);
// 返回Profit数值
double GetProfit();
};
-
有工具自动插入生成的,不要作没有意义的注释
-
为常量加注释【为什么取这个值】
理解设计意图,是否能够变更,是否合法
// 不少于一个人每天可阅读的RSS文章数即可
const int MAX_RSS_SUBSCRIPTIONS = 1000;
# 只要大于(2 * NUM_PROCESSORS)即可
NUM_THREADS = 8
- 解释HACK:【留下指引,此处有深意,否则容易踩坑】
vector<float> data;
void Clear() {
// 不直接使用data.clear的原因请参考:STL_swap trick;
vector<float>().swap(data);
}
- 巧用注解Annotation更好, (注释非线程安全)
// 提示可能陷阱
class Price {
public:
// 非线程安全
void inc_prices(int data) {
_price += delta;
}
}
Annotation 更好
@property(atomic, assign) int height;
注释的基本原则
-
好注释:what和why 是什么,为什么这么考虑,为什么取这个值
-
坏注释:how 我是怎么实现的(代码是白写的吗?),Useless无有效信息(说的都是废话)
3、逻辑【表达式书写】
左边?右边
书写原则:
- 变量与常量:变量在左、常量在右
【你超过18岁了吗? 18岁比你大吗?】 - 变量与变量: 小于号原则【约定:和数轴一致】,符合直觉。
if(length >= 10)
if(NULL != ptr)
while(received < expected)
- 临时变量+ 好的命名【将很难独栋的用变量和方法抽离】
if line.split(":")[0] == "root":
改为:
username = line.split(":")[0]
if(username == "root"):
if(lhs.min <= rhs.max && lhs.max >= rhs.min) {
}
改为:
bool isOverlap = (...);
if(isOverlap) {
...
}
或者
bool isOverlap = (...);
if(isOverlap(lhs, rhs)) {
...
}
- 关于循环,尽量用for循环,先想能不能用for表达出来。
Node* node = list->head;
if(node == NULL) return;
while(node->next != NULL) {
print(node-> data);
node = node->next;
}
if(node != NULL) {
print(node->data);
}
用for-loop表示:
for(Node* node = list-> head;
node != NULL;
node = node-> next) {
print(node->data);
}
- 循环注意点
for-loop 清晰地迭代关系和结束条件;
do-while 最易出错,需要小心检查边界条件
while和do-while: 需要小心控制退出条件,易死循环
4、结构:减少嵌套
嵌套太深: 异常太多,用if-return形式 最后做主流程,能突出
改为:if-return
结构:善用空行。【接口意思相近的放一起】
小结:
编码背后
为什么会有烂代码?
- 烂代码只是表象
- 代码是逐步腐化的
设计&思考缺失
- 思路不明确导致代码不明朗
- 层次、职责划分不明确
- 与现实需求不相符
工作方法
- 反复强调,反复发作
1、设计问题
var vote_changed = function(old_vote, new_vote) {
var score = get_score();
if(new_vote !== old_vote) {
if(new_vote === 'Up') {
score += (old_vote === 'Donw' ? 2 : 1);
} else if(new_vote == 'Down') {
score -= (old_vote === 'Up' ? 2 : 1);
} else if(new_vote = '') {
score += (old_vote === 'Up' ? -1 : 1);
}
}
set_score(score);
}
var vote_changed = function(old_vote, new_vot) {
var changedNum = 0;
if(new_vote === 'Down' && old_vote == 'Down') {
changedNum -= 1;
}
if(new_vote === 'Down' && old_vote == 'Up') {
changedNum -=2;
}
if(new_vote === 'Up' && old_vote == 'Up') {
changedNum += 1;
}
if(new_vote === 'Up' && old_vote == 'Down') {
changedNum +=2;
}
if(new_vote == '') {
changedNum += old_vote == 'Up' ? -1 : 1;
}
set_score(changeNum + get_score);
}
根本问题:没有理清业务逻辑:
拆分:投票计分值 + 改变计分算法【分清这是两件事情,一件是本质的投票计分定义,一件是改变计分算法】
var vote_value = function(vote) {
if(vote ==='Up') {return 1;}
if(vote === 'Down') {return -1;}
return 0;
}
var vote_changed = function(old_vote, new_vote) {
var score = get_score();
socre -= vote_value(old_vote);
socre += vote_value(new_vote);
set_socre(score);
}
常见设计原则
-
SOC 关注点分离(Separation of Concerns)
不同的知识点,放置在不同的部分迭代
-
SRP 单一职责(Single Responsiblity Principle)
- 【了解系统中每一个组件】
- 能用简单的一句话表明其职责
- 更有利于命名
- 良好设计的基础
-
DRY Do not Repeat Yourself 【消冗】
关键:提高自己?
-
在抽象层面思考;
-
多思考,谋定而后动
2、思考缺失
第一版:岁月静好
判断用户名是不是应该的用户名,不是返回无权限。
if($document) {
if($document['username'] != _SESSION['username']) {
return not_authorized();
}
} else {
return not_authorized();
}
PM:加入Admin管理功能
v1修改之后
$is_admin = is_admin_request()
if($document) {
if(!admin && $document['username'] != _SESSION['username']) {
return not_authorized();
}
} else {
if( !$is_admin) {
return not_authorized();
}
}
整理业务逻辑
1.你是管理员
2.你拥有当前文档(如果有当前文档的话),否则,无法授权。
按照这个逻辑重写
在日常迭代过程中,反复梳理业务逻辑,如果做了这个工作,按照这个业务逻辑写code
if(is_admin_request) {
// authorized
} else if($ document && document['username'] == $_SESSION['username']) {
// authorized
} else {
return not_authorized;
}
3、遗留代码
复杂度
复杂度==软件的生命周期
设计的本质:控制复杂度,内在复杂性 vs 实现复杂性。
复杂性随着时间逐步上升,每次跳着上升都是因为你在加功能的时候。
分模块和画流程本质都是把这个复杂度降下来。
重构技术
不改变程序外观行为的变更
可回归、易于自动化测试
重构时机
-
事不过三【相似场景的验证】
-
小步快走:持续集成 【反复迭代】
-
两顶帽子
交替执行,但是每次提交不要放在一起。
重构1:
目标:扫除新功能实现障碍。保证重构后,新功能非常好写。
如需要,补充测试
新功能:
纯粹新功能开发,新测试用例积累
重构2:
提升可读性、可维护性命名、风格、api微调
例子
1.接口改造项目
- 需求:
项目早期使用的是C-struct接口,现在想把它改成protobuf的
- 问题:
模块实现的时候,接受到的请求结构体,被到处通过引用访问。
如:Request中有个query字段,到处都是直接req.query的直接访问
- 做法:
处于上线的考虑,切换期间,需要兼容两套接口。
- 方案:
方案一:直接上
if(req.use_new_interface) {
_req.query()...
} else {
req.query
}
特征:需要对各种代码排查,grep确保不会漏改,访问到不存在的字段,可能在季度末因为工作努力获得勋章一枚。
方案二:我得先想想
先重构再完成需求
做法:
将C-struct封装到class内,所有对象都通过方法访问;
分离接口与实现,模块代码仅依赖接口
req.query -> req.query()
实现新的impl类,构造时切换
目的:
不是为了重构而重构,而是为了实现后续的功能。
注意点:
-
不要整个模块操作,分解在日常迭代中;如果一个team经常这样,那么经常变更的地方可能会出现越来越好的状态。
-
code base会逐渐平衡上来,处理遗留问题
if(is_new_interface) {
req = new ProtobufRequest();
} else {
req = new CSytleRequest();
}
小结
能力上:
编码能力、设计能力
对问题的理解程度
技术掌握:重构&持续集成
观念上:
腐化代码是结果不是成因
关键在平时【思考有没有更好的写法】
知识+技能+素养==写好代码
百度的要求
编码规范
- http://styleguide.baidu.com
- 覆盖主流语言
- 统一风格、编码建议
- 规范落地
- 自动检查:本地、后台度量
- Code Review、GoodCoder考试
- 自我追求
代码准入
基本:满足规范、通过CR
自我验收checklist
- 是否达到设计描述的验收标准
- 验收标准是否能用测试用例表达
- 测试用例是否可回归&自动化
- 是否满足编码规范&可维护性要求
- 重点审视API设计
为什么API这么重要?
API发布出去,不像内部模块,很难有机会再变动。
只有一次正确的机会,深刻理解结构,设计,背后的业务流程。
总结
- 编码技巧
- 命名、注释、逻辑、结构
- 自解释:Don’t make me think
- 一些常见的技巧、方法
- 工作习惯
- 设计水平
- 对问题的思考深度
- 重构&两顶帽子
- 公司要求
- 编码规范
- 代码准入checklist
推荐阅读
《编写可读代码的艺术》
《Code Complete》
《设计模式》
《重构-改善既有代码的设计》
经典开源项目
注意点
校招新人的代码水平很容易收到第一次进入团队的code base的水平影响,不要受这个圈子影响,多看经典开源项目。
一定要知道好的代码应该是怎么样的。