大一学C语言时,我最不喜欢的事情之一就是帮其他同学调代码。如果只是不知道怎么实现一个功能倒还好说,但往往面临的情况是,知道程序运行的结果错了,不知道代码错在哪。不论是调试还是printf,至少要知道代码可能错在哪才好下手,这就到了最考验人耐心的时候了——看代码。
我们写的代码可谓是千人千面,我看别人的代码,仿佛是武汉人在听湖南人讲湖南话——明明说的都是中国话,怎么听不太懂呢;明明都是C语言,怎么看不太明白呢。如果大家都是一样的编程风格,我想问题应该能得到解决。当然,对于刚学C语言的我们来说,这只是一个美好的幻想——这个时候大部分同学应该还在为程序中各种各样的bug抓耳挠腮,无暇顾及自己编的代码是不是赏心悦目。
但问题一直存在,终究需要我们去解决,尤其是到了跳出一百来行代码的舒适圈,面对一个工程的时候。
老师在教我们汇编的时候就提到了这本书,然后我就把它下载下来,然后,就没有然后了。这恰好验证了书中提到的勒布朗法则:稍后等于永不(Laterequalsnever)。
这本书指出了很多程序员写代码时的通病,大大小小的问题使我们的代码看起来不够整洁,降低了代码的可读性——读书的时候我常常会想,这不就是说的我吗。
作者提到,写新代码时,我们一直在读旧代码,读与写花费时间的比例超过10:1。因此,为了节约我们和团队成员的时间,同时使编程的体验变得愉悦,写出整洁的代码是一个负责、优秀的程序员理应做到的。虽然这本书我大概只看了个皮毛,很多规范还需要通过实践来练习,但是从中也能领会到作者的一些想法了。下面就其中几点谈一谈我的收获:
一.关于命名
命名要名副其实,即,读者可以不通过另外的注释来了解这个变量/函数要做什么或者用来做什么。
命名要避免误导。比如:尽量不要用一些人们熟知的命名来承载我们创造的新意义;名字之间的差距要一目了然,避免阅读代码时还要浪费时间在“找不同”上;尽量避免用小写字母l和大写字母O作为变量名,因为它们看起来像是数字1和0······
命名时要做有意义的区分。避免a1、a2······aN式命名(我大一时偶尔会这样偷懒命名),这样的命名没有任何有用信息。
使用读得出来的名称。我记得老师上课时也提到过这一点,命名时要使用恰当的英语单词,而非意义不明的自造词。
使用可搜索的名称。这一点在我这段时间调程序的时候就有体会。单字母名称和数字常量很难在大段代码中找出来,它可能是某些文件名或其他常量定义的一部分,出现在因不同意图而采用的各种表达式中。名称长短应与其作用域大小相对应,单字母名称仅用于短方法中的本地变量,比如我们常常用i、j来表示循环结构中的计数变量。若变量或常量可能在代码中多处使用,则应赋其以便于搜索的名称。但只要短名称足够清楚,就要比长名称好。
二.关于函数
作者认为,好的函数应该有下面这些特点:
短小。短小的标准是:每个函数都一目了然。每个函数都只说一件事。而且,每个函数都依序把你带到下一个函数。
只做一件事。函数应该做一件事,做好这件事,只做这一件事。但作者同时也完全说出我的内心想法:问题在于很难知道那件该做的事是什么。要判断函数是否不止做了一件事,还有一个方法,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。只做一件事的函数无法被合理地切分为多个区段。
每个函数一个抽象层级。我们期望能够按照顺序阅读程序,而不是跳来跳去。这就要求每个函数后面都跟着位于下一抽象层级的函数,即被调用的函数应该放在执行调用的函数下面。
函数的形参要尽量少。这样既能减少阅读代码时细节上的负担,也能减少测试时可能带来的麻烦。
避免不同函数中反复出现相同的算法。重复的算法不仅使代码看起来臃肿,而且当算法改变时还增加了修改的难度——需要修改的地方越多,发生二次错误的可能性越大。
我感觉写出好的函数可能是我谈到的这几点里面最困难的一部分了。写出好函数难以一步到位,需要我们反复打磨。
三.关于注释
在作者看来,若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。因为很多程序员不能坚持维护注释,注释存在的时间越久,就离其所描述的代码越远,越来越变得全然错误。所以,尽管有时也需要注释,我们也该多花心思尽量减少注释量。带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样得多。与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。简而言之,就是好的代码才是主体,注释的作用应是锦上添花。
坏的注释包括程序员的喃喃自语、多余的注释、误导性注释、日志式注释和废话注释等。
好的注释可以提供基本信息、提供某个决定后面的意图、把某些晦涩难明的参数或返回值的意义翻译为某种可读形式和用于警告其他程序员会出现某种后果等。
另外,作者写到,有时可以用//TODO形式在源代码中放置要做的工作列表,TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。但我想这句话的意思并不是要为拖延症患者找理由——毕竟前面刚提到”Laterequalsnever”,更多的是一种规划。
作者还提到,直接把代码注释掉是讨厌的做法。其他人不敢删除注释掉的代码。他们会想,代码依然放在那儿,一定有其原因,而且这段代码很重要,不能删除。而我们在调试阶段常常会这么做,因为不确定这段代码之后是否还会用到,但我想最终发布的产品中是不应该出现这样误导他人的代码的。
四.关于格式
学C语言的时候,陈朔鹰老师就和我们强调过代码格式的重要性——虽然最后我好像就记住了要缩进,但什么时候需要缩进,我的回答大概是“跟着感觉走”。
好的代码格式应该是这样的:
垂直格式方面,通过空白行隔开独立概念,通过靠近的代码行表示紧密关系。另外,应该建立一种自顶向下贯穿源代码模块的良好信息流,即像函数部分提到的那样,被调用的函数应该放在执行调用的函数下面。
横向格式方面,应该尽力保持代码行短小,每行代码不应该超过120个字符。同时要合理运用空格符。比如,在赋值操作符周围加上空格字符,以此达到强调目的;不在函数名和左圆括号之间加空格,因为函数与其参数密切相关······
关于为什么需要缩进格式,作者提到:源文件是一种继承结构,而不是一种大纲结构。其中的信息涉及整个文件、文件中每个类、类中的方法、方法中的代码块,也涉及代码块中的代码块。这种继承结构中的每一层级都圈出一个范围,名称可以在其中声明,而声明和执行语句也可以在其中解释。要让这种范围式继承结构可见,我们依源代码行在继承结构中的位置对源代码行做缩进处理。程序员相当依赖这种缩进模式。他们从代码行左边査看自己在什么范围中工作。这让他们能快速跳过与当前关注的情形无关的范围,例如if或while语句的实现之类。他们的眼光扫过左边,査找新的方法声明、新变量,甚至新类。没有缩进的话,程序就会变得无法阅读。
另外,好的软件系统是由一系列读起来不错的代码文件组成的,它们需要拥有一致和顺畅的风格。即,团队任务需要制定相应的团队规则,虽然实现起来有些难度——因为每个人都有自己的个人特点和习惯,但确实是很有必要且长远来看是能大幅提高效率的。
纸上得来终觉浅,绝知此事要躬行。书看了,更多的东西还是要付诸实践才能体会。