簡(jiǎn)介
說(shuō)明
本文介紹精選的Java編程規(guī)范(代碼規(guī)范)。遵守這些規(guī)范,代碼的bug數(shù)將會(huì)大幅減少,代碼可維護(hù)性、可讀性、擴(kuò)展性會(huì)大幅上升。(本文持續(xù)更新)
為什么要有編程規(guī)范?
編程規(guī)范有如下作用:
- 提高代碼可讀性、維護(hù)性、擴(kuò)展性
- 提高開(kāi)發(fā)速度、減少bug
- 有助于留住人才(接手別人的垃圾代碼占離職原因的很大一部分)
好的編程規(guī)范是怎樣的?
- 規(guī)范要精簡(jiǎn),否則不利于普及
規(guī)范的落實(shí)
- 編程規(guī)范要配合代碼審核,否則都是空話。
- 代碼審核必須在代碼提測(cè)之前進(jìn)行。(因?yàn)槿绻麥y(cè)試通過(guò)了,由于不符合編程規(guī)范再改動(dòng)的話,需要費(fèi)時(shí)間進(jìn)行回歸測(cè)試,導(dǎo)致影響上線)
- 代碼評(píng)審要以平臺(tái)的方式進(jìn)行:被評(píng)審者將代碼提到評(píng)審平臺(tái),然后評(píng)審者在此平臺(tái)進(jìn)行批注,被評(píng)審者進(jìn)行代碼修改。
- 這種代碼評(píng)審基本是毫無(wú)卵用的:多個(gè)人聚在一個(gè)會(huì)議室里,盯著一個(gè)電腦投屏進(jìn)行評(píng)審。
- 代碼規(guī)范要加入績(jī)效考核(占比在1%~5%之間),否則無(wú)法引起重視。
- 好的大公司就是這么做的,代碼極好,擴(kuò)展性極好,技術(shù)氛圍極好,項(xiàng)目的質(zhì)量極好、用戶的反饋極好...
格式
- 單個(gè)方法不能超過(guò)60行,如果超過(guò)必須拆分
- IDEA大概能顯示40行代碼,60行對(duì)一個(gè)方法來(lái)說(shuō)肯定是夠用的。
- 反例:所有邏輯堆在一個(gè)方法里,單個(gè)方法成百上千行
- 單行代碼不能超過(guò)100個(gè)字符(包括空格),如果超過(guò)必須換行。換行時(shí)遵循如下原則:
- 第二行相對(duì)第一行縮進(jìn) 4 個(gè)空格;從第三行開(kāi)始,不再繼續(xù)縮進(jìn)
- 運(yùn)算符與下文一起換行;方法調(diào)用的點(diǎn)符號(hào)與下文一起換行
- if/for/while/switch/do 等保留字與括號(hào)之間必須加空格。
命名
- 命名可以很長(zhǎng),不能為了縮短長(zhǎng)度進(jìn)行單詞的簡(jiǎn)寫(xiě)(提高可讀性)。(例外:接口實(shí)現(xiàn)類以Impl結(jié)尾等通用的縮寫(xiě))
- 正例:interfaceOperationCode
- 反例:intOpCode
- 字段名第一個(gè)字母必須小寫(xiě)。
- 重大bug:用lombok,若前兩個(gè)字母大寫(xiě),用Jackson序列化時(shí)第二個(gè)大寫(xiě)會(huì)變成小寫(xiě)。
-
類名和包名使用單數(shù)形式,但是如果類名有復(fù)數(shù)含義,類名可以使用復(fù)數(shù)形式
- 正例:com.abc.user.detail
- 反例:com.abc.users.detail
- 包名統(tǒng)一使用小寫(xiě),點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞。如果必須要用兩個(gè)單詞,也都要小寫(xiě)
- 正例 : com.alibaba.open.util、com.abc.userdetail
- 抽象類命名使用Abstract開(kāi)頭;異常類命名用Exception結(jié)尾;接口類命名不要添加無(wú)用的前綴與后綴(比如:I開(kāi)頭、Interface結(jié)尾)
- POJO類中布爾類型變量不要加is前綴,否則部分框架解析會(huì)引起序列化錯(cuò)誤。
- 反例:將是否刪除定義為:Boolean isDeleted
- 正例:將是否刪除定義為:Boolean deletedFlag
- Controller的url命名規(guī)范:
- 駝峰命名。
- 統(tǒng)一是這種:/表名/增刪改查,比如:用戶詳情的增加接口:/userDetail/add。增刪改查對(duì)應(yīng)的英文如下:
- 增加:add
- 編輯:edit
- 刪除:delete
- 分頁(yè)查詢:page
- 列表查詢:list
常量
- 不要使用一個(gè)常量類維護(hù)所有常量,應(yīng)該按常量功能進(jìn)行歸類,分開(kāi)維護(hù)(提高可維護(hù)性)。
- 正例:緩存相關(guān)的常量: CacheConstant;配置相關(guān)的常量: ConfigConstant。
-
常量命名全部大寫(xiě),單詞間用下劃線隔開(kāi)。
- 正例 : MAX_STOCK_COUNT
- 反例 : Max_Stock_Count
類型
-
禁止使用Map作為方法的入?yún)⒒蚍祷刂担仨毷褂脤?duì)象明確列出所有字段。(例外:有些地方只能是Map類型,比如:短信發(fā)送接口,參數(shù)是不確定的,調(diào)用方自定義key和value)
- 反例:給別人提供Feign接口時(shí),返回值是Map類型。
- 正例:定義一個(gè)XxxVO,將字段寫(xiě)到XxxVO里,作為接口返回值。
- 正例:查很多用戶詳情時(shí),為提高接口速度,將單個(gè)SQL轉(zhuǎn)成批量SQL(將多個(gè)=轉(zhuǎn)化成單個(gè)IN),然后將其結(jié)果轉(zhuǎn)成key為用戶id,value為用戶數(shù)據(jù)的Map,再進(jìn)行后續(xù)操作。
- 如果代碼內(nèi)部用到了Map,必須在map定義處寫(xiě)明key和value分別是什么。
- 這些地方必須使用包裝數(shù)據(jù)類型:所有的 POJO 類屬性、RPC 方法的返回值和參數(shù)
- 即:要用Integer、Long等,不要用int、long
- 實(shí)體類不要implements?Serializable
- Serializable是序列化,現(xiàn)在都是使用JSON了,不要再實(shí)現(xiàn)Serializable了!(題外話:寫(xiě)代碼時(shí)要思考,看究竟有什么用,不明白的就去搞明白,不要糊里糊涂地寫(xiě))。
- 當(dāng)然,有時(shí)候就必須要實(shí)現(xiàn)Serializable,比如:Dubbo的實(shí)體類,默認(rèn)用的是Serializable的序列化來(lái)傳遞數(shù)據(jù),所以必須實(shí)現(xiàn)Serializable。
參數(shù)與返回值
-
不能修改入?yún)?duì)象的字段,如果要修改,必須新創(chuàng)建一個(gè)對(duì)象
- 例外:如果方法就是為了給入?yún)?duì)象賦值的,可以去修改。
- 方法入?yún)⒌膫€(gè)數(shù)必須小于5
- 如果大于等于5,必須將入?yún)⒎庋b為一個(gè)類。
- 分頁(yè)接口的請(qǐng)求和響應(yīng)必須繼承公共父類,如下:
分頁(yè)的請(qǐng)求類
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class PageRequest {
@ApiModelProperty("當(dāng)前頁(yè)")
private Long currentPage = 0L;
@ApiModelProperty("每頁(yè)個(gè)數(shù)")
private Long pageSize = 10L;
@ApiModelProperty("創(chuàng)建時(shí)間開(kāi)始")
private LocalDateTime createTimeStart;
@ApiModelProperty("創(chuàng)建時(shí)間結(jié)束")
private LocalDateTime createTimeEnd;
}
分頁(yè)的響應(yīng)類
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
public class PageResponse<T> {
@ApiModelProperty("當(dāng)前頁(yè)")
private Long currentPage = 0L;
@ApiModelProperty("每頁(yè)個(gè)數(shù)")
private Long pageSize = 10L;
@ApiModelProperty("總個(gè)數(shù)")
private Long totalSize = 0L;
private List<T> dataList;
}
注釋
- 所有接口說(shuō)明都使用knife4j的,不要重復(fù)寫(xiě)注釋。
- 方法注釋中沒(méi)用的說(shuō)明要?jiǎng)h掉,比如:參數(shù)后如果沒(méi)加詳細(xì)說(shuō)明,就把這行參數(shù)注釋刪掉。
- 文件前不要讓Idea自動(dòng)生成創(chuàng)建時(shí)間、創(chuàng)建人。
- 原因:這是多余的操作,git已經(jīng)記錄了此文件的創(chuàng)建人和創(chuàng)建時(shí)間
復(fù)用
- 邏輯重復(fù)的代碼要抽取為同一個(gè)(提高復(fù)用性)
- 正例:頁(yè)面上的分頁(yè)查詢、Excel導(dǎo)出查詢的數(shù)據(jù),這兩個(gè)共用查數(shù)據(jù)的方法。
單一職責(zé)
- 每個(gè)接口的參數(shù)和返回值必須是獨(dú)立的。
- 比如:查詢用戶的入?yún)⒑头祷刂凳牵篣serPageQueryBO,UserPageQueryVO;保存用戶的入?yún)⒑头祷刂凳牵篣serSaveBO,UserSaveVO。雖然UserPageQueryBO和UserSaveBO會(huì)有很多重復(fù)的字段,但也不能合并為一個(gè)BO。如果進(jìn)行了合并,會(huì)有如下致命缺點(diǎn):
- 看接口入?yún)o(wú)法確定用到了哪些字段,必須看代碼實(shí)現(xiàn)才能確定,極不清晰。
- 接口文檔不清晰,當(dāng)導(dǎo)出接口文檔時(shí),前端開(kāi)發(fā)人員也不好分辨。
- 此情況例外:功能極其接近時(shí)允許用同一個(gè)參數(shù)和返回值。
- 比如查詢用戶需要提供兩個(gè)接口:分頁(yè)查詢和全量查詢,此時(shí)可以共用查詢?nèi)雲(yún)ⅲ瑢⑵涠x為:UserQueryBO。
- 比如:查詢用戶的入?yún)⒑头祷刂凳牵篣serPageQueryBO,UserPageQueryVO;保存用戶的入?yún)⒑头祷刂凳牵篣serSaveBO,UserSaveVO。雖然UserPageQueryBO和UserSaveBO會(huì)有很多重復(fù)的字段,但也不能合并為一個(gè)BO。如果進(jìn)行了合并,會(huì)有如下致命缺點(diǎn):
- 每個(gè)類的字段必須是有效的
- 比如:查詢用戶的入?yún)?shí)體類里,只包含必須的字段,比如:用戶名、手機(jī)號(hào),不要包含用不到的字段,比如:用戶id,用戶的密碼。
數(shù)據(jù)庫(kù)
注意事項(xiàng)
- 狀態(tài)、類型等字段Java代碼使用枚舉類型,數(shù)據(jù)庫(kù)必須使用字符串(提高可維護(hù)性)
- 具體方法見(jiàn):SpringBoot--在Entity(DAO)中使用枚舉類型_IT利刃出鞘的博客-CSDN博客
- 創(chuàng)建時(shí)間、更新時(shí)間、是否刪除等字段,必須放到列的最后。(提高可讀性)
- 后邊如果有新加的字段,要加在這些字段之前,永遠(yuǎn)保持這些字段在最后!
- 主鍵ID一律使用MyBatis-Plus自帶的雪花算法來(lái)生成。(有利于數(shù)據(jù)庫(kù)遷移等)
- 查數(shù)據(jù)的方式要最優(yōu),如下方式要優(yōu)先考慮前邊的方式。
- lambdaQuery、在代碼中用字符串拼MyBatis-Plus條件(用字符串指定字段名)、Mapper中寫(xiě)注解、在XML中寫(xiě)。
- 查數(shù)據(jù)時(shí)盡量不要聯(lián)表查。(因?yàn)閿?shù)據(jù)量大時(shí),聯(lián)表查會(huì)很慢)。
建表的必要字段
說(shuō)明
為了便于排查問(wèn)題,建表時(shí)需要加一些必要的字段:創(chuàng)建人、更新人、創(chuàng)建時(shí)間、更新時(shí)間、刪除標(biāo)記。
SQL
ALTER TABLE `庫(kù)名`.`表名`
ADD COLUMN `id` bigint NOT NULL COMMENT '主鍵',
ADD COLUMN `create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
ADD COLUMN `update_time` datetime NOT NULL COMMENT '修改時(shí)間',
ADD COLUMN `create_id` varchar(32) COMMENT '創(chuàng)建人ID',
ADD COLUMN `create_name` varchar(32) COMMENT '創(chuàng)建人ID',
ADD COLUMN `update_id` varchar(32) COMMENT '修改人名字',
ADD COLUMN `update_name` varchar(32) COMMENT '修改人名字',
ADD COLUMN `delete_flag` bigint NOT NULL DEFAULT 0 COMMENT '刪除標(biāo)記。0:未刪除;其他:已刪除';
詳解
- 創(chuàng)建時(shí)間和更新時(shí)間的字段名
- 阿里開(kāi)發(fā)手冊(cè)的規(guī)范:
- 嵩山版、華山版等:表必備三字段:id,create_time,update_time。(推薦)
- 泰山版等:表必備三字段:id,gmt_create,gmt_modified。(不推薦)
- 注意:時(shí)間相關(guān)的字段名應(yīng)該有“time”才好。而且有一些Mock工具(例如:ApiFox)會(huì)根據(jù)字段名自動(dòng)構(gòu)造假數(shù)據(jù),字段中帶“time”工具就會(huì)自動(dòng)生成很接近實(shí)際的mock數(shù)據(jù),有利于前端自測(cè)代碼。
- 阿里開(kāi)發(fā)手冊(cè)的規(guī)范:
- 更新時(shí)間不要設(shè)置為自動(dòng)更新
- 不要讓更新時(shí)間自動(dòng)更新,即:不要這樣寫(xiě):ADD COLUMN `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間',
- 原因:
- 數(shù)據(jù)庫(kù)服務(wù)器很可能沒(méi)有正確設(shè)置時(shí)區(qū),如果“更新時(shí)間”字段使用了數(shù)據(jù)庫(kù)服務(wù)器的時(shí)區(qū)進(jìn)行了自動(dòng)更新,那這個(gè)時(shí)間就是不正確的。
- 以我工作的幾家公司來(lái)看,數(shù)據(jù)庫(kù)服務(wù)器的時(shí)區(qū)基本都是錯(cuò)誤的。
- 正確的方法是:“創(chuàng)建時(shí)間”和“更新時(shí)間”全部通過(guò)應(yīng)用來(lái)自動(dòng)生成。比如MybatisPlus的自動(dòng)填充:MyBatis-Plus--自動(dòng)填充的用法_IT利刃出鞘的博客-CSDN博客
- 刪除標(biāo)記不要使用0,1來(lái)表示,在刪除時(shí)應(yīng)該將id的值賦值給delete_flag
- 如果用0,1表示,會(huì)影響唯一索引。見(jiàn):MyBatis-Plus--解決邏輯刪除與唯一索引的問(wèn)題--方法/實(shí)例_IT利刃出鞘的博客-CSDN博客
- 數(shù)據(jù)庫(kù)的時(shí)間字段對(duì)應(yīng)的Java的DAO的字段類型要用LocalDateTime
- 現(xiàn)在21世紀(jì)了,不要再用Date了。
- LocalDateTime的優(yōu)點(diǎn):
- 可明確知曉:這是個(gè)日期+時(shí)間的字段。(Date不能一眼看出是日期還是時(shí)間還是兩者)
- LocalDateTime的格式化等操作是線程安全的。
- LocalDateTime的方法很豐富。
上邊的字段必須有公共的實(shí)體類(CommonEntity),使用Xxx extends CommonEntity的方式,不要在自己實(shí)體類手寫(xiě)這些字段。
CommonEntity如下:
package com.example.knife.common.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 數(shù)據(jù)庫(kù)公共實(shí)體類
*/
@Data
public class CommonEntity {
/**
* 主鍵
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 創(chuàng)建時(shí)間
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 修改時(shí)間
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 創(chuàng)建人ID
*/
private String createId;
/**
* 創(chuàng)建人名字
*/
private String createName;
/**
* 更新人ID
*/
private String updateId;
/**
* 更新人名字
*/
private String updateName;
/**
* 刪除標(biāo)記。0:未刪除;其他:已刪除
*/
@TableLogic("id")
private Long deleteFlag;
}
API選用
- 時(shí)間類型必須使用JDK8的類型,比如:LocalDateTime,LocalDate,LocalTime
- 不要使用Date類型(Date的API難用,Date含義不清晰(不知道是日期還是時(shí)間)多線程格式化時(shí)要考慮線程安全等)。這老一套的東西該摒棄了。
- 小數(shù)數(shù)字類型必須使用BigDecimal。
- double和float會(huì)失真。
- BigDecimal使用時(shí)也要小心,盡量學(xué)一下再用,它還是要注意一些問(wèn)題的,比如:若不指定精度,除不盡的算術(shù)操作會(huì)拋異常。詳見(jiàn):Java之BigDecimal系列--使用/教程/實(shí)例_IT利刃出鞘的博客-CSDN博客
- 題外話:對(duì)于IT來(lái)說(shuō),不是說(shuō)知道某個(gè)技術(shù)就行了,而要深入下去,無(wú)論是普通的API還是高級(jí)技術(shù),無(wú)論是哪個(gè)技術(shù)都有需要注意的地方,都可能有坑。如果對(duì)技術(shù)淺嘗輒止,那么就很難進(jìn)步,很可能就是一年的經(jīng)驗(yàn)用十年,難以成長(zhǎng)為技術(shù)大佬。
- Lombok的使用
- 不要使用@Accessors,可以使用@Builder
- 為了方便給字段賦值,lombok提供了一些注解,但不要用@Accessors,因?yàn)樗傻膕et方法有返回值,會(huì)存在問(wèn)題:有些中間件會(huì)判斷方法是否有返回值進(jìn)而進(jìn)行操作,比如:EasyExcel。(如果使用了@Accessors,會(huì)導(dǎo)致導(dǎo)入Excel時(shí)取不到值)
- 題外話:lombok等中間件固然很方便,但任何API都要選用,不要全部采用。其中一個(gè)重要的原則就是:不要改變正常的邏輯,比如:set方法就不應(yīng)該有返回值。
- 不要使用@Accessors,可以使用@Builder
- 要使用@Autowired,不要用@Resource。
- Spring的項(xiàng)目,就盡量用Spring的東西。
技術(shù)選型
必須使用主流且穩(wěn)定的技術(shù)棧(見(jiàn):Java后端開(kāi)發(fā)技術(shù)選型_IT利刃出鞘的博客-CSDN博客)
不得使用如下技術(shù)(如下技術(shù)都有穩(wěn)定、成熟的同類技術(shù)可以替代):
不穩(wěn)定,bug多的技術(shù)
- fastjson
- hutool
資源消耗很大的技術(shù)
- FileBeat
全局處理
異常
- 必須將錯(cuò)誤信息作為異常拋出來(lái),讓全局異常處理器去處理。
- 不能自己返回錯(cuò)誤信息。(會(huì)增加代碼的復(fù)雜度,如果代碼有多個(gè)調(diào)用,層層傳遞錯(cuò)誤信息會(huì)無(wú)法維護(hù))
- 不能自己捕獲了異常然后不處理信息。(會(huì)導(dǎo)致無(wú)法排查問(wèn)題)
包裝返回值給前端文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-454898.html
- 自己不要去包裝返回值給前端,由AOP統(tǒng)一去包裝。(減少代碼量,便于維護(hù))
feign調(diào)用文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-454898.html
- 自己不要去包裝返回值給調(diào)用方,由AOP統(tǒng)一去包裝。(減少代碼量,便于維護(hù))
- 調(diào)用方不要手動(dòng)去解析包裝數(shù)據(jù),需要由統(tǒng)一的解碼器去處理。
邏輯
- json字符串解析為對(duì)象
- 一個(gè)json字符串必須有一個(gè)完全對(duì)應(yīng)的java對(duì)象。(代碼可讀性高)
- 必須一步到位將json字符串解析為整個(gè)java對(duì)象,不能解析完外層再解析內(nèi)層。
- get方法不能有其他邏輯,必須直接返回字段本身。
- 如果必須要手動(dòng)捕獲異常,必須要輸出詳細(xì)堆棧 將堆棧打出來(lái)
- 如果有日志組件,必須使用日志組件。
- 如果沒(méi)有日志組件,用log.error("xxx", e);?
- 所有地方必須判斷null
- 如果是null,必須拋出異常,信息為:xxx不能為null。
- 禁止將業(yè)務(wù)對(duì)象作為Map的key,禁止覆寫(xiě)equals和hashCode。
- 因?yàn)閯e人也會(huì)用這個(gè)業(yè)務(wù)對(duì)象,改了這些會(huì)導(dǎo)致別人的業(yè)務(wù)出問(wèn)題!
其他
- 代碼中不得存在被IDEA警告的代碼,若存在則必須看IDEA的提示并進(jìn)行修改。(包括:錯(cuò)的單詞拼寫(xiě),泛型警告,優(yōu)化的提示等)
到了這里,關(guān)于Java編程規(guī)范(代碼規(guī)范)--精選的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!