Redian新闻
>
使用策略模式消除冗长的if-else|记一次smart-auto重构总结

使用策略模式消除冗长的if-else|记一次smart-auto重构总结

科技

阿里妹导读


作者针对smart-auto接口测试相关的核心代码进行了一次重构,使代码变得更清晰和可维护。

一、背景

smart-auto工具经过多年的迭代和好几代测试同学的开发,现在功能已经非常强大,支持各种HSF接口调用返回值的对比和断言能力,每日跑的测试件已经占到了淘宝买菜所有自动化测试件的55%以上。但是随着功能的不断增加,代码也越来越来庞大,之前单一的功能也变得复杂,代码的可维护性也在不停的降低。所以针对smart-auto接口测试相关的核心代码进行了一次重构,使代码变得更清晰和可维护。

二、现状分析

可以看下优化之前接口测试相关的核心代码,可以由下面简单的表述下这段代码意思:
1.工具中针对,Hsf接口校验共有四种方式HsfCheck1、HsfCheck2、HsfCheck3、HsfCheck4;
2.所有的接口校验方式都包含在一个Handler中,且不同的方式之间全部通过各种复杂的if-else分支来判断;
3.整体代码大概有200多行;
 public CheckOutputModel doHandle(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) throws Exception {     if(!jsonPathList.isEmpty()){          if (attributeModel.isCompare()) {                HsfCheck1          }     }else{         if(attributeModel.isCompare()) {                HsfCheck2         }     }     if ( checkConfigStatusObject == null ||checkConfigStatusObject.isEmpty() || checkConfigStatusObject.getBoolean("isCheck") == false  ){                return result;            }else{             if(assertExpectStr == null || !node.isCustom()){                HsfCheck3             }else{                HsfCheck4             }            } }
完整的代码如下:
@Service("commonCheckHandlerAbandon")public class CommonCheckHandler implements CheckHandler{    @Resource  private CaseConfigHandlerService caseConfigHandlerService;    @Resource    private CheckDataCaseService checkDataCaseService;    private Logger logger = LoggerFactory.getLogger(this.getClass());    @Override    public CheckOutputModel doHandle(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) throws Exception {        ThubNodeConfig node = JSON.parseObject(checkRecordModel.getTestsuiteDO().getStepConfig(), ThubNodeConfig.class);        TestsuiteAttributeModel attributeModel = JSON.parseObject(checkRecordModel.getAttributes(), TestsuiteAttributeModel.class);        if (checkRecordModel.getTestsuiteDO().getShadow()) {            // 全链路压测标            EagleEye.putUserData("t", "1");        }        CheckOutputModel result = new CheckOutputModel();        if(node==null){            result.setSuccess(false);            return result;        }        List<String> jsonPathList = Collections.emptyList();        if(node.getJsonPath() != null && !node.getJsonPath().trim().isEmpty() ){            jsonPathList = Arrays.asList(node.getJsonPath().split(";"));        }        try{            //如果jsonPathList不为空,则执行jsonPath解析,执行jsonPath之后的对比            if(!jsonPathList.isEmpty()){                List<CheckDiffModel> totalFailInfo = new ArrayList<>();                List<String> errorMessages = new ArrayList<>(); // 用于存储错误消息                for (String jsonPath  : jsonPathList) {                    try {                        if (attributeModel.isCompare()) {                            String actualResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.ACTUAL_RESULT));                            String expectResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));                            Object actualResult = null;                            Object expectResult = null;                            if (StringUtils.isNoneBlank(actualResultStr)) {                                Object actualValueObject = JsonPath.read(actualResultStr, jsonPath);                                String actualValue = JSON.toJSONString(actualValueObject);                                if (JSON.isValidObject(actualValue)) {                                    actualResult = JSON.parseObject(actualValue);                                } else if (JSON.isValidArray(actualValue)) {                                    actualResult = JSON.parseArray(actualValue);                                } else {                                    actualResult = JSON.parse(actualValue);                                }                            }                            if (StringUtils.isNoneBlank(expectResultStr)) {                                Object expectValueObject = JsonPath.read(expectResultStr, jsonPath);                                String expectValue = JSON.toJSONString(expectValueObject);                                if (JSON.isValidObject(expectValue)) {                                    expectResult = JSON.parseObject(expectValue);                                } else if (JSON.isValidArray(expectValue)) {                                    expectResult = JSON.parseArray(expectValue);                                } else {                                    expectResult = JSON.parse(expectValue);                                }                            }                            StringBuffer ignorBuffer = new StringBuffer();                            ignorBuffer.append(node.getIgnorConfig());                            List<CheckDiffModel> failInfo = QAssert.getReflectionDiffInfo("assert diff", expectResult, actualResult, ignorBuffer.toString(),                                    ReflectionComparatorMode.LENIENT_ORDER, ReflectionComparatorMode.LENIENT_DATES, ReflectionComparatorMode.IGNORE_DEFAULTS);                            failInfo.forEach(i -> i.setNodeName(jsonPath + "---" + i.getNodeName()));                            totalFailInfo.addAll(failInfo);                        }                    } catch (Exception e) {                        // 记录错误消息                        String errorMessage = "Error with JSON path: " + jsonPath + " - " + e.getMessage();                        errorMessages.add(errorMessage);                        logger.error(errorMessage, e);                    }                }                if (!totalFailInfo.isEmpty()||!errorMessages.isEmpty()) {                    if(!totalFailInfo.isEmpty()){                        errorMessages.add(0, "value not same");                    }                    // 组合错误消息,用回车符分隔                    String combinedErrorMessages = String.join("\n", errorMessages);                    result.setSuccess(false);                    result.setErrorCode(combinedErrorMessages);                    result.setFailInfoList(totalFailInfo);                } else {                    result.setSuccess(true);                }//如果jsonPathList为空,走正常对比逻辑            }else {                result.setTraceId(EagleEye.getTraceId());                if(attributeModel.isCompare()) {                    String actualResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.ACTUAL_RESULT));                    String expectResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));                    Object actualResult = null;                    Object expectResult = null;                    if (StringUtils.isNoneBlank(actualResultStr)) {                        if (JSON.isValidObject(actualResultStr)) {                            actualResult = JSON.parseObject(actualResultStr);                        } else if (JSON.isValidArray(actualResultStr)) {                            actualResult = JSON.parseArray(actualResultStr);                        } else {                            actualResult = JSON.parse(actualResultStr);                        }                    }                    if (StringUtils.isNoneBlank(expectResultStr)) {                        if (JSON.isValidObject(expectResultStr)) {                            expectResult = JSON.parseObject(expectResultStr);                        } else if (JSON.isValidArray(expectResultStr)) {                            expectResult = JSON.parseArray(expectResultStr);                        } else {                            expectResult = JSON.parse(expectResultStr);                        }                    }                    StringBuffer ignorBuffer = new StringBuffer();                    ignorBuffer.append(node.getIgnorConfig());                    List<CheckDiffModel> failInfo = QAssert.getReflectionDiffInfo("assert diff", expectResult, actualResult, ignorBuffer.toString(),                            ReflectionComparatorMode.LENIENT_ORDER, ReflectionComparatorMode.LENIENT_DATES, ReflectionComparatorMode.IGNORE_DEFAULTS);                    if (!failInfo.isEmpty()) {                        result.setSuccess(false);                        result.setErrorCode("value not same");                        result.setFailInfoList(failInfo);                    } else {                        result.setSuccess(true);                    }                }            }            //执行断言校验            JSONObject checkConfigStatusObject = JSON.parseObject(checkRecordModel.getTestsuiteDO().getCheckConfigStatus());//无断言直接返回            if ( checkConfigStatusObject == null ||checkConfigStatusObject.isEmpty() || checkConfigStatusObject.getBoolean("isCheck") == false  ){                return result;            }else{//执行断言校验                String assertActualStr = StringEscapeUtils.unescapeJavaScript((String)executeResultModel.getValueByKey(CheckCaseInfoConst.ACTUAL_RESULT));                String assertExpectStr = StringEscapeUtils.unescapeJavaScript((String)executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));                CheckDataCaseDO checkDataCaseDO = caseParam.getCaseDO();                //断言对比                if(assertExpectStr == null || !node.isCustom()){                    boolean checkResult = caseConfigHandlerService.resultAssert(checkRecordModel.getTestsuiteDO(), checkDataCaseDO,assertActualStr);                    if (!checkResult){                        result.setSuccess(false);                        return result;                    }                    CheckDataCaseQueryDO checkDataCaseQueryDO = new CheckDataCaseQueryDO();                    checkDataCaseQueryDO.setId(checkDataCaseDO.getId());                    List<CheckCaseResult> checkResultList = checkDataCaseService.queryCheckCaseResult(checkDataCaseQueryDO).getResult();                    List<CheckDiffModel> checkCoonfigFailInfo = new ArrayList<>();                    for (CheckCaseResult checkCaseResult : checkResultList) {                        String checkConfigResult = checkCaseResult.getCheckConfigResult();                        List<Map> checkParse = JSONArray.parseArray(checkConfigResult, Map.class);                        for (Map map : checkParse) {                            CheckDiffModel checkDiffModel = new CheckDiffModel();                            String checkConfig = String.valueOf(map.get("checkConfigResult"));                            StringBuffer stringBuffer = new StringBuffer();                            if(!StringUtils.equals(checkConfig,"true")){                                stringBuffer.append((String)map.get("assertNode")+map.get("assertCondition")+map.get("assertErpect"));                                checkDiffModel.setActualValue("false");                                checkDiffModel.setNodeName(String.valueOf(stringBuffer));                                checkCoonfigFailInfo.add(checkDiffModel);                            }                        }                    }                    if (checkCoonfigFailInfo.size() != 0) {                        result.setSuccess(false);                        result.setErrorCode("value not same");                        result.setFailInfoList(checkCoonfigFailInfo);                    } else{                        result.setSuccess(true);                    }                    //跨应用断言校验                }else {                    boolean checkResult = caseConfigHandlerService.resultAssertComp(checkRecordModel.getTestsuiteDO(), checkDataCaseDO, assertActualStr,assertExpectStr);                    if (!checkResult){                        result.setSuccess(false);                        return result;                    }                    CheckDataCaseQueryDO checkDataCaseQueryDO = new CheckDataCaseQueryDO();                    checkDataCaseQueryDO.setId(checkDataCaseDO.getId());                    List<CheckCaseResult> checkResultList = checkDataCaseService.queryCheckCaseResult(checkDataCaseQueryDO).getResult();                    List<CheckDiffModel> checkCoonfigFailInfo = new ArrayList<>();                    for (CheckCaseResult checkCaseResult : checkResultList) {                        String checkConfigResult = checkCaseResult.getCheckConfigResult();                        List<Map> checkParse = JSONArray.parseArray(checkConfigResult, Map.class);                        CheckDiffModel checkDiffModel = new CheckDiffModel();                        StringBuffer stringBuffer = new StringBuffer();                        for (Map map : checkParse) {                            Boolean checkConfig = (Boolean) map.get("checkConfigResult");                            if(!checkConfig){                                stringBuffer.append((String)map.get("assertNode")+map.get("assertCondition")+map.get("assertErpect"));                                stringBuffer.append(",");                                checkDiffModel.setActualValue("false");                            }                        }                        checkDiffModel.setNodeName(String.valueOf(stringBuffer));                        checkCoonfigFailInfo.add(checkDiffModel);                    }                    if (checkCoonfigFailInfo.get(0).getActualValue() != null) {                        result.setSuccess(false);                        result.setErrorCode("value not same");                        result.setFailInfoList(checkCoonfigFailInfo);                    } else{                        result.setSuccess(true);                    }                }                }        }catch(Exception e){            e.printStackTrace();            result.setSuccess(false);            result.setMsgInfo(e.getMessage());        }finally{            EagleEye.removeUserData("t");        }        return result;    }}
以上代码有以下几点问题:
1.这段代码是由冗长的if-else分支判断组合起来的,且if-else的逻辑也比较混乱,然后这段代码把4种Hsf的接口检查都耦合在了一起,没有扩展性。后续增加任何功能,都需要在原来耦合的代码里添加代码,有可能会影响原有功能。
2.这段代码没有做到开闭原则,一段良好的代码需要做到对扩展开发,对修改关闭。
3.所有实现Hsf校验的逻辑都在一个handler类中,导致这个类中的代码很多,从而影响了代码的可读性、可维护性。
4.这段代码的if-else条件判断很难懂,无法判断某个条件中的校验到底是校验哪一种Hsf校验类型,每次查看这段代码都要研究好久。

三、解决方案

可以使用策略工厂模式来解决以上问题,把每种Hsf校验的方式封装起来,然后通过策略工厂模式来路由下发,把冗长的代码解耦出来,形成了一套框架,并且保证了代码的扩展性。废话不多说,直接看代码。
先构建一个策略工厂类:
public class CheckStrategyFactory {    private final Map<CheckStrategySelector, HsfInterfaceCheck> strategyRegistry = new HashMap<>();
@Autowired public CheckStrategyFactory(HsfAssertCheck hsfAssertCheck, HsfCrossInterfaceAssertCompare hsfCrossInterfaceAssertCompare, HsfFullCompareCheck hsfFullCompareCheck, HsfMultipleJsonPathCompareCheck hsfMultipleJsonPathCompareCheck, JsonPathCompareStrategySelector jsonPathCompareStrategySelector, CrossInterfaceAssertCompareStrategySelector crossInterfaceAssertCompareStrategySelector, FullCompareStrategySelector fullCompareStrategySelector, AssertStrategySelector assertStrategySelector) {
// 在构造函数或初始化块中注册所有策略 strategyRegistry.put(assertStrategySelector, hsfAssertCheck); strategyRegistry.put(crossInterfaceAssertCompareStrategySelector, hsfCrossInterfaceAssertCompare); strategyRegistry.put(fullCompareStrategySelector, hsfFullCompareCheck); strategyRegistry.put(jsonPathCompareStrategySelector, hsfMultipleJsonPathCompareCheck); // ... 注册更多策略 ... } public HsfInterfaceCheck getStrategy(ThubNodeConfig node, JSONObject checkConfigStatusObject,TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel) {
for (Map.Entry<CheckStrategySelector, HsfInterfaceCheck> entry : strategyRegistry.entrySet()) { if (entry.getKey().matches(node, checkConfigStatusObject, attributeModel, executeResultModel)) { return entry.getValue(); } }
return null; // 兜底检查策略返回null }}
再创建2个接口,一个策略选择接口CheckStrategySelector,一个Hsf校验接口HsfInterfaceCheck。
public interface CheckStrategySelector {    boolean matches(ThubNodeConfig node, JSONObject checkConfigStatusObject , TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel);}
public interface HsfInterfaceCheck {    CheckOutputModel  check(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel);}
再创建4个策略类和4个Hsf校验类分别实现策略选择接口CheckStrategySelector和Hsf校验接口HsfInterfaceCheck。
以下是HsfCheck1和HsfCheck2策略选择类,省略其他2个。
public class AssertStrategySelector implements CheckStrategySelector {    @Override    public boolean matches(ThubNodeConfig node, JSONObject checkConfigStatusObject, TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel) {        String assertExpectStr = StringEscapeUtils.unescapeJavaScript((String)executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));        return !(checkConfigStatusObject == null ||checkConfigStatusObject.isEmpty() || checkConfigStatusObject.getBoolean("isCheck") == false) && (assertExpectStr == null || !node.isCustom());    }}
public class FullCompareStrategySelector implements CheckStrategySelector {    @Override    public boolean matches(ThubNodeConfig node, JSONObject checkConfigStatusObject, TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel) {        return attributeModel.isCompare() && (node.getJsonPath() == null || node.getJsonPath().trim().isEmpty());    }}

以下是HsfCheck1和HsfCheck2校验类,省略其他2个。

@Service("hsfAssertCheck")public class HsfAssertCheck implements HsfInterfaceCheck {    @Resource    private CaseConfigHandlerService caseConfigHandlerService;    @Resource    private CheckDataCaseService checkDataCaseService;    @Override    public CheckOutputModel check(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) {
}
@Service("hsfFullCompareCheck")public class HsfFullCompareCheck implements HsfInterfaceCheck {
@Override public CheckOutputModel check(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) {
}}
最后Handler代码改造成了这一段。
@Service("commonCheckHandler")public class CommonCheckHandler implements CheckHandler{    private final CheckStrategyFactory factory;
public CommonCheckHandler(CheckStrategyFactory factory) { this.factory = factory; }

@Override public CheckOutputModel doHandle(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) throws Exception { ThubNodeConfig node = JSON.parseObject(checkRecordModel.getTestsuiteDO().getStepConfig(), ThubNodeConfig.class); TestsuiteAttributeModel attributeModel = JSON.parseObject(checkRecordModel.getAttributes(), TestsuiteAttributeModel.class); JSONObject checkConfigStatusObject = JSON.parseObject(checkRecordModel.getTestsuiteDO().getCheckConfigStatus()); CheckOutputModel result = new CheckOutputModel(); if(node==null){ result.setSuccess(false); return result; }
HsfInterfaceCheck hsfInterfaceCheckStrategy = factory.getStrategy(node, checkConfigStatusObject, attributeModel, executeResultModel); if(hsfInterfaceCheckStrategy != null){ return hsfInterfaceCheckStrategy.check(caseParam, checkRecordModel, executeResultModel); } else { result.setSuccess(false); result.setErrorCode("未找到对应的校验策略"); return result; } }}

以上通过策略工厂模式把那段代码拆成了多个文件,通过策略工厂模式把冗长的if-else代码给分解了,我们再来看一下重构后的代码是不是更好呢。下图展示了重构的整体逻辑:
重构之后,创建了工厂类,由工厂类中的策略判断逻辑来决定是哪一种策略类型,在运动时动态确定使用哪种策略,最终路由到对应的校验方法里。
1.最终代码实现了以下几个点:重构后的代码符合了开闭原则,添加新策略的时候,最小化、集中化代码改动、减少引入bug的风险。
2.重构后的代码解耦了之前代码的复杂度,解耦了策略的定义、创建和使用,控制代码复杂度,让每个部分的代码不至于太复杂、代码量过多。现在每个类的代码基本上在一显示屏就能展示完成。
3.大大增加了代码的可读性和可维护性。


四、总结

当然,并不是所有if-else分支都是烂代码,只要if-else分支不复杂,代码不多,这并没有问题,只要遵循KISS原则,怎么简单怎么来,就是最好的设计。但是一旦if-else分支很多,且每个分支都包含很多复杂的逻辑判断,这个时候就可以考虑是不是通过策略模式可以更清晰的梳理代码,使得代码维护性更强。

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
七大策略规模以上机构3月业绩快报:3月各大策略普遍上涨,程序化期货策略位居榜首【高级公寓】Elevate|Cambridge|查尔斯河岸景观一览无余免半个月|高级公寓|Hub50 House|Boston|市中心奢华公寓Property to Virtual Goods, More Young Chinese Are Drafting Wills免费无门槛!Wetzel’s Pretzels免费送Original Pretzels啦!【买房】Melrose|2B2B|标价 $585,000IRS Tax Seminar - IRS Expert Reveals Tax Saving Secrets for You!旅行的尽头是日本(22)“椿山庄”的会席料理——锦水Can This Café Help People With Autism Find Acceptance in China?ASML前CTO,加入ASMIJellycat限时巨折!2折起:英国优衣库/Diesel/GoldenGoose/We11done等!【就今天】Wetzel’s Pretzels 30周年庆典,免费送美食!领取方式在此突发!LSE申请门槛再提高,2025Fall多个专业Alevel要求上调,增加TMUA笔试!西直门一个桥墩上的故事美股基本面 - 2024_02_24 * 晨报 * 真成“股神”了?美媒惊讶:佩洛西丈夫3个月内在英伟达股票上净赚超125万美【高级公寓】One North|Chelsea|远离喧嚣尽享安逸Hilton与Small Luxury Hotels of The World达成合作伙伴关系 部分合作细节泄露,可用积分和FN同济大学章小清/刘玲/房玉江团队Cell Metabolism发现胞内pH-Smad5信号通路控制胰岛素加工与分泌新机制未央播报 | 去年末金融业机构总资产为461.09万亿元 互金协会开展变相高息“现金贷”“套路贷”自查整改​突发!LSE 2025入学要求公布,多个热门专业A-level上调且增加笔试环节!!“PPT中的SmartArt,千万别用!!”Chelsea超高性价比公寓|Studio - 2b2b|2500+|免半年停车Can City Walks Fix What Ails Chinese Urbanism?【高级公寓】Revolution|Somerville|Assembly Square的明星公寓[电脑] 记一次装机|ROG半个全家桶Shanghai Bans Mandatory Use of Facial Recognition in HotelsChelsea 高级公寓|Studio - 2b2b|2500+|免6周房租Out in the Cold: In Northeast China, a Season of Wild Extremes记一次hosts配置内容过多引起的故障一场告别Chinese Startup Unveils AI Video Software to Rival OpenAI’s SoraLSE又现新拒法!拒信面前众生平等!冲LSE前,你需要了解这些...《阴阳鱼》连载第34章:时间如刀,空间如砧板,而你我都不过是鱼肉Wells Fargo Autograph Journey 信用卡【3月9日实体店上线,5x 酒店,4x 航空,60k开卡奖励】【高级公寓】Hub50 House|Boston|市中心奢华公寓
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。