代码洁癖系列(五):外在的格式美 ' D( R" x4 r7 B! R / f7 m; q$ @% E5 ]3 l8 B我们在阅读一些优秀项目的源码时,一定会感叹他们代码的整洁和一致性。而作为第一印象,代码格式的整齐是让人能够继续阅读下去的动力。今天我们分别从垂直格式和横向格式两个方面来讨论代码的格式。修正格式的方法有:间隔、靠近和调整顺序。% \( m, H3 i( r* Q; i, [) u; u
垂直格式) t0 x; l4 S$ J3 Z
% t" z5 H0 E, t) x: v2 v: ~3 Y
在垂直格式方面,我们要向报纸的排版学习。; \1 K8 j- [: J$ _3 G8 E x & f% B& z' s) r: b0 C4 U$ x
2 w6 q' `1 [ }) h: w
( t" ]. _) w3 m% x# k
首先有一个标题告诉你这栏新闻要讲什么,好让你知道是否要继续阅读下去。对应到代码中就是类的名字,我们要力求只通过名字就可以知道这个类要描述什么事情;然后,代码的第一段往往都交代了整个故事的概要,类似于代码中的接口,我们往往通过接口了解类中有哪些函数,每个函数都是干什么的。了解了这些之后,才会去读详细的内容。. t0 G* D" _% f- g+ }; r+ k- ^( z
7 o" W9 M0 p! c4 m9 ~
newspaper code( U3 s1 o& {! z3 K) X: b
标题 类名 1 F% R1 o# X/ \/ S/ S& `$ p+ e第一段 接口9 G+ H+ e v# V' j
内容 方法体* ^6 V% `: z% l; W& u% A* t$ P
此外,还需要注意的就是报纸的排版、段落和段落之间用空行做间隔。写代码也可以用同样的方式,例如,我们习惯于在包声明、导入声明和每个函数之间用空白行来分隔。我们直接拿代码来解释。( n ] X0 y& \8 X6 ^) W. l) K
7 V1 g8 {* J2 f6 K5 M
package com.thoughtworks.selenium; H4 V2 u8 U' G9 k# S
import org.testng.ITestContext; / G& K' C3 |! Z' i$ X# `& q4 iimport org.testng.ITestResult;6 U" R/ t" d1 {" \& z
import org.testng.Reporter; ; s4 W) V; t) W* C! timport org.testng.internal.IResultListener;; T% O) ]+ m/ g
: C. u) y+ P% e* d8 q6 eimport java.io.File; v( J# U- U6 |1 M
1 ^1 t9 h% i8 L, d1 _" w# ?; k
public class ScreenshotListener implements IResultListener {* N+ u/ a2 D9 Y4 O# u
& U/ N! k0 u z9 f% H6 n0 q$ T
File outputDirectory; a: c$ V+ l' _3 ^+ K3 I Selenium selenium;6 S7 ]* s# ~ z* O) K! r
& j( g) }& V0 N& }+ ?# ]: C
public ScreenshotListener(File outputDirectory, Selenium selenium) {( V+ Q; A" \5 d; q) V
this.outputDirectory = outputDirectory; 2 O8 d8 i4 a. V9 ~) A6 p+ ] this.selenium = selenium;. k( Z/ l7 p4 L' ?' \ e6 k
}4 k8 t" U! [2 |! H2 y& i9 y+ t* r% l
* H' W$ p, r6 {9 u4 [1 ~/ }# c
public void onTestFailure(ITestResult result) { 2 n# \+ j- e" `6 x6 `7 q Reporter.setCurrentTestResult(result); % W: ?1 \# [7 w0 o" i' ^, \9 J; U6 k& \) \
try {2 l# z6 c3 ^' b" h, w6 B
outputDirectory.mkdirs(); & _6 s! M3 y, [ b File outFile = File.createTempFile("TEST-" + result.getName(), ".png", outputDirectory);' X& X. O& \+ s0 E6 n9 D5 Q
outFile.delete();9 m N: R6 H' O8 @! @; }
selenium.captureScreenshot(outFile.getAbsolutePath());5 T# n$ t" Y' t8 o8 F0 E1 L
Reporter.log("<a href='" +3 S6 Q. V- C! }+ D" h" ?
outFile.getName() +; S# W0 B" Z/ W: w4 W
"'>screenshot</a>"); 4 H4 u! _" d1 ^. }4 ?) e) l } catch (Exception e) {$ [3 F" `- ]5 ]; q
e.printStackTrace(); " o0 I! J! x L# e2 i Reporter.log("Couldn't create screenshot"); ! _% Z5 M4 D- s# O% H! Q/ Z Reporter.log(e.getMessage());! g# J) Z+ `" L$ Y4 y/ k; A
}5 x8 G/ V! i" L( [8 f( s
7 b0 m2 _+ U0 q
Reporter.setCurrentTestResult(null);; D. O) |, b7 l4 y/ Z
} - v; [& G& L1 T} * `1 }4 z( W4 C8 ^4 R, h# Q ( K# t3 w: A3 Z$ Wpackage com.thoughtworks.selenium; 0 z8 { ]" n1 \* v5 bimport org.testng.ITestContext; # Q: F* V2 c+ N4 n! Iimport org.testng.ITestResult;; ~( F8 w A: i9 {
import org.testng.Reporter; : E! x/ d! U" ]+ kimport org.testng.internal.IResultListener; # v6 H0 e e% d+ {9 O, O0 z0 bimport java.io.File; 3 h6 v4 P$ |! b) bpublic class ScreenshotListener implements IResultListener {" d5 U M8 |, F' t( V3 t' A8 r
File outputDirectory; 6 a! q! Z4 Y/ a6 r9 p5 d Selenium selenium; 4 a% R7 E6 `4 V( B public ScreenshotListener(File outputDirectory, Selenium selenium) { % x& L- z5 s; x4 y. x6 J O this.outputDirectory = outputDirectory; ' z4 Z; |9 ? x3 e this.selenium = selenium;, w# i/ m. ~6 B% X+ u' x. L
} 9 M- O# [9 a9 W; T# F) y9 { public void onTestFailure(ITestResult result) {1 X2 W/ r7 J9 U
Reporter.setCurrentTestResult(result); 2 c8 Y3 S4 g, b, R3 O7 I5 O try { * M# l& [: W( U' }9 N outputDirectory.mkdirs(); 0 @3 z( u9 o) L7 Q+ I& w. T File outFile = File.createTempFile("TEST-" + result.getName(), ".png", outputDirectory);+ a2 F" j. c3 h. ]& U& ~0 _
outFile.delete(); ' j n! @& i' f" B/ W selenium.captureScreenshot(outFile.getAbsolutePath());: G# [- }" P9 o6 s
Reporter.log("<a href='" +2 C/ I; E- K! j: Q$ A
outFile.getName() +' @: `. Y" \3 ^: r' r- |
"'>screenshot</a>");9 O0 u$ M5 j F! \
} catch (Exception e) {8 @) L. |4 x0 j
e.printStackTrace();- h/ c: f: o S7 F: K
Reporter.log("Couldn't create screenshot");) ]# u j7 j2 k( ?5 d) W
Reporter.log(e.getMessage()); % e$ b; |1 t$ @0 l4 H r } 1 J3 J6 I8 _( D Reporter.setCurrentTestResult(null); $ ~( R0 l, v8 F& j, ^9 J }. k8 I+ J! y. }1 |# m
}% s5 ~& l3 S* I0 Q x; o
是不是适当增加空白行就提高了代码的可读性呢,这里说的是适当增加空白行,并不代表随意增加。事实上,如果增加一些无意义的空白行反而会使代码的可读性变差。 . a2 U" Z2 U; a% Q/ l' w & k" x I; N. w# i, |& d我们习惯于使有关联的代码彼此靠近,无明显关联的代码相互分隔。因此我们不但需要使用空白行间隔代码,还要调整代码位置,把有关联的代码放在一起,通常我们把被调用的函数放在调用函数的下面。这样别人在读我们的代码的时候再也不用经历来回“跳跃”的痛苦了。- c3 e. f2 X% `* W. T
3 K+ L* z' O6 Z9 V4 F$ K; N4 S最后,我们通常把实体变量定义在类的顶部,这个只是我们Java程序员的习惯操作,如果突然在类的中间位置出现一个变量声明会让人觉得很奇怪。如果你想说,定义在中间是不想让定义位置和调用位置离太远,那么只能说明你的类定义出现了问题。建议你看一下旧文代码洁癖系列(三):整洁的类和函数。 0 X; R3 F4 |- u U3 ] * H0 `) D' y- z- z0 }! N横向格式 4 W$ D. e: d. r% f' o; s " C# s6 C+ N5 p j4 V介绍完垂直格式,紧接着自然要来介绍一下它的兄弟,横向格式。或许你会问为什么不叫水平格式,我的回答是:也可以叫水平格式,只要你喜欢。横向格式也是需要间隔和靠近的,这里的间隔主要是为了起到强调的效果。举个例子。% i7 v& Z6 j& t' v1 \3 S
) X( N c# v" N. z" h
int lineSize = line.length();7 o5 x' s2 k- Q( W9 { Y8 z
1 - k4 D% F! v! I这里等号两边的空格分别是为了强调左右两边的元素。横向格式另一个比较重要的元素就是缩进,Java程序对缩进没有强制性要求,而Python程序对缩进的要求非常严格,稍有不慎,执行的结果就会不同。但是Java程序员也要注意缩进,因为我们的代码是层级关系,而缩进可以帮我们快速理清层级关系。" @2 Z" P5 I7 F3 ]4 D. B, M
7 E/ q1 ^ D* K P* J1 n& J3 v最后,横向代码格式对每行代码长度是有要求的,如果代码过长,那么在阅读的时候就需要左右滑动,而这个操作其实是不受人喜欢的。虽然现在代码编辑器可显示的长度变大了,但我们还是习惯每行代码最多100个字符。 # R T3 F7 r" b Z. h $ J( ~1 Z! M; T团队的规则 : \6 y! y/ i& }$ q `% W& G8 ?- |: M8 A b _0 p, Y
每一个优秀的团队都已一套属于自己的代码格式要求,有些是特定的,有些是使用公共的。我们team所用的代码规范就是Google的代码规范,阿里的代码规范也是比较被大家认可的。这里给大家一个小福利,在我的公众号后台回复【代码规范】,就可以获得一份阿里的Java开发手册。/ k/ }2 e+ d& ]( R$ V7 b$ h
--------------------- 2 w. Q- v. y5 Z2 x0 x& @" p8 Y, u4 {5 A |9 p$ p2 F2 r+ P0 X4 H
% c" ]9 t \7 K$ Z0 @/ A
" L# w. x3 ?% r& d. @3 l/ p3 D3 U# R+ k
}" n- x2 A4 n N* Y