本文列舉了我在周圍同事的Java程式碼中看到的一些比較典型的錯誤。顯然,靜態程式碼分析(我們團隊用的是qulice)不可能發現所有的問題,這也是為什麼我要在這裡列出它們的原因。
如果你覺得少了什麼,請不吝賜教,我會很樂意把它們加上。
下面列出的所有這些錯誤基本上都與物件導向程式設計有關,尤其是Java的OOP。
當然,不要使用前綴或後綴來區分類和介面。比方說,這些名字就是錯誤的:IRecord, IfaceEmployee, 或RecordInterface。通常來說,介面名稱應該是真實生活中的實體的名字,類別名稱應該可以說明它的實作細節。如果這個實作沒有什麼特別可說明的,可以把它叫作Default, Simple或類似的什麼。比如說:
class SimpleUser implements User {}; class DefaultRecord implements Record {}; class Suffixed implements Name {}; class Validated implements Content {};
方法名稱
方法可以回傳值也可以回傳void。如果方法傳回值的話,它的名字應該能說明它回傳了什麼,比如說(永遠不要使用get前綴):
boolean isValid(String name); String content(); int ageOf(File file);
如果它回傳void,那麼它的名字應該要說明它做了什麼。例如:
void save(File file); void process(Work work); void append(File file, String line);
剛才提到的這些規則只有一個例外──JUnit的test方法不算。下面將會說到這個。
test方法的名字
在JUnit的測試案例中,方法名稱應該是沒有空格的英文語句。用一個例子來說明會更清楚一些:
/** * HttpRequest can return its content in Unicode. * @throws Exception If test fails */ public void returnsItsContentInUnicode() throws Exception { }
你的JavaDoc裡的第一句話的開頭應該是你要測試的那個類別的名字,然後是一個can。因此,你的第一句話應該是類似「somebody can do something」。
方法名也是一樣的,只是沒有主題而已。如果我在方法名稱中間加一個主題的話,我就能得到一個完整的句子,正如上面那個例子中那樣:「HttpRequest returns its content in unicode」。
請注意test方法的名字是不以can開頭的。只有JavaDoc裡的的註解會以can開頭。除此之外,方法名不應該以動詞開頭。
實務中最好將測試方法宣告為拋出Exception的。
變數名
避免組合的變數名,比如說timeOfDay, firstItem,或是httpRequest。類別變數及方法內的變數都是如此。變數名稱應該夠長,避免在它的可見作用域內產生歧義,但是如果可以的話也不要太長。名字應該是單數或複數形式的名詞,或是適當的縮寫。例如:
List<String> names; void sendThroughProxy(File file, Protocol proto); private File content; public HttpRequest request;
有的時候,如果建構方法要將入參保存到一個新初始化的物件中的時候,它的參數和類別屬性的名字可能會衝突。這種情況,我建議是去掉元音,使用縮寫。
範例:
public class Message { private String recipient; public Message(String rcpt) { this.recipient = rcpt; } }
很多時候,看一下變數的類別名稱就知道變數該取什麼名字了。就用它的小寫形式就好了,像這樣就很可靠:
File file; User user; Branch branch;
然而,基礎類型的話,永遠不要這麼做,比如Integer number或String string。
如果存在多個不同性質的變數的話,可以考慮使用形容詞。例如:
String contact(String left, String right);
構造方法
不考慮異常的話,應該只有一個建構方法用來將資料儲存到物件變數中。其它構造方法則使用不同的參數來呼叫這個構造方法。比如說:
public class Server { private String address; public Server(String uri) { this.address = uri; } public Server(URI uri) { this(uri.toString()); } }
一次性變數
無論如何都應該避免使用一次性變數。這裡我所說的「一次性「指的是只使用一次的變數。例如下面這個:
String name = "data.txt"; return new File(name);
上述的變數只會使用一次,因此這段程式碼可以重構成這樣:
return new File("data.txt");
有的時候,比較罕見的情況中——主要是為了格式更好看些——可能會用到一次性變數。然而,還是應盡量避免這種情況。
異常
毋庸贅言,永遠不要自己吞掉異常,而是應該當它盡量往上傳遞。私有方法應該始終把受檢查異常往外面拋。
不要使用異常來進行流程控制。比方說下面這段程式碼就是錯的:
int size; try { size = this.fileSize(); } catch (IOException ex) { size = 0; }
那如果IOException提示「磁碟已滿」的話該怎麼辦?你還會認為這個檔案大小為0,然後繼續往下處理?
縮排
關於縮進,主要的規則就是左括號要么在該行的末尾,要么就在同一行上閉合(對於右括號來說則相反)。比如說,下面這個就不正確,因為第一個左括號沒有在同一行上閉合,而它後面還有別的字元。第二個括號也有問題,因為它前面有字符,但對應的開括號又沒在同一行上:
final File file = new File(directory, "file.txt");
正確的縮進應該是這樣的:
StringUtils.join( Arrays.asList( "first line", "second line", StringUtils.join( Arrays.asList("a", "b") ) ), "separator" );
關於縮進,第二條重要的規則就是同時一行中應該盡量多寫一些——上限是80個字元。上面的例子並不滿足這一點,它還可以收縮一下:
StringUtils.join( Arrays.asList( "first line", "second line", StringUtils.join(Arrays.asList("a", "b")) ), "separator" );
多餘的常數
当你希望在类的方法中共享信息的时候,应当使用类常量,这些信息应该是你这个类所特有的。不要把常量当作字符串或数值字面量的替代品来使用——这是非常糟糕的实践方式,它会对代码造成污染。常量(正如OOP中的任何对象一样)应当在真实世界中有它自己的含义。看下这些常量在真实生活中的意思是什么:
class Document { private static final String D_LETTER = "D"; // bad practice private static final String EXTENSION = ".doc"; // good practice }
另一个常见的错误就是在单元测试中使用常量来避免测试方法中出现冗余的字符串或者数值的字面量。不要这么做!每个测试方法都应该有自己专属的输入值。
在每个新的测试方法中使用新的文本或者数值。它们是相互独立的。那么为什么它们还要共享同样的输入常量呢?
测试数据耦合
下面是测试方法中数据耦合的一个例子:
User user = new User("Jeff"); // maybe some other code here MatcherAssert.assertThat(user.name(), Matchers.equalTo("Jeff"));
最后一行中,”Jeff”和第一行中的同一个字符串字面值发生了耦合。如果过了几个月,有人想把第三行这个值换一下,那么他还得花时间找出同一个方法中哪里也使用了这个”Jeff”。
为了避免这种情况,你最好还是引入一个变量。
更多Java编程中的一些常见问题汇总相关文章请关注PHP中文网!