【华为云技术分享】程序员真香定律:源码即设计

咱们常常谈论架构,讨论设计,却甚少关注实现和代码自己,架构和设计当然重要,但要说代码自己不重要,我不一样意,Robert C.Martin大叔也不一样意,Martin认为“源码即设计”。linux

在讨论具体的实施细则以前,咱们不妨讨论一下什么是好代码?萝卜特 C.Martin认为:衡量代码质量的惟一标准是:WTF/min,也就是review代码的时候每分钟说“握草”的次数。这个定义虽有辱斯文,但粗野中不失奔放,调皮中又蕴含哲理。nginx

好的代码如同文笔优美的散文,行云流水,赏心悦目,阅读的时候,如沐春风,带给人愉悦与启迪。c++

好的代码犹如构思精巧的小说,它或许不够平铺直述,却足够引人入胜,读到最后,你会豁然开朗,我去,原来是这样的啊,那一刻,你会以为过程当中的曲折和探索都是值得的。程序员

好的代码,透过一个个函数,你仿佛能够窥视到做者有趣的灵魂;透过一行行代码,你仿佛在与一个充满智慧的朋友聊天,她老是条理清晰,逻辑严谨,有条不紊,娓娓道来。编程

而坏的代码,犹如病毒,它不只瘫痪你的程序,还有很强的传播效应,等到它扩散开来,神仙难治。安全

坏的代码,像一个泥团,阅读的时候,你仿佛被困于黑暗的迷宫,又仿佛在跟一个絮絮不休的人交谈,她的脑回路常常短路,说话含混不清,主次不分,叨逼半天,你依然get不到她的中心思想,你经常感受智商受到了莫大的侮辱,你面露艰难神色,心中万马奔腾。架构

有不少区分好坏代码的规则,我也看过一些,对于文章中提到的一些标准作法,就不重复嚼舌头根子了,我想结合本身的工做经历,谈一谈本身的切身体会。框架

闲扯半日,言归正传,要编写弥漫好味道的代码,要遵循哪些约束和指引呢?模块化

 

一致性

锲而不舍的听从一致性规则,在代码风格上,争论个三天三夜估计也定不出个好坏出来,但好的风格必定是强一致性的,这一点应该比较容易达成一致吧?风格的好坏其实更多受习惯的影响,头发少一点的程序员应该都有本身风格变迁的经历,多年前本身笃信不疑的good style或许正是当前深恶痛绝的bad style,因此我主张在style上搁置嘴炮,一个项目应该有一个编码规则,好的规则应该是以理服人的,好的规则应该是拒绝任性夹带私货的,规则定了以后,就遵守执行吧,可能某个风格跟你不相符,但不要紧,你要知道,这并不意味,你在style之战败下阵来,也并不表示它说服了你,你遵照的是规则和纪律自己。函数

变量(包括文件、类/结构体、函数)命名,好比ohmygod,你可能搞不清哪些字母是一伙的,因此须要界定单词。驼峰经过单词首字母大写来界定单词,另外一个惯用作法是用下划线拼接单词。驼峰的弊端是丑,下划线拼接的弊端是增长了标识符长度(相比首字母大写),好处是跟std c/c++、linux kernel的作法一致,喜欢kernel的码农容易找到如家般的归属感。

c++有namespace避免冲突,c常常用prefix防止命名污染全局空间,但我认为命名简洁扼要很重要,因此我支持简短的前缀而反对冗长的前缀。

 

代码密度

实现一样的功能,你喜欢100行代码,仍是20行代码?若是贵leader不以代码行数考核绩效我建议你把代码写的精简,而若是贵leader以代码行数考核绩效,我建议你转行,开滴滴,送外卖或者摆摊都行,由于在这样的leader下面耗费青春基本上也不会有什么发展前途。

把简单的东西搞复杂化很容易,你只须要找一个能力平庸的人就能实现化简为繁的愿望,而化繁为简则堪称化腐朽为神奇。也许你要说,我欠缺简化的能力,这并不奇怪,坦白讲,这不是一件容易的事,你作不到不要紧,但你拥有正确的理念更重要,它将帮助你认清前进的方向,而不是在错误的道路上越走越远。

有些项目,充斥各类无效代码,其实你只须要稍加思考,你就能识别出来。

好比大块注释掉的代码像发臭的尸体同样遍及其中。好比大量功能重复的代码像垃圾同样堆砌在那里。好比本不须要返回值的函数恒定的返回true。

又或者函数一进来,无论三七二十一,对入参一顿检查,全然忘记你在编写的是一个私有实现函数,你在调用它以前已经检查过一遍,私有函数是一个受控的安全上下文,这不只不优雅并且不绿色(低效耗电)而且不安全(在该崩的时候没崩把雷埋到了更隐蔽的地方),话说你看标准库函数strcpy/strcat,vector operator[]检查传参了吗?

提升代码密度或者说浓度有利于理清思路,有利于突出重点,有利于提升维护性,而充斥各类无效语句的代码只会把关键语句淹没在汪洋大海,使得review代码的人get不到重点,看不清主次。像听一个絮絮不休的人作报告,满篇废话,像看一个剧情拖沓的连续剧,昏昏欲睡,像喝一瓶二锅头兑十斤白开水,口能淡出个鸟来。

重构是程序员的口头禅,重构是在保持程序功能不变的状况下调整架构和实现,我认为提升代码密度应做为重构的一项追求。

linux kernel、lua、nginx、skynet这些优秀的开源库代码浓度都很高,建议读者朋友品尝一下。

 

封装

咱们最常干的一件事就是把重复编写的代码封装到一个函数里去,用多处调用替代重复编写,这个很好理解,但其实即便不被多处调用,把相关的一段代码封装到一个实现函数也是有必要的,由于把代码平铺开来,把细节暴露出来,容易掩盖重要的东西,即框架和脉络会变得不够清晰。

一个见名知义的函数调用比堆砌在那里的一段代码给个人感觉好,我若是关心它是怎么作的,我能够跳转到定义看看实现。

封装的一个核心原则是单一职责,符合单一职责的函数更易于被复用。

 

避免特例

linus大神分享过他心中的好代码,说的是针对链表的操做,他更喜欢统一性的处理方式,而不是作特例化的处理,我想这个例子颇有表明性,它其实表明一种理念,那就是自始至终,咱们的头脑里必须优先考虑normal化的处理方式,固然这实际上是一个比较高层次的要求,菜鸟互啄能够先跳过这一层要求。

 

缩写

慎用缩写,相比缩写带来的含混不清,我宁愿多敲几下键盘,若是要缩写请符合惯例听从常规,好比AI,好比App,好比cfg,可是你把threshold缩写成threshod,把Item缩写成Iem,我特木真的搞不懂这是拼错了仍是缩歪了?

 

解耦

构建松散耦合的系统一直是软件工程的一个目标,模块化的一个方向即是解耦,但咱们口口声趁心心念想的解耦,在实施层面又有几分体现呢?

好比,我常常干的一件事就是把相似配置文件,或者宏定义的东西集中的一个头文件里去,看起来很统一也很正规,起码我以前也是这样认为的,但突然有一天,发现本身这样作显得很不聪明的样子,为何呢?由于你想把全部模块配置相关的东西都塞进配置公共文件真的合适吗?是否是把公共接口抽离出来更好,把配置相关的数据下沉到各模块更合适?

另外,把宏都定义到一块儿,这意味随便改点东西,都会须要修改宏头文件,而这个头文件就会成为程序世界的中心,修改公共宏文件几乎会引发整个系统的全部源文件rebuild,这简直就是AOE团灭啊。因此更好的方式是分而治之,去集中式。

咱们知道c/c++的编译单元是source file(.c/.cpp),编译的第一步是预处理,全部include都会展开替换,因此咱们要避免引入任何没必要要的头文件,也应该把本编译单元用到的头文件都include进来,这就是所谓的头文件自给自足。这点很重要,但不少人会不觉得然,甚至有些人会自做聪明的搞一个allincluded.h,把经常使用的一些头文件所有include进来,而后自认为一劳永逸的完美的解决了问题,包含没必要要的头文件会增长编译时间,会增长依赖,咱们不只应该避免错误的包含,还应该精心设计和划分文件,使得每一个文件的功能足够内聚单一。

 

听从标准

我遇到每一个模块单独定义本身的各类原生(build-in)数据类型,但我建议不要这样作。若是你只是须要解决不一样体系结构下long等整型的长度差别,我想告诉你,c库头文件stdint.h已经从标准层面统一解决了这个问题,里面int8_t/16_t/32_t/64_t,还有uint8_t等等应有尽有。

 

宏是c的一个有效武器,在有些状况下确实行之有效,关于宏,我是骑墙派,我既反对禁用宏,也反对滥用宏,inline能够部分替代宏,但不能彻底替代宏。

若是项目里处处都是宏,全大写,至少1/3的代码都是各类诡异的宏,你review代码的时候,不停的跳来跳去,看了一眼,哦,就这样啊,而后切回来,频繁的上下文切换是低效的,它打断了你的思路,其实不少时候彻底没有必要。

 

命名

命名有一些指引,好比类/结构体应该用名词,函数应该用相似动词或者doSomething这样的动宾结构,这些规矩都是耳熟能详的。

我主张命名应该简明扼要,不要罗里吧嗦,要准确的表达出它要作的事情,若是你碰到命名困难,你可能须要考虑你的类定义或者接口划分是否合适。

命名是接口的一部分,很重要,好的命名是自注释的。

我反对匈牙利命名法,理由:不能一致性的解释各类类型,把类型编码进变量不合理,变量名自己就能体现它的类型,没法适用template状况,始做俑者ms放弃了它。

若是你没有思路,那我建议你参考一下STD C/C++ API,毕竟这些接口历经几十年没有大的变化,算是经受住了历史的考验,好比malloc/free/atoi,stl 容器的成员函数也有点意思:size()、 capacity()、resize()、reserve()、push()、pop()、top()、back(),很干脆,不废话,我以为很好。

因此,若是你编写的是某某管理器,好比ItemManager,我建议你直接取名add(),remove(),而不用AddItem(),RemoveItem(),由于你自己就是Item的Manager,操做的必然是Item,并且从参数上也能体现出来,少便是多,多不如少。

 

扩展性

开闭原则是应对扩展性的rule,人无远虑必有近忧,说的是咱们不能局限于眼前,但也请不要过分设计,不可盲目迷信扩展性,戏太多也是病。

知乎有一篇神贴讲的是如何把helloworld搞成一个big project,当你想给别人项目挑刺的时候,你能够用扩展性说事,但我建议你离开口闭口扩展性的人远一点,据我观察,这种人大多比较虚伪并且很水。

 

高效而鲁棒

有不少避免运行低效的作法,好比哈希、减小拷贝、提升局部性、buffer/cache、空间时间置换、内联、分支预测、判断前置、计算延迟、无锁编程。

提升鲁棒性的关键是保持简单,任何引入复杂性的动做都须要保持足够警戒。不信任/零信任设计,面向failed编程,假设依赖的上下文,上下游都是不可靠的,去中心化去关键路径,熔断,降级,避免惊群效应,方法不少,不一一列举了。

 

做者:华为云专家  人民副首席码仔

相关文章
相关标签/搜索