对于代码可读性的个人理解

8 views
Skip to first unread message

xish

unread,
Jun 17, 2014, 12:03:52 PM6/17/14
to scrumga...@googlegroups.com
近期一直在对项目组内一段很老的C代码进行修改,在修改的同时一并进行重构。之前没有单元测试的支持,因此补测试用例补的很痛苦。不过整个重构的过程总算还是在稳步推进中。根据这段时间以来重构的经验,想就程序可读性这个问题谈一下自己的想法,水平有限,就当是抛砖引玉吧。
程序为什么会难以理解?其实根本原因是在于计算机程序是给编译器(解释器)读的,只要机器能够理解编译器就让你按照功能去执行。你写的程序当然是要编译通过才能交付的,因此完工的程序编译器一定能理解。但是计算机程序所代表形式语言和自然语言毕竟是两个不同的东西,这样写出来的代码人并不一定能够理解,我们在面对一段陌生的程序的时候总是会把它默认为是一篇文章,然后使用平时读书的习惯去读程序。回想一下你在阅读的时候什么事情让你不爽?回读,冷僻字,从句连从句的长难句,这些现象在计算机程序中可以说是经常出现,我就举几个例子

1. 长循环
自然语言中更多的是直线式的思路,或许有分支的概念,但肯定没有循环的概念。你能想象一句话每次都需要反复读几遍才能理解意思么?但在程序中没有循环几乎是不可能的,除非你使用递归来实现,但没有良好的分割,递归会更难理解。
在实际阅读程序代码的过程中,我们看到循环的时候一般都会试图去整体性地理解它。比如说,某个while循环是遍历整个目录,某个for循环是在遍历一个读入内存中的数据库表。某两个嵌套在一起的循环是在遍历一个矩阵或者地图信息什么的。因此整个循环的总长度最好不要太长。过长的循环会让阅读者需要反复翻页来确认前后代码内容,增加了理解的难度。

2. 长分支,else,分支嵌套
分支语句中的处理代码稍微长一点其实还好,但是多路分支,每个都很长就会让人受不了——分支有点像自然语言中的从句,而长分支就相当于长难句,对于人来说,理解长难句往往需要反复地回读句中的每个部分,要剖析语句中的成分,这往往是一个很痛苦的过程。但再长的从句一页(一屏)总能放下,长分支还往往需要滚屏阅读……因此理解长分支要很多时候是需要注释的。else和分支嵌套就更痛苦了,else没有进入循环的条件,要到前面去查,分支嵌套的话,等于从句套从句,而且某个特定分支会特别地长,这些都会打断人的理解顺序,影响对整个程序的理解。

3. 循环中的退出条件控制
循环的话,总会有循环变量和终止条件,由于continue和break的存在,循环的退出点不一定是一个。这个东西很微妙,用得好能够提高代码的可读性(例如可以在检查异常情况的时候使用,用来减少长分支),但用得不好就会对代码的可读性/可维护性造成影响,原因?尾巴,比如一些需要释放的内存,或者算法、业务逻辑上要求回归的状态。break很容易,但这些需要恢复的状态却很容易漏,漏了很难查出来,而且放在那里,无形中又是重复语句,增加代码长度,也容易产生长分支。

4. 复杂的判别式和表达式
其实这也是“长难句”的一种,理解不易,但一般这种东西都是程序逻辑上必须的,不是很好避免,不过可以通过重构方法中的Extract method(提取方法)来规避,提高其可读性。

5. 变量名,临时变量
程序语言的语法都是不复杂的,记忆起来也容易。对于某种语言的程序员而言,你一定是了解其中的语法的,因此语法、关键字、甚至与库函数都不会是理解程序的难点,难点在于程序员自己原创的东西——各种自定义变量和函数。对于以一个并不在项目组内的程序员来说,这些自定义变量和函数的名称就是文章中的冷僻字,需要“查字典”才能理解其含义。但库函数可以查字典,自定义变量的含义只能依靠变量名和上下文去理解。于是为了理解这些变量的意义,往往需要反复回读以前的代码。起个容易理解的名字和注释确实有用,但个人编码的实际观感告诉我,仅仅做到这些仍旧是不够的,最好的方法是,让变量参与到与其他变量的互动(程序的运作)中去,在实际运用的过程中渐渐地被阅读者所理解。这个非常像学英语生词,死记能短时间内记住很多,但一会儿就忘,只有在例句中才能真正被理解。

于是乎,如何提高程序的可读性呢?其实总的原则很简单,尽可能地减少程序语言中那些不符合自然语言习惯的东西。对于这一点,我个人的建议是这样的。

1. 尽可能使用静态内存分配,在遇到对于内存空间需求不定的情况时,可以考虑使用容器。
对于C/C++程序员来说,动态内存分配会带来很多麻烦,为了避免内存泄露,所有动态分配的内存都需要及时释放。这样你就不能愉快地在循环体以及子函数中使用break等语句了。有类的情况下通过析构函数能解决不少问题,因此C++对动态内存的支持要好不少。
java等有回收机制的语言要好一些,但是内存回收是有代价的,可以的情况下,还是减少频繁第申请新的内存空间。

2. 尽可能地裁剪功能,把较长的循环体缩短
长循环多数都是功能太复杂造成的,通过必要的分割和重构手段(最常用的就是提取新方法),可以把很长的循环体有效地分割成几个功能独立的模块,从而缩短原循环体的长度。

3. 利用break和continue来处理异常情况,避免多路分支语句和else语句
很多多重分支语句都是为了应对异常情况的,包含一些很简单的出错处理语句(例如记日志,启动告警模块等),写在循环体和子函数中,如果使用多路分支语句(if...else if ...else)会给人一种“这是主要程序逻辑”的错觉。一个简单的if加上break,return等语句,能够很好地避免这种情况的发生。

4. 利用常量数组(容器)来使用循环替代多路分支。
当待判断的内容很单一很有用的一个小技巧。最常见应用的就是给定一个月份,输出这个月有几天。还有对寻找一个矩阵中上下左右四个单元的值,其增量也可以放到一个常量数组中去。当然这是应对数值连续的情况,在面对数值有中断的时候(比如说数字金额转大写,需要涉及0~9,10,100,1000等数字的汉字表达),可以考虑使用容器。

5. 对一些复杂,但又常见的程序逻辑,尽可能形成定式
这个想法其实也是从人学习自然语言的角度联想开的。人学习的过程其实就是重复的过程,在不断重复的过程中,人自然会越来越容易理解旧有程序的含义,比如说,递归扫描一个文件系统的目录结构,一般用深度优先搜索去做。我个人的编程习惯会把这个递归函数分成两部分,前面一个if写终结条件,然后在后面写一个while或者for循环,在循环体内进行一些操作(准备递归使用的变量)然后递归调用函数。类似的设计深度优先搜索算法的问题都可以用这种格式写,这样一是方便后来者阅读,二也是方便自己总结和重构。

6. 一些实现同一层次,但因为元数据不同而进行多路分支处理的长程序段,可以考虑使用多态-工厂模式解决多路分支的问题。
这也是设计多态和工厂模式的初衷,通过这样做,把多路分支的细节隐藏起来。不过这样做代价和工作量都比较大,有时还需要很精妙的设计,需要斟酌考虑后再下决定。

一些未能解决的问题和疑惑
最主要的问题就是发现如果重构一段代码时间太长了,就不知不觉中会进行过度设计,怎样避免这种情况的发生?

xish

unread,
Jun 17, 2014, 12:04:44 PM6/17/14
to scrumga...@googlegroups.com
顺便再问一下,我的eclipse一直没有用vi插件,有啥好用的vi插件么?

在 2014年6月18日星期三UTC+8上午12时03分52秒,xish写道:

Joseph Yao

unread,
Jun 17, 2014, 9:37:20 PM6/17/14
to scrumga...@googlegroups.com, agiles...@googlegroups.com
先转到agileshanghai组. 讨论代码可读性, 这个话题我喜欢, 回头找时间好好理解邮件的内容, 再回复. :)

Eclipse中的vi插件, 我记得有个叫vrapper, 还算可以用吧. 我的开发环境已经转到IntelliJ上面了, 真心比Eclipse要好用一点啊. (当然Eclipse也算是不错了, 就是细节上做的差一点)

--
--
您收到此信息是由于您订阅了 Google 论坛“Scrum Gathering”论坛。
要在此论坛发帖,请发电子邮件到 scrumga...@googlegroups.com
要退订此论坛,请发邮件至 scrumgatherin...@googlegroups.com
更多选项,请通过 http://groups.google.com/group/scrumgathering?hl=en 访
问该论坛
网站: http://scrumgathering.cn
微博: http://weibo.com/scrumgathering

---
You received this message because you are subscribed to the Google Groups "Scrum Gathering" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scrumgatherin...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

hkliya

unread,
Jun 17, 2014, 9:52:19 PM6/17/14
to scrumga...@googlegroups.com, agiles...@googlegroups.com
IntelliJ比Eclipse哪里才只好一点啊。哪天一起pair交流下啊。

-- 
hkliya
Sent with Sparrow

Joseph Yao

unread,
Jun 17, 2014, 9:58:14 PM6/17/14
to scrumga...@googlegroups.com, agiles...@googlegroups.com
“好一点” 只是个通用的说法了. :) 不过, IDE的交流和这个有关代码可读性的讨论放在一起, 感觉有点跑题了. 需要的话, 再新开一个讨论好了. 

Pair交流是个好主意. 如果是远程Pair的话, 建议装一个Screenhero, 然后用我这个邮箱加好友就可以了. :)

赵然

unread,
Jun 17, 2014, 11:33:54 PM6/17/14
to agiles...@googlegroups.com, scrumga...@googlegroups.com
《The Art of Readable Code》也是非常好的一本书,用大量代码片段演示了在各种情况下如何写出可读代码,Terry翻译了中文版http://book.douban.com/subject/10797189/


--
您收到此邮件是因为您订阅了Google网上论坛中的“agileshanghai”论坛。
要退订此论坛并停止接收此论坛的电子邮件,请发送电子邮件到agileshangha...@googlegroups.com
要查看更多选项,请访问https://groups.google.com/d/optout



--
================================= 

【扫描二维码进行下载】 


上海水渡石信息技术有限公司

赵然
地址:
上海市徐汇区桂平路680号创业园31号二层
手机:15921440864

xis...@gmail.com

unread,
Jun 18, 2014, 12:19:40 PM6/18/14
to agiles...@googlegroups.com, scrumga...@googlegroups.com
谢谢推荐,我一定找机会看看。

在 2014年6月18日星期三UTC+8上午11时04分30秒,ZHAO Ran写道:

--
您收到此邮件是因为您订阅了Google网上论坛中的“agileshanghai”论坛。
要退订此论坛并停止接收此论坛的电子邮件,请发送电子邮件到agileshanghai+unsubscribe@googlegroups.com
要查看更多选项,请访问https://groups.google.com/d/optout

hkliya

unread,
Jun 18, 2014, 8:07:50 PM6/18/14
to scrumga...@googlegroups.com, agiles...@googlegroups.com
还有”Clean Code”(代码整洁之道)也很好啊。

-- 
hkliya
Sent with Sparrow

Reply all
Reply to author
Forward
0 new messages