<style> html, body, .ui-content { background-color: #333; color: #ddd; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { color: #ddd; } .markdown-body h1, .markdown-body h2 { border-bottom-color: #ffffff69; } .markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { color: #fff; } .markdown-body img { background-color: transparent; } .ui-toc-dropdown .nav>.active:focus>a, .ui-toc-dropdown .nav>.active:hover>a, .ui-toc-dropdown .nav>.active>a { color: white; border-left: 2px solid white; } .expand-toggle:hover, .expand-toggle:focus, .back-to-top:hover, .back-to-top:focus, .go-to-bottom:hover, .go-to-bottom:focus { color: white; } .ui-toc-dropdown { background-color: #333; } .ui-toc-label.btn { background-color: #191919; color: white; } .ui-toc-dropdown .nav>li>a:focus, .ui-toc-dropdown .nav>li>a:hover { color: white; border-left: 1px solid white; } .markdown-body blockquote { color: #bcbcbc; } .markdown-body table tr { background-color: #5f5f5f; } .markdown-body table tr:nth-child(2n) { background-color: #4f4f4f; } .markdown-body code, .markdown-body tt { color: #eee; background-color: rgba(230, 230, 230, 0.36); } a, .open-files-container li.selected a { color: #5EB7E0; } </style> # ==**Code Review指導原則**== - 變數相關 1. 區域變數 ``` java String a = ""; // 錯誤示範,請使用有意義命名 String member = ""; // 正確,定義可識別的變數名稱 String form_id = ""; // 錯誤,要以駝峰命名 String formId = ""; // 正確 ``` 2. 常數 ```java private final String init = ""; // 錯誤,常數請用大寫 private final String INIT = ""; // 正確 private final String initFirst = ""; // 錯誤,常數請不要用駝峰命名 private final String INIT_FIRST = ""; // 正確 ``` 3. function命名 ```java // 請勿使用底線命名 public void get_id(){ // some code here } // 請使用駝峰命名 public void getId (){ // some code here } // 呈第1點,參數需要有意義的命名 public void getId(int a){ // 請勿取a這種奇怪的命名 // some code here } ``` --- - 善用第三方套件,防止NullPointException : 1. StringUtils - 主要用途:針對是否為空字串、字串相等、任一字為空...等第三方套件 ```java String item1 = someObject.getSomeString(); String item2 = someObject.getSomeString(); // 1.物件比較使用StringUtils,避免NPE StringUtils.equals(item1,item2); // 2. 倘若100%確定任一字串有值,即可使用原生equlas,但僅限有值的放在前面 String item11 = "item"; String item22 = someObject.getSomeString(); item11.equals(item22); ``` 2. CollectionUtils - 主要用途:針對List的元素操作 ```java List aList = someObject.getList(); List bList = someObject.getList(); // 1. 物件是否為空使用 CollectionUtils.isEmpty(aList); // 2. 是否包含XXX物件 CollectionUtils.containsAny(xxx); ``` 3. MapUtils - 主要用途:針對Map元素進行操作,並可視情況給予Default值 ```java public void getMapValue(Map<k,v> map) { // 取int值 Integet intValue = MapUtils.getInteger(map,"intKey" , 0); // 取String值 String stringValue = MapUtils.getString(map,"stringKey","預設值") // ... 其餘用法請參考API文件 } ``` ==*上述三個範例套件,主要都是防止Npe操作用,視程式邏輯使用,不一定要每次使用*== --- - 實作寫法篇 : 1. 禁止變數濫用 ==Example1== ``` java public String function1(){ String a = someObject.getString(); // 其他業務邏輯 return a; } ``` 上述這個案例,a只有使用一次,就可以不必再單獨宣告物件 ==Example2== ```java public String funtion2(){ String a = somObject.getString(); // 其他業務邏輯 if(StringUtils.isBlank(a)){ a = anthoerObject.getString(); } return a; } ``` 上述這第二個案例,a因有超過一處的地方使用,才需要單獨宣告出來 2. 禁止Exception濫用 ==Example1== ```java public void testException1(){ // some code here // sudo code if(Boolean result){ throw new Exception(exception.getMessage); } } ``` 上述這個案例,直接丟出最大的Exception,不是一個好選擇。應需視業務情況,選擇拋出相對應的Exception ==Example2== ```java public void testException2(){ // some code here try{ // 針對DB進行資料處裡 xxxDao.insert(Object); } catch(DataAccessException e){ // 發生資料重複的例外,執行例外流程 log.error("insert error ! message :{}" , e.getMessage,e); throw e; } catch(ConnectException e){ // 例外流程 throw e; } } ``` 上述這個案例,針對不同的例外類別,應有不同處理流程。不應該一視同仁,全部丟出最大的Exception 3.迴圈裡面禁止針對==DB進行操作== : ==Example1== ```java public void testLoop1(){ List<?> result = new ArrayList(); for(Element e : dataList){ // some code here ... // use select sql SomeObject resultObject = xxxDao.findByElement(e); if(Boolean Result){ result.add(resultObject); } } } ``` 上述這種情境,會在迴圈裏面,跑多次的DB Select,此為禁止行為! 應改用sql cmd解決 ==Example2== ```sql select * from xxxTable where filter in (elements); ``` 再接續處裡後面邏輯 ```java public void testLoop2(){ List<?> result = new ArrayList(); // 取消for loop List<?> resultList = xxxDao.getList(); // dao背後的sql cmd如同上面的範例2 // some code here } ``` 4. 交易控管 : 針對不同的交易行為,因設定好交易控管(==Transactional==),但禁止設定無效交易 ==Example== ```java @AutoWired private Aservice aService; @AutoWired private Bservice bService; @Transactional public void testTransition1(){ // some code here // A Service針對DB進行資料管控 aService.insert(someObject); // B Service針對DB進行資料管控 bService.insert(someObject); } ``` 依上述這個範例而言,將交易控管綁在主流程,這樣只要testTransition1這個方法,執行過程中,有任何例外,所有的交易就會回滾。 ```java @Transactional public void aServiceMethod1(someObject){ aDao.insert(someObject); } ``` 上面這個[aServiceMethod1]所綁的交易,則為無意義的交易控管,因為已在「另一個Class裡的testTransition1」綁住交易。 事務回滾範圍參考 : https://matthung0807.blogspot.com/2020/11/spring-transactional-methods-call-rollback-boundaries.html 5. SOLID概念(非強制,供設計建議) : 以單一職責舉例,盡量避免單一method負責處理多項業務功能 ==Example1== ```java // 這是一個購票網站的方法 public Object testSolid1(Map<k,v> map){ // step 1 確認是否已登入會員 // step 2 確認參數是否正確 // step 3 選擇購票交易類型、日期、時間 // step 4 DB交易 // step 5 返回交易結果 } ``` 以上述的流程概念,應將不太相干的業務行為,拆分成另一獨立method,甚至是Class ==Example2== ```java public Object testSolid2(Map<k,v> map){ // step 1 確認是否已登入會員 aCheckService.checkIsLogin(); // step 2 確認參數是否正確 bService.checkParameter(); // step 3 選擇購票交易類型、日期、時間 this.choseTicket(); // step 4 DB交易 this.insertData(); // step 5 返回交易結果 return someObject; } ``` SOLID Design Pattern參考資料 : https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E7%9B%AE%E9%8C%84-b33fdfc983ca 5. DB交易 資料管控是最重要的地方,針對交易行為時,需要嚴格管控! ==Example1== ```sql /* 在下查詢語法的時候,盡量帶著where條件,避免撈取資料過大,浪費資源 */ -- 資料查詢 select * from xxxTable where xxxColumn = 'xxx'; ``` ```sql /* sql在下更新語句時,絕對要帶入where條件,避免直接將整個DB的資料都給update掉 刪除的語法亦是如此 */ update xxxTable set aColumn = 參數1, bColumn = 參數2 where xxxColumn = 參數3 ``` 而在Java中,可以針對新增、修改、刪除的DB交易行為,將其異動結果,返回一個int的值 ==Example2== ```java public void testData2(){ // 示範:新增 int saveResult = xxxDao.save(); // 預期 saveResult應為1筆,如果不為1筆,要走異常處理流程 if(saveResult != 1){ throw new xxxException("db error!") } } ``` 上述用意為,程式設計師要確保資料的異動結果,是在期望值內,若不符預期,則需善用資料回滾的機制 6.Log資訊機制 log是記錄系統資訊流程很重要的東西,但需善用,避免增加系統負荷 ==Example1== ```java public void testLog(){ // some code here if(Boolean result){ // 僅紀錄某些特定字串 log.info("some message here"); } if(Boolean result){ // 除特定字串外,需額外紀錄某些資訊,用 {}及, 進行串接 log.info("some object :{}",someObject); } if(Boolean result){ try{ // some code here }catch(xxxException e){ // 此處因有例外發生,除了例外訊息外,建議也將例外類別印出,以利後續異常排除 log.error("error ! message :{}",e.getMessage,e) } } ``` *注意 :使用原則* 1. log所要記錄的內容,資訊有什麼幫助?真的有需要嗎? 2. 大部分的日誌層級,皆使用info及error即可滿足,除非有特殊需求,要使用到warn等等,需於程式註解說明 3. 避免無意義的log資訊,如下方範例 ```java public ResponseBondy testLog2(){ String message = "some information"; // 某一物件 ResponseBondy res = new ResponseBondy(); res.setMessage(message); log.info(message); // 無意義,可刪除 // 此處回傳的物件,已有將message塞入,故上一行的log紀錄為多餘 // 除非有特殊需求,需於程式端進行註解 return res; } ``` ==Example2== 大部分的使用情境為排程 || 監聽器 ```java // 以排程執行為範例 public void testSchedule(){ try{ log.info("xxx排程執行開始"); // 排程執行方法 xxxSchedule.someMethod(); }catch(xxxEcception e){ log.error("Schedule error ! message :{}",e.getMessage,e); // some code here }finally{ log.info("xxx排程執行結束"); } } ``` 以上述為例,為確保排程程式有正確運行,故於接口一進入,即info執行資訊。而會設計finally的原因為,確保排程有成功執行完成(無論是否例外) 7. Loop相關 盡量以forEach取代傳統for迴圈 ==Example== ```java public void testLoop(){ // 寫法1 for(int i = 0 ; i < 10 ; i++){ // some code here } // 寫法2 for(Element e : eList){ // some code here } } ``` 在可允許的範圍內,以寫法2為優先選擇,原因如下 : - 執行效能較優 - 傳統for loop的閱讀性較差 -> 上方的 ==**int i**==,若無註解,較難直觀閱讀其用意 - 傳統for loop沒有控制好,易有indexOutOfBoundException --- - 註解相關 1. class/interface註解 : 於類別上方使用 /***/方式,說明類別用途 ![](https://hackmd.io/_uploads/BkkvRnP3h.png) 2. method註解 : 於方法上使用/***/方式,說明方法用途、參數/回傳資訊 ![](https://hackmd.io/_uploads/S15yJpv22.png) 3. 流程註解 : 紀錄系統執行流程的意義 ![](https://hackmd.io/_uploads/HkMqJpP3h.png) 4. 物件詳細註解 : 紀錄物件內部的資料資訊 ![](https://hackmd.io/_uploads/HJ7oZaw2h.png) --- - 其他注意事項 - Spring Bean注入方式,使用建構子注入,禁止使用@Autowired - log紀錄以info層級為主,僅有捕捉例外時,使用log.error --- - 版本控制 1. git commit message 格式 ==Example== ```text 「commit類別」: 此次commit主要功能 1. 附加說明1 2. 附加說明2 3. 附加說明3 「功能名稱」+「底線」+「分支名稱」 ``` 上述的附加說明,若主要功能可說明完成,即可忽略 commit類別說明 - feat : 新增加的功能/需求 - reafactor : 程式碼重構 - docs : 新增註解 - format : 程式碼排版 - fix : Bug修復 - codeReview : 程式碼review後的修改 - entity : 僅新增與DB對應的實體類別 ==實際Example== ```text feat: 新增jwt登入驗證功能 1. security配置檔案配置 2. token過期時間reSet edmFlow_Harvey_issue0814 ``` 2. commit規範: - 嚴禁一次commit多筆資料(Ex:超過10筆) - 依功能別commit,禁止一次commit多功能檔案 - 若有同一功能,多筆檔案需commit,請拆解成模組commit(Ex: 查詢模組/新增模組) - 嚴禁commit message非依格式commit 3. Branch規範: - 創建本地Branch : 測試分支為base,拉出本地Branch - Merge流程 : localBranch -> devBranch ->masterBranch ==**特別注意**== 1. 所有的localBranch,必須完成測試後,才能申請MergeRequest 2. 所有Merge到devBranch的code,皆必須是測試完成無Bug 4. Create Merge Request (GitLab) - 每當分支需合併時,需開立merge request - Assigner請指向Team Leader - Reviewer請指向codeReview的人 - Reviewer所comment的問題,僅有Reviewer能夠關閉comment,developer禁止關閉 --- ==**Format**== - 採google java格式 - https://blog.miniasp.com/post/2022/09/03/Google-Java-Style-Guide-for-Eclipse-STS4-and-IntellJ-IDEA - https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml