天天看點

java開發規範(持續更新)一、程式設計規範二、項目規範三、MVC 規範四、資料庫 規範五、其他

該規範基于阿裡開發規範和1024創新實驗室團隊的開發規範整合自己公司的規範整理而成

文章目錄

  • 一、程式設計規範
    • 1、項目命名規範
    • 2、`TODO/FIXME` 規範
    • 3、方法參數規範
    • 4、注釋
      • 4.1、方法内邏輯注釋
      • 4.2、方法注釋
    • 5、狀态型取值應使用枚舉規範
    • 6、固定常量使用常量類
    • 7、所有mapper接口和service接口需要加上注釋
    • 8、ThreadLocal在set()後需要在正确的時間remove()
  • 二、項目規範
    • 1、目錄結構
    • 2、domain中的javabean規範
      • 1)javabean整體要求:
      • 2)model包下javabean要求:
      • 3)傳輸對象;`XxxxDTO`,要求:
      • 4)視圖對象;`XxxxVO`,要求:
      • 5)業務對象; `BO`,要求:
  • 三、MVC 規範
    • 1、整體分層
    • 2、 `controller` 層規範
    • 3、 `service` 層規範
    • 4、 `manager` 層規範
    • 5、 `mapper` 層規範
    • 6、`boolean`類型的屬性命名規範
  • 四、資料庫 規範
    • 1、建表規範
    • 2、枚舉類表字段注釋需要将所有枚舉含義進行注
    • 3、資料庫編碼
    • 4、合理結合業務給表字段添加索引和唯一索引具體索引規範請參照《阿裡巴巴 Java 開發手冊》索引規約
  • 五、其他
    • 1、代碼送出規範
    • 3、保持項目整潔

一、程式設計規範

1、項目命名規範

全部采用小寫方式, 以中劃線分隔。

正例:

mall-management-system / order-service-client / user-api

反例:

mall_management-system / mallManagementSystem / orderServiceClient

2、

TODO/FIXME

規範

TODO/TBD(to be determined)

注釋一般用來描述已知待改進、待補充的修改點,并且加上作者名稱。

FIXME

注釋一般用來描述已知缺陷,它們都應該有統一風格,友善文本搜尋統一處理。如:

// TODO <author-name>: 補充XX處理
// FIXME <author-name>: XX缺陷
           

3、方法參數規範

無論是

controller,service,manager,dao

亦或是其他的代碼,每個方法最多

3

個參數,如果超出

3

個參數的話,要封裝成

javabean

對象。

  1. 友善他人調用,降低出錯幾率。尤其是當參數是同一種類型,僅僅依靠順序區分,稍有不慎便是災難性後果,而且排查起來也極其惡心。
  2. 保持代碼整潔、清晰度。當一個個方法裡充斥着一堆堆參數的時候,再堅強的人,也會身心疲憊。

反例:

/**
* 使用證書加密資料工具方法
*
* @param param
* @param password 加密密碼
* @param priCert 私鑰
* @param pubCert 公鑰
* @return 傳回加密後的字元串
*/
public String signEnvelop(JdRequestParam param, String password, String priCert, String pubCert){}
           

4、注釋

4.1、方法内邏輯注釋

注釋除了說明作用、邏輯之外。還有一個很重要的原因:當業務邏輯過于複雜,代碼過于龐大的時候,注釋就變成了一道道美化環境、分離與整理邏輯思路的路标。這是很重要的一點,它能有效得幫助我們免于陷入代碼與業務邏輯的泥沼之中。

/**
* 開始抽獎方法
* 儲存中獎資訊、獎勵使用者積分等
* @param luckDrawDTO
* @return ResponseDTO 傳回中獎資訊
*/
public ResponseDTO<String> startLuckDraw(LuckDrawDTO luckDrawDTO) {

    // -------------- 1、校驗抽獎活動基本資訊 ------------------------
    xxx僞代碼一頓操作

    // -------------- 2、新增抽獎記錄 -------------------------------
    xxx僞代碼一頓操作

    // -------------- 3、如果需要消耗積分,則扣除鋼镚積分 -------------
    xxx僞代碼一頓操作

    // -------------- 4、擷取獎品資訊,開始翻滾吧 --------------------
    xxx僞代碼一頓操作

    return ResponseDTO.succ(luckDrawPrizeVO);
}
           

4.2、方法注釋

方法要盡量通過方法名自解釋,不要寫無用、資訊備援的方法頭,不要寫空有格式的方法頭注釋。

方法頭注釋内容可選,但不限于:功能說明、傳回值,用法、算法實作等等。尤其是對外的方法接口聲明,其注釋,應當将重要、有用的資訊表達清楚。

正例:

/**
 * 解析轉換時間字元串為 LocalDate 時間類
 * 調用前必須校驗字元串格式 否則可能造成解析失敗的錯誤異常
 *
 * @param dateStr 必須是 yyyy-MM-dd 格式的字元串
 * @return LocalDate
 */
public static LocalDate parseYMD(String dateStr){}
           

反例:

/**
 * 校驗對象
 *
 * @param t
 * @return String
 */
public static <T> String checkObj(T t);
           

反例中出現的問題:

  • 方法注釋沒有說明具體的作用、使用事項。
  • 參數、傳回值,空有格式沒内容。這是非常重要一點,任何人調用任何方法之前都需要知道方法對參數的要求,以及傳回值是什麼。

5、狀态型取值應使用枚舉規範

舉例:

@Getter
public enum BasketStatusEnum {

    FREE(1, "空閑"),
    BOUND(2, "已綁定"),
    DELIVERING(3, "配送中(已上車)"),
    IN_SCHOOL(4, "在學校(已下車)"),
    BREAK(7, "損壞");

    private int code;
    private String name;

    BasketStatusEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }
}
           

6、固定常量使用常量類

例如無加工的加工方式code固定為

WJG

,應該使用常量類收納該常量

常量類應該私有化構造方法,且使用

final

修飾類,防止常量類被繼承後,常量類中的常量影響子類中的字段

/**
 * @author zhangxing
 * @date 2020/04/20
 */
public final class ApplicationConstant {

    private ApplicationConstant() {
    }

    public static final String PROFILE_DEVELOPMENT = "dev";
    public static final String PROFILE_PRODUCTION = "prod";

    /**
     * 無加工加工方式
     */
    public static final String NO_MANUFACTURE_HANDLE_METHOD = "WJG";

}
           

7、所有mapper接口和service接口需要加上注釋

8、ThreadLocal在set()後需要在正确的時間remove()

二、項目規範

1、目錄結構

top.sclf.jyhpv.子產品名              源碼目錄
|-- domain                            javabean(下方有domain的專門說明)
|-- |--- bo                               業務對象(Business Object)
|-- |--- dto                         			傳輸層對象(Data Transfer Object)
|-- |--- vo                         			視圖對象(View Object)
|-- |--- model                         		資料對象(和資料庫一一對應的對象)
|-- converter                         用于但不限于domain下的對象轉換

|-- controller                        控制器
|-- service                           業務層
|-- |--- impl                         業務實作層
|-- manager                           manager層,下方會詳細說明
|-- mapper                            mapper接口和xml

|-- enums                             枚舉
|-- constant                          常量(使用class,且class上添加final,請勿使用interface和enum)
|-- annotations                       注解
|-- validator                         自定義校驗類
|-- commons                           通用對象
|-- config                            配置類,如swagger配置等..
|-- listener                          監聽器(包含接口與實作)
|-- factory                           工廠類
|-- server                            服務相關,如WebSocket伺服器
|-- aspect                            切面
|-- database                          資料庫/資料源相關
|-- exception                         異常相關
|-- util                              工具類
|-- task                              定時任務
|-- export                            資料導出業務
|-- interceptor                       攔截器

|-- Application.java                  啟動類
           

2、domain中的javabean規範

1)javabean整體要求:

  • 不得有任何的業務邏輯或者計算
  • 基本資料類型必須使用包裝類型

    (Integer, Double、Boolean 等)

  • 不允許有任何的預設值
  • 每個屬性必須添加注釋,并且必須使用多行注釋。
  • 必須使用

    lombok

    簡化

    getter/setter

    方法
  • 建議對象使用

    lombok

    @Builder ,@NoArgsConstructor

    ,同時使用這兩個注解,簡化對象構造方法以及set方法。

2)model包下javabean要求:

  • Xxxx 與資料庫表名保持一緻,駝峰規則
  • 類中字段要與資料庫字段保持一緻,不能缺失或者多餘
  • 類中的每個字段添加注釋,并與資料庫注釋保持一緻
  • 不允許有組合
  • 項目内的日期類型必須統一,使用

    java.time.LocalDateTime

3)傳輸對象;

XxxxDTO

,要求:

  • 不可繼承自

    Entity

  • VO

    可以繼承、組合其他

    DTO,VO,BO

    等對象
  • VO

    隻能用于傳回前端、rpc 的業務資料封裝對象

4)視圖對象;

XxxxVO

,要求:

  • 不可繼承自

    Entity

  • VO

    可以繼承、組合其他

    DTO,VO,BO

    等對象
  • VO

    隻能用于傳回前端、rpc 的業務資料封裝對象

5)業務對象;

BO

,要求:

  • 不可以繼承自

    Entity

  • BO

    對象隻能用于

    service,manager,dao

    層,不得用于

    controller

三、MVC 規範

1、整體分層

  • controller 層
  • service 層
  • manager 層
  • dao 層

2、

controller

層規範

1)RESTful的URL設計參考https://cizixs.com/2016/12/12/restful-api-design-guide/

2)每個方法必須添加

swagger

文檔注解

@ApiOperation

,并填寫接口描述資訊,描述最後必須加上作者資訊

@author zhangxing

正例:

@ApiOperation("更新部門資訊 @author zhangxing")
    @PostMapping("/department/update")
    public ResponseDTO<String> updateDepartment(@Valid @RequestBody DeptUpdateDTO deptUpdateDTO) {
        return departmentService.updateDepartment(deptUpdateDTO);
    }
           

3)controller 負責協同和委派業務,充當路由的角色,每個方法要保持簡潔:

  • 不做任何的業務邏輯操作
  • 不做任何的參數、業務校驗,參數校驗隻允許使用@Valid 注解做簡單的校驗
  • 不做任何的資料組合、拼裝、指派等操作
  • 允許設定預設排序,并且需要判斷排序規則為空時可以設定預設排序

正例:

@ApiOperation("分頁查詢訂單明細變更記錄 @author zhangxing")
    @GetMapping("/pages")
    public PageInfo<DemandHisOrderDetailChange> getOrderDetailChangePage(Pagination pagination, DemandHisOrderDetailChangeQueryDTO queryDTO) {
        if (StringUtils.isBlank(pagination.getSort())) {
            pagination.setSort("delivery_date, use_date");
            pagination.setOrder("ASC");
        }
        return demandHisOrderDetailChangeService.getPage(pagination, queryDTO);
    }
           

3、

service

層規範

1)BaseService的使用,BaseService中注入了相應的mapper,不強制要求使用

XxxService接口繼承

BaseService<T, Q>

接口

T,Q分别為對應的資料庫model和查詢條件對象。舉例:

/**
 * <p>
 * 系統日志表 服務類
 * </p>
 */
public interface SysLogService extends BaseService<SysLog, SysLogQueryDTO> {

}
           

XxxServiceImpl實作XxxService接口,繼承

BaseServiceImpl<M, T, Q>

M,T,Q分别為Mapper層和model層和查詢條件,需要實作

getList

方法。舉例:

/**
 * <p>
 * 系統日志表 服務實作類
 * </p>
 */
  @Service
  public class SysLogServiceImpl extends BaseServiceImpl<SysLogMapper, SysLog, SysLogQueryDTO> implements SysLogService {
 
    @Override
    public List<SysLog> getList(SysLogQueryDTO queryDTO) {
        return mapper.selectList(queryDTO);
    }

}

           

2)合理拆分 service 檔案,如果業務較大,請拆分為多個 service。

如訂單業務,所有業務都寫到 OrderService 中會導緻檔案過大,故需要進行拆分如下:

  • OrderQueryService

    訂單查詢業務
  • OrderCreateService

    訂單建立業務
  • OrderDeliverService

    訂單發貨業務
  • OrderValidatorService

    訂單驗證業務

3)謹慎處理

@Transactional

事務注解的使用,不要簡單對

service

的方法添加個

@Transactional

注解就覺得萬事大吉了。應當合并對資料庫的操作,盡量減少添加了

@Transactional

方法内的業務邏輯。

@Transactional

注解内的

rollbackFor

值必須使用異常的基類

Throwable.class

對于@Transactional 注解,當 spring 遇到該注解時,會自動從資料庫連接配接池中擷取 connection,并開啟事務然後綁定到 ThreadLocal 上,如果業務并沒有進入到最終的 操作資料庫環節,那麼就沒有必要擷取連接配接并開啟事務,應該直接将 connection 傳回給資料庫連接配接池,供其他使用(比較難以講解清楚,如果不懂的話就主動去問)。

反例:

@Transactional(rollbackFor = Throwable.class)
  public ResponseDTO<String> upOrDown(Long departmentId, Long swapId) {
      // 驗證 1
      DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
      if (departmentEntity == null) {
          return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
      }
      // 驗證 2
      DepartmentEntity swapEntity = departmentDao.selectById(swapId);
      if (swapEntity == null) {
          return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
      }
      // 驗證 3
      Long count = employeeDao.countByDepartmentId(departmentId)
      if (count != null && count > 0) {
          return ResponseDTO.wrap(DepartmentResponseCodeConst.EXIST_EMPLOYEE);
      }
      // 操作資料庫 4
      Long departmentSort = departmentEntity.getSort();
      departmentEntity.setSort(swapEntity.getSort());
      departmentDao.updateById(departmentEntity);
      swapEntity.setSort(departmentSort);
      departmentDao.updateById(swapEntity);
      return ResponseDTO.succ();
  }
           

以上代碼前三步都是使用 connection 進行驗證操作,由于方法上有@Transactional 注解,是以這三個驗證都是使用的同一個 connection。

若對于複雜業務、複雜的驗證邏輯,會導緻整個驗證過程始終占用該 connection 連接配接,占用時間可能會很長,直至方法結束,connection 才會交還給資料庫連接配接池。

對于複雜業務的不可預計的情況,長時間占用同一個 connection 連接配接不是好的事情,應該盡量縮短占用時間。

正例:

DepartmentService.java
    
    public ResponseDTO<String> upOrDown(Long departmentId, Long swapId) {
        DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
        if (departmentEntity == null) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
        }
        DepartmentEntity swapEntity = departmentDao.selectById(swapId);
        if (swapEntity == null) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.NOT_EXISTS);
        }
        Long count = employeeDao.countByDepartmentId(departmentId)
        if (count != null && count > 0) {
            return ResponseDTO.wrap(DepartmentResponseCodeConst.EXIST_EMPLOYEE);
        }
        departmentManager.upOrDown(departmentSort,swapEntity);
        return ResponseDTO.succ();
    }


    DepartmentManager.java
    
    @Transactional(rollbackFor = Throwable.class)
    public void upOrDown(DepartmentEntity departmentEntity ,DepartmentEntity swapEntity){
        Long departmentSort = departmentEntity.getSort();
        departmentEntity.setSort(swapEntity.getSort());
        departmentDao.updateById(departmentEntity);
        swapEntity.setSort(departmentSort);
        departmentDao.updateById(swapEntity);
    }
           

将資料在 service 層準備好,然後傳遞給 manager 層,由 manager 層添加@Transactional 進行資料庫操作。

4)需要注意的是:注解

@Transactional

事務在類的内部方法調用是不會生效的

反例:如果發生異常,saveData方法上的事務注解并不會起作用

@Service
public class OrderService{

    public void createOrder(OrderCreateDTO createDTO){
        this.saveData(createDTO);
    }
    
    @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.insert(createDTO);
    }
}
           
Spring采用動态代理(AOP)實作對bean的管理和切片,它為我們的每個class生成一個代理對象。隻有在代理對象之間進行調用時,可以觸發切面邏輯。而在同一個class中,方法A調用方法B,調用的是原對象的方法,而不通過代理對象。是以Spring無法攔截到這次調用,也就無法通過注解保證事務了。簡單來說,在同一個類中的方法調用,不會被方法攔截器攔截到,是以事務不會起作用。

解決方案:

  1. 可以将方法放入另一個類,如新增

    manager層

    ,通過spring注入,這樣符合了在對象之間調用的條件。
  2. 啟動類添加

    @EnableAspectJAutoProxy(exposeProxy = true)

    ,方法内使用

    AopContext.currentProxy()

    獲得代理類,使用事務。
SpringBootApplication.java

@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class SpringBootApplication {}

OrderService.java

public void createOrder(OrderCreateDTO createDTO){
    OrderService orderService = (OrderService)AopContext.currentProxy();
    orderService.saveData(createDTO);
}
           

5)service是具體的業務處理邏輯服務層,盡量避免将web層某些參數傳遞到service中。

反例:

public ResponseDTO<String> handlePinganRequest(HttpServletRequest request){
    InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), "GBK");
    BufferedReader reader = new BufferedReader(inputStreamReader);
    StringBuilder sb = new StringBuilder();
    String str;
    while ((str = reader.readLine()) != null) {
        sb.append(str);
    }
    if(!JSON.isValid(msg)){
      return ResponseDTO.wrap(ResponseCodeConst.ERROR_PARAM);
    }
    PinganMsgDTO PinganMsgDTO = JSON.parseObject(msg,PinganMsgDTO.class);
    // 示例結束
}
           

反例中出現的問題:

  • 反例中把

    HttpServletRequest

    傳遞到service中,是為了擷取Request流中的字元資訊,然後才是真正的業務處理。按照分層的初衷:将代碼、業務邏輯解耦,正确的做法應該是

    handlePinganRequest

    方法将

    String

    字元作為參數直接處理業務,将從

    Request

    中擷取字元的操作放入

    controller

    中。
  • 另一個壞處是不友善做單元測試,還得一個

    new

    一個

    HttpServletRequest

    并制造一個

    InputStream

    ,然而這樣做并不能模拟到真實的業務情景及資料。

4、

manager

層規範

manager 層的作用(引自《阿裡 java 手冊》):

  • 對第三方平台封裝的層,預處理傳回結果及轉化異常資訊;
  • 對 Service 層通用能力的下沉,如緩存方案、中間件通用處理;

    緩存示例

    XxxManager.java

    public class XxxManager {
        
        @Autowired
        private XxxMapper mapper;
        
        @AutoWired
        private XxxCacheService cacheService;
    
      	public Xxx getById(id) {
          Xxx x = cacheService.getById(id)
          if(x == null){
            x = mapper.getById(id);
            cacheService.setById(x);
          }
          return x;
        }
        
      }
               
  • 與 mapper 層互動,對多個 mapper 的組合複用。

5、

mapper

層規範

優先使用 tk的通用mapper架構。

1)所有 mapper接口 繼承自

BaseMapper<T>

2)禁止使用 tk通用mapper的

Example

條件建構器

3)盡量不直接在 mybatis xml 中寫死常量,應從 mapper 中傳入到 xml 中

3)建議不要使用星号

*

代替所有字段

正例:

NoticeDao.java
    
    Integer noticeCount(@Param("sendStatus") Integer sendStatus);
---------------------------------------------
    NoticeMapper.xml
    
    <select id="noticeCount" resultType="integer">
        select
        count(1)
        from t_notice
        where
        send_status = #{sendStatus}
    </select>
           

反例:

NoticeDao.java
    
    Integer noticeCount();
---------------------------------------------
    NoticeMapper.xml
    
    <select id="noticeCount" resultType="integer">
        select
        count(1)
        from t_notice
        where
        send_status = 0
    </select>
           

3)mapper層方法命名規範

  • 擷取單個對象的方法用

    find

    做字首。
  • 擷取多個對象的方法用

    list

    做字首。
  • 擷取統計值的方法用

    count

    做字首。
  • 插入的方法用

    insert

    做字首。
  • 删除的方法用

    delete

    做字首。
  • 修改的方法用

    update

    做字首。

建議:mapper層方法命名盡量以sql語義命名,避免與業務關聯。

正例:

反例:

反例中出現的不規範操作:

  • find代表單個查詢,批量查詢的應該 list 開頭。
  • 命名與業務關聯,局限了mapper方法的使用場景和範圍,降低了方法的複用性,造成他人困惑以及重複造輪子。

6、

boolean

類型的屬性命名規範

類中布爾類型的變量,都不要加is,否則部分架構解析會引起序列化錯誤。反例:定義為基本資料類型 Boolean isDeleted;的屬性,它的方法也是 isDeleted(),RPC在反向解析的時候,“以為”對應的屬性名稱是 deleted,導緻屬性擷取不到,進而抛出異常。

這是阿裡巴巴開發手冊中的原文,我們團隊的規定是:

boolean

類型的類屬性和資料表字段都統一使用

flag

結尾。雖然使用

isDeleted,is_deleted

從字面語義上更直覺,但是比起可能出現的潛在錯誤,這點犧牲還是值得的。

正例:

deletedFlag,deleted_flag,onlineFlag,online_flag
           

四、資料庫 規範

1、建表規範

非中間關系表必備三字段:id, creator_id, creator_name, modifier_id, modifier_name, create_time, modify_time

  • id 字段 Long 類型,單表自增,自增長度為 1
  • create_time 字段 datetime 類型,預設值 CURRENT_TIMESTAMP
  • update_time 字段 datetime 類型,預設值 CURRENT_TIMESTAMP

2、枚舉類表字段注釋需要将所有枚舉含義進行注

修改或增加字段的狀态描述,必須要及時同步更新注釋。

如下表的

sync_status

字段

同步狀态 0 未開始 1同步中 2同步成功 3失敗

正例:

CREATE TABLE `t_change_data` (
	`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
	`sync_status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '同步狀态 0 未開始 1同步中 2同步成功 3失敗',
	`sync_time` DATETIME NULL DEFAULT NULL COMMENT '同步時間',
	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`update_time` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`)
)
           

反例:

CREATE TABLE `t_change_data` (
	`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
	`sync_status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '同步狀态 ',
	`sync_time` DATETIME NULL DEFAULT NULL COMMENT '同步時間',
	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`update_time` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`)
)
           

3、資料庫編碼

字元集:

utf8mb4

排序規則:

utf8mb4_general_ci

4、合理結合業務給表字段添加索引和唯一索引具體索引規範請參照《阿裡巴巴 Java 開發手冊》索引規約

五、其他

1、代碼送出規範

  • 如果改動影響代碼非常多,必須新增一個分支送出
  • 送出前應該冷靜、仔細檢查一下,確定沒有忘記加入版本控制或不應該送出的檔案。
  • 送出前應該先編譯一次(idea裡ctrl+F9),防止出現編譯都報錯的情況。
  • 送出前先更新pull一次代碼,送出前發生沖突要比送出後發生沖突容易解決的多。
  • 送出前檢查代碼是否格式化,是否符合代碼規範,無用的包引入、變量是否清除等等。
  • 送出時檢查注釋是否準确簡潔的表達出了本次送出的内容。
  • 送出代碼時必須填寫詳細備注,如完成功能,注釋為“新增XX功能”;
  • 若此次送出代碼對應禅道中的任務或者bug,格式如下:
task#[任務id] [任務标題] [具體事項]
bug#[bug id] [bug标題] [具體事項]
           
  • 例子如下:
task#1101 開發smartreload功能 完成線程池的編碼
bug#1102 smartreload時間不正确 線程池的大小問題
           

3、保持項目整潔

使用git,必須添加 .gitignore 忽略配置檔案。

不要送出與項目無關的内容檔案:idea配置、target包等。