掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
領(lǐng)導(dǎo):“這個項目,今后就給你維護(hù)了啊,仔細(xì)點?!?小貓:“好,沒問題”。 可當(dāng)滿懷信心的小貓打開項目工程包翻看一些代碼之后,瞬間懵逼沒了信心。

10年積累的成都網(wǎng)站設(shè)計、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站設(shè)計后付款的網(wǎng)站建設(shè)流程,更有港南免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
是這樣的:
還是這樣的:
平級的if else密密麻麻就算了,但是深套五六層的if else甚至七八層的真的是讓人摸不著北。
那么就上面小貓遇到的這種情況,面對著幾代程序員精心堆積的屎山,試問閣下該如何應(yīng)對?不慌,老貓羅列了以下解決方案,如果各位還有比較好的優(yōu)化方法也歡迎留言。
我們對著上述目錄從簡單的開始介紹吧:
當(dāng)我們遇到空對象或者有部分滿足條件之后才能執(zhí)行的時候,不要只想著正向邏輯,其實可以逆向思維,把不滿足條件的優(yōu)先排除掉。這樣可以有效避免if else的深嵌套。 優(yōu)化前代碼:
if(condition){
//doSomething
}else{
}
return;優(yōu)化后如下:
if(!condition){
return;
}
原來的代碼:
public Result addUser() {
if (StrUtil.equals(userStatus, "online")) {
return doStep1();
} else {
return doStep2();
}
// else 后面沒有其他業(yè)務(wù)時,可省略最后的else,使代碼簡潔
}優(yōu)化后的代碼:
public Result addUser() {
if (StrUtil.equals(userStatus, "online")) {
return doStep1();
}
return doStep2();
}當(dāng)然這里面要注意的點是,一定要確認(rèn)是最后的else,并沒有其他的業(yè)務(wù)邏輯。
還是基于上面的代碼,如果只有兩種業(yè)務(wù)的話,其實在一個方法里面直接用三目運算法進(jìn)行執(zhí)行即可。如下改造:
public Result addUser() {
return StrUtil.equals(userStatus, "online")) ?doStep1() : doStep2();
}一個方法一行代碼搞定。
很多業(yè)務(wù)場景下,其實我們寫if 是為了判空,自從java8之后其實多了一個Optional神器,Optional 是個容器,它可以保存類型 T 的值,或者僅僅保存null。Optional 提供了很多方法,這樣我們就不用顯式進(jìn)行空值檢測。Optional 類的引入很好的解決空指針異常。我們看下下面的優(yōu)化方式: 代碼優(yōu)化前:
if (user == null) {
throw new Exception("未查詢到用戶信息");
}
if (user != null) {
update(user); // 執(zhí)行方法調(diào)用
}
代碼優(yōu)化后:
Optional.ofNullable(user).orElseThrow(() -> new Exception("未查詢到用戶信息"));
Optional.ofNullable(user).ifPresent(user -> update(user));隱式調(diào)用相當(dāng)優(yōu)雅。
設(shè)計模式優(yōu)化法其實也是針對不同的場景使用不同的設(shè)計模式從而簡化多余的if else。
(1) 第一種,合理使用責(zé)任鏈模式。
我們再具體結(jié)合一種場景,比方說現(xiàn)在頁面上有新注冊的用戶,他需要提交相關(guān)的身份信息進(jìn)行認(rèn)證,此時,我們底層往往會對他提交的信息做相關(guān)的校驗處理。 底層我們的校驗方式(1)需要驗證基本字非空性 (2)需要驗證身份信息基礎(chǔ)字段合法性 (2)需要調(diào)用第三方進(jìn)行要素認(rèn)證。 原始代碼如下:
public void addUser(User user) {
// 1.非空校驗
if (StrUtil.isBlank(user.getUsername())) {
throw new RuntimeException("用戶名為空!");
}
if (StrUtil.isBlank(user.getPassword())) {
throw new RuntimeException("密碼為空!");
}
...
// 2.格式校驗
if (!ValidUtil.isIdCardNo(user.getIdCardNo())) {
throw new RuntimeException("身份證號格式錯誤!");
}
if (!ValidUtil.isEmail(user.getEmail())) {
throw new RuntimeException("手機(jī)號格式錯誤!");
}
if (!ValidUtil.isEmail(user.getEmail())) {
throw new RuntimeException("郵箱格式錯誤!");
}
...
// 3.要四素認(rèn)證校驗
if(!doFourStampVerify(User user)){
throw new RuntimeException("四要素認(rèn)證失??!");
}
}此處可能還有很多其他的省略的場景。所以單個文件中的If else可能比想象中多的多。那么我們?nèi)绾斡秘?zé)任鏈模式進(jìn)行優(yōu)化呢? 改造代碼如下,首先定義一個處理器接口:
/**
* 處理器鏈接口
*/
public interface UserChainHandler {
void handler(User user);
}剩下不同的場景校驗只要去實現(xiàn)這個接口就可以了,不過需要定義好順序
@Component
@Order(1) // 指定注入順序
public class UserParamNullValidChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 1.非空校驗
if (StrUtil.isBlank(user.getUsername())) {
throw new RuntimeException("用戶名為空!");
}
if (StrUtil.isBlank(user.getPassword())) {
throw new RuntimeException("密碼為空!");
}
}
@Component
@Order(1) // 指定注入順序
public class UserParamNullValidChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 1.非空校驗
if (StrUtil.isBlank(user.getUsername())) {
throw new RuntimeException("用戶名為空!");
}
...
}
/**
* 格式校驗處理器
*/
@Component
@Order(2) // 指定注入順序
public class UserParamFormatValidChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 2.格式校驗
if (!ValidUtil.isIdCardNo(user.getIdCardNo())) {
throw new RuntimeException("身份證號格式錯誤!");
}
...
}
/**
* 四要素處理器
*/
@Component
@Order(3) // 指定注入順序
public class FourElementVerifyChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 2.格式校驗
if (!doFourStampVerify(User user)) {
throw new RuntimeException("四要素認(rèn)證失敗!");
}
}
//進(jìn)行組裝
@Component
@RequiredArgsConstructor
public class UserChainContext {
private final List userChainHandlerList; // 自動注入責(zé)任鏈處理器
/**
* 責(zé)任鏈組件執(zhí)行
*
* @param requestParam 請求參數(shù)
*/
public void handler(User user) {
// 此處根據(jù) Ordered 實際值進(jìn)行排序處理
userChainHandlerList.forEach(x -> x.handler(user));
}
} 最終咱們的原來的add方法進(jìn)行這樣調(diào)用就好了:
public void addUser(User user) {
// 執(zhí)行責(zé)任鏈
userChainContext.handler(user);
}(2) 第二種,合理使用策略模式+工廠模式。
假設(shè)我們遇到這樣一個場景,我們目前底層是一個會員系統(tǒng),目前系統(tǒng)需要計算各種會員套餐的價格,然后套餐的具體模式主要是由上層系統(tǒng)傳遞指定給我們。如果只關(guān)注業(yè)務(wù)直接擼代碼的話,應(yīng)該是如下。
public Result calcPrice(CalcPriceParam calcPriceParam){
//判斷對應(yīng)的計算價格的場景
Integer type = judgeType(calcPriceParam);
//根據(jù)場景調(diào)用不同的方法 ,建議更好的編碼習(xí)慣是把type改成枚舉類型哈~
if(type == 1){
return calcPriceForTypeOne();
}
if(type == 2){
return calcPriceForTypeTwo();
}
if(type == 3){
return calcPriceForTypeThree();
}
.....
if(typr == 10){
return calcPriceForTypeTen();
}
}顯而易見隨著會員價格場景套餐越來越多,我們的if也會越來越多。 但是如果使用策略模式的話,我們可以做到如下:
public interface Strategy {
Result calcPrice(CalcPriceParam calcPriceParam);
int getBizType();
}
@Service
public Class firstStragy implement Strategy {
Result calcPrice(CalcPriceParam calcPriceParam) {
....
return result;
}
int getBizType() {
return 1;
}
}
public Class secondStragy implement Strategy {
Result calcPrice(CalcPriceParam calcPriceParam) {
....
return result;
}
int getBizType() {
return 2;
}
}
@Service
public class StrategyContext{
Map strategyContextMap = new HashMap<>();
//注入對應(yīng)的策略類
@Autowired
Strategy[] strategys;
@PostConstruct
public void setStrategyContextMap(){
for(Stragegy stragegy:strategys){
strategyContextMap.put(stragegy.getCode,stragegy);
}
}
//根據(jù)場景調(diào)用不同的方法
public Result calcPrice(CalcPriceParam calcPriceParam){
Integer type = judgeType(calcPriceParam);
CalcPriceInterface calcPriceInstance = strategyContextMap.get(type);
return calcPriceInstance.calcPrice(calcPriceParam);
}
} 這樣一來,咱們上面的第一個方法中的If else的實現(xiàn)將會變得很簡單,如下:
@Autowired
StrategyContext strategyContext;
public Result calcPrice(CalcPriceParam calcPriceParam){
strategyContext.calcPrice(calcPriceParam);
}這樣即使新增新的計算模式,我們只需去實現(xiàn)Strategy接口并且重寫里面兩個方法即可完成后續(xù)業(yè)務(wù)的拓展。代碼優(yōu)雅簡單,可維護(hù)性強(qiáng)。 以上就是用設(shè)計模式針對大量if else進(jìn)行改造。
這種方式個人覺得有點像策略模式,但是又不需要單獨抽出相關(guān)類去承載注冊方法,而是簡單地將方法通過函數(shù)式的方式放到Map中,等到需要使用的時候再進(jìn)行調(diào)用。 原始爛代碼,我們還是參考上述會員費用金額計算的場景。我們可以進(jìn)行如下方式優(yōu)化:
Map action> actionMap = new HashMap<>();
action.put("type1",() -> {calcPriceForTypeOne()});
action.put("type2",() -> {calcPriceForTypeTwo()});
action.put("type3",() -> {calcPriceForTypeThree()});
...
// 使用
actionMap.get(action).apply(); 當(dāng)然如果想要再優(yōu)化得好一些的話,可以進(jìn)行接口抽取,然后進(jìn)行實現(xiàn),在此不展開,留下給小伙伴們思考一下。
我們再回到之前小貓遇到的那兩個代碼截圖,其實我們可以看到有個大量if else并排的代碼其實主要是想要比較相關(guān)的屬性有沒有發(fā)生變化,如果發(fā)生變化,那么則返回false,沒有變化則返回true。其實我們想想是不是可以通過重寫LogisticDO這個對象的equals方法來進(jìn)行實現(xiàn)呢?這樣是不是也規(guī)避了大量的if else。
還有其他一些當(dāng)然也是根據(jù)具體場景來解決,比方說,我需要根據(jù)不同的type類型,進(jìn)行獲取不同的描述信息,那么此時我們是不是可以使用enum去維護(hù)呢? 如下:
if(status.equals(1)){
return "訂單未支付";
}else if(status.equals(2)){
return "訂單已支付"
}else if(status.equals(3)){
return "訂單已發(fā)貨"
}
.....優(yōu)化后:
@Getter
@AllArgsConstructor
public enum OrderStatusEnum {
UN_PAID("1","訂單未支付"),
PAIDED("2","訂單已支付"),
SENDED("3","訂單已發(fā)貨"),
.....;
private String status;
private String statusDes;
static OrderStatusEnum of(String status) {
for (OrderStatusEnum statusEnum : OrderStatusEnum.values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
}
String orderStatusDes = OrderStatusEnum.of(orderStatus).getStatusDes();等等還有其他一些,由于這些優(yōu)化個人認(rèn)為是沒法標(biāo)準(zhǔn)化的優(yōu)化原則,不同的業(yè)務(wù)場景都不同,所以在此,老貓不將其放在通用優(yōu)化中,認(rèn)為這個是其他優(yōu)化方式。
之前在某個技術(shù)論壇上看到大家在爭論這么一個問題“如何避免將維護(hù)的項目發(fā)展成屎山?”大家發(fā)言踴躍。有說前期做好設(shè)計,有人說代碼質(zhì)量需要高一些,合理場景套用一些設(shè)計模式等等。 不過老貓認(rèn)為項目無法避免發(fā)展成屎山,只是快慢而已,我也認(rèn)為項目無法避免發(fā)展成“屎山”。其原因有三點:
說了這么多,其實老貓最終想表達(dá)的是,雖然項目會最終淪為屎山,但是作為一個有追求的研發(fā),我們就應(yīng)當(dāng)從每個小的if else著手,至少讓當(dāng)前這個項目在你維護(hù)期間,讓其發(fā)展成屎山的速度變慢一些,或者能替之前的老前輩還掉一些技術(shù)債才是最好的,各位小伙伴你們覺得呢?

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流