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

引言:什么是好代码

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
The release of Java SE 8 introduced significant enhancements that impact the Core Java technologies and APIs at the heart of the Java platform. Many old Java idioms are no longer required and new features like lambda expressions will increase programmer productivity, but navigating these changes can be challenging., , Core Java® for the Impatient is a complete but concise guide to Java SE 8. Written by Cay Horstmann—the author of Java SE 8 for the Really Impatient and Core Java™, the classic, two-volume introduction to the Java language—this indispensable new tutorial offers a faster, easier pathway for learning the language and libraries. Given the size of the language and the scope of the new features introduced in Java SE 8, there’s plenty of material to cover, but it’s presented in small chunks organized for quick access and easy understanding., , If you’re an experienced programmer, Horstmann’s practical insights and sample code will help you quickly take advantage of lambda expressions (closures), streams, and other Java language and platform improvements. Horstmann covers everything developers need to know about modern Java, including, - Crisp and effective coverage of lambda expressions, enabling you to express actions with a concise syntax, - A thorough introduction to the new streams API, which makes working with data far more flexible and efficient, - A treatment of concurrent programming that encourages you to design your programs in terms of cooperating tasks instead of low-level threads and locks, - Up-to-date coverage of new libraries like Date and Time, - Other new features that will be especially valuable for server-side or mobile programmers, Whether you are just getting started with modern Java or are an experienced developer, this guide will be invaluable for anyone who wants to write tomorrow’s most robust, efficient, and secure Java code.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值