百度技术培训中心-编写可读代码的艺术

本文探讨了如何通过编码规范、良好的命名、注释和逻辑表达,提高代码的可读性和维护性。自解释性代码、命名技巧、减少嵌套和优化逻辑,是打造高效代码的关键。同时,遵循设计原则和重构策略,以避免烂代码的产生和维护复杂度的提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言:什么是好代码

  • 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的水平影响,不要受这个圈子影响,多看经典开源项目。

一定要知道好的代码应该是怎么样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值