_注:正文中的引用是直接引用作者Bob大叔的话,两条横线中间的段落的是我自己的观点,其他大约都可以算是笔记了。 _
从这一章的第一段就能看出来,Bob大叔对格式化是非常看重的,他连着使用了几个排比句来说明代码的格式化对于一个工程作为一个整体的重要性。所有的代码---不论是一个人不同时期写的代码,还是一个团队不同的成员写的代码---都应该是一致的、优雅的。
为什么要格式化
代码的格式化直接影响到「使用代码沟通」的问题,而「使用代码沟通」是一个专业的开发者的第一要务,而不是「只要能让代码能运行起来就行」。
你今天写的功能可能不久之后就会需要被更改,但是代码的「可读性」将会对「未来代码的修改或重构」产生深远的影响。
接下来是一些对「使用代码沟通」能力有影响的方面:
垂直方向上的格式化
垂直方向上的格式化简单说就是代码的长度。
我们在开发的过程中应该尽量去追求写出较小的代码源文件,作者给出了一个参考值是:「大多数的文件应该都小于200行,最长的文件最好要小于500行」。但是作者也说这条规则不应该成为一个硬性的标准,而是我们在开发过程中不断追求的境界。毕竟有时我们不可避免地必须写出一些比较长的源文件。
1. 报纸的比喻
一个源文件读起来应该像报纸里的文章一样,名字应该简短但能表达文章的基本意思,源文件刚开始的部分应该能够提供一些高层的抽象和算法,然后具体的实现细节在接下来的章节中依次展开,直到源文件的最后,在那里我们能找到最低等级的函数和最细节的实现。
一张报纸(一个项目)会有很多文章(源文件)组成,大多数都很短,偶尔有一些稍微有些长,极少数的情况也会出现占据整个页面的长度的文章。
2. 不同概念之间的垂直间隔
在源文件中不同的概念(往往指的是函数)之间应该使用空行来隔开,这样在读者阅读这段代码时会非常舒服。
3. 垂直聚集
与上一条中阐述的内容刚刚相反,相似或者相关性较强的概念之间往往不应该有分隔(往往是注释,或者是无意义的空行),而应该聚集在一起。
4. 垂直距离
我们经常会碰到这种情况,我们阅读代码时迷失在了寻找「变量与变量间、函数与函数间、类与类之间的关系」的过程中不能自拔,大量的时间花在了寻找「那段代码在哪里」。
相近的概念应该在垂直距离上尽量接近,相似的概念应该尽量存在于同一个函数(除非你有比较好的原因不去这么做),protected的变量应该被禁止使用(这条可能在某些语言里不存在)。
变量的定义 应该尽量靠近它被使用的地方。
实例变量(instance variable) (类的动态变量)应该被定义在类的起始。
不论是遵循「剪刀原则」,将实例变量定义在类的最低端,还是根据Java的惯例,将实例变量定义在类的最顶端,都是好的。但请不要把实例变量放在除了这两种之外的其他位置。
有依赖关系的函数 应该在垂直的距离上尽量靠近,而且往往调用者应该在被调用者的上边(如果可能的话)。
概念上关系较密切的代码 应该根据他们关系的密切程度来选择他们间的距离
5. 垂直排序
垂直坐标上的元素(可能是代码块,函数,变量等)应该按照自上而下的阅读顺序进行排列,这样更易于阅读。
水平方向上的格式化
作者对于一些典型的应用(Junit, FitNesse, testNG, Time and Money, JDepend, Ant, Tomcat)的行的宽度进行了统计,绝大多数的宽度都在60个字符以下,大于80个字符以上宽度的占比非常小。通常的说,代码的行宽应该小于100或120。
1. 水平的间隔和聚集
作者在这个小节里说了一个比较微妙的事物---空格,因为要使用空格来区别水平方向上的不同概念
//代码4-1
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
totalChars += lineSize;
lineWidthHistogram.addLine(lineSize, lineCount); recordWidestLine(lineSize);
}
在水平方向上,作者主张在强相关的元素之间不使用空格,而在弱相关的元素之间添加空格来予以区分,比如代码4-1。
- 赋值语句的左右是两个区别很大的部分,那么使用空格来突出赋值操作符(就是= )并对赋值语句的左右两部分加以区分。
- 函数名和左括号之间是十分相关的,所以就不使用空格
- 参数之间的逗号分隔符之后使用空格,用以突出分隔符并区分两个不同参数。
2. 水平对齐
有些人(包括Bob大叔自己早期)喜欢在有一堆的变量定义或赋值语句时,使用对齐来使代码变得更易读,但这样是没有用的。使用这样的对齐会喧宾夺主,把代码阅读者的阅读重心吸引到了错误的地方。
//代码4-1
public class FitNesseExpediter implements ResponseSender
{
private Socket socket;
private InputStream input;
private OutputStream output;
private Request request;
private Response response;
private FitNesseContext context;
protected long requestParsingTimeLimit;
private long requestProgress;
private long requestParsingDeadline;
private boolean hasError;
public FitNesseExpediter(Socket s,
FitNesseContext context) throws Exception
{
this.context = context;
socket = s;
input = s.getInputStream();
output = s.getOutputStream();
requestParsingTimeLimit = 10000;
}
如果你的代码中出现了很长的变量定义或赋值语句列表,长到需要使用左对齐来使它们更易读,那么主要问题应该是你的代码中不应该出现这么长的变量定义或赋值列表,而不是缺少了左对齐。
3. 缩进
源文件里的内容与其说是「提纲和内容」的关系,不如说是「层级」关系。
在代码中,要传达的信息之间的从属关系从文件到类、类到方法、方法到代码块、代码块到子代码块,逐级往下,为了区分这种层级,开发者非常喜欢使用代码的缩进来使代码更加易读。
缩进 是代码质量管理里很重要的一环,我个人比较喜欢使用制表符(TAB,往往是4个空格)来标识缩进,然而我有个同学就特别喜欢使用2个空格来表示缩进,这种事情仁者见仁智者见智。
不要破坏缩进(breaking indentation)。谨记一定要使用缩进。
有时候一个if语句或者for循环代码块或者函数非常小,开发者会觉得不需要使用缩进,而把大括号,执行语句等都写在同一行,如代码4-4中所示,这种是必须要避免的。
//代码4-3
public class CommentWidget extends TextWidget {
public static final String REGEXP = "^#[^\\r\\n]*(?:(?:\\r\\n)|\\n|\\r)?";
public CommentWidget(ParentWidget parent, String text){super(parent, text);}
public String render() throws Exception {return ""; }
}
4. 空语句
我个人非常不喜欢空语句,并且在开发过程中是会尽力避免使用空语句的,如果我不得不使用它,也一定会加上大括号并且使用缩进来让代码更易读。
团队规则(Team Rules)
如果一个项目是一个团队协同开发,那么团队中的每个人都要遵循这个团队定下来的格式化要求,来保证一个项目中的格式是顺滑的、一致的。