艺术与编程
编程是一种创造性的工作,是一门艺术。精通任何一门艺术,都需要很多的练习和领悟。
王垠(作者)根据自己的编码经验,总结了以下内容,欢迎探讨。(原文删减版)
原文链接:http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
反复推敲代码
代码量与编程水平没有直接关系。如果总是匆匆写出代码,却从来不回头去推敲,修改和提炼,其实是不可能提高编程水平的。好的程序员,他们删掉的代码,比留下来的还要多很多。如果你看见一个人写了很多代码,却没有删掉多少,那他的代码一定有很多不足。
优秀的代码是不可能一蹴而就的。就算再厉害的程序员,也需要经过一段时间,才能发现最简单优雅的写法。有时候你反复提炼一段代码,觉得很完美了,可是过几个月后,又会发现好多可以改进和简化的地方。这跟写文章一模一样,回头看几个月或者几年前写的东西,你总会发现一些改进。所以,如果反复提炼代码已经不再有进展,那么你可以暂时把它放下。过几个星期或者几个月再回头来看,也许就有焕然一新的灵感。这样反反复复很多次之后,你就积累起了灵感和智慧,从而能够在遇到新问题的时候直接朝正确,或者接近正确的方向前进。
写优雅的代码
人们都讨厌“面条代码”(spaghetti code),因为它就像面条一样绕来绕去,没法理清头绪。那么优雅的代码一般是什么形状的呢?经过多年的观察,我发现优雅的代码,在形状上有一些明显的特征。
如果我们忽略具体的内容,从大体结构上来看,优雅的代码看起来就像是一些整整齐齐,套在一起的盒子。如果跟整理房间做一个类比,就很容易理解。如果你把所有物品都丢在一个很大的抽屉里,那么它们全都混在一起。你就很难整理,很难迅速的找到需要的东西。但是如果你在抽屉里再放几个小盒子,把物品分门别类放进去,那么它们就不会到处乱跑,你就可以比较容易的找到和管理它们。
优雅的代码的另一个特征是,它的逻辑大体上看起来,是枝丫分明的树状结构(tree)。这是因为程序所做的几乎一切事情,都是信息的传递和分支。你可以把代码看成是一个电路,电流经过导线,分流或者汇合。如果你是这样思考的,你的代码里就会比较少出现只有一个分支的if语句,它看起来就会像这个样子:
if (...) {
if (...) {
...
} else {
...
}
} else if (...) {
...
} else {
...
}
注意到了吗?在我的代码里,if语句几乎总是有两个分支。它们有可能嵌套,有多层的缩进,而且else分支里面有可能出现少量重复的代码。然而这样的结构,逻辑却非常严密和清晰。在后面我会告诉你为什么if语句最好有两个分支。
写模块化的代码
模块化并非将代码放到多个文件或目录中,模块化更多指的是逻辑意义上的分块儿。一个模块应该像一个电路芯片,它有定义良好的输入和输出。实际上一种很好的模块化方法早已经存在,它的名字叫做“函数”。每一个函数都有明确的输入(参数)和输出(返回值),同一个文件里可以包含多个函数,所以你其实根本不需要把代码分开在多个文件或者目录里面,同样可以完成代码的模块化。我可以把代码全都写在同一文件里,却仍然是非常模块化的代码。
想要达到模块化,需要做到以下几点:
- 避免写太长的函数。一个函数尽量不要超过笔记本电脑的屏幕所能容纳的行数,这样可以不用滚屏看完整个函数。
- 制造小的工具函数。提取重复的代码,可以简化主要函数里面的逻辑。
有些人不喜欢使用小的函数,因为他们想避免函数调用的开销,结果他们写出几百行之大的函数。这是一种过时的观念。现代的编译器都能自动的把小的函数内联(inline)到调用它的地方,所以根本不产生函数调用,也就不会产生任何多余的开销。 - 每个函数只做一件简单的事情。
- 避免使用全局变量和类成员来传递信息,尽量是用局部变量和参数。有些人写代码,经常用类成员来传递信息,就像这样:
class A {
String x;
void findX() {
...
x = ...;
}
void foo() {
findX();
...
print(x);
}
}
首先,他使用findX(),把一个值写入成员x。然后,使用x的值。这样,x就变成了findX和print之间的数据通道。由于x属于class A,这样程序就失去了模块化的结构。由于这两个函数依赖成员x,它们不再有明确的输入和输出,而是依赖全局的数据。findX和foo不再能够离开class A而存在,而且由于类成员还有可能被其他代码改变,代码变得难以理解,难以确保正确性。
使用局部变量而不是类成员来传递信息,这两个函数就不需要依赖于某一个class,而且更加容易理解,不易出错:
String findX() {
...
x = ...;
return x;
}
void foo() {
String x = findX();
print(x);
}