寫代碼有這些想法,同事纔不會認爲你是複製粘貼程序員

前言

最近作完12月份版本需求,有一些思考不夠深刻的代碼,所以寫一下總結,但願你們平常寫代碼多點思考,多點總結,加油!同時哪裏有不對的,也望指出。程序員

1、複雜的邏輯條件,是否能夠調整順序,讓程序更高效呢。

假設業務需求是這樣:會員,第一次登錄時,須要發一條感謝短信。若是沒有通過思考,代碼直接這樣寫了sql

if(isUserVip && isFirstLogin){
    sendMsg();
}
複製代碼

假設總共有5個請求,isUserVip經過的有3個請求,isFirstLogin經過的有1個請求。 那麼以上代碼,isUserVip執行的次數爲5次,isFirstLogin執行的次數也是3次,以下:數據庫

若是調整一下isUserVip和isFirstLogin的順序呢?後端

if(isFirstLogin && isUserVip ){
    sendMsg();
}
複製代碼

isFirstLogin執行的次數是5次,isUserVip執行的次數是1次,以下:緩存

醬紫你的程序是否更高效呢?bash

2、你的程序是否不經意間建立了沒必要要的對象。

舉個粟子吧,判斷用戶會員是否處於有效期,一般有如下相似代碼:網絡

//判斷用戶會員是否在有效期
public boolean isUserVIPValid() {
  Date now = new Date();
  Calendar gmtCal = Calendar.getInstance();
  gmtCal.set(2019, Calendar.JANUARY, 1, 0, 0, 0);
  Date beginTime = gmtCal.getTime();
  gmtCal.set(2020, Calendar.JANUARY, 1, 0, 0, 0);
  Date endTime= gmtCal.getTime();
  return now.compareTo(beginTime) >= 0 && now.compareTo(endTime) <= 0;
}

複製代碼

可是呢,每次調用isUserVIPValid方法,都會建立Calendar和Date對象。其實吧,除了New Date,其餘對象都是不變的,咱們能夠抽出全局變量避免建立了沒必要要的對象,從而提升程序效率,以下:mybatis

public class Test {

    private static final Date BEGIN_TIME;
    private static final Date END_TIME;
    static {
        Calendar gmtCal = Calendar.getInstance();
        gmtCal.set(2019, Calendar.JANUARY, 1, 0, 0, 0);
        BEGIN_TIME = gmtCal.getTime();
        gmtCal.set(2020, Calendar.JANUARY, 1, 0, 0, 0);
        END_TIME = gmtCal.getTime();
    }

    //判斷用戶會員是否在有效期
    public boolean isUserVIPValid() {
        Date now = new Date();
        return now.compareTo(BEGIN_TIME) >= 0 && now.compareTo(END_TIME) <= 0;
    }
}
複製代碼

3、查詢數據庫時,你有沒有查多了數據?

你們都知道,查庫是比較耗時的操做,尤爲數據量大的時候。因此,查詢DB時,咱們取所需就好,沒有必要大包大攬。併發

假設業務場景是這樣:查詢某個用戶是不是會員。曾經看過實現代碼是這樣。。。異步

List<Long> userIds = sqlMap.queryList("select userId from user where vip=1");
boolean isVip = userIds.contains(userId);
複製代碼

爲何先把全部會有查出來,再判斷是否包含這個useId,來肯定useId是不是會員呢?直接把userId傳進sql,它不香嗎?以下:

Long userId = sqlMap.queryObject("select userId from user where userId='userId' and vip='1' ")
boolean isVip = userId!=null;
複製代碼

實際上,咱們除了把查詢條件都傳過去,避免數據庫查多餘的數據回來,還能夠經過select 具體字段代替select *,從而使程序更高效。

4、加了一行通知類的代碼,總不能影響到主要流程吧。

假設業務流程這樣:須要在用戶登錄時,添加個短信通知它的粉絲。 很容易想到的實現流程以下:

假設提供sendMsgNotify服務的系統掛了,或者調用sendMsgNotify失敗了,那麼用戶登錄就失敗了。。。

一個通知功能致使了登錄主流程不可用,明顯的撿了芝麻丟西瓜。那麼有沒有魚魚熊掌兼得的方法呢?有的,給發短信接口捕獲異常處理,或者另開線程異步處理,以下:

所以,咱們添加通知類等不是非主要,可降級的接口時,應該靜下心來考慮是否會影響主要流程,思考怎麼處理最好。

5、對空指針保持嗅覺,如使用equals比較時,常量或肯定值放左邊。

NullPointException在Java世界早已司空見慣,咱們在寫代碼時,能夠三思然後寫,儘可能避免低級的空指針問題。

好比有如下業務場景,判斷用戶是不是會員,常常可見以下代碼:

boolean isVip = user.getUserFlag().equals("1");
複製代碼

若是讓這個行代碼上生產環境,待君驀然回首,可能那空指針bug,就在燈火闌珊處。顯然,這樣可能會產生空指針異常,由於user.getUserFlag()多是null。

怎樣避免空指針問題呢?把常量1放到左邊就能夠啦,以下:

boolean isVip = "1".equals(user.getUserFlag());
複製代碼

6、你的關鍵業務代碼是否有日誌保駕護航?

關鍵業務代碼不管身處何地,都應該有足夠的日誌保駕護航。

好比:你實現轉帳業務,轉個幾百萬,而後轉失敗了,接着客戶投訴,而後你尚未打印到日誌,想一想那種水深火熱的困境下,你卻毫無辦法。。。

那麼,你的轉帳業務都須要那些日誌信息呢?至少,方法調用前,入參須要打印須要吧,接口調用後,須要捕獲一下異常吧,同時打印異常相關日誌吧,以下:

public void transfer(TransferDTO transferDTO){
    log.info("invoke tranfer begin");
    //打印入參
    log.info("invoke tranfer,paramters:{}",transferDTO);
    try {
      res=  transferService.transfer(transferDTO);
    }catch(Exception e){
     log.error("transfer fail,cifno:{},account:{}",transferDTO.getCifno(),
     transferDTO.getaccount())
     log.error("transfer fail,exception:{}",e);
    }
    log.info("invoke tranfer end");
    }
複製代碼

除了打印足夠的日誌,咱們還須要注意一點是,日誌級別別混淆使用,別本該打印info的日誌,你卻打印成error級別,告警半夜三更催你起來排查問題就很差了。

7、對於行數比較多的函數,是否能夠劃分小函數來優化呢?

咱們在維護老代碼的時候,常常會見到一坨坨的代碼,有些函數幾百行甚至上千行,閱讀起來比較吃力。

假設如今有如下代碼

public class Test {
    private String name;
    private Vector<Order> orders = new Vector<Order>();

    public void printOwing() {
        //print banner
        System.out.println("****************");
        System.out.println("*****customer Owes *****");
        System.out.println("****************");

        //calculate totalAmount
        Enumeration env = orders.elements();
        double totalAmount = 0.0;
        while (env.hasMoreElements()) {
            Order order = (Order) env.nextElement();
            totalAmount += order.getAmout();
        }

        //print details
        System.out.println("name:" + name);
        System.out.println("amount:" + totalAmount);
    }
}
複製代碼

劃分爲功能單一的小函數後:

public class Test {
    private String name;
    private Vector<Order> orders = new Vector<Order>();

    public void printOwing() {

        //print banner
        printBanner();
        //calculate totalAmount
        double totalAmount = getTotalAmount();
        //print details
        printDetail(totalAmount);
    }

    void printBanner(){
        System.out.println("****************");
        System.out.println("*****customer Owes *****");
        System.out.println("****************");
    }

    double getTotalAmount(){
        Enumeration env = orders.elements();
        double totalAmount = 0.0;
        while (env.hasMoreElements()) {
            Order order = (Order) env.nextElement();
            totalAmount += order.getAmout();
        }
        return totalAmount;
    }

    void printDetail(double totalAmount){
        System.out.println("name:" + name);
        System.out.println("amount:" + totalAmount);
    }
    
}
複製代碼

一個過於冗長的函數或者一段須要註釋才能讓人理解用途的代碼,能夠考慮把它切分紅一個功能明確的函數單元,並定義清晰簡短的函數名,這樣會讓代碼變得更加優雅。

8、某些可變因素,如紅包皮膚等等,作成配置化是否會更好呢。

假如產品提了個紅包需求,聖誕節的時候,紅包皮膚爲聖誕節相關的,春節的時候,紅包皮膚等。

若是在代碼寫死控制,可有相似如下代碼:

if(duringChristmas){
   img = redPacketChristmasSkin;
}else if(duringSpringFestival){
   img =  redSpringFestivalSkin;
}
......
複製代碼

若是到了元宵節的時候,運營小姐姐忽然又有想法,紅包皮膚換成燈籠相關的,這時候,是否是要去修改代碼了,從新發布了?

從一開始,實現一張紅包皮膚的配置表,將紅包皮膚作成配置化呢?更換紅包皮膚,只需修改一下表數據就行了。

9、多餘的import 類,局部變量,沒引用是否是應該刪除

若是看到代碼存在沒使用的import 類,沒被使用到的局部變量等,就刪掉吧,以下這些:

這些沒被引用的局部變量,若是沒被使用到,就刪掉吧,它又不是陳年的女兒紅,留着會愈加醇香。它仍是會一塊兒被編譯的,就是說它仍是耗着資源的呢。

10、查詢大表時,是否加了索引,你的sql走了索引嘛。

查詢數據量比較大的表時,咱們須要確認三點:

  • 你的表是否建了索引
  • 你的查詢sql是否命中索引
  • 你的sql是否還有優化餘地

通常狀況下,數據量超過10萬的表,就要考慮給表加索引了。哪些狀況下,索引會失效呢?like通配符、索引列運算等會致使索引失效。有興趣的朋友能夠看一下我這篇文章。 後端程序員必備:索引失效的十大雜症

11、你的方法到底應該返回空集合仍是 null呢?

若是返回null,調用方在忘記檢測的時候,可能會拋出空指針異常。返回一個空集合呢,就省去該問題了。

mybatis查詢的時候,若是返回一個集合,結果爲空時也會返回一個空集合,而不是null。

正例

public static List<UserResult> getUserResultList(){
    return Collections.EMPTY_LIST;
}
複製代碼

12、初始化集合時儘可能指定其大小

阿里開發手冊推薦了這一點

假設你的map要存儲的元素個數是15個左右,最優寫法以下

//initialCapacity = 15/0.75+1=21
 Map map = new HashMap(21);
複製代碼

十3、查詢數據庫時,若是數據返回過多,考慮分批進行。

假設你的訂單表有10萬數據要更新狀態,不能一次性查詢全部未更新的訂單,要分批。

反例:

List<Order> list = sqlMap.queryList("select * from Order where status='0'");
for(Order order:list){
  order.setStatus(1);
  sqlMap.update(order);  
}
複製代碼

正例:

Integer count = sqlMap.queryCount(select count(1) from Order where status ='0');
while(true){
    int size=sqlMap.batchUpdate(params);
    if(size<500){
        break;
    }
}
複製代碼

十4、你的接口是否考慮到冪等性,併發狀況呢?

冪等性是什麼? 一次和屢次請求某一個資源對於資源自己應該具備一樣的結果。就是說,其任意屢次執行對資源自己所產生的影響均與一次執行的影響相同。

爲何須要冪等性?

  • 用戶在APP上連續點擊了屢次提交訂單,總不能生成多個訂單吧
  • 用戶由於網絡卡了,連續點擊發送消息,接受者總不能收到重複的同一條消息吧。

假設有業務場景:

用戶點擊下載按鈕,系統開始下載文件,用戶再次點擊下載,會提示文件正在下載中。

有一部分人會這樣實現:

Integer count = sqlMap.selectCount("select count(1) from excel where state=1");
if(count<=0){
    Excel.setStatus(1);
    updateExcelStatus();
    downLoadExcel();
}else{
    "文件正在下載中"
}
複製代碼

咱們能夠看一下,兩個請求過來可能會有什麼問題?

執行流程:

  • 第一步,A查詢沒有下載中的文件。
  • 第二步,B查詢沒有下載中的文件。
  • 第三步,A開始下載文件
  • 第四部,B 開始下載文件

顯然,這樣有問題,同時兩個文件在下載了。正確的實現方式呢?

if(updateExcelStatus(1){
    downLoadExcel(); 
}else{
    "文件正在下載中"
}
複製代碼

十5、用一個私有構造器強化你的工具類,此不美哉?

工具類的方法都是靜態方法,經過類來直接調用便可。可是有些調用方可能會先實例化,再用對象去調用,而這就很差了。怎麼避免這種狀況,讓你的工具類到達可控狀態呢,添加私有構造器

public class StringUtis{
       private StringUtis(){} ///私有構造類,防止意外實例出現
       public static bool validataString(String str){
           
       }
}
複製代碼

十6、基本不變的用戶數據,緩存起來,性能是否有所提高呢

假設你的接口須要查詢不少次數據庫,獲取到各中數據,而後再根據這些數據進行各類排序等等操做,這一系列猛如虎的操做下來,接口性能確定很差。典型應用場景好比:直播列表這些。

那麼,怎麼優化呢?剖析你排序的各部分數據,實時變的數據,繼續查DB,不變的數據,如用戶年齡這些,搞個定時任務,把它們從DB拉取到緩存,直接走緩存。

所以,這個點的思考就是,在恰當地時機,適當的使用緩存。

待補充...

我的公衆號

  • 若是你是個愛學習的好孩子,能夠關注我公衆號,一塊兒學習討論。
  • 若是你以爲本文有哪些不正確的地方,能夠評論,也能夠關注我公衆號,私聊我,你們一塊兒學習進步哈。
相關文章
相關標籤/搜索