重構思考——減少項目中的if-else

2024年2月6日 19点热度 0人点赞

1. 背景

最近在幫老師做項目,讓我重構一個項目,看到項目的代碼後,真是小刀拉屁股——開了眼了。一個方法,三千多行,一堆if-else的判斷條件,而且變量的命名,居然是a,b,c,d,xx,yy這種格式的,看了老半天,沒看懂在幹啥,最後咬咬牙,按照之前的文檔,重新寫了一個方法,三千多行的代碼,實際上有66%以上的代碼是重復的,就是復制某個if塊的邏輯,到另外一個if塊中,因此寫下這篇文章,記錄一下在項目中,如何減少if-else的出現。

2. 常用優化方式

2.1. 去除不必要的else

如果if-else代碼塊中,隻有一個判斷條件,可以把多餘else幹掉,使代碼更整潔

優化前:

if(condition){
	// dosomething1
  return;
} else {
	// dosomething2
  return;
}

優化後:

if(condition){
	// dosomething1
  return;
} 
// dosomething2
return;

2.2. 使用三目運算符

對於某些if-else,可以使用三目運算符來進行簡化,使代碼更簡潔

優化前:

int price;
if(condition){
	price = 80;
} else {
	price = 100;
}

優化後:

int price = condition ? 80 : 100;

2.3. 使用Optional優化判空

在項目中,很多時候會用到if來判斷空指針,這個我們可以使用Optional進行優化

優化前:

Person p = new Person();
if(address != null) {
	p.setAddress(address);
}
if(phone != null) {
	p.setPhone(phone);
}

優化後:

Person p = new Person();
Optional.ofNullable(address).ifPresent(Person::setAddress);
Optional.ofNullable(phone).ifPresent(Person::setPhone);

2.4. 使用枚舉

在項目中,有時候要根據某個狀態值,設置對應的屬性,這個其實可以使用枚舉來代替。

優化前:

Order order = new Order();
if(status == 1) {
	order.setStatus("支付中");
} else if (status == 2) {
	order.setStatus("已支付");
} else if (status == 3) {
...
} else {
	...
}

優化後:

// 定義一個枚舉類
public enum OrderStatus {
	PAYIING(1, "支付中"),
  PAID(2, "已支付"),
  ...;
  private int code;
  private String status;
  OrderStatus(int code, String status) {
    this.code = code;
    this.status = status;
  }
  public Orderstatus getOrderByCode(int code) {
    ...
  }
}
Order order = new Order();
OrderStatus orderStatus = OrderStatus.getOrderByCode(status);
order.setStatus(orderStatus.getStatus());

2.5. 策略模式 工廠模式

對於if-else代碼塊中,內容比較復雜的時候,我們可以使用策略模式 工廠模式來進行改造,以減少代碼塊中的if-else。

策略模式:一種解耦的方法,它對算法進行封裝,使得算法的調用和算法本身分離,使用策略模式客戶端代碼不需要調整,算法之間可以互相替換,因為不同的算法實現的是同一個接口。策略模式是一種對象行為型模式,策略模式符合“開閉原則”。

策略模式包含如下角色:

1)Context:環境類

2)Strategy:抽象策略類

3)ConcreteStrategy:具體策略類

業務場景:在我們的微信錢包中,可以綁定多張銀行卡,假設綁定銀行卡時,銀行卡的提供商會對綁定行為進行風控(假設不同銀行卡的風控行為不同)。

2.5.1. 改造前

//銀行卡父類Card.java
package com.young.pojo;
import lombok.Data;
@Data
public class Card {
    private String cardNo;
    private String bankCode;
    private String bankName;
    private Long registerTime;
    private Integer balance;
}
//農業銀行卡 
package com.young.pojo;
public class AgriculturalCard extends Card{
}
//建設銀行卡
package com.young.pojo;
public class BuilderCard extends Card{
}
//郵政銀行卡
package com.young.pojo;
public class PostalCard extends Card{
}
//銀行卡枚舉類
package com.young.enums;
public enum CardEnum {
    BUILDER,
    AGRICULTURAL,
    POSTAL;
}
//銀行卡風控請求類
package com.young.request;
import lombok.Data;
@Data
public class CardRiskRequest {
    private String cardNo;
    private String bankCode;
    private Long registerTime;
    private String userName;
}
//銀行卡風控響應類
package com.young.response;
import lombok.Data;
@Data
public class CardRiskResponse {
    private Boolean result;
    private String unableReason;
    private String challengeType;
}

銀行卡風控處理的service:

package com.young.service.impl;
import com.young.enums.CardEnum;
import com.young.request.CardRiskRequest;
import com.young.response.CardRiskResponse;
import com.young.service.CardRiskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
@Slf4j
public class EasyCardRiskService implements CardRiskService {
    @Override
    public CardRiskResponse consultRisk(CardRiskRequest cardRiskRequest) {
        if (cardRiskRequest.getBankCode().equals(CardEnum.AGRICULTURAL.name())){
            return agriculturalRisk(cardRiskRequest);
        }else if (cardRiskRequest.getBankCode().equals(CardEnum.BUILDER.name())){
            return builderRisk(cardRiskRequest);
        } else if (cardRiskRequest.getBankCode().equals(CardEnum.POSTAL.name())) {
            return postalRisk(cardRiskRequest);
        }
        throw new UnsupportedOperationException();
    }
    private CardRiskResponse postalRisk(CardRiskRequest cardRiskRequest) {
        log.info("郵政銀行判斷綁卡是否需要風控");
        CardRiskResponse cardRiskResponse=new CardRiskResponse();
        if (cardRiskRequest.getUserName().equals("cxy")){
            cardRiskResponse.setResult(true);
        }else{
            cardRiskResponse.setResult(false);
            cardRiskResponse.setChallengeType("OTP");
            cardRiskResponse.setUnableReason("用戶信息需要風控");
        }
        return cardRiskResponse;
    }
    private CardRiskResponse builderRisk(CardRiskRequest cardRiskRequest) {
        log.info("建設銀行判斷綁卡是否需要風控");
        CardRiskResponse cardRiskResponse=new CardRiskResponse();
        if (cardRiskRequest.getRegisterTime()%2==0){
            cardRiskResponse.setResult(true);
        }else{
            cardRiskResponse.setResult(false);
            cardRiskResponse.setChallengeType("OTP");
            cardRiskResponse.setUnableReason("註冊時間需要驗證");
        }
        return cardRiskResponse;
    }
    private CardRiskResponse agriculturalRisk(CardRiskRequest cardRiskRequest) {
        log.info("農業銀行判斷綁卡是否需要風控");
        CardRiskResponse cardRiskResponse=new CardRiskResponse();
        if (cardRiskRequest.getCardNo().startsWith("1000")){
            cardRiskResponse.setResult(true);
        }else{
            cardRiskResponse.setResult(false);
            cardRiskResponse.setChallengeType("OTP");
            cardRiskResponse.setUnableReason("不知名原因");
        }
        return cardRiskResponse;
    }
}

2.5.2. 改造後

上面的EasyCardRiskService這個類中,存在大量if...else...,當我們要支持其他銀行卡時,必須在這個service中進行代碼改動,違反開閉原則。

下面我們用策略模式結合工廠模式進行改造。

首先定義一個風控接口

package com.young.service;
import com.young.request.CardRiskRequest;
import com.young.response.CardRiskResponse;
public interface CardRiskService {
    default CardRiskResponse consultRisk(CardRiskRequest cardRiskRequest){
        throw new UnsupportedOperationException();
    }
}

依次對農業銀行、建設銀行和郵政銀行進行實現

//AgriculturalCardRiskService
package com.young.service.impl;
import com.young.enums.CardEnum;
import com.young.factory.CardRiskServiceFactory;
import com.young.request.CardRiskRequest;
import com.young.response.CardRiskResponse;
import com.young.service.CardRiskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
@Slf4j
public class AgriculturalCardRiskService implements CardRiskService, InitializingBean {
    @Override
    public CardRiskResponse consultRisk(CardRiskRequest cardRiskRequest) {
        log.info("農業銀行判斷綁卡是否需要風控");
        CardRiskResponse cardRiskResponse=new CardRiskResponse();
        if (cardRiskRequest.getCardNo().startsWith("1000")){
            cardRiskResponse.setResult(true);
        }else{
            cardRiskResponse.setResult(false);
            cardRiskResponse.setChallengeType("OTP");
            cardRiskResponse.setUnableReason("不知名原因");
        }
        return cardRiskResponse;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        CardRiskServiceFactory.put(CardEnum.AGRICULTURAL.name(), this);
    }
}
//BuilderCardRiskService
package com.young.service.impl;
import com.young.enums.CardEnum;
import com.young.factory.CardRiskServiceFactory;
import com.young.request.CardRiskRequest;
import com.young.response.CardRiskResponse;
import com.young.service.CardRiskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
@Slf4j
public class BuilderCardRiskService implements CardRiskService , InitializingBean {
    @Override
    public CardRiskResponse consultRisk(CardRiskRequest cardRiskRequest) {
        log.info("建設銀行判斷綁卡是否需要風控");
        CardRiskResponse cardRiskResponse=new CardRiskResponse();
        if (cardRiskRequest.getRegisterTime()%2==0){
            cardRiskResponse.setResult(true);
        }else{
            cardRiskResponse.setResult(false);
            cardRiskResponse.setChallengeType("OTP");
            cardRiskResponse.setUnableReason("註冊時間需要驗證");
        }
        return cardRiskResponse;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        CardRiskServiceFactory.put(CardEnum.BUILDER.name(), this);
    }
}
//PostCardRiskService
package com.young.service.impl;
import com.young.enums.CardEnum;
import com.young.factory.CardRiskServiceFactory;
import com.young.request.CardRiskRequest;
import com.young.response.CardRiskResponse;
import com.young.service.CardRiskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component
@Slf4j
public class PostalCardRiskService implements CardRiskService, InitializingBean {
    @Override
    public CardRiskResponse consultRisk(CardRiskRequest cardRiskRequest) {
        log.info("郵政銀行判斷綁卡是否需要風控");
        CardRiskResponse cardRiskResponse=new CardRiskResponse();
        if (cardRiskRequest.getUserName().equals("cxy")){
            cardRiskResponse.setResult(true);
        }else{
            cardRiskResponse.setResult(false);
            cardRiskResponse.setChallengeType("OTP");
            cardRiskResponse.setUnableReason("用戶信息需要風控");
        }
        return cardRiskResponse;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        CardRiskServiceFactory.put(CardEnum.POSTAL.name(), this);
    }
}

創建CardRiskService工廠類

package com.young.factory;
import com.young.service.CardRiskService;
import org.springframework.util.ConcurrentReferenceHashMap;
import java.util.Map;
public class CardRiskServiceFactory {
    private static Map<String, CardRiskService>cardRiskServiceMap=new ConcurrentReferenceHashMap<>();
    public static CardRiskService getInstance(String bankCode){
        return cardRiskServiceMap.get(bankCode);
    }
    public static void put(String bankCode,CardRiskService cardRiskService){
        cardRiskServiceMap.put(bankCode,cardRiskService);
    }
}