消除代碼壞味道:及時(shí)重構(gòu)降低過長代碼行數(shù)
“本文是筆者新書中關(guān)于編碼與重構(gòu)技巧的一節(jié),希望文章中的案例與思考能夠幫助讀者在日常開發(fā)中,消除過長代碼帶來的壞味道,提升代碼可讀性。
”
在日常開發(fā)過程中,過長的代碼行數(shù)也是一種壞味道。
筆者認(rèn)為一個(gè)方法行數(shù)至少不能超過一屏,換算成行數(shù)大約是50行左右。這是一種偏感性的認(rèn)識(shí),如果超過一屏,就需要不斷上下翻動(dòng),影響代碼的閱讀體驗(yàn)。
代碼行數(shù)過長之所以會(huì)出現(xiàn),主要原因在于對業(yè)務(wù)邏輯的編寫過程是平鋪直敘的,或者通俗的講,代碼是“面條式”的,將各種業(yè)務(wù)邏輯都放在一個(gè)方法中。有的程序員會(huì)細(xì)心的通過分段以及配合注釋的方式便于維護(hù)者理解代碼邏輯,而大多數(shù)情況下是幾百行的代碼既沒有注釋有沒有層次感,讓閱讀者心力交瘁。
來看一段這樣的“面條式”代碼,感受一下過長的代碼行數(shù)帶來的壞味道。
public PlayerRealNameCheckResponse checkReal(String userCode) throws IOException {
RestTemplate restTemplate = new RestTemplate();
PlayerRealNameCheckResponse playerRealNameCheckResponse = new PlayerRealNameCheckResponse();
// 1. 設(shè)置請求頭
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 2. 設(shè)置請求參數(shù)
String requestUrl = GlobalConstant.CHECK_URL;
// 要求10位時(shí)間戳,即精確到秒
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String sign = SignUtil.realCheckSign(userCode, timeStamp, GlobalConstant.KEY);
MultiValueMap<String, String> requestParam= new LinkedMultiValueMap<>();
requestParam.add("UserCode", userCode);
requestParam.add("Timestamp", timeStamp);
requestParam.add("Sign", sign);
LOGGER.info("請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證,地址:{}, 入?yún)?{}", requestUrl, JSON.toJSONString(requestParam));
// 3. 請求開始
HttpEntity<MultiValueMap<String, String>> requestEntity =
new HttpEntity<>(requestParam, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(
requestUrl, HttpMethod.POST, requestEntity, String.class);
// 4. 返回參校驗(yàn)
if (responseEntity == null) {
LOGGER.error("[checkReal失敗]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證為空, userCode:{}", userCode);
return null;
}
// 5. 解析返回參
String checkResponseBody = responseEntity.getBody();
if (StringUtils.isBlank(checkResponseBody)) {
LOGGER.error("[checkReal失敗]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證responseBody為空,userCode:{}", userCode);
return null;
}
// 反序列化為JsonNode
JsonNode responseNode = OBJECT_MAPPER.readTree(checkResponseBody);
String status = responseNode.get("Status").asText();
String msg = responseNode.get("Msg").asText();
JsonNode dataNode = responseNode.get("Data");
if (dataNode == null) {
LOGGER.error("[checkReal失敗]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證Json格式異常,缺失data節(jié)點(diǎn).userCode:{}, status:{}, msg:{}",
userCode, status, msg);
return null;
}
JsonNode userCodeNode = dataNode.get(userCode);
if (userCodeNode == null) {
LOGGER.error("[checkReal失敗]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證Json格式異常,缺失data.userCode節(jié)點(diǎn).userCode:{}, status:{}, msg:{}",
userCode, status, msg);
return null;
}
String originUserCode = userCodeNode.get("UserCode").asText();
String code = userCodeNode.get("Code").asText();
String isReal = userCodeNode.get("IsReal").asText();
String isAdult = userCodeNode.get("IsAdult").asText();
if (GlobalConstant.CODE_REAL_CHECK_STATUS_SUCCESS.equals(status)) {
LOGGER.info("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證, [獲取數(shù)據(jù)成功!], userCode:{}, userCode:{}, status:{}",
userCode, status, msg);
if (!userCode.equals(originUserCode)) {
LOGGER.error("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證, [請求userCode與返回userCode不匹配], userCode:{}, originUserCode:{}, status:{}, msg:{}",
userCode, originUserCode, status, msg);
return null;
}
playerRealNameCheckResponse.setUserCode(userCode);
if (code.equals("0")) {
// 正常游戲用戶
playerRealNameCheckResponse.setUserStatusCode(PlayerStatusCodeEnum.STATUS_LEGAL.getType());
} else {
LOGGER.warn("當(dāng)前用戶為非法用戶,請關(guān)注! userCode:{}", userCode);
playerRealNameCheckResponse.setUserStatusCode(PlayerStatusCodeEnum.STATUS_ILLEGAL.getType());
}
// isREAL 是 0 才校驗(yàn)isAdult 否則不校驗(yàn),因?yàn)閷γ婺J(rèn)對未實(shí)名的玩家標(biāo)記為未成年人
if (isReal.equals("0")) {
// 已經(jīng)實(shí)名認(rèn)證
playerRealNameCheckResponse.setRealCheckStatus(RealCheckStatusEnum.HAS_REAL_CHECK.getType());
if (isAdult.equals("0")) {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.ADULT.getType());
} else if (isAdult.equals("1")) {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.CHILDREN.getType());
} else {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.VISITOR.getType());
}
LOGGER.info("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證,[實(shí)名認(rèn)證成功], 返回參playerRealNameCheckResponse:[{}]", JSON.toJSONString(playerRealNameCheckResponse));
} else {
// 未進(jìn)行實(shí)名驗(yàn)證的用戶可以判斷為游客模式
playerRealNameCheckResponse.setRealCheckStatus(RealCheckStatusEnum.NOT_REAL_CHECK.getType());
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.VISITOR.getType());
LOGGER.info("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證,[未成功進(jìn)行實(shí)名認(rèn)證], 返回參playerRealNameCheckResponse:[{}]", JSON.toJSONString(playerRealNameCheckResponse));
}
return playerRealNameCheckResponse;
} else {
LOGGER.error("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證, [獲取數(shù)據(jù)失敗或異常!], userCode:{}, status:{}, msg:{}",
userCode, status, msg);
return null;
}
}
這段代碼主要含義為:請求遠(yuǎn)程的實(shí)名認(rèn)證接口判斷當(dāng)前用戶角色是否為實(shí)名用戶,如圖所示。

這段代碼的主要流程為:
首先初始化RestTemplate與返回體PlayerRealNameCheckResponse,設(shè)置HTTP請求頭、并將請求參數(shù)設(shè)置到請求體HttpEntity中; 向遠(yuǎn)程接口發(fā)起HTTP調(diào)用,獲取返回結(jié)果,判斷返回結(jié)果是否為空,如果不為空則獲取返回結(jié)果的ResponseBody,否則請求結(jié)束; 解析ResponseBody中的狀態(tài)碼、業(yè)務(wù)參數(shù)Data,判斷Data是否為空,如果非空則獲取具體業(yè)務(wù)參數(shù),否則請求結(jié)束; 獲取用戶狀態(tài)code,判斷code是否為0,如果為0則為正常用戶,否則為非法用戶請求結(jié)束; 獲取用戶實(shí)名認(rèn)證標(biāo)識(shí)isReal,對isReal具體值進(jìn)行判斷,如果為0則用戶角色為成年人,如果為1則用戶角色為未成年人,否則為游客; 組裝實(shí)名認(rèn)證返回體,結(jié)束請求。
回過頭看代碼,會(huì)發(fā)現(xiàn)這段代碼比較長,筆者的開發(fā)環(huán)境中顯示有124行,這已經(jīng)屬于過長方法的范疇了.
因此需要考慮對其進(jìn)行重構(gòu)。
(1)首先將請求頭、請求參數(shù)的設(shè)置以及發(fā)送HTTP請求相關(guān)的代碼進(jìn)行抽取,方法名為sendHttpRequest(),代碼如下:
private ResponseEntity<String> sendHttpRequest(String userCode, RestTemplate restTemplate) {
// 1. 設(shè)置請求頭
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 2. 設(shè)置請求參數(shù)
String requestUrl = GlobalConstant.CHECK_URL;
// 要求10位時(shí)間戳,即精確到秒
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String sign = SignUtil.realCheckSign(userCode, timeStamp, GlobalConstant.KEY);
MultiValueMap<String, String> requestParam= new LinkedMultiValueMap<>();
requestParam.add("UserCode", userCode);
requestParam.add("Timestamp", timeStamp);
requestParam.add("Sign", sign);
LOGGER.info("請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證,地址:{}, 入?yún)?{}", requestUrl, JSON.toJSONString(requestParam));
// 3. 請求開始
HttpEntity<MultiValueMap<String, String>> requestEntity =
new HttpEntity<>(requestParam, headers);
return restTemplate.exchange(
requestUrl, HttpMethod.POST, requestEntity, String.class);
}
(2)將從responseBody中獲取Data參數(shù)的代碼抽取為獨(dú)立的方法,命名為parseDataFromResponse(),代碼如下:
private JsonNode parseDataFromResponse(String userCode, JsonNode responseNode) {
JsonNode dataNode = responseNode.get("Data");
if (dataNode == null) {
LOGGER.error("[checkReal失敗]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證Json格式異常,缺失data節(jié)點(diǎn).userCode:{}", userCode);
return null;
}
return dataNode;
}
(3)將業(yè)務(wù)邏輯中對用戶角色的判斷單獨(dú)抽取為一個(gè)方法,方法名為selectUserRole,代碼如下:
private PlayerRealNameCheckResponse selectUserRole(String userCode, PlayerRealNameCheckResponse playerRealNameCheckResponse, String checkResponseBody, JsonNode responseNode, JsonNode dataNode) {
String msg = responseNode.get("Msg").asText();
JsonNode userCodeNode = dataNode.get(userCode);
if (userCodeNode == null) {
LOGGER.error("[checkReal失敗]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證Json格式異常,缺失data.userCode節(jié)點(diǎn).userCode:{}, msg:{}",
userCode, msg);
return null;
}
String status = responseNode.get("Status").asText();
String originUserCode = userCodeNode.get("UserCode").asText();
String code = userCodeNode.get("Code").asText();
String isReal = userCodeNode.get("IsReal").asText();
String isAdult = userCodeNode.get("IsAdult").asText();
if (GlobalConstant.CODE_REAL_CHECK_STATUS_SUCCESS.equals(status)) {
LOGGER.info("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證, [獲取數(shù)據(jù)成功!], userCode:{}", userCode);
if (!userCode.equals(originUserCode)) {
LOGGER.error("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證, [請求userCode與返回userCode不匹配], userCode:{}, originUserCode:{}",
userCode, originUserCode);
return null;
}
playerRealNameCheckResponse.setUserCode(userCode);
if (code.equals("0")) {
// 正常游戲用戶
playerRealNameCheckResponse.setUserStatusCode(PlayerStatusCodeEnum.STATUS_LEGAL.getType());
} else {
LOGGER.warn("當(dāng)前用戶為非法用戶,請關(guān)注! userCode:{}", userCode);
playerRealNameCheckResponse.setUserStatusCode(PlayerStatusCodeEnum.STATUS_ILLEGAL.getType());
}
// isREAL 是 0 才校驗(yàn)isAdult 否則不校驗(yàn),因?yàn)閷γ婺J(rèn)對未實(shí)名的玩家標(biāo)記為未成年人
if (isReal.equals("0")) {
// 已經(jīng)實(shí)名認(rèn)證
playerRealNameCheckResponse.setRealCheckStatus(RealCheckStatusEnum.HAS_REAL_CHECK.getType());
if (isAdult.equals("0")) {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.ADULT.getType());
} else if (isAdult.equals("1")) {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.CHILDREN.getType());
} else {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.VISITOR.getType());
}
LOGGER.info("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證,[實(shí)名認(rèn)證成功], 返回參playerRealNameCheckResponse:[{}]", JSON.toJSONString(playerRealNameCheckResponse));
} else {
// 未進(jìn)行實(shí)名驗(yàn)證的用戶可以判斷為游客模式
playerRealNameCheckResponse.setRealCheckStatus(RealCheckStatusEnum.NOT_REAL_CHECK.getType());
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.VISITOR.getType());
LOGGER.info("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證,[未成功進(jìn)行實(shí)名認(rèn)證], 返回參playerRealNameCheckResponse:[{}]", JSON.toJSONString(playerRealNameCheckResponse));
}
return playerRealNameCheckResponse;
} else {
LOGGER.error("[checkReal]-請求遠(yuǎn)程接口進(jìn)行實(shí)名認(rèn)證, [獲取數(shù)據(jù)失敗或異常!], checkResponseBody:{}", checkResponseBody);
return null;
}
}
(4)對第(3)步中的代碼繼續(xù)進(jìn)行重構(gòu),將設(shè)置用戶狀態(tài)碼StatusCode的業(yè)務(wù)邏輯進(jìn)行抽取,根據(jù)code設(shè)置用戶為正常用戶還是非法用戶,代碼如下:
private void setUserStatusCode(String userCode, PlayerRealNameCheckResponse playerRealNameCheckResponse, String code) {
if (code.equals("0")) {
// 正常游戲用戶
playerRealNameCheckResponse.setUserStatusCode(
PlayerStatusCodeEnum.STATUS_LEGAL.getType());
} else {
LOGGER.warn("當(dāng)前用戶為非法用戶,請關(guān)注! userCode:{}", userCode);
playerRealNameCheckResponse.setUserStatusCode(
PlayerStatusCodeEnum.STATUS_ILLEGAL.getType());
}
}
(5)將選擇用戶角色的代碼進(jìn)行抽取,根據(jù)isReal具體值返回用戶角色,代碼如下:
private void setUserRole(PlayerRealNameCheckResponse playerRealNameCheckResponse, String isReal, String isAdult) {
// isREAL 是 0 才校驗(yàn)isAdult 否則不校驗(yàn),因?yàn)閷γ婺J(rèn)對未實(shí)名的玩家標(biāo)記為未成年人
if (isReal.equals("0")) {
// 已經(jīng)實(shí)名認(rèn)證
playerRealNameCheckResponse.setRealCheckStatus(RealCheckStatusEnum.HAS_REAL_CHECK.getType());
if (isAdult.equals("0")) {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.ADULT.getType());
} else if (isAdult.equals("1")) {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.CHILDREN.getType());
} else {
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.VISITOR.getType());
}
} else {
// 未進(jìn)行實(shí)名驗(yàn)證的用戶可以判斷為游客模式
playerRealNameCheckResponse.setRealCheckStatus(RealCheckStatusEnum.NOT_REAL_CHECK.getType());
playerRealNameCheckResponse.setUserRole(PlayerRoleEnum.VISITOR.getType());
}
}
(6)主流程checkReal方法重構(gòu)后的代碼如下:
public PlayerRealNameCheckResponse checkReal(String userCode) throws IOException {
RestTemplate restTemplate = new RestTemplate();
PlayerRealNameCheckResponse playerRealNameCheckResponse = new PlayerRealNameCheckResponse();
// 發(fā)送HTTP請求
ResponseEntity<String> responseEntity = sendHttpRequest(userCode, restTemplate);
// 返回參校驗(yàn)
if (responseEntity == null) {
return null;
}
// 解析返回參
String checkResponseBody = responseEntity.getBody();
if (StringUtils.isBlank(checkResponseBody)) {
return null;
}
// 反序列化為JsonNode
JsonNode responseNode = OBJECT_MAPPER.readTree(checkResponseBody);
// 解析Data
JsonNode dataNode = parseDataFromResponse(userCode, responseNode);
if (dataNode == null) {
return null;
}
// 選擇用戶角色
return selectUserRole(userCode, playerRealNameCheckResponse, checkResponseBody, responseNode, dataNode);
}
小結(jié)與思考
可以看到,將不同的操作都封裝為單獨(dú)的方法,在主流程中只保留重要的操作步驟,不僅提升了閱讀者的體驗(yàn),而且使得代碼的邏輯更加有條理。對于主流程而言,代碼行數(shù)得到了明顯減少,相比重構(gòu)前代碼行數(shù)減少了一半以上。
通過對方法進(jìn)行重構(gòu),是降低代碼行數(shù)的一種行之有效的方法。在開發(fā)階段就應(yīng)當(dāng)不斷地對代碼進(jìn)行重構(gòu),使代碼邏輯更加具備層次感,避免過長的代碼行所帶來的壞味道。
