
一、背景
-
!! 后指定的類路徑是否準確 -
yaml中的key是否equal類中field的name -
value是否能夠轉換成類中field的型別 -
……

二、代碼比較
1 結構設計
before:

after:

after:增加抽象類中的celtVisitMapping層代碼,對多個代碼檢查模塊做統一代理,做了錯誤的捕獲,后面也可以做一些其他的統一處理(日志、標識引數等),方便拓展,
2 代碼可讀性
2.1命名
一個好的命名能輸出更多的資訊,它會告訴你,它為什么存在,它是做什么事的,應該怎么使用,
2.1.1 類
|
功能 |
時間 |
類名稱 |
|
檢查yaml檔案是否可以成功反序列化成專案中的物件, |
before |
YamlBaseInspection |
|
after |
CeltClassInspection |
比較:
類的命名要做到見名知意,before的命名YamlBaseInspection做不到這一點,通過類名并不能夠獲取到有用的資訊,對于CeltClassInspection的命名格式,在了解插件功能的基礎上,可以直接判斷出屬于yaml類格式檢查,
2.1.2 函式
|
功能 |
時間 |
函式名稱 |
|
比較value是否可以反序列化成PsiClass |
before |
compareNameAndValue |
|
after |
compareKeyAndValue |
1.name是Class中field中的name,通過函式名稱并不能夠看出,函式名傳達資訊不準確,
after:函式名前后單位統一,key和Value是一個yaml中map的兩個概念,能從函式名得出函式功能:檢驗Key和Value的是否準確,
2.1.3 變數
//before
ASTNode node = mapping.getNode().findChildByType(YAMLTokenTypes.TAG);
String className = node.getText().substring(2);
//after
ASTNode node = mapping.getNode().findChildByType(YAMLTokenTypes.TAG);
String tagClassName = node.getText().substring(2);
比較:
String className 來源可以有兩個:
1.通過yaml中tag標簽在專案中查找得到,
after:通過變數名 tagClass 可以快速準確的獲取變數名屬于上述來源中的第一個,能夠降低閱讀代碼的復雜度,變數名可以傳遞更多有用的資訊,
2.2 注釋
2.2.1 注釋格式
-
before 1.無注釋 2.有注釋不符合規范 -
after 有注釋符合JavaDoc規范
//before
private boolean checkSimpleValue(PsiClass psiClass, PsiElement value)
/**
* 檢查列舉類的value
* @return
*/
boolean checkEnum(PsiClass psiClass,String text)
//after
/**
* @param psiClass
* @param value
* @return true 正常;false 例外
*/
private boolean checkSimpleValue(PsiClass psiClass, PsiElement value, ProblemsHolder holder)
2.2.2 注釋位置
before:
//simple型別,檢查keyName 和 value格式
if (PsiClassUtil.isSimpleType(psiClass)) {
//泛型(T)、Object、白名單:不進行檢查
} else if (PsiClassUtil.isGenericType(psiClass)) {
//complex型別
} else {
}
after:
// simpleValue 為 null 或者 "null"
if (YamlUtil.isNull(value)) {
}
if (PsiClassUtil.isSimpleType(psiClass)) {
// simple型別,檢查keyName 和 value格式
checkSimpleValue(psiClass, value, holder);
} else if (PsiClassUtil.isGenericType(psiClass)) {
//泛型(T)、Object、白名單:不進行檢查
} else {
checkComplexValue(psiClass, value, holder);
}
行內注釋應該在解釋的代碼塊內,
2.3 方法抽象
before:
public void compareNameAndValue(PsiClass psiClass, YAMLValue value) {
//simple型別,檢查keyName 和 value格式
if (PsiClassUtil.isSimpleType(psiClass)) {
//泛型(T)、Object、白名單:不進行檢查
} else if (PsiClassUtil.isGenericType(psiClass)) {
//complex型別
} else {
Map<String, PsiType> map = new HashMap<>();
Map<YAMLKeyValue, PsiType> keyValuePsiTypeMap = new HashMap<>();
//init Map<KeyValue,PsiType>, 注冊keyName Error的錯誤
PsiField[] allFields = psiClass.getAllFields();
YAMLMapping mapping = (YAMLMapping) value;
Collection<YAMLKeyValue> keyValues = mapping.getKeyValues();
for (PsiField field : allFields) {
map.put(field.getName(), field.getType());
}
for (YAMLKeyValue keyValue : keyValues) {
if (map.containsKey(keyValue.getName())) {
keyValuePsiTypeMap.put(keyValue, map.get(keyValue.getName()));
} else {
holder.registerProblem(keyValue.getKey(), "找不到這個屬性", ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
keyValuePsiTypeMap.forEach((yamlKeyValue, psiType) -> {
//todo:陣列型別type 的 check
if (psiType instanceof PsiArrayType || PsiClassUtil.isCollectionOrMap(PsiTypeUtil.getPsiCLass(psiType, yamlKeyValue))) {
} else {
compareNameAndValue(PsiTypeUtil.getPsiCLass(psiType, yamlKeyValue), yamlKeyValue.getValue());
}
});
}
}
after:
public void compareKeyAndValue(PsiClass psiClass, YAMLValue value, ProblemsHolder holder) {
// simpleValue 為 null 或者 "null"
if (YamlUtil.isNull(value)) {
return;
}
if (PsiClassUtil.isSimpleType(psiClass)) {
// simple型別,檢查keyName 和 value格式
checkSimpleValue(psiClass, value, holder);
} else if (PsiClassUtil.isGenericType(psiClass)) {
//泛型(T)、Object、白名單:不進行檢查
} else {
checkComplexValue(psiClass, value, holder);
}
}
boolean checkComplexValue();
比較:
before: compareNameAndValue方法代碼過長,一個螢屏不能瀏覽整個方法,方法的框架不能夠簡潔明亮,即要負責判斷型別,進行分發處理,還需要負責complex型別的比較,功能耦合,
after:把對complex物件的比較抽離出一個方法,該方法負責進行復雜型別的比較,原方法只負責區分型別,并呼叫實際的方法比較,能夠清晰的看出方法架構,代碼后期易維護,
2.4 if復雜判斷

after

before:代碼中使用復雜的if嵌套,if是造成閱讀代碼困難的最重要因素之一,if和for回圈的嵌套深V嵌套,代碼邏輯不清晰,代碼維護比較高,拓展復雜,
after:減少了if嵌套,代碼理解成本低,代碼易維護,易拓展,
3.魯棒性
3.1 報錯資訊精準
//before
holder.registerProblem(value, "型別無法轉換", ProblemHighlightType.GENERIC_ERROR);
//after
String errorMsg = String.format("cannot find field:%s in class:%s", yamlKeyValue.getName(), psiClass.getQualifiedName());
holder.registerProblem(yamlKeyValue.getKey(), errorMsg, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
before:對于格式檢查出的錯誤提示很隨意,只說明了型別無法轉換,from是什么?to是什么?都沒有說明白,很多有用的資訊并沒有反饋到用戶,用戶使用體驗會比較差,像是一個完全不成熟的產品,
after:提示無法在class中找到某一個field,并且明確說明了是哪一個field,哪一個class,幫組用戶及時準確定位錯誤并解決,
3.2 代碼健壯性(例外處理)
空指標
before:
代碼需要考慮例外(空指標、預期之外的場景),下面代碼有空指標例外,deleteSqlList可能為null,3行呼叫會拋出NPE,程式沒有捕獲處理,
YAMLKeyValue deleteSqlList = mapping.getKeyValueByKey("deleteSQLList");
YAMLSequence sequence = (YAMLSequence) deleteSqlList.getValue();
List<YAMLSequenceItem> items = sequence.getItems();
for (YAMLSequenceItem item : items) {
if (!DELETE_SQL_PATTERN.matcher(item.getValue().getText()).find()) {
holder.registerProblem(item.getValue(), "sql error", ProblemHighlightType.GENERIC_ERROR);
}
}
after:
@Override
public void doVisitMapping(@NotNull YAMLMapping mapping, @NotNull ProblemsHolder holder) {
ASTNode node = mapping.getNode().findChildByType(YAMLTokenTypes.TAG);
//取出node
if (YamlUtil.isNull(node)) {
return;
}
if (node.getText() == null || !node.getText().startsWith("!!")) {
// throw new RuntimeException("yaml插件監測例外,YAMLQuotedTextImpl text is null或者不是!!開頭");
holder.registerProblem(node.getPsi(), "yaml插件監測例外,YAMLQuotedTextImpl text is null或者不是!!開頭", ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
return;
}
String tagClassName = node.getText().substring(2);
PsiClass[] psiClasses = ProjectService.findPsiClasses(tagClassName, mapping.getProject());
if (ArrayUtils.isEmpty(psiClasses)) {
String errorMsg = String.format("cannot find className = %s", tagClassName);
holder.registerProblem(node.getPsi(), errorMsg, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
return;
}
if (psiClasses.length == 1) {
compareKeyAndValue(psiClasses[0], mapping, holder);
}
}
比較:
after:代碼對例外場景考慮更全面,tagString格式非法,空指標,陣列越界等等情況,代碼更健壯,
switch中的default
before:
switch (className) {
case "java.lang.Boolean":
break;
case "java.lang.Character":
break;
case "java.math.BigDecimal":
break;
case "java.util.Date":
break;
default:
}
after:
switch (className) {
case "java.lang.Boolean":
break;
case "java.lang.Character":
break;
case "java.math.BigDecimal":
break;
case "java.util.Date":
case "java.lang.String":
return true;
default:
holder.registerProblem(value, "未識別的className:" +className, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
return false;
}
比較:
before:代碼存在隱藏邏輯String型別會走default邏輯不處理,增加代碼理解的難度,未對非simple型別的default有例外處理,
after:對String型別寫到具體case,暴漏隱藏邏輯,并對default做例外處理,代碼更健壯,
作者|王耀興(承録)
本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/How-to-eliminate-bad-code-smell.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/544166.html
標籤:Java
上一篇:找素數(java)
