优秀程序员的代码经验总结,技术小记

于是恶性循环开始了。日复一日,死代码最终会埋葬你的代码。这正是经典的破窗效应。

在过去的几个月里,我努力将这些元素提炼成10种编写代码的实践,重点是提高可读性和降低复杂性。不幸的是,许多人将这些视为过于琐碎。太基础了。但我向你保证,我遇到的每一段不好的代码都无法应用这些做法。每一点好的代码都可以找到这些实践中的一个(如果不是很多)。

标识符命名与定义

建议使用unix like风格给标识符命名,即单词用小写字母,每个单词直接用下划线“_”分割。

例如:text_mutextest_onekernel_text_address。

我们对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改。标识符的命名要清晰明了有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。

常见缩写:

buffer 可缩写为 buffcommand 可缩写为 cmdcompare 可缩写为 cmperror 可缩写为 errinitialize 可缩写为 initmaximum 可缩写为 maxmessage 可缩写为 msgparameter 可缩写为 paraprevious 可缩写为 prevregister 可缩写为 regtemp 可缩写为 tmp

示例

好的命名:

int error_number;int number_of_completed_connection; 

不好的命名:

int n;int n_comp_conns;

原则

  • 除了常见的通用缩写以外,不得使用单词缩写,不得使用汉语拼音
  • 代码中的命名均不能以下划线开始,也不能以下划线符号结束。
  • 禁止使用单字母命名变量,但允许且仅能定义ijkl作为局部循环变量。
  • 严禁使用未经初始化的变量作为右值
  • 全局变量应增加“g_”前缀,全局数组应增加“a_”前缀。

建议

  • 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。
  • 重构/修改部分代码时,应保持和原有代码的命名风格一致。
  • 常量和宏的定义应该全部大写,多个单词应使用“_”相隔。
  • 变量初始化必须赋予初值。

应该努力返回更有意义的值。理想情况下,最好是即使在反面情况下也能让调用者继续执行的值。如果真的是异常情况,那么最好用其他方式来通信,而不是使用null。

几乎所有代码的基础都是逻辑。通过逻辑可以进行决策,迭代和计算。常常会有逻辑分支或循环代码,从而导致深层嵌套的代码块。虽然这对于计算机执行没有什么问题,但会阻碍阅读代码者的阅读效率:代码看起来很复杂且难以理解。

图片 1image

虽然计算机很容易阅读这种代码,但对于人类则是非常大的精神负担。因此,代码会变得复杂、难以阅读。应该通过防御语句、提前返回或使用函数式编程等方式消灭嵌套代码。

这是一组编写代码的10个实践,重点是提高可读性和降低复杂性。我已经编写了20多年的代码。我与17个编写不同语言的团队合作,共建了数百个项目。这些内容包括从简单的博客站点到支持3,000个请求/秒的API,以及畅销应用程序。

规范制定说明

本套C语言编程规范为提高代码质量、便于维护、协同编码、可移植等特点而编写。要求所有参与编码人员要严格遵循本编程规范。

参考文献:

  • 华为C语言开发编程规范。
  • 阿里巴巴Java开发手册。

示例】 展示的代码片段,做参考使用。

原则】 在编写程序时必须严格遵守的规范。

建议】 在编写程序时需要认真参考的规范。

粗体】 在规范中特别强调的内容。

当然,如果哈勃望远镜要和古老的适配器连接,而后者返回一个意思不明的687,这种情况肯定需要注释来说明。但大多数其他情况下,你应该尽量重写代码使得它不需要注释也能看懂。

当你确定自己拥有大量代码时,就要随时识别,重新组合和重构这些代码。这个简单的过程能让你发现代码块的上下文和抽象级别,又能将代码正确地重构为更易读和更简单的块。

排版与格式

程序的可读性和可维护性除了编码好坏外,还要很重要的因素是编码时的排版与格式。应该注意排版风格一致、美观。

示例

int swiotlb_map_sg_attrs(struct device *hwdev, struct scatterlist *sgl, int nelems, enum dma_data_direction dir, struct dma_attrs *attrs){ struct scatterlist *sg; int i; BUG_ON(dir == DMA_NONE); for_each_sg(sgl, sg, nelems, i) { phys_addr_t paddr = sg_phys; dma_addr_t dev_addr = phys_to_dma(hwdev, paddr); if (swiotlb_force || !dma_capable(hwdev, dev_addr, sg->length)) { void *map = map_single(hwdev, sg_phys,sg->length, dir); if  { /* Don't panic here, we expect map_sg users to do proper error handling. */ swiotlb_full(hwdev, sg->length, dir, 0); swiotlb_unmap_sg_attrs(hwdev, sgl, i, dir,attrs); sgl[0].dma_length = 0; return 0; } sg->dma_address = swiotlb_virt_to_bus(hwdev, map); } else { sg->dma_address = dev_addr; } sg->dma_length = sg->length; } return nelems;}EXPORT_SYMBOL(swiotlb_map_sg_attrs);

原则

  • 程序块应采用缩进风格编写,每级缩进为1个tab(1个tab设为4个空格)
  • if、for、do、while、case、switch、default等语句独占一行。
  • 相对独立的程序块之间、变量说明之后必须加空行。

建议

  • 多个短语句不允许写在同一行内,即一行只写一条语句。
  • 任何运算符左右必须加一个空格。
  • 一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定。
  • 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符,后不应加空格。
  • 注释符(包括„/‟„//‟„/‟)与注释内容之间要用一个空格进行分隔。
  • 所有花括号必须独占一行,if语句即使一条表达式也要加花括号。

最后一条实践经验能给所有代码的可读性带来诗一般的润色,那就是对称性。这条来自Kent Beck的《实现模式》一书,书中说到:

4.使用对象

欢迎大家访问我的个人博客:吴佳轶 | WuJiaYi,第一时间获取最新的文章。

死代码

格式化可能会浪费太多精力,比如有很多标签与空格,格式虽然不是编程中的很重要部分,但是采用标准格式并让格式化能够自动化,你就可以重新专注编写代码。

程序效率

本章节后面所有的原则和建议,都应在不影响前述可读性等质量属性的前提下实施。不能一味地追求代码效率,而对软件的正确、简洁、可维护性、可靠性及可测性造成影响。

记住:让一个正确的程序更快速,比让一个足够快的程序正确,要容易得太多。大多数时候,不要把注意力集中在如何使代码更快上,应首先关注让代码尽可能地清晰易读和更可靠。

示例

int foo(){ if  { 异常处理; return ERR_CODE_1; } if  { 异常处理; return ERR_CODE_2; } 正常处理; return SUCCESS;} 

上面的示例代码看起来很清晰,而且也避免了大量的if else嵌套。但是从性能的角度来看,应该把执行概率较大的分支放在前面处理,由于正常情况下的执行概率更大,若首先考虑性能。应如下书写:

int foo(){ if  { 正常处理; return SUCCESS; } else if (概率比较大的异常条件) { 异常处理; return ERR_CODE_1; } else { 异常处理; return ERR_CODE_2; }}

除非证明foo函数是性能瓶颈,否则按照本规则,应优先选用前面一种写法。

原则

  • 通过对数据结构、程序算法的优化来提高效率。
  • 将不变条件的计算移到循环体外。

因此,你需要花上许多年去追求对称性。但是,一旦开始在代码中使用对称性,就会迅速呈现纯粹的形式,代码的形状也会迅速变好。

2.死代码

函数

函数设计的精髓:编写整洁函数,同时把代码有效组织起来。

整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。

说明:扇出是指一个函数直接调用其它函数的数目,而扇入是指有多少上级函数调用它。

设计高扇入,合理扇出的函数。扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出通常是3~5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。

扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。较良

static void calibrate_run_measurement_overhead{ u64 T0, T1, delta, min_delta = 1000000000ULL; int i; for (i = 0; i < 10; i++) { T0 = get_nsecs(); burn_nsecs; T1 = get_nsecs(); delta = T1-T0; min_delta = min(min_delta, delta); } run_measurement_overhead = min_delta; printf("run measurement overhead: %Ld nsecsn", min_delta);}

原则

  • 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
  • 避免函数过长,新增函数不超过50行。
  • 设计高扇入,合理扇出的函数。
  • 废弃代码(没有被调用的函数和变量)要及时清除。

建议

  • 一个函数仅完成一个功能。
  • 重复代码应该尽可能提炼成函数。
  • 函数的参数个数不超过5个。

这些项目从最简单的博客网站,到支持每秒3000多次请求的API,还有曾经热卖过的应用。

代码中的对称性是指在任何地方出现相同的想法。

关于注释

建议所有变量定义语句的上方使用单行注释加以阐述和说明此变量的作用,语言简洁、易懂。最好不要超过一行。

优秀的代码可以自我解释,不通过注释即可轻易读懂。优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。

示例

注释不能消除代码的坏味道:

/* 判断m是否为素数*//* 返回值:: 是素数,: 不是素数*/int p{ int k = sqrt; for (int i = 2; i <= k; i++) if (m % i == 0) break; /* 发现整除,表示m不为素数,结束遍历*/ /* 遍历中没有发现整除的情况,返回*/ if  return 1; /* 遍历中没有发现整除的情况,返回*/ else return 0;}

重构代码后,不需要注释:

int IsPrimeNumber{ int sqrt_of_num = sqrt ; for (int i = 2; i <= sqrt_of_num; i++) { if (num % i == 0) { return FALSE; } } return TRUE;}

示例

功能说明:

/* * lib/prio_tree.c - priority search tree * * Copyright  2004, Rajesh Venkatasubramanian <vrajesh@umich.edu> * * This file is released under the GPL v2. * * Based on the radix priority search tree proposed by Edward M. McCreight * SIAM Journal of Computing, vol. 14, no.2, pages 257-276, May 1985 * * 02Feb2004 Initial version */#include <linux/init.h>#include <linux/mm.h>#include <linux/prio_tree.h>

单行注释:

/* * Maximum heap_index that can be stored in a PST with index_bits bits */static inline unsigned long prio_tree_maxindex(unsigned int bits){ return index_bits_to_maxindex[bits - 1];}

原则

  • 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
  • 如果模块或函数有重大重构修改时应标明修改时间和修改内容。
  • 注释的内容要清楚、明了,含义准确,防止注释二义性
  • 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除
  • 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。
  • 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。

建议

  • 对于每个模块应使用块注释在模块上方说明作用和功能,语言简洁、易懂。
  • 建议不要在模块或函数内部加入过多的注释影响阅读。注释的作用是解释难以表达的抽象意图,而不是重复描述代码。
  • 建议注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。

这是一篇值得收藏起来,隔三差五就拿来重读的文章!因为作者向你保证,他“遇到的所有糟糕的代码,都是因为没采纳这些实践经验。而任何一段优秀的代码,都采纳了至少部分实践经验。”

出现三次重复,就要重构了。

时间: 2019-08-13阅读: 322标签: 经验

  1. 对称

这样不仅能让数据结构变得正规,还能容纳所有重复的、使用原始数据的重复的逻辑。

  1. 符合逻辑的方法返回值

代码中的对称性是说,同样的思想在任何地方都使用同样的实现。

所有那些没有注释的代码块,未使用的变量和无法访问的代码都是腐烂的。实际在表明:“没有人在关心这段代码”。因此,随着时间的推移,这些死代码将腐蚀你的代码库。这是经典的破窗理论。你必须寻找和销毁死代码。

根据这些经验,再结合我读过的书,我认为编程中最重要的是:# 可读性。#

6.命名

使用对象

根据这些经验,结合我读过的书籍,我已经确定了代码中最重要的东西:可读性。

我在BaseCode中写过这些详细内容,并将其应用到真实世界的代码片段中。

你的目标应该是返回更合理的值,理想情况下,即使在空的情况下,也需要让调用者的代码能继续进行。如果确实要返回null也要用更好的方式表达。

格式

7.忽视评论

对称性

时间: 2019-11-30阅读: 148标签: 代码

所有注释掉的代码块、未使用的变量和无法到达的的代码都是垃圾。他们就像在对读者说,“我不关心这段代码”。

  1. 三规则

不过说起来容易做起来难。对称性体现了编程的创造性。它是许多其他实践的基础:命名、结构、对象、模式等。不同语言之间、不同代码之间和不同团队之间对于对称性的定义都可能不一样。

当然,命名事情很难。有一个小技巧适用于编程:延迟命名,不要卡在命名上,只需保持不断编码。如果必须,将变量命名为句子;只需保持不断编码,我保证,当你完成功能或工作时,一个好名字将出现。

考虑一下数学上的序列。给出数字2并问你,“下一个数字是什么?”可能是3可能是4,但也可能是1或2.1。实际上你没办法知道。然后我提供了序列中的下一个数字2, 4然后问,“下一个是什么?”可能是6,8,也可能是16。

这是让我走上专注于可读性的道路,尽管我努力分享,但至少有一个人会讨厌我。你的代码别人有评论是绝对必要的,相反,如果你的代码别人没有评论可能就要重写。

这个简单的过程可以让你确定代码块的上下文和抽象级别,以便正确地找出代码的任务,并将代码重构到更加易于阅读、更简单的代码块中。

  1. 格式化

这个例子虽然跟编程没关系,但它告诉我们,我们不应该太早做抽象。三的原则能阻止我们过早消除重复的努力,直到有了足够多的信息后再做出决定。用Sandi Mets的话说,“重复的代价远远低于错误的抽象。”

尽管目前面向对象编程时代,我们仍然沉浸在原始的痴迷中。我们使用长参数列表、数据块和自定义数组/字典结构,这些可以重构为对象,这不仅形式化了数据结构,而且提供了原始数据所伴随的所有重复逻辑的基础。这是OO的好处。

就算是用一整句话命名一个变量都没问题,继续写代码就好。我可以保证,当你完成整个功能之后,更好的名字就会浮出水面。

这是直接来自肯特贝克的实施模式,它简单地指出:

我们在格式上消耗了太多精力。制表符还是空格,Allman还是KR。总会有一天,你会意识到格式在编程中并不是最重要的。

这说起来容易做起来难。对称性体现了写作的创造性。它可能因语言、代码库到代码库以及团队而异。因此,你可以花费你的自然生命来追求它。然而,一旦开始在代码中应用对称性,就会出现一个更纯粹的形式,并且代码会很快形成。

大型代码块

  1. 大块代码

嵌套代码

从表面上看,可读性似乎是主观的。在语言,代码库,团队之间可能有所不同。但是当你仔细观察时,所有代码中都有核心元素使它具有可读性。许多程序员离计算机太近了,如果只看代码运行,则没有其他问题。但是离人类就太远了。

删除注释

3.嵌套代码

表面上看来,可读性似乎很主观。不同语言、代码、和团队对于可读性的定义不尽相同。但如果深入本质的话,就会发现代码可读性有一些非常关键的因素。

同样,尽管猜对的可能性增加了,但还是不能确定。然后我提供了数列中的第三个数字,2, 4, 16,然后问“下一个是什么?”有了三个数字之后,程序员的大脑很容易看出这是个平方序列,于是确定下一个数字是256。这就是三的原则。

我已经写了20年代码了,在此期间曾与17个团队共事过,使用不同的语言做过数百个项目。

最近几个月, 我在努力将这些人为因素提炼成11条写程序的实践经验,专门讨论如何增强可读性并降低复杂度。

当然,好的命名很困难,但只是因为我们人为增加了难度。有个小技巧在编程的许多方面都能用得上,包括命名,那就是——延后。不要纠结某个东西的命名,继续写代码就好。

我们总是选择返回最奇怪的值,特别是对于边界条件的情况。像-1、687或null。然后就得写很多代码来处理这些值。实际上,null的创造者称它为“10亿美元的错误”。

可读性

逻辑几乎是一切代码的基础。我们写代码是为了做决策、迭代和计算。一般情况下都会导致分支或嵌套,从而造成嵌套得很深的代码块。

许多程序员太倾向于计算机了,只要程序能运行就一了百了。尽管是老生常谈,但这种方式完全断绝了人参与的可能性。

必须要找出并干掉死代码。虽然不需要把精力主要放在这里,但一定要时时留意。

三的原则

虽然没有具体的数字,但代码块的长度应该是有限制的。如果你认为你的代码块过大,就应该对其进行识别、重组并重构。

在我看来这一条至关重要,删了注释我才能把精力放到可读性上。不管我如何解释删除注释的必要性,总会有人跟我抬杠,然后举出一个绝对需要注释的例子。

命名规则

尽管现在是面向对象编程的时代,我们依然使用了过多的原始指令。

长长的参数列表,杂乱的数据,自定义的数组或字典结构等。这些都可以重构成对象。

合理的返回

许多人会认为这些太基础、无关紧要,可以忽视。但我可以向你保证,我遇到的所有糟糕的代码都是因为没采纳这些实践经验。而任何一段优秀的代码都采纳了至少部分实践经验。

还等什么?赶快看看这些经验就是什么吧?

选择一种格式,应用到代码中,然后将这个过程自动化。然后就可以重新专注于写代码本身了。

本文由澳门威斯尼人平台登录发布于Web前端,转载请注明出处:优秀程序员的代码经验总结,技术小记

相关阅读