最近作完12月份版本需求,有一些思考不夠深刻的代碼,所以寫一下總結,但願你們平常寫代碼多點思考,多點總結,加油!同時哪裏有不對的,也望指出。程序員
假設業務需求是這樣:會員,第一次登錄時,須要發一條感謝短信。若是沒有通過思考,代碼直接這樣寫了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
舉個粟子吧,判斷用戶會員是否處於有效期,一般有如下相似代碼:網絡
//判斷用戶會員是否在有效期
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;
}
}
複製代碼
你們都知道,查庫是比較耗時的操做,尤爲數據量大的時候。因此,查詢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 *
,從而使程序更高效。
假設業務流程這樣:須要在用戶登錄時,添加個短信通知它的粉絲。 很容易想到的實現流程以下:
假設提供sendMsgNotify服務的系統掛了,或者調用sendMsgNotify失敗了,那麼用戶登錄就失敗了。。。
一個通知功能致使了登錄主流程不可用,明顯的撿了芝麻丟西瓜。那麼有沒有魚魚熊掌兼得的方法呢?有的,給發短信接口捕獲異常處理,或者另開線程異步處理,以下:
所以,咱們添加通知類等不是非主要,可降級的接口時,應該靜下心來考慮是否會影響主要流程,思考怎麼處理最好。
NullPointException在Java世界早已司空見慣,咱們在寫代碼時,能夠三思然後寫,儘可能避免低級的空指針問題。
好比有如下業務場景,判斷用戶是不是會員,常常可見以下代碼:
boolean isVip = user.getUserFlag().equals("1");
複製代碼
若是讓這個行代碼上生產環境,待君驀然回首,可能那空指針bug,就在燈火闌珊處。顯然,這樣可能會產生空指針異常,由於user.getUserFlag()多是null。
怎樣避免空指針問題呢?把常量1放到左邊就能夠啦,以下:
boolean isVip = "1".equals(user.getUserFlag());
複製代碼
關鍵業務代碼不管身處何地,都應該有足夠的日誌保駕護航。
好比:你實現轉帳業務,轉個幾百萬,而後轉失敗了,接着客戶投訴,而後你尚未打印到日誌,想一想那種水深火熱的困境下,你卻毫無辦法。。。
那麼,你的轉帳業務都須要那些日誌信息呢?至少,方法調用前,入參須要打印須要吧,接口調用後,須要捕獲一下異常吧,同時打印異常相關日誌吧,以下:
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級別,告警半夜三更催你起來排查問題就很差了。
咱們在維護老代碼的時候,常常會見到一坨坨的代碼,有些函數幾百行甚至上千行,閱讀起來比較吃力。
假設如今有如下代碼
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);
}
}
複製代碼
一個過於冗長的函數或者一段須要註釋才能讓人理解用途的代碼,能夠考慮把它切分紅一個功能明確的函數單元,並定義清晰簡短的函數名,這樣會讓代碼變得更加優雅。
假如產品提了個紅包需求,聖誕節的時候,紅包皮膚爲聖誕節相關的,春節的時候,紅包皮膚等。
若是在代碼寫死控制,可有相似如下代碼:
if(duringChristmas){
img = redPacketChristmasSkin;
}else if(duringSpringFestival){
img = redSpringFestivalSkin;
}
......
複製代碼
若是到了元宵節的時候,運營小姐姐忽然又有想法,紅包皮膚換成燈籠相關的,這時候,是否是要去修改代碼了,從新發布了?
從一開始,實現一張紅包皮膚的配置表,將紅包皮膚作成配置化呢?更換紅包皮膚,只需修改一下表數據就行了。
若是看到代碼存在沒使用的import 類,沒被使用到的局部變量等,就刪掉吧,以下這些:
這些沒被引用的局部變量,若是沒被使用到,就刪掉吧,它又不是陳年的女兒紅,留着會愈加醇香。它仍是會一塊兒被編譯的,就是說它仍是耗着資源的呢。
查詢數據量比較大的表時,咱們須要確認三點:
通常狀況下,數據量超過10萬的表,就要考慮給表加索引了。哪些狀況下,索引會失效呢?like通配符、索引列運算等會致使索引失效。有興趣的朋友能夠看一下我這篇文章。 後端程序員必備:索引失效的十大雜症
若是返回null,調用方在忘記檢測的時候,可能會拋出空指針異常。返回一個空集合呢,就省去該問題了。
mybatis查詢的時候,若是返回一個集合,結果爲空時也會返回一個空集合,而不是null。
正例
public static List<UserResult> getUserResultList(){
return Collections.EMPTY_LIST;
}
複製代碼
阿里開發手冊推薦了這一點
假設你的map要存儲的元素個數是15個左右,最優寫法以下
//initialCapacity = 15/0.75+1=21
Map map = new HashMap(21);
複製代碼
假設你的訂單表有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;
}
}
複製代碼
冪等性是什麼? 一次和屢次請求某一個資源對於資源自己應該具備一樣的結果。就是說,其任意屢次執行對資源自己所產生的影響均與一次執行的影響相同。
爲何須要冪等性?
假設有業務場景:
用戶點擊下載按鈕,系統開始下載文件,用戶再次點擊下載,會提示文件正在下載中。
有一部分人會這樣實現:
Integer count = sqlMap.selectCount("select count(1) from excel where state=1");
if(count<=0){
Excel.setStatus(1);
updateExcelStatus();
downLoadExcel();
}else{
"文件正在下載中"
}
複製代碼
咱們能夠看一下,兩個請求過來可能會有什麼問題?
執行流程:
顯然,這樣有問題,同時兩個文件在下載了。正確的實現方式呢?
if(updateExcelStatus(1){
downLoadExcel();
}else{
"文件正在下載中"
}
複製代碼
工具類的方法都是靜態方法,經過類來直接調用便可。可是有些調用方可能會先實例化,再用對象去調用,而這就很差了。怎麼避免這種狀況,讓你的工具類到達可控狀態呢,添加私有構造器
public class StringUtis{
private StringUtis(){} ///私有構造類,防止意外實例出現
public static bool validataString(String str){
}
}
複製代碼
假設你的接口須要查詢不少次數據庫,獲取到各中數據,而後再根據這些數據進行各類排序等等操做,這一系列猛如虎的操做下來,接口性能確定很差。典型應用場景好比:直播列表這些。
那麼,怎麼優化呢?剖析你排序的各部分數據,實時變的數據,繼續查DB,不變的數據,如用戶年齡這些,搞個定時任務,把它們從DB拉取到緩存,直接走緩存。
所以,這個點的思考就是,在恰當地時機,適當的使用緩存。