1. 程式人生 > >Java編碼中的典型錯誤

Java編碼中的典型錯誤

這篇文章包含了我所看到和我一起工作的人在java編碼中出現的最典型錯誤。靜態分析(我們用qulice)不能捕獲所有顯而易見的錯誤,這就是為什麼我決定在這裡把它們列出來。 
如果你想在這裡看到別的補充請告訴我,我很樂意效勞。 
列出的所有錯誤總得來說和麵向物件程式設計有關,特別是java。

類名

你的類應該是一個沒有“驗證”、“控制器”、“管理者”等等的現實生活實體的一個抽象。如果你的類名以“-er”結尾——它是一個糟糕的設計。順便說一句,這是一個好物件的七個優點。此外,這篇文章更加詳盡的闡述了這個觀點:。 
而且,當然工具類是反例,比如Apache的StringUtils、FileUtils和IOUtils。這些是可怕設計的完美例子。讀這篇文章繼續跟進:。 
當然,不要新增字尾或者字首來區分介面和類。舉個例子,這些命名全都是極其錯誤的:IRecord

IfaceEmployee 或者 
RecordInterface。通常,介面的名稱是一個現實生活實體的名稱,而類名應該解釋它的實現細節。如果關於實現沒有具體的要講的,把它命名為DefaultSimple 
或者類似的。例如:

class SimpleUser implements User {}; 
class DefaultRecord implements Record {}; 
class Suffixed implements Name {}; 
class Validated implements Content {};

方法名

方法可以有返回值可以沒有返回值,如果有返回值,那麼它的名字應該釋返回的是什麼。例如(永遠不要使用 get

 作為字首):

boolean isValid(String name); 
String content();
int ageOf(File file);
  • 1
  • 2
  • 3

如果沒有返回值,那麼它的名字應該解釋它做了什麼。例如:

void save(File file); 
void process(Work work);
void append(File file, String line);
  • 1
  • 2
  • 3

你可以在書的2.4節中讀到更多關於這個觀點。對於剛才提到的規則只有一個例外——JUnit測試方法。下面將對它們進行解釋。

測試方法名

在JUnit測試中,方法名應該為沒有空格的英語句子。很容易用例子解釋:

/**
 * HttpRequest can return its content in Unicode.
 * @throws Exception If test fails
 */ 
 @Test
 public void returnsItsContentInUnicode() throws Exception { } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

重點是你的JavaDoc第一句話應該以類名開始,正在測試的內容跟在 can 後面,這樣,你的第一句話總是類似於“某人能做某事”。 
方法名宣告完全一致,只是沒有主語。如果我在方法名前加上一個主語,那麼我應該得到一個完整的英語句子,就像上面的例子:“HttpRequest returns its content in Unicode”。 注意測試方法不能以 can 開頭。只有JavaDoc的註釋用 
can 開始。此外,方法名不能用動詞開始。 這是一個很好的習慣,在宣告測試方法的同時丟擲異常。

變數名

避免合成變數的名稱,像timeOfDayfirstItem 或者 httpRequest ,我指的既有類的變數又有方法內部。變數名應該足夠長從而避免在其作用域範圍內造成混淆,但儘量不要太長。名稱應該是單數或複數形式的名詞或恰當的縮寫。更多關於在這篇文章: 。 例如:

List<String> names;
void sendThroughProxy(File file, Protocol proto);
private File content;
public HttpRequest request;
  • 1
  • 2
  • 3
  • 4

有時,當建構函式在例項化物件中儲存傳入資料時建構函式的引數和類的屬性可能會產生衝突。這種情況下,我個人建議通過去掉母音字母的方式來建立縮寫()。

另一個例子:

public class Message {
  private String recipient;
  public Message(String rcpt) {
    this.recipient = rcpt;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

很多情況下,變數名的最佳提示可以通過讀取它的類名來確定。只要把它寫成小寫字母,就應該可以了:

File file;
User user;
Branch branch;
  • 1
  • 2
  • 3

然而,千萬不要用相同的方式處理原始資料型別,比如 Integer 或者 String。 
你也可以使用一個形容詞,當存在多個具有不同特徵的變數時。例如:

String contact(String left, String right);
  • 1

建構函式

不出意外的話,應該只有一個建構函式將資料儲存在物件變數中。所有其他建構函式使用不同的引數呼叫這個函式。例如:

public class Server {
  private String address;
  public Server(String uri) {
    this.address = uri;
  }
  public Server(URI uri) {
    this(uri.toString());
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

一次性變數

不惜一切代價的避免一次性變數。我指的“一次性”是說變數只被使用過一次。就想這個例子:

String name = "data.txt";
return new File(name);
  • 1
  • 2

上面的程式碼變數只使用過一次,那麼程式碼需要被重構:

return new File("data.txt");
  • 1

有時,在非常罕見的情況下-主要是因為更好的格式,一次性變數也是可以使用。儘管如此,還是應當不惜一切代價的去儘量避免這樣的情況。

異常

不必說,你永遠都不應該把異常吞嚥掉,而是應該讓它們儘可能的往上冒。私有方法應該總是把Checked異常丟擲去。 
不要為流操作使用異常。例如,這個程式碼是錯誤的:

int size;
try {
  size = this.fileSize();
} catch (IOException ex) {
  size = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

說真的,如果那次異常是因為“磁碟滿了”,難道你依然認為檔案的大小為零,然後繼續進行嗎?

縮排

對於縮排,主要的規則是,括號要麼在一行的結尾要麼在該行就被關閉(相反的規則適用於閉括號)。舉個例子,下面的程式碼是不正確的因為它的第一個括號沒有在同一行被關閉,而且有符號跟在它後面。第二個括號也出了漏子,因為在它的前面有符號,而且它也不是在同一行開啟的:

final File file = new File(directory,
  "file.txt");
  • 1
  • 2

正確的說縮排應該看起來像這樣:

StringUtils.join(
  Arrays.asList(
    "first line",
    "second line",
    StringUtils.join(
      Arrays.asList("a", "b")
    )
  ),
  "separator"
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第二條重要的規則是說:你應該儘可能多地放在一行內-在80個字元的限制範圍內。上面的例子是無效的,因為它可以被壓縮:

StringUtils.join(
  Arrays.asList(
    "first line", "second line",
    StringUtils.join(Arrays.asList("a", "b"))
  ),
  "separator"
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注:通常遇到這種情況,我一般會按以下方式寫:

StringUtils.join(
  Arrays.asList(
    "first line", 
    "second line",
    StringUtils.join(Arrays.asList("a", "b"))
  ),
  "separator"
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

當一個方法呼叫的引數不能全部放在同一行時,我更願意每行放相等個數的引數,這樣便於我再次閱讀程式碼時能夠一眼看清引數,不會遺漏1

冗餘常量

類的常量應該被用於當你想要在類的方法之間共享資訊,而且這個資訊是你的類的一個特性時。不要用常量作為字串或者數字的替代——這是一個會導致程式碼汙染的不好現象。常量(和麵向物件程式中的所以物件一樣)在真是的世界中有某種意義。這些常量在現實世界中有什麼意義:

class Document {
  private static final String D_LETTER = "D"; // bad practice
  private static final String EXTENSION = ".doc"; // good practice
}
  • 1
  • 2
  • 3
  • 4

另一個常見的錯誤是在單元測試中,使用常量去避免重複的字串/數字在測試方法中。千萬別這樣做!每一個測試方法都應該使用它自己的一組輸入值。

在每一個新的測試方法中使用新的文字和數字。他們是獨立的。那麼,為什麼他們要共享相同的輸入常量呢?

測試資料耦合

這是一個在測試方法中資料耦合的例子:

User user = new User("Jeff");
// maybe some other code here
MatcherAssert.assertThat(user.name(), Matchers.equalTo("Jeff"));
  • 1
  • 2
  • 3

在最後一行,我們與第一行有共同的字串“Jeff”。如果,幾個月後,有人想要改變第三行的值,他/她不得不花額外的時間去查詢在這個方法中有用到“Jeff”的其他地方。 
為了避免這樣的資料耦合,你應該引入一個變數。這兒有更多關於:

本文是一篇譯文,點選檢視原文,如有翻譯不當的地方歡迎指出。如需轉載,請標明原文和譯文的出處,謝謝。