寫這篇文章,來記錄一下一個優秀的后端開發程序員,應該有哪些好的開發習慣。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
1.注釋盡可能全面,寫有意義的注釋
接口方法、類、復雜的業務邏輯,都應該添加有意義的注釋
對于接口方法的注釋,應該包含詳細的入參和結果說明,有異常拋出的情況也要詳細敘述
類的注釋應該包含類的功能說明、作者和修改者。
如果是業務邏輯很復雜的代碼,真的非常有必要寫清楚注釋。
清楚的注釋,更有利于后面的維護。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
2.項目拆分合理的目錄結構
記得讀大學那會,剛學做各種各樣的管理系統,都是用MVC模式,也就是controller、service、mapper、entity。如果未來業務擴展,你沒有拆分業務結構的話,很可能就會發現,一個service包下,有上百個服務。。。
正確的做法,如果服務過多,應該根據不同的業務進行劃分,比如訂單、登陸、積分等等
當然,你也可以根據不同的業務劃分模塊,比如建一個moudles包,然后按訂單、登陸等業務劃分,每個業務都有自己的controller、service、mapper、entity。
我們拆分的目的,就是讓項目結構更清晰,可讀性更強,更容易維護 而已。
3. 不在循環里遠程調用、或者數據庫操作,優先考慮批量進行。
遠程操作或者數據庫操作都是比較耗網絡、IO資源 的,所以盡量不在循環里遠程調用、不在循環里操作數據庫,能批量一次性查回來盡量不要循環多次去查 。(但是呢,如果是操作數據庫,也不要一次性查太多數據哈,可以分批500一次醬紫)。
正例:
remoteBatchQuery(param);
反例:
for(inti=0;i
4. 封裝方法形參
如果你的方法參數過多,要封裝一個對象出來。反例如下:
publicvoidgetUserInfo(Stringname,Stringage,Stringsex,Stringmobile,StringidNo){ //dosomething... }
如果參數很多,做新老接口兼容處理也比較麻煩。建議寫個對象出來,如下:
publicvoidgetUserInfo(UserInfoParamDTOuserInfoParamDTO){ //dosomething... } classUserInfoParamDTO{ privateStringname; privateStringage; privateStringsex; privateStringmobile; privateStringidNo; }
5. 封裝通用模板
一個優秀的后端開發,應該具備封裝通用模板 的編碼能力。
我們來看一個業務需求:假設我們有這么一個業務場景:內部系統不同商戶,調用我們系統接口,去跟外部第三方系統交互(http方式)。走類似這么一個流程,如下:
一個請求都會經歷這幾個流程:
查詢商戶信息
對請求報文加簽
發送http請求出去
對返回的報文驗簽
通過HTTP發請求出去時,有的商戶可能是走代理 的,有的是走直連。假設當前有A,B商戶接入,不少伙伴可能這么實現,偽代碼如下:
//商戶A處理句柄 CompanyAHandlerimplementsRequestHandler{ Resphander(req){ //查詢商戶信息 queryMerchantInfo(); //加簽 signature(); //http請求(A商戶假設走的是代理) httpRequestbyProxy() //驗簽 verify(); } } //商戶B處理句柄 CompanyBHandlerimplementsRequestHandler{ Resphander(Rreq){ //查詢商戶信息 queryMerchantInfo(); //加簽 signature(); //http請求(B商戶不走代理,直連) httpRequestbyDirect(); //驗簽 verify(); } }
假設新加一個C商戶接入,你需要再實現一套這樣的代碼。顯然,這樣代碼就重復了。這時候我們可以封裝一個通用模板 !我們就可以定義一個抽象類,包含請求流程的幾個方法,偽代碼如下:
abstractclassAbstractMerchantService{ //模板方法流程 ResphandlerTempPlate(req){ //查詢商戶信息 queryMerchantInfo(); //加簽 signature(); //http請求 httpRequest(); //驗簽 verifySinature(); } //Http是否走代理(提供給子類實現) abstractbooleanisRequestByProxy(); }
然后所有商戶接入,都做這個流程。如果這個通用模板是你抽取的,別的小伙伴接到開發任務,都是接入你的模板,是不是會有點自豪呀,哈哈~
封裝通用模板 ,就是抽個模板模式嘛?其實不僅僅是,而是自己對需求、代碼的思考與總結 ,一種編程思想的升華 。
6. 封裝復雜的邏輯判斷條件
我們來看下這段代碼:
publicvoidtest(UserStatususerStatus){ if(userStatus!=UserStatus.BANNED&&userStatus!=UserStatus.DELETED&&userStatus!=UserStatus.FROZEN){ //doSomeThing return } }
這段代碼有什么問題呢?是的,邏輯判斷條件太復雜啦,我們可以封裝一下它 。如下:
publicvoidtest(UserStatususerStatus){ if(isUserActive(userStatus)){ //doSomeThing } } privatebooleanisUserActive(UserStatususerStatus){ returnuserStatus!=UserStatus.BANNED&&userStatus!=UserStatus.DELETED&&userStatus!=UserStatus.FROZEN; }
7. 保持優化性能的嗅覺
優秀的后端開發,應該保持優化性能的嗅覺。比如避免創建比必要的對象、異步處理、使用緩沖流,減少IO操作等等。
比如,我們設計一個APP首頁的接口,它需要查用戶信息、需要查banner信息、需要查彈窗信息等等。假設耗時如下:
查用戶信息200ms,查banner信息100ms、查彈窗信息50ms,那一共就耗時350ms了。如果還查其他信息,那耗時就更大了。如何優化它呢?可以并行發起,耗時可以降為200ms。如下:
8. 可變參數的配置化處理
日常開發中,我們經常會遇到一些可變參數,比如用戶多少天沒登錄注銷、運營活動,不同節日紅包皮膚切換、訂單多久沒付款就刪除等等。對于這些可變的參數,不用該直接寫死在代碼。優秀的后端,要做配置化處理,你可以把這些可變參數,放到數據庫一個配置表里面,也可以放到項目的配置文件或者apollo上。
比如產品經理提了個紅包需求,圣誕節的時候,紅包皮膚為圣誕節相關的,春節的時候,為春節紅包皮膚等。如果在代碼寫死控制,可有類似以下代碼:
if(duringChristmas){ img=redPacketChristmasSkin; }elseif(duringSpringFestival){ img=redSpringFestivalSkin; }
如果到了元宵節的時候,運營小姐姐突然又有想法,紅包皮膚換成燈籠相關的,這時候,是不是要去修改代碼了,重新發布了?
從一開始接口設計時,可以實現一張紅包皮膚的配置表 ,將紅包皮膚做成配置化呢?更換紅包皮膚,只需修改一下表數據就好了。當然,還有一些場景適合一些配置化的參數:一個分頁多少數量控制、某個搶紅包多久時間過期這些,都可以搞到參數配置化表里面。這也是擴展性思想的一種體現。
9. 會總結并使用工具類。
很多小伙伴,判斷一個list是否為空,會這么寫:
if(list==null||list.size()==0){ returnnull; }
這樣寫呢,邏輯是沒什么問題的。但是更建議用工具類,比如:
if(CollectionUtils.isEmpty(list)){ returnnull; }
日常開發中,我們既要會用工具類,更要學會自己去總結工具類。比如去文件處理工具類、日期處理工具類等等。這些都是優秀后端開發的一些好習慣。
10. 控制方法函數復雜度
你的方法不要寫得太復雜,邏輯不要混亂,也不要太長 。一個函數不能超過80行。寫代碼不僅僅是能跑就行,而是為了以后更好的維護。
反例如下:
publicclassTest{ privateStringname; privateVectororders=newVector (); publicvoidprintOwing(){ //printbanner System.out.println("****************"); System.out.println("*****customerOwes*****"); System.out.println("****************"); //calculatetotalAmount Enumerationenv=orders.elements(); doubletotalAmount=0.0; while(env.hasMoreElements()){ Orderorder=(Order)env.nextElement(); totalAmount+=order.getAmout(); } //printdetails System.out.println("name:"+name); System.out.println("amount:"+totalAmount); ...... } }
其實可以使用Extract Method,抽取功能單一的代碼段,組成命名清晰的小函數,去解決長函數問題,正例如下:
publicclassTest{ privateStringname; privateVectororders=newVector (); publicvoidprintOwing(){ //printbanner printBanner(); //calculatetotalAmount doubletotalAmount=getTotalAmount(); //printdetails printDetail(totalAmount); } voidprintBanner(){ System.out.println("****************"); System.out.println("*****customerOwes*****"); System.out.println("****************"); } doublegetTotalAmount(){ Enumerationenv=orders.elements(); doubletotalAmount=0.0; while(env.hasMoreElements()){ Orderorder=(Order)env.nextElement(); totalAmount+=order.getAmout(); } returntotalAmount; } voidprintDetail(doubletotalAmount){ System.out.println("name:"+name); System.out.println("amount:"+totalAmount); } }
11. 在finally塊中對資源進行釋放
應該大家都有過這樣的經歷,windows系統桌面如果打開太多文件或者系統軟件,就會覺得電腦很卡。當然,我們linux服務器也一樣,平時操作文件,或者數據庫連接,IO資源流如果沒關閉,那么這個IO資源就會被它占著,這樣別人就沒有辦法用了,這就造成資源浪費。
我們操作完文件資源,需要在在finally塊中對資源進行釋放。
FileInputStreamfdIn=null; try{ fdIn=newFileInputStream(newFile("/撿田螺的小男孩.txt")); }catch(FileNotFoundExceptione){ log.error(e); }catch(IOExceptione){ log.error(e); }finally{ try{ if(fdIn!=null){ fdIn.close(); } }catch(IOExceptione){ log.error(e); } }
12.把日志打印好
日常開發中,一定需要把日志打印好。比如:你實現轉賬業務,轉個幾百萬,然后轉失敗了,接著客戶投訴,然后你還沒有打印到日志,想想那種水深火熱的困境下,你卻毫無辦法。。。
一般情況,方法入參、出參需要打印日志,異常的時候,也要打印日志等等,如下:
publicvoidtransfer(TransferDTOtransferDTO){ log.info("invoketranferbegin"); //打印入參 log.info("invoketranfer,paramters:{}",transferDTO); try{ res=transferService.transfer(transferDTO); }catch(Exceptione){ log.error("transferfail,account:{}", transferDTO.getAccount()) log.error("transferfail,exception:{}",e); } log.info("invoketranferend"); }
13. 考慮異常,處理好異常
優秀的后端開發,應當考慮到異常,并做好異常處理。田螺哥給大家提了10個異常處理的建議:
盡量不要使用e.printStackTrace(),而是使用log打印。因為e.printStackTrace()語句可能會導致內存占滿。
catch住異常時,建議打印出具體的exception,利于更好定位問題
不要用一個Exception捕捉所有可能的異常
記得使用finally關閉流資源或者直接使用try-with-resource。
捕獲異常與拋出異常必須是完全匹配,或者捕獲異常是拋異常的父類
捕獲到的異常,不能忽略它,至少打點日志吧
注意異常對你的代碼層次結構的侵染
自定義封裝異常,不要丟棄原始異常的信息Throwable cause
運行時異常RuntimeException ,不應該通過catch的方式來處理,而是先預檢查,比如:NullPointerException處理
注意異常匹配的順序,優先捕獲具體的異常
14. 考慮系統、接口的兼容性
優秀的后端開發,會考慮系統、接口的兼容性。
如果修改了對外舊接口,但是卻不做兼容。這個問題可能比較嚴重,甚至會直接導致系統發版失敗的。新手程序員很容易犯這個錯誤哦~
因此,如果你的需求是在原來接口上修改,尤其這個接口是對外提供服務的話,一定要考慮接口兼容。舉個例子吧,比如dubbo接口,原本是只接收A,B參數,現在你加了一個參數C,就可以考慮這樣處理:
//老接口 voidoldService(A,B){ //兼容新接口,傳個null代替C newService(A,B,null); } //新接口,暫時不能刪掉老接口,需要做兼容。 voidnewService(A,B,C){ ... }
15. 采取措施避免運行時錯誤
優秀的后端開發,應該在編寫代碼階段,就采取措施,避免運行時錯誤 ,如數組邊界溢出,被零整除,空指針等運行時錯誤。類似代碼比較常見:
Stringname=list.get(1).getName();//list可能越界,因為不一定有2個元素哈
所以,應該采取措施,預防一下數組邊界溢出,正例如下:
if(CollectionsUtil.isNotEmpty(list)&&list.size()>1){ Stringname=list.get(1).getName(); }
審核編輯:劉清
-
Linux系統
+關注
關注
4文章
592瀏覽量
27360 -
MVC
+關注
關注
0文章
73瀏覽量
13841 -
HTTP協議
+關注
關注
0文章
61瀏覽量
9706
原文標題:用1個月重構了同事寫的爛代碼,我總結出了15條重寫爛代碼的經驗!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論