一、可读性的重要性

编程有很大一部分时间是在阅读代码,不仅要阅读自己的代码,而且要阅读别人的代码。因此,可读性良好的代码能够大大提高编程效率。

可读性良好的代码往往会让代码架构更好,因为程序员更愿意去修改这部分代码,而且也更容易修改。

只有在核心领域为了效率才可以放弃可读性,否则可读性是第一位。

二、用名字表达代码含义

一些比较有表达力的单词:

单词 可替代单词
send deliver、dispatch、announce、distribute、route
find search、extract、locate、recover
start launch、create、begin、open
make create、set up、build、generate、compose、add、new

使用 i、j、k 作为循环迭代器的名字过于简单,user_i、member_i 这种名字会更有表达力。因为循环层次越多,代码越难理解,有表达力的迭代器名字可读性会更高。

为名字添加形容词等信息能让名字更具有表达力,但是名字也会变长。名字长短的准则是:作用域越大,名字越长。因此只有在短作用域才能使用一些简单名字。

三、名字不能带来歧义

起完名字要思考一下别人会对这个名字有何解读,会不会误解了原本想表达的含义。

布尔相关的命名加上 is、can、should、has 等前缀。

  • 用 min、max 表示数量范围;

  • 用 first、last 表示访问空间的包含范围;

  • begin、end 表示访问空间的排除范围,即 end 不包含尾部。

四、良好的代码风格

适当的空行和缩进。

排列整齐的注释:

int a = 1;   // 注释
int b = 11;  // 注释
int c = 111; // 注释

语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致。

五、为何编写注释

阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。

不能因为有注释就随便起个名字,而是争取起个好名字而不写注释。

可以用注释来记录采用当前解决办法的思考过程,从而让读者更容易理解代码。

注释用来提醒一些特殊情况。

用 TODO 等做标记:

标记 用法
TODO 待做
FIXME 待修复
HACK 粗糙的解决方案
XXX 危险!这里有重要的问题

六、如何编写注释

尽量简洁明了:

// The first String is student's name
// The Second Integer is student's score
Map<String, Integer> scoreMap = new HashMap<>();
// Student's name -> Student's score
Map<String, Integer> scoreMap = new HashMap<>();

添加测试用例来说明:

// ...
// Example: add(1, 2), return 3
int add(int x, int y) {return x + y;
}

使用专业名词来缩短概念上的解释,比如用设计模式名来说明代码。

七、提高控制流的可读性

条件表达式中,左侧是变量,右侧是常数。比如下面第一个语句正确:

if (len < 10)
if (10 > len)

只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else;

do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。

如果只有一个 goto 目标,那么 goto 尚且还能接受,但是过于复杂的 goto 会让代码可读性特别差,应该避免使用 goto。

在嵌套的循环中,用一些 return 语句往往能减少嵌套的层数。

八、拆分长表达式

长表达式的可读性很差,可以引入一些解释变量从而拆分表达式:

if line.split(':')[0].strip() == "root":...
username = line.split(':')[0].strip()
if username == "root":...

使用摩根定理简化一些逻辑表达式:

if (!a && !b) {...
}
if (!(a || b)) {...
}

九、变量与可读性

去除控制流变量 。在循环中通过使用 break 或者 return 可以减少控制流变量的使用。

boolean done = false;
while (/* condition */ && !done) {...if ( ... ) {done = true;continue;}
}
while(/* condition */) {...if ( ... ) {break;}
}

减小变量作用域 。作用域越小,越容易定位到变量所有使用的地方。

JavaScript 可以用闭包减小作用域。以下代码中 submit_form 是函数变量,submitted 变量控制函数不会被提交两次。第一个实现中 submitted 是全局变量,第二个实现把 submitted 放到匿名函数中,从而限制了起作用域范围。

submitted = false;
var submit_form = function(form_name) {if (submitted) {return;}submitted = true;
};
var submit_form = (function() {var submitted = false;return function(form_name) {if(submitted) {return;}submitted = true;}
}());  // () 使得外层匿名函数立即执行

JavaScript 中没有用 var 声明的变量都是全局变量,而全局变量很容易造成迷惑,因此应当总是用 var 来声明变量。

变量定义的位置应当离它使用的位置最近。

实例解析

在一个网页中有以下文本输入字段:

<input type = "text" id = "input1" value = "a">
<input type = "text" id = "input2" value = "b">
<input type = "text" id = "input3" value = "">
<input type = "text" id = "input4" value = "d">

现在要接受一个字符串并把它放到第一个空的 input 字段中,初始实现如下:

var setFirstEmptyInput = function(new_alue) {var found = false;var i = 1;var elem = document.getElementById('input' + i);while (elem != null) {if (elem.value === '') {found = true;break;}i++;elem = document.getElementById('input' + i);}if (found) elem.value = new_value;return elem;
}

以上实现有以下问题:

  • found 可以去除;
  • elem 作用域过大;
  • 可以用 for 循环代替 while 循环;
var setFirstEmptyInput = function(new_value) {for (var i = 1; true; i++) {var elem = document.getElementById('input' + i);if (elem === null) {return null;}if (elem.value === '') {elem.value = new_value;return elem;}}
};

十、抽取函数

工程学就是把大问题拆分成小问题再把这些问题的解决方案放回一起。

首先应该明确一个函数的高层次目标,然后对于不是直接为了这个目标工作的代码,抽取出来放到独立的函数中。

介绍性的代码:

int findClostElement(int[] arr) {int clostIdx;int clostDist = Interger.MAX_VALUE;for (int i = 0; i < arr.length; i++) {int x = ...;int y = ...;int z = ...;int value = x * y * z;int dist = Math.sqrt(Math.pow(value, 2), Math.pow(arr[i], 2));if (dist < clostDist) {clostIdx = i;clostDist = value;}}return clostIdx;
}

以上代码中循环部分主要计算距离,这部分不属于代码高层次目标,高层次目标是寻找最小距离的值,因此可以把这部分代替提取到独立的函数中。这样做也带来一个额外的好处有:可以单独进行测试、可以快速找到程序错误并修改。

public int findClostElement(int[] arr) {int clostIdx;int clostDist = Interger.MAX_VALUE;for (int i = 0; i < arr.length; i++) {int dist = computDist(arr, i);if (dist < clostDist) {clostIdx = i;clostDist = value;}}return clostIdx;
}

并不是函数抽取的越多越好,如果抽取过多,在阅读代码的时候可能需要不断跳来跳去。只有在当前函数不需要去了解某一块代码细节而能够表达其内容时,把这块代码抽取成子函数才是好的。

函数抽取也用于减小代码的冗余。

十一、一次只做一件事

只做一件事的代码很容易让人知道其要做的事;

基本流程:列出代码所做的所有任务;把每个任务拆分到不同的函数,或者不同的段落。

十二、用自然语言表述代码

先用自然语言书写代码逻辑,也就是伪代码,然后再写代码,这样代码逻辑会更清晰。

十三、减少代码量

不要过度设计,编码过程会有很多变化,过度设计的内容到最后往往是无用的。

多用标准库实现。

参考资料

  • Dustin, Boswell, Trevor, 等. 编写可读代码的艺术 [M]. 机械工业出版社, 2012.

leader:你的代码太烂了我根本看不懂相关推荐

  1. 打开游戏要运行 19.8 亿次 if 语句?黑客嘲讽 RockStar 游戏代码太烂了

    贾浩楠 发自 凹非寺 量子位 报道 | 公众号 QbitAI 博客原文 https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by- ...

  2. 你以为这样写代码很6,但我看不懂

    来源 | 沉默王二 责编| Carol 封图| CSDN│下载于视觉中国 为了提高 Java 编程的技艺,作者最近在 GitHub 上学习一些高手编写的代码.下面这一行代码(出自大牛之手)据说可以征服 ...

  3. 你以为这样写Java代码很6,但我看不懂

    为了提高 Java 编程的技艺,我最近在 GitHub 上学习一些高手编写的代码.下面这一行代码(出自大牛之手)据说可以征服你的朋友,让他们觉得你写的代码很 6,来欣赏一下吧. IntStream.r ...

  4. 代码太烂,可能是他离职的原因吧!

    程序员的领域有句名言: 程序员都认为自己写的代码是最好的. 昨天,粉丝群里,一位小伙伴遇到了一个问题,让大家帮他看看(改别人的代码,已经离职了). 随之,大家要求把源码贴出来,小伙也就把部分源码贴出来 ...

  5. Advanced: Making Dynamic Decisions and the Bi-LSTM CRF(对官网发的代码的一些理解~,来自看不懂英文也没学过前置课程的小白两周的心血)

    1.前置小知识 1)log_sum_exp 这个是升级版的softmax,防止出现上溢或下溢,详见关于LogSumExp - 知乎 # Compute log sum exp in a numeric ...

  6. 嫌学校 App 太“烂”,极客父母做了开源版本,却遭官方报警?

    整理 | 孙胜 出品 | CSDN(ID:CSDNnews) 众所周知,校园管理系统大多Bug满满,且无比卡顿.其中有部分原因是学校IT预算有限,导致系统优化不够完善.但你能想到吗,有一个花费7.5亿 ...

  7. 40岁程序员写出租车一键下单电话叫车软件后被发帖骂骗子写得太烂

    俺从2004年开始入行,从asp写到c++..net.java.ruby.PHP.object-c.单片机:至今已经17年整. 老老实实写了17年代码,经历了"程序员只能吃青春饭,到了30岁 ...

  8. 嫌学校App太“烂”,极客父母做了开源版本,却遭官方报警

    出品 | CSDN(ID:CSDNnews) 众所周知,校园管理系统大多Bug满满,且无比卡顿.其中有部分原因是学校IT预算有限,导致系统优化不够完善.但你能想到吗,有一个花费7.5亿元研发资金的校园 ...

  9. Linus:Linux太烂,我要删了它,你们用Windows XP吧!

    一般技术大神可能都有点神经质,总是让人迅雷不及掩耳,无法理解一些事情. 这不前两天,就是是1月25号, Linux之父 Linus Torvalds 好像又有点情绪失控. 不过这次他没有骂人,他悄悄地 ...

最新文章

  1. Piranha实验总结
  2. 【电信增值业务学习笔记】8 3G视频类增值业务
  3. telerik 某些ajax拿数据方式下 load on demand 不起作用
  4. 支持向量机SVM算法原理
  5. 【C++设计技巧】C++中的RAII机制
  6. 第一章 基础知识---1.4Crack小实验--复现
  7. sql2005生成sql2000脚本的时候出现“User.UserType: NoLogin 不是SQL Server 2005 的有效选项“ 的解决方案...
  8. 金三银四产品人跳槽指南:这9本书帮你搞定升职加薪
  9. 从零开始学前端:过渡和动画 --- 今天你学习了吗?(CSS:Day20)
  10. dosbox运行C语言,[转载]dosbox的使用方法
  11. 商品信息SKU数据库设计
  12. opencv 双目测距
  13. Android App应用市场功能的框架图
  14. Photoshop中的标尺、参考线
  15. 相关系数|皮尔逊和斯皮尔曼
  16. 如何开启bios虚拟化
  17. 趣图:程序员睡不着数绵羊清单
  18. 【VMW】虚拟机安装小攻略
  19. windows服务器上无法运行bat文件,ad域用户bat脚本运行不了
  20. Collections.sort()方法对象排序

热门文章

  1. python opencv输出mp4_Python玩转视频处理(四):视频按场景进行分割
  2. redis查询所有key命令_想在生产搞事情?那试试这些 Redis 命令
  3. error C4668: 没有将“_WIN32_WINNT_WIN10_TH2”定义为预处理器宏,用“0”替换“#if/#elif”
  4. 使用Wireshark进行SIP包解析
  5. python linux运维教程 推荐_Linux运维人员成长之路学习书籍推荐
  6. python中什么是数据驱动_利用Python如何实现数据驱动的接口自动化测试
  7. 小学有学计算机课程,如何进行小学计算机课程有效教学.doc
  8. C# 有什么惊艳到你的地方?
  9. Flume-ng 高可用搭建-与测试
  10. cdn转发防攻击_高防CDN和高防服务器的区别?