【代碼質量】-幾個可以讓代碼快速變優雅的小技巧,你值得擁有!

一.多使用lombok的新特性

lombok對每個java後端來說應該都不陌生,但對它的使用不應該僅停留在@Data,@Getter,@Setter...上,推薦多使用以下幾個註解:

@Builder

讓類轉換爲建造者模式,可以讓類的創建和賦值變得更優雅,特別是在該類有很多屬性需要設置的時候

        Employee employee = new Employee();
        employee.setName("lombok");
        employee.setAge(10);

        Employee employee1 = Employee.builder()
                .name("lombok")
                .age(10)
                .build();

@RequiredArgsConstructor(onConstructor = @__(@Autowired))

在Spring項目中,當一個類裏面依賴的模塊很多,會有很多Service被注入進來,脫離Lombok,我們通常會用下面這種方式進行注入:

public class TestController {

    @Autowired
    private TestService1 testService1;
    @Autowired
    private TestService2 testService2;
    ...
}

這樣注入會有大量重複的@Autowired註解,如果你用的是Idea編輯器,還會收到煩人的injection not recomand,通過Lombok註解,就可以同時化解上面量大問題:

@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {

    private final TestService1 testService1;
    private final TestService2 testService2;
    ...
}

當有新的類需要被注入時,不再需要加@Autowied註解,直接引入即可,值得注意的是,加了該@RequiredArgsConstructor(onConstructor = @__(@Autowired))註解後,引入的類需要定義爲final類型或者加@NotNull註解,推薦用final.

另外還有@AllArgsConstructor,@NoArgsconstructor註解也推薦使用,不要再手動寫全參/無參構造了,交給插件去處理不香嗎?

 

二.巧用Strsubstitutor類處理字符串拼接

在實際項目開發中,總會有一些場景需要字符串拼接,大量的字符串拼接不僅會影響性能,更會讓你的代碼看着很醜很Low,即便可以用StringBuilder來改善性能,但代碼依舊非常醜,這裏我舉個例子,假設最終想要的字符串爲: 2020年5月14日 午餐(11:30-12:30)

如果用傳統方式來實現:

        //用字符串拼接
        String date = "2020年5月14日";
        String skuName = "午餐";
        String startTime = "11:30";
        String endTime = "12:30";
        String result = date + " " + skuName + "(" + startTime + "-" + endTime + ")";
        
        //用StringBuilder實現
        String result1 = new StringBuilder()
                .append(date)
                .append(" ")
                .append(skuName)
                .append("(")
                .append(startTime)
                .append("-")
                .append(endTime)
                .append(")")
                .toString();

這樣寫代碼似乎還能看,但如果拼接的內容比上面還多,那代碼就又醜性能又差, 可以考慮用apache-commons-lang3提供的Strsubstitutor重構一下:

        Map<String, String> valueMap = new HashMap<>();
        valueMap.put("date", date);
        valueMap.put("skuName", skuName);
        valueMap.put("startTime", startTime);
        valueMap.put("endTime", endTime);

        final String template = "${date} ${skuName}(${startTime}-${endTime})";
        StrSubstitutor strSub = new StrSubstitutor(valueMap);
        String result2 = strSub.replace(template);

這樣除去向Map中put的操作,實際僅需要三行代碼即可完成複雜的字符串拼接,再多拼接都不怕!而且底層用的是正則匹配,沒有頻繁的創建String對象,性能要比前面這兩種實現方式高很多,代碼也優雅不少.

三.統一的日誌打印工具

日誌的打印其實是很有講究的,好的日誌不僅可以幫助開發排查線上問題,還可以提高系統性能,但遺憾的是目前大部分公司的日誌都沒有什麼規範可言,到開發這裏更是隨心所欲...我曾在上家公司的生產環境下用Jenkins做過測試,日誌對系統吞吐量有不小的影響,關閉info級別日誌和開啓,QPS大約能相差100多,一分鐘下來就是6000-8000條請求的差距! 所以日誌該如何打,打什麼信息真的值得深究.

這裏舉兩個真實的反例:

A.某開發同學在線上系統,調用一個批量返回數據的rpc接口,並在所有場景下都打印該rpc接口返回的數據,偏偏該接口的使用頻率很高,沒過幾個月,監控開始告警了,磁盤空間不夠用,排查發現日誌文件竟高達28GB...

B.一個項目組有多個成員共同開發,每個人都有自己的日誌打印風格,而且同一個人在不同的接口打印的日誌風格也有差異,於是整個代碼在日誌這塊看着真是一言難盡,非常亂...更可怕的是,有人在接口調用異常的時候居然沒有打日誌,而是僅在每次成功的時候打,當有天線上出了故障,沒有日誌記錄案發現場,排查無從下手...

所以對於日誌,千萬不要忽視,好的日誌應該是記錄必要的案發現場信息,對不同級別的日誌打印內容區別處理,最大限度提高日誌效率,同時儘量做到統一,所有人都一套風格,不要一個系統,N種風格,會讓代碼醜陋,下面我貼一個工具類,供大家參考,以後若有日誌需要打印,可以Copy此工具類到系統中,當然也可以自己封裝.

public class LogUtil {

    public static void dataIncoming(Logger log, String methodName, String invokeParam) {
        LogUtil.info(log,
                StrFormatter.format("dataIncoming;{}", methodName),
                "",
                invokeParam);
    }

    public static void invokeSuccess(Logger log, String methodName, String invokeParam, String invokeResult) {
        LogUtil.info(log,
                StrFormatter.format("{};invoke;success", methodName),
                StrFormatter.format("{}", invokeResult),
                invokeParam);
    }

    public static void invokeFail(Logger log, String methodName, String invokeParam, String errorMsg) {
        LogUtil.warn(log,
                StrFormatter.format("{};invoke;fail", methodName),
                StrFormatter.format("errorMsg={}", errorMsg),
                invokeParam);
    }

    public static void invokeAbort(Logger log, String methodName, String invokeParam, String abortReason) {
        LogUtil.info(log,
                StrFormatter.format("{};invoke;abort", methodName),
                StrFormatter.format("{}", abortReason),
                invokeParam);
    }

    public static void invokeEmptyResult(Logger log, String methodName, String invokeParam, String invokeResult) {
        LogUtil.warn(log,
                StrFormatter.format("{};invoke;emptyResult", methodName),
                StrFormatter.format("{}", invokeResult),
                invokeParam);
    }

    public static void invokeError(Logger log, String methodName, String invokeParam, Throwable e) {
        LogUtil.error(log,
                StrFormatter.format("{};invoke;error", methodName),
                StrFormatter.format("cause={}", e.getMessage()),
                invokeParam, e);
    }

    public static void invokeBlocked(Logger log, String methodName, String invokeParam, Throwable e) {
        LogUtil.error(log,
                StrFormatter.format("{};invoke;blocked by sentinel", methodName),
                StrFormatter.format("cause={}", e.getMessage()),
                invokeParam, e);
    }

    /**
     * 打印日誌,格式如下:
     * 執行了什麼操作|得到了什麼結果|對應的參數
     *
     * @param logger  logger
     * @param operate 執行了什麼操作,不能爲空
     * @param result  得到了什麼結果,可能爲空
     * @param param   對應的參數,可能爲空,若有多個可以轉成json
     */
    public static void debug(Logger logger, String operate, String result, String param) {
        logger.debug("{}|{}|{}", operate, result, param);
    }

    /**
     * 打印日誌,格式如下:
     * 執行了什麼操作|得到了什麼結果|對應的參數
     *
     * @param logger  logger
     * @param operate 執行了什麼操作,不能爲空
     * @param result  得到了什麼結果,可能爲空
     * @param param   對應的參數,可能爲空,若有多個可以轉成json
     */
    public static void info(Logger logger, String operate, String result, String param) {
        logger.info("{}|{}|{}", operate, result, param);
    }

    /**
     * 打印日誌,格式如下:
     * 執行了什麼操作|得到了什麼結果|對應的參數
     *
     * @param logger  logger
     * @param operate 執行了什麼操作,不能爲空
     * @param result  得到了什麼結果,可能爲空
     * @param param   對應的參數,可能爲空,若有多個可以轉成json
     */
    public static void warn(Logger logger, String operate, String result, String param) {
        logger.warn("{}|{}|{}", operate, result, param);
    }

    /**
     * 打印日誌,格式如下:
     * 執行了什麼操作|得到了什麼結果|對應的參數
     *
     * @param logger  logger
     * @param operate 執行了什麼操作,不能爲空
     * @param result  得到了什麼結果,可能爲空
     * @param param   對應的參數,可能爲空,若有多個可以轉成json
     * @param e       出現的異常
     */
    public static void error(Logger logger, String operate, String result, String param, Throwable e) {
        logger.error(StrFormatter.format("{}|{}|{}", operate, result, param), e);
    }

}

四.統一的參數合法性校驗

對於入參的校驗,有很多種方式,從最簡單的If判斷到自己寫AssertUtil,使用hutoolUtil工具類,到Hibernate Validator框架... 對於三個以上的參數校驗,個人更推崇用框架來實現,會讓代碼更工整優雅,而且還可以輕鬆通過框架實現校驗提示內容的國際化。

比如我有一個商品對象,裏面有非常多的字段需要做非空,長度,字段類型等合法性校驗,如果不合法,則封裝後統一返回給前端,展示給用戶

@Data
public class ProductDTO {
    @NotEmpty(message = "商品名稱必填")
    @Length(max = 15,message = "商品名稱最多隻能輸入15個字符")
    private String productName;

    @NotEmpty(message = "商品編號必填")
    @Length(max = 6,message = "商品編號最多隻能輸入6個字符")
    private String productCode;
    
    ...
}

 通過框架,僅需2行代碼就可以拿到所有錯誤信息的集合Set,非常強大,而且建議將Validator封裝僅Util類中,之後需要參數校驗的地方,僅需一行代碼就可以搞定,非常優雅!

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory().getValidator();
        Set<ConstraintViolation<ProductDTO>> validResult = validator.validate(productDTO);

如果有國際化需求的可以參考這兩篇文檔:

https://www.ibm.com/developerworks/cn/java/j-cn-hibernate-validator/index.html

https://docs.oracle.com/javase/tutorial/i18n/locale/create.html


關於代碼優雅,是學不完的,最有效的幾種方式就是啃阿里巴巴代碼規範,啃設計模式,然後在平時多學習別人寫的代碼,實時總結,取其精華,他爲己用,畢竟每個人都有值得學習的地方,如果以上內容有收穫,不妨點個贊加個關注啥的,防迷路!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章