Chapter 5: Formatting
1-为什么要格式化我们的代码?
如果我们的代码遵循着某种规则,干净利落,那么我们的想法可以顺利自然的表达出来。
代码的格式化之于可读性如同说话的技巧之于沟通交流啊。
纵向格式
2-源文件大小-行数
作者统计了各种开源软件的代码,做出结论:基本上200行,不超过500行。
3-空行间隔
适当的空行间隔可以美化代码,使其看起来更舒服。(然而 空行也不要太多,一般不要连续空行)
比较以下代码
// 代码1
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
Pattern.MULTILINE + Pattern.DOTALL
);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>"); html.append(childHtml()).append("</b>");
return html.toString();
}
}
// 代码2
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
Pattern.MULTILINE + Pattern.DOTALL );
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>"); html.append(childHtml()).append("</b>");
return html.toString();
}
}
4-行的密度
我们应当将联系紧密的代码上下紧密连接,使之更容易阅读。
顺便吐槽一下,那些没用的注释,删掉他们可以使得代码更紧凑,更好看。
变量声明: 变量应该尽可能的靠近用它的那段代码。 当我们的函数比较短的时候,我们会将变量声明放在函数的开始。
- 实例变量(instance variable):实例变量定义在对象一级,它可以被类中的任何方法或者其他类中的方法访问,但是不能被静态方法访问。
- java: 全部放在顶部
- c++: 放在底部
- 实例变量(instance variable):实例变量定义在对象一级,它可以被类中的任何方法或者其他类中的方法访问,但是不能被静态方法访问。
有依赖关系的函数
如果一个函数调用了其他的函数,那么他们应该放在一起,而且被调用者放在调用者上面概念上类似的函数放在一起
// 代码3
// 这些在概念上都是一个类型的代码,我们应该将他们放在一起
public class Assert {
static public void assertTrue(String message, boolean condition) {
if (!condition) fail(message);
}
static public void assertTrue(boolean condition) {
assertTrue(null, condition);
}
static public void assertFalse(String message, boolean condition) {
assertTrue(message, !condition);
}
static public void assertFalse(boolean condition) {
assertFalse(null, condition);
}
...
另外,在java经常要写get/set函数,把所有的get/set都放在一起也是显而易见的。
5-整体顺序布局
- 函数:被调用者放在调用者上面。
- 最重要的概念放在最上面。
横向格式
6-横向长度
- 永远不要写到 需要滚动屏幕 才能看到的长度!
- 字符数 < 120 //因为你可以调小字体大小使其满足1= =
7-横向空格间隙
看如下代码
// 代码4, 关注函数声明部分和+-等操作符部分
public class Quadratic {
public static double root1(double a, double b, double c) {
double determinant = determinant(a, b, c);
return (-b + Math.sqrt(determinant)) / (2*a);
}
public static double root2(int a, int b, int c) {
double determinant = determinant(a, b, c);
return (-b - Math.sqrt(determinant)) / (2*a);
}
private static double determinant(double a, double b, double c) {
return b*b - 4*a*c;
}
}
8-对齐
假如代码4这么写
// 代码4-2, 理解难度+max
public class Quadratic {
public static double root1(double a, double b, double c) {
double determinant = determinant(a, b, c);
return (-b + Math.sqrt(determinant)) / (2*a);}
public static double root2(int a, int b, int c) {
double determinant = determinant(a, b, c);
return (-b - Math.sqrt(determinant)) / (2*a);}
private static double determinant(double a, double b, double c) {
return b*b - 4*a*c;}
}
一段对齐的代码一眼看去就能感觉其中的结构,使得我们更容易的理解代码的意思。
实际上,在我们平时写代码的时候,因为函数,while等语句很短,我们会写作一行。
作者认为不要这样。他认为坚持对齐格式是更好的选择。
我自己的代码(反例):
// 代码5
// 快排的一个函数,选定pivot,然后交换
int partition(int arr_int[], int start, int end) {
int pivot_value = arr_int[end];
int left_index = start;
int right_index = end - 1;
while (true) {
// 以下两个while 和一个if 都写作了一行
while (arr_int[left_index] <= pivot_value && left_index < end) ++left_index;
while (arr_int[right_index] >= pivot_value && right_index > start) --right_index;
if (left_index >= right_index) break;
swap_int(arr_int[left_index], arr_int[right_index]);
}
swap_int(arr_int[left_index], arr_int[end]);
return left_index;
}
更极端的情况,我们可能会写成这样
// 代码6
while (arr_int[left_index++] <= pivot_value && left_index < end);
while (arr_int[right_index++] >= pivot_value && right_index > start);
作者认为这样是非常不可取的,因为其他人在读代码的时候很可能就犯蒙bile。因为万一读代码的人没看到这个分号,你懂的。。。
9-开发同个项目的整个团队应有相同的代码风格
10-作者自己代码风格(java)
作者给出了自己的代码。
从中看到:
- 函数之间空行1
- 函数内部不空行
- while if 不单行
- 只有一句话的if while 省去了 {}
- 数学操作符只有明显分块的时候会空开,一般不空
// 代码7
public class CodeAnalyzer implements JavaFileAnalysis {
private int lineCount;
private int maxLineWidth;
private int widestLineNumber;
private LineWidthHistogram lineWidthHistogram;
private int totalChars;
public CodeAnalyzer() {
lineWidthHistogram = new LineWidthHistogram();
}
public static List<File> findJavaFiles(File parentDirectory) {
List<File> files = new ArrayList<File>();
findJavaFiles(parentDirectory, files);
return files;
}
private static void findJavaFiles(File parentDirectory, List<File> files) {
for (File file : parentDirectory.listFiles()) {
if (file.getName().endsWith(".java"))
files.add(file);
else if (file.isDirectory())
findJavaFiles(file, files);
}
}
public void analyzeFile(File javaFile) throws Exception {
BufferedReader br = new BufferedReader(new FileReader(javaFile));
String line;
while ((line = br.readLine()) != null)
measureLine(line);
}
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
totalChars += lineSize;
lineWidthHistogram.addLine(lineSize, lineCount);
recordWidestLine(lineSize);
}
private void recordWidestLine(int lineSize) {
if (lineSize > maxLineWidth) {
maxLineWidth = lineSize;
widestLineNumber = lineCount;
}
}
public int getLineCount() {
return lineCount;
}
public int getMaxLineWidth() {
return maxLineWidth;
}
public int getWidestLineNumber() {
return widestLineNumber;
}
public LineWidthHistogram getLineWidthHistogram() {
return lineWidthHistogram;
}
public double getMeanLineWidth() {
return (double)totalChars/lineCount;
}
public int getMedianLineWidth() {
Integer[] sortedWidths = getSortedWidths();
int cumulativeLineCount = 0;
for (int width : sortedWidths) {
cumulativeLineCount += lineCountForWidth(width);
if (cumulativeLineCount > lineCount/2)
return width;
}
throw new Error("Cannot get here");
}
private int lineCountForWidth(int width) {
return lineWidthHistogram.getLinesforWidth(width).size();
}
private Integer[] getSortedWidths() {
Set<Integer> widths = lineWidthHistogram.getWidths();
Integer[] sortedWidths = (widths.toArray(new Integer[0]));
Arrays.sort(sortedWidths);
return sortedWidths;
}
}
代码5 按照作者的风格可以改成如下排版
// 代码8
int partition(int arr_int[], int start, int end) {
int pivot_value = arr_int[end];
int left_index = start;
int right_index = end - 1;
while (true) {
while (arr_int[left_index] <= pivot_value && left_index < end)
++left_index;
while (arr_int[right_index] >= pivot_value && right_index > start)
--right_index;
if (left_index >= right_index)
break;
swap_int(arr_int[left_index], arr_int[right_index]);
}
swap_int(arr_int[left_index], arr_int[end]);
return left_index;
}
总结
选择一种流行的format,在你所有的代码中都遵守它!