<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註解 : 於類別上方使用 /***/方式,說明類別用途

2. method註解 : 於方法上使用/***/方式,說明方法用途、參數/回傳資訊

3. 流程註解 : 紀錄系統執行流程的意義

4. 物件詳細註解 : 紀錄物件內部的資料資訊

---
- 其他注意事項
- 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