java代碼規范參考文檔


一、目的

java代碼規范編寫主要是為了給開發人員編碼時提供一份參考文檔,在協作開發及多項目切換開發中,有規范可循,實現代碼高質量、高可讀以及可維護。本文結合阿里java代碼規范、項目實際情況以及個人的開發經驗編寫而成。本java開發規范的預期讀者為系統設計人員、軟件開發人員、技術經理。

 

二、適用范圍

適用於軟件開發人員閱讀

 

三、術語表

序號

術語或縮略語

說明性定義

1

魔法值

是指在代碼中直接出現的數值,只有在這個數值記述的那部分代碼中才能明確了解其含義

2

POJO

簡單的Java對象,實際就是普通JavaBeans,僅有一些屬性及其getter setter方法的類,沒有業務邏輯

3

事務

事務是指一個單元的工作,這些工作要么全做,要么全部不做。事務是必須滿足4個條件(ACID)::原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。

4

基本數據類型

基本數據類型包括:byte、short、int、long、float、double

5

包裝數據類型

包裝數據類型包括:Byte、Short、IntegerLong、Float、Double

6

方法                                                                                                                         

用來解決一類問題的代碼的有序組合,是一個功能模塊

四、代碼規范

4.1、強制遵循

1. 禁用魔法值

除預定義的外,在編碼中禁止使用魔術值。建議定義枚舉值或者靜態常量值,並寫好注釋說明。

 

反例:

if (key.equals("Id#taobao_1")) {  
//...  
}  

 

正例:

String KEY_PRE = "Id#taobao_1";  
if (KEY_PRE.equals(key)) {  
//...  
} 

 

2. POJO類必須寫toString方法

在方法執行拋出異常時,可以直接調用POJOtoString()方法打印其屬性值,便於排查問題。說明:使用工具類source> generate toString時,如果繼承了另一個POJO類,注意在前面加一下super.toString

 

正例:

public class ToStringDemo extends Super{  
private String secondName;  
@Override  
public String toString() {  
return super.toString() + "ToStringDemo{" + "secondName='" + secondName + '\'' + '}';  
}  
}  
class Super {  
private String firstName;  
@Override  
public String toString() {  
return "Super{" + "firstName=" + firstName + '\'' + '}';  
}  
}  

 

3. 手動回滾事務

事務場景中,拋出異常被catch后,如果需要回滾,一定要手動回滾事務。

 

反例:

@Transactional
public class UserServiceImpl implements UserService {  
@Override  
public void save(User user) {  
//some code  
//db operation  
}  
}  

 

正例1

@Transactional(rollbackFor = Exception.class)  
public class UserServiceImpl implements UserService {  
@Override  
public void save(User user) {  
//some code  
//db operation  
}  
}  

 

 

正例2

public class UserServiceImpl implements UserService {  
@Override  
@Transactional(rollbackFor = Exception.class)  
public void save(User user) {  
//some code  
//db operation  
}  
} 

 

正例3

public class UserServiceImpl implements UserService {  
@Autowired  
private DataSourceTransactionManager transactionManager;  
@Override  
@Transactional  
public void save(User user) {  
DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
// explicitly setting the transaction name is something that can only be done programmatically  
def.setName("SomeTxName");  
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
TransactionStatus status = transactionManager.getTransaction(def);  
try {  
// execute your business logic here  
//db operation  
} catch (Exception ex) {  
transactionManager.rollback(status);  
throw ex;  
}  
} 
}

 

4. 單個方法的總行數不超過80行

除注釋之外的方法簽名、結束右大括號、方法內代碼、空行、回車及任何不可見字符的總行數不超過80行。代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨出來成為額外方法,使主干代碼更加清晰;共性邏輯抽取成為共性方法,便於復用和維護。

5. 及時清理不再使用的代碼段或配置信息

對於垃圾代碼或過時配置,堅決清理干凈,避免程序過度臃腫,代碼冗余。如果是暫時注釋,請在上方使用 /// 標記說明。

 

正例:

public static void hello() {  
/// 暫時注釋.  
// Business business = new Business();  
// business.active();  
System.out.println("it's finished");  
}  

 

6. 集合初始化時,指定集合初始值大小

HashMap使用如下構造方法進行初始化,如果暫時無法確定集合大小,那么指定默認值(16)即可。

 

反例:

Map<String, String> map = new HashMap<String, String>();  

 

正例:

Map<String, String> map = new HashMap<String, String>(16);  

 

7. 內部的實現類用Impl的后綴與接口區別

對於ServiceDAO類,基於SOA的理念,暴露出來的服務一定是接口,內部的實現類用Impl的后綴與接口區別。

 

正例:

public interface DemoService{  
void f();  
}  
public class DemoServiceImpl implements DemoService {  
@Override  
public void f(){  
System.out.println("hello world");  
}  
}  

 

8. 定義DO/DTO/VO等POJO類時,不要加任何屬性默認值

POJO類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。

 

反例:

POJO類的createTime默認值為newDate(),但是這個屬性在數據提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。

public class DemoDO {    
String name = "demo";    
Date createTime = new Date();    
}    

 

正例:

public class DemoDO {    
String name;    
Date createTime;
}    

 

9. 循環體內,字符串的聯接方式

循環體內,字符串的聯接方式,使用StringBuilderappend方法進行擴展。 說明:反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,然后進行append操作,最后通過toString方法返回String對象,造成內存資源浪費。

 

反例1

String result;  
for (String string : tagNameList) {  
result = result + string;  
}  

 

反例2

StringBuilder stringBuilder = new StringBuilder();  
for (String string : tagNameList) {  
stringBuilder.append(string + ",");  
}  
String result = stringBuilder.toString();  

 

正例:

StringBuilder stringBuilder = new StringBuilder();  
for (String string : tagNameList) {  
stringBuilder.append(string);  
stringBuilder.append(",");  
}  
String result = stringBuilder.toString(); 

  

10. 不能在finally塊中使用return

不能在finally塊中使用returnfinally塊中的return返回后方法結束執行,不會再執行try塊中的return語句。

 

反例:

public static Long readFileLength(String fileName) {  
try {  
File file = new File(fileName);  
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");  
return randomAccessFile.length();  
} catch (Exception e) {  
logger.error(e.getMessage(), e);  
} finally {  
countDownLatch.countDown();  
return 0L;  
}  
}  

 

11. switch塊使用

在一個switch塊內,每個case要么通過break/return等來終止,要么注釋說明程序將繼續執行到哪一個case為止;在一個switch塊內,都必須包含一個default語句並且放在最后,即使它什么代碼也沒有。

 

正例:

switch (x) {  
case 1:  
break;  
case 2:  
break;  
default:  
}  

 

12. SimpleDateFormat 是線程不安全的類

SimpleDateFormat 是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用DateUtils工具類。 說明:如果是JDK8的應用,可以使用LocalDate代替DateLocalDateTime代替CalendarDateTimeFormatter代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe

 

正例1

private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";  
public String getFormat(Date date){  
SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);  
return sdf.format(date);  
}  

 

正例2

private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
public void getFormat(){  
synchronized (sdf){  
sdf.format(new Date());  
….;  
}  

 

正例3

private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {  
@Override  
protected DateFormat initialValue() {  
return new SimpleDateFormat("yyyy-MM-dd");  
}  
}

 

 

13. 數據類型相等比較方式,equals 和 == 的選擇標准

1) 比較的兩個對象中其中一個為基本數據類型時使用==

2) 比較的兩個對象中兩個都為包裝數據類型時使用equals

 

例子:

Byte c = (byte)1;  
Byte d = (byte)1;  
Integer a = 200;  
Integer b = 200;  
Integer e = 10;  
Integer f = 10;  
System.out.println(c.equals(d)); // ture  
System.out.println(c.equals(1)); // false  
System.out.println(c == 1); // true  
System.out.println(a.equals(b)); //true  
System.out.println(a == b); //false 因為byte、Integer long short在-128-127范圍是共享的堆,超出就會新建一個對象  
System.out.println(e.equals(f));//true  
System.out.println(e == f); //true  

 

14. 基本數據類型與包裝數據類型的使用標准

關於基本數據類型與包裝數據類型的使用標准如下:

1) 所有的POJO類屬性必須使用包裝數據類型。

2RPC方法的返回值和參數必須使用包裝數據類型。

3) 所有的局部變量推薦使用基本數據類型。

 

反例:

返回類型為基本數據類型,return包裝數據類型的對象時,自動拆箱有可能產生NPE

public int demo(){  
Integer result = null;  
return result;  
}  

 

15. 命名標准

包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有復數含義,類名可以使用復數形式。

 

反例:

cn.gov.shunde.detentionhouse.alarmModle 
 

 

正例:

cn.gov.shunde.detentionhouse.alarm.modle  

 

16. 常量命名標准

常量命名應該全部大寫,單詞間用下划線隔開,力求語義表達完整清楚,不要嫌名字長。

 

正例:

public class ConstantNameDemo {  
/** 
* max stock count 
*/  
  public static final Long MAX_STOCK_COUNT = 50000L; 
}

 

 

17. 抽象類命名使用Abstract或Base開頭

正例:

abstract class BaseControllerDemo{  
}  
abstract class AbstractActionDemo{  
}  

 

 

18. 類名使用UpperCamelCase風格

類名使用UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:(領域模型的相關命名)DO / BO / DTO / VO / DAO

 

19. 方法名、參數名、成員變量、局部變量使用lowerCamelCase風格

方法名、參數名、成員變量、局部變量都統一使用lowerCamelCase,必須遵從駝峰形式。

 

20. 杜絕完全不規范的縮寫

1)杜絕完全不規范的縮寫,避免望文不知義。

2)為了達到代碼自解釋的目標,任何自定義編程元素在命名時,使用盡量完整的單詞組合來表達。

3)類名應該是個名詞或名詞詞組的名字,如CustomerWikiPageAccountAddressParser等。

4)變量名應該是名詞,如stockNumber username等。

5)方法名應該是一個動詞或動詞詞組,如postPayment()deletePage()save()等。

 

反例:

// 姓名  
String xm = "xiaomin";  
// 編號  
String num = "A001";  

 

正例:

// 姓名  
String username = "xiaomin";  
// 編號  
String number = "A001";  

 

21. 所有的抽象方法(包括接口中的方法)必須要用javadoc注釋

所有的抽象方法(包括接口中的方法)必須要用javadoc注釋、除了返回值、參數、異常說明外,還必須指出該方法做什么事情,實現什么功能。 說明:如有實現和調用注意事項,請一並說明。

 

正例:

/** 
* fetch data by rule id 
*  
* @param ruleId rule id 
* @param page page number 
* @param jsonContext json format context 
* @return Result<XxxxDO> 
*/  
Result<XxxxDO> fetchDataByRuleId(Long ruleId, Integer page, String jsonContext);  

 

22. 所有的類都必須添加創建者信息

所有的類都必須添加創建者信息。 說明:在設置模板時,注意IDEA@author${USER},而eclipse@author${user},大小寫有區別,而日期的設置統一為yyyy/MM/dd的格式。

 

正例:

/** 
* Demo class 
*  
* @author keriezhang 
* @date 2016/10/31 
*/  
public class CodeNoteDemo {  
}  

 

23. 方法注釋

方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使用/* */注釋。注意與代碼對齊。

 

正例:

public void method() {  
// Put single line comment above code. (Note: align '//' comment with code)  
int a = 3;  
/** 
* Some description about follow code. (Note: align '/**' comment with code) 
*/  
int b = 4;  
}  

 

24. 枚舉類型字段必須要有注釋

所有的枚舉類型字段必須要有注釋,說明每個數據項的用途。

 

正例:

public enum TestEnum {  
/** 
* agree 
*/  
agree("agree"),  
/** 
* reject 
*/  
reject("reject");  
private String action;  
TestEnum(String action) {  
this.action = action;  
}  
public String getAction() {  
return action;  
}  
} 

 

25. 所有的覆寫方法,必須加@Override注解

getObject()get0bject()的問題。一個是字母的O,一個是數字的0,加@Override可以准確判斷是否覆蓋成功。另外,還有一個好處如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。

 

26. Map/Set的key為自定義對象時,必須重寫hashCode和equals

關於hashCodeequals的處理,遵循如下規則:  

只要重寫equals,就必須重寫hashCode。  

因為Set存儲的是不重復的對象,依據hashCodeequals進行判斷,所以Set存儲的對象必須重寫這兩個方法。  

如果自定義對象做為Map的鍵,那么必須重寫hashCodeequals

 

4.2、建議遵循

1.各層命名規約

Service/DAO層方法命名規

1) 獲取單個對象的方法用get做前綴, 如:getUsergetUserByName等。

2) 獲取多個對象的方法用list做前綴,復數結尾,如:listUserslistUsersByIdspageUsersByIds

3) 獲取統計值的方法用count做前綴。

4) 插入的方法用save/insert做前綴。

5) 刪除的方法用remove/delete做前綴。

6) 修改的方法用update做前綴。

 

正例:

User getUserById(Long id);  
List<User> listUsersByIds(Set<Long> ids);  
void addUser(User user);  
void updateUsername(Long id,String username);  
void removeUser(Long id);  

 

領域模型命名規約

1) 數據對象:xxxDOxxx即為數據表名。

2) 數據傳輸對象:xxxDTOxxx為業務領域相關的名稱。

3) 展示對象:xxxVOxxx一般為接口相關名稱。

4 入參對象:xxxPOxxx一般為接口相關名稱。

5 業務對象:xxxBOxxx一般為業務相關名稱。

6 POJODO/DTO/BO/VO的統稱,禁止命名成xxxPOJO

2. 重復代碼提煉

建議:

1把重復的代碼封裝成一個函數,讓需要的地方調用這個函數即可;

2如果重復的代碼是出現在互為兄弟類中,也就是這兩個類有共同的父類,可以把相同的代碼提到父類中;

3如果代碼只是類似而不是完全相同,可以把相同的部分和相異的部分提煉出來,設計成模板模式;

4如果兩個互不相干的類出現重復的代碼,可以把相同的部分,提到一個父類中或提取到一個接口中;

5重復代碼的類只屬於某一個函數,而且讓另外的類可以直接調用這個類中函數。

3. 發散式變化

問題:如果增加一個新功能,需要對原有功能做出大量的修改。這就是散發式變化的征兆。

處理:如果發生變化的兩個方向自然地形成了先后次序(比如說,先從數據庫取出數據,再對其進行金融邏輯處理),就可以用拆分階段將兩者分開,兩者之間通過一個清晰的數據結構進行溝通。不要讓一段代碼同時處理不同的事情

4. 主從關系明確的類應使用內部類

當一個對象A是另一個對象B的屬性時,並且對象A只會被對象B引用,此時應該將對象A定義為對象B的內部類,有利於避免對象A被其它對象引用或者被修改的風險。

 

正例:

public class FaceDetailVO {  
private Long faceId;  
private List<TogetherPersonInfo> togetherPersons;  
public static class  TogetherPersonInfo{       
private String name;         
private String idCard;  
}  
}  

 

5. 方法參數列表應該使用內部基本類型還是自定義DTO

定義方法時,參數較多,會讓代碼變得混亂。但使用對象對參數進行封裝,可讀性又差,而且無法保證必要參數都有賦值。從而建議使用以下的判斷依據:

1)方法參數超過4個,可以通過創建DTO對象對方法入參進行封裝。

2)如果方法參數不超過4個,可以直接作為入參。

例子:

@Data  
public class CheckPasswordDTO {  
private String username;  
private String password;  
}  
// 不建議  
Boolean checkPassword(CheckPasswordDTO checkPasswordDTO );  
// 建議,更直觀  
Boolean checkPassword(String username,String password);  

 

6. 方法參數及方法返回值不建議使用Map

 Map是一種鍵值對集合,使用Map作為方法參數及方法返回值,會讓調用者無法直觀地了解Map里到底是什么數據,並且容易造成調用者使用不當問題。在迫不得已,不得不使用Map的情況下,需要進行詳細的對鍵值對進行描述。

 

反例:

Map<Long, User> userMap(){  
// key 為用戶id
HashMap<Long, User> userMap = new HashMap<>();  
// do something  
return  userMap;  
} 

 

正例:

List<User> listUsers(){  
ArrayList<User> users = new ArrayList<>();  
// do something  
return  users;  
}  

 

7. 業務方法中,盡可能使用拋出異常來代替if-else

在編輯業務方法時,要盡量突出主業務邏輯,故針對一些必要條件的判斷,應該在不滿足的情況下直接拋出異常,而不是在代碼中編碼大量if-else嵌套。

 

反例:

public Boolean  login(User user){   
if (user != null) {  
if ( user.getUsername() != null && user.getUsername() != "") {    
if (user.getPassword() != null && user.getPassword() != "") {    
if (user.getStatus() != null && user.getStatus() != 0) {    
// do something    
return true;    
}    
}    
}   
}   
return false;    
}

 

正例:

public void  login(User user){    
if ( user == null) {  
throw BusinessException.operate("用戶對象不能為空");  
}  
if ( user.getUsername() == null || user.getUsername() == "" ) {    
throw BusinessException.operate("用戶名不能為空");    
}    
if ( user.getPassword() == null || user.getPassword() == "") {    
throw BusinessException.operate("密碼不能為空");    
}    
if ( user.getStatus() == null || user.getStatus() == 0) {    
throw BusinessException.operate("用戶已注銷");    
}                
// do something          
}    

 

8. 循環體內,不要操作數據庫

建議:

查詢時將數據批量查詢出來,得到List集合;

根據List集合得到相關外鍵集合,根據外鍵集合查詢相當記錄,並轉為Map

后面通過根據相應的外鍵從Map中取出相應的對象;

 

正例:

List<Supervisor> records = iPage.getRecords();  
Set<Long> identityIds = records.stream().map(Supervisor::getIdentityId).collect(Collectors.toSet());  
Map<Long, Identity> identityMap = identityService.listByIds(identityIds).stream().collect(Collectors.toMap(Identity::getId, Function.identity()));  
ArrayList<SupervisorVO> supervisorVOs = new ArrayList<>();  
for (Supervisor record : records) {  
Identity identity = identityMap.get(record.getIdentityId());  
SupervisorVO supervisorVO = SupervisorVO.builder()  
.gender(identity != null ? identity.getGender() : (byte)0)  
.id(record.getId())  
.idCard(record.getIdCard())  
.name(record.getName())  
.build();  
supervisorVOs.add(supervisorVO);  
}  

 

9. 領域模型使用區域

建議:

1數據對象:xxxDOxxx即為數據表名,建議只在dao層及service層出現。

2)數據傳輸對象:xxxDTOxxx為業務領域相關的名稱,可以穿梭在各層中。

3)展示對象:xxxVOxxx一般為接口相關名稱,建議只在controller層及service層出現,並且只作為接口或方法返回對象。

4入參對象:xxxPOxxx一般為接口相關名稱,建議只在controller層及service層出現,並且只作為接口或方法參數對象。

5業務對象:xxxBOxxx一般為業務相關名稱,根據業務需求,可以穿梭在各層中。

  

 

五、參考資料

5.1、javadoc標簽使用

1. 常用標簽

標簽

說明

@author

作者標識

@version

版本號

@return

對函數返回值的描述

@deprecated

標識過期API(為了保證兼容性,仍可用,但不推薦用)

@throws

構造函數或方法會拋出的異常

@exception

@throws

@see

引用,查看相關的內容,如類,方法,變量等,必須頂頭寫

{@link .#成員}

引用,同@see,但可寫在任意位置

{@value}

對常量注釋,如果其值包含在文檔中,通過改標簽引用常量的值

{@code}}

{@code text}將文本標記為code,會被解析成 text } ,Javadoc成只要涉及到類名或者方法名,都需要使用@code進行標記

@param

說明方法的參數

@inheritDoc

用於繼承父類中的Javadoc,父類的文檔注釋,被繼承到了子類

 

vm中要加:

錯誤“編碼 GBK 的不可映射字符”

-encoding utf-8 -charset utf-8

 

錯誤:未知標記:date

-tag date:a:"日期:"

2. 方法上的文檔標注

寫在方法上的文檔標注一般分為三段:

第一段:概要描述,通常用一句或者一段話簡要描述該方法的作用,以英文句號作為結束

第二段:<p>詳細描述,通常用一段或者多段話來詳細描述該方法的作用,一般每段話都以英文句號作為結束,段中分行使用<br>

第三段:文檔標注,用於標注參數、返回值、異常、參閱等

 

方法必要的標簽

/**

*概述.

*

*<p>描述.

*

*@param

*@return

*@author

*@since

*/

例子:

/** 
* 添加緩存. 
* 
* <p>支持將已實現Serializable的類存入緩存中. 
* 
* @author wangquanqing 
* @since 2020/12/15 17:03 
* @param key 唯一標識 
* @param value 數據體 
* @param timeout 過期時長,單位秒 
* @return void 
*/  
<T extends Serializable> void put(String key, T value, Integer timeout);  

 

 

3. 類上的文檔標

寫在類上的文檔標注一般分為三段:

第一段:概要描述,通常用一句或者一段話簡要描述該類的作用,以英文句號作為結束

第二段:<p>詳細描述,通常用一段或者多段話來詳細描述該類的作用,一般每段話都以英文句號作為結束,段中分行使用<br>

第三段:文檔標注,用於標注作者、創建時間、參閱類等信息

 

類必要的標簽

/**

*概述.

*

*<p>描述.

*

*@author

*@since

*/

例子:

/** 
* 緩存服務接口. 
* 
* <p>緩存服務接口. 
* 
* @author wangquanqing 
* @since 2020/12/15 17:12 
*/  
public interface CacheService {  
}  

        

4.idea類注釋模板配置

路徑:File>Settings>Editor>File and Code Templates

在File Header文檔中添加:

/**
* @author ${USER}
* @since ${DATE} ${TIME}
*/

 

 

 

5.idea方法注釋模板配置

路徑:File>Settings>Editor>Live Templates

點擊右上角+,新增Template Group,例如myGroup

選中新增的myGroup,點擊右上角+,新Live Template,命名為*

模板內容:

*
* $summary$.
*
* <p>$describe$.
*
* @author $user$
* @since $date$ $time$
$params$
* @return $returns$
*/

 

 

 

點擊Edit variables,按下圖選擇表達式

 

 

 params中的Default value為:

groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {result+='* @param ' + params[i] + ((i < params.size() - 1) ? '\\n ' : '')}; return result", methodParameters())

使用方法:

在方法名上敲寫

/**

然后按"Tab"鍵

 

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM