简介: 咱们始终在说零碎很简单,那到底什么是零碎复杂度呢?作为团队的稳定性底盘负责人,也常常和大家探讨为什么会因为圈复杂度高而被扣分。那么,怎么能力写的一手可读,可扩大,可保护的好代码?本文作者尝试联合在团队外部的实际,分享下过程核心得。
作者 | 陈胜利(李渔)起源 | 阿里开发者公众号网上有个段子,说修建工程师不会轻易许可会给摩天大楼减少一个地下室,但代码开发工程师却常常在干这样的事,并且总有人会对你说“这个需要很简略”。到土里埋个雷,这的确不简单,但咱们往往面临的实在场景其实是“在一片雷区的土里埋一个雷”。而雷区里哪里有雷,任何人都不晓得。回到咱们日常的写代码的场景,咱们始终在说零碎很简单,那到底什么是零碎复杂度呢?最近几个月,蚂蚁代码力平台(注:是蚂蚁的代码评估平台)进入大家视线,很多同学开始关注起本人代码力的得分状况。作为团队的稳定性底盘负责人,也常常和大家探讨为什么会因为圈复杂度高而被扣分。那么,怎么能力写的一手可读,可扩大,可保护[注1]的好代码?本文作者尝试联合在团队外部的实际,分享下过程核心得,心愿对大家的代码圈复杂度治理提供强劲的帮忙。什么是圈复杂度先看看圈复杂度的通用的定义,圈复杂度(Cyclomatic complexity,简写CC)[注2]也称为条件复杂度/循环复杂度,是一种代码复杂度的衡量标准。由托马斯·J·麦凯布(Thomas J. McCabe, Sr.)于1976年提出,用来示意程序的复杂度,其符号为VG。它能够用来掂量一个模块断定构造的复杂程度,数量上体现为独立现行门路条数,也可了解为笼罩所有的可能状况起码应用的测试用例数。说人话,圈复杂度关系到品质同学起码须要设计多少用例能力笼罩你的代码分支。怎么计算圈复杂度蚂蚁广目平台给出了比拟具体的阐明,这里间接援用,网上也能够查到相似内容。节点判断计算公式为:V (G) = P + 1 注:除了节点判断法,还有其余办法,如点边判断法,这里只选一个用于阐明。其中P为条件节点数,条件节点类型为:a.条件语句if语句while语句(蕴含do...while...语句)for语句(蕴含foreach语句)switch case语句try catch语句b.条件表达式(二元或多元)&& 表达式|| 表达式三元运算符举例如下(局部代码省略后用xxx代替)://案例1,圈复杂度V(G) = 1(if) + 1(catch) + 1 = 3
public String myMethod1(){
if(xxx){ try { //xxx; } catch (IOException e) { //xxx; }}else{ xxx;}return xx;
}
//案例2,圈复杂度V(G) = 2(if) + 1(&&) + 1 = 4
public String myMethod2() {
if (xxx) { //xxx;} else { if (xxx && xxx) { //xxx; } else { //xxx; } xx();}return xx;
} 为什么要关注圈复杂度好了,理解了圈复杂度的定义之后,咱们根本能够得出一个论断,圈复杂度大阐明程序逻辑简单,不利于代码的浏览,保护,和后续扩大。如果须要看懂一个圈复杂度高的办法,须要小心翼翼整顿所有的分支状况,而改变这类代码更像踏入雷区一样。上面,咱们来看一段代码案例(局部内容已省略)public XXresult doSave( XXDTO newScriptDTO) {
String type = Enums.ScriptType.CUSTOM;Boolean containsTryCatch = StringUtil.contains(content, "try") && StringUtil.contains(content, "catch");if (StringUtil.isBlank(scriptName)) { baseOperationResult.setMessage("XXX"); return baseOperationResult;}if (!scriptName.matches("^[(\\d)|_|a-z|A-Z]+$")) { baseOperationResult.setMessage("XXX"); return baseOperationResult;}NewScript tempScript = null;try { tempScript = newScriptManager.findByName(StringUtil.trim(scriptName));} catch (Exception e) { baseOperationResult.setMessage("XXX"); return baseOperationResult;}if (StringUtil.isBlank(id)) { if (tempScript != null) { baseOperationResult.setMessage("XXX"); return baseOperationResult; }} else { Integer editScriptId = Integer.parseInt(id); if (null != tempScript) { if (!editScriptId.equals(tempScript.getId())) { baseOperationResult.setMessage("XXX"); return baseOperationResult; } }}if (!Enums.NewScriptTypeEnum.XX.contains(scriptType)) { baseOperationResult.setMessage("XX"); return baseOperationResult;}Boolean needSubtypeMode = true;if (StringUtils.equals(scriptType, Enums.XX.XX) || StringUtils.equals(scriptType, Enums.XX.PRE)) { needSubtypeMode = false;}NewScript script = new NewScript();script.setScriptType(scriptType);if (StringUtil.isNumeric(status)) { script.setStatus(Integer.parseInt(status));}if (StringUtil.isNotBlank(scriptCategory)) { script.setScriptCategory(ScriptCategory.getByCode(scriptCategory));}String subType = "";if (needSubtypeMode) { if (StringUtil.isBlank(subtypeandtip)) { baseOperationResult.setMessage("XXX"); return baseOperationResult; }}if (needSubtypeMode) { List< NewScript> allActiveAndTestRunScripts = newScriptManager .findAllActiveAndTestRunScripts(); List< String> allActiveAndTestRunSubTypeList = new ArrayList<>(); for (NewScript activeAndTestRunScript : allActiveAndTestRunScripts) { List< String> subTypeListEveryScript = Arrays .asList(Optional.ofNullable(activeAndTestRunScript.getSubType()) .orElse(new String()).split(",")); for (String subTypeTemp : subTypeListEveryScript) { if (StringUtil.isNotBlank(subTypeTemp)) { allActiveAndTestRunSubTypeList.add(subTypeTemp); } } } try { JSONArray subtypetipsArray = JSON.parseArray(subtypeandtip); if (StringUtil.isBlank(id)) { for (Object object : subtypetipsArray) { JSONObject subtypetipsObject = (JSONObject) object; String subtypeSingle = subtypetipsObject.getString("subtype"); if (StringUtil.isBlank(subtypeSingle)) { baseOperationResult.setSuccess(false); return baseOperationResult; } if (CollectionUtils.contains(allActiveAndTestRunSubTypeList.iterator(), subtypeSingle)) { baseOperationResult.setSuccess(false); return baseOperationResult; } } } else { if ("1".equals(status) || "2".equals(status)) { for (Object object : subtypetipsArray) { //省略局部内容XXX; if (StringUtil.isBlank(subtypeSingle)) { baseOperationResult.setSuccess(false); return baseOperationResult; } for (NewScript oldNewScript : allActiveAndTestRunScripts) { if (oldNewScript.getId().equals(Integer.parseInt(id))) { continue; } //省略局部内容XXX; if (CollectionUtils.contains(filtered.iterator(), subtypeSingle)) { baseOperationResult.setSuccess(false); return baseOperationResult; } } } } } for (Object object : subtypetipsArray) { if (1 == script.getStatus() || 2 == script.getStatus()) { SubtypeTips subtypeTips = null; subtypeTips = subtypeTipsManager.findBySubtype(subtypeSingle); if (subtypeTips == null) { subtypeTips = new SubtypeTips(); } subtypeTips.setSubtype(subtypeSingle); subtypeTips.setInternalTips(innertips); subtypeTips.setExternalTips(externaltips); subtypeTips.setShareLink(shareLink); subtypeTips.setStatus(1); subtypeTipsManager.save(subtypeTips); } } subType = StringUtil.substring(subType, 0, subType.length() - 1); } catch (Exception e) { baseOperationResult.setSuccess(false); baseOperationResult.setMessage("XXX"); return baseOperationResult; }}boolean needCreateTestRunScript = false;if (StringUtils.isNotBlank(id)) { script.setId(Integer.parseInt(id)); NewScript orgin = newScriptManager.findById(Integer.parseInt(id)); if (null != orgin && 1 == orgin.getStatus() && "1".equals(status)) { if (StringUtil.isNotBlank(orgin.getContent())) { String originContentHash = CodeUtil .getMd5(StringUtil.deleteWhitespace(orgin.getContent())); String contentHash = CodeUtil.getMd5(StringUtil.deleteWhitespace(content)); if (!StringUtil.equals(originContentHash, contentHash)) { needCreateTestRunScript = true; } } }} else { script.setSubmitter(user.getLoginName());}Set< String> systemList = new HashSet< String>();if (StringUtil.isNotBlank(systems)) { String[] systemArray = systems.split(","); for (int i = 0; i < systemArray.length; i++) { systemList.add(systemArray[i]); }}if (needCreateTestRunScript) { if (needSubtypeMode) { content = replaceContent(content, subType); String testScriptSubType = ""; List< String> subTypeList = Arrays.asList(StringUtil.split(subType, ",")); for (int i = 0; i < subTypeList.size(); i++) { testScriptSubType += this.UPDATE_SCRIPT + subTypeList.get(i); if (i != subTypeList.size() - 1) { testScriptSubType += ","; } } subType = testScriptSubType; } scriptName = this.UPDATE_SCRIPT + scriptName; NewScript oldUpdateScript = newScriptManager.findByName(scriptName); if (null != oldUpdateScript) script.setId(oldUpdateScript.getId()); else { script.setId(null); } baseOperationResult.setNeedAudit(true);}if (StringUtil.isBlank(fileSuffix)) { //如果全空的话 默认全扫 script.setSuffix(".*");} else { script.setSuffix(fileSuffix);}script.setName(scriptName);if (StringUtil.equals(allPath, "Y")) { script.setAllPath("Y");} else { script.setAllPath("");}script.setEnvTag(tenantScope);script.setNeedAutoScan(needAutoScan);if (StringUtil.isNotBlank(scopes)) { for (String each : StringUtil.split(scopes, ",")) { each = StringUtil.replace(each, " ", ""); script.addScope(each); }}if (StringUtil.isNotBlank(content)) { BaseOperationResult preLoadResult = syntaxCheck(script); if (!preLoadResult.isSuccess()) { baseOperationResult.setMessage(preLoadResult.getMessage()); return baseOperationResult; }}if (StringUtil.contains(content, "new Bug")) { baseOperationResult.setSuccess(false); return baseOperationResult;}try { Result< NewScript> result = newScriptManager.saveCustomScript(script); if (result.isSuccess()) { if (EnvUtil.isProdEnv() && EnvUtil.isLinux()) { if (!needCreateTestRunScript) { //省略局部内容XX } else { //省略局部内容XX } } Boolean hasOldScript = processOldEngineRule(scriptName); if (containsTryCatch) { if (hasOldScript) { //省略局部内容XX } else { //省略局部内容XX } } else { if (hasOldScript) { baseOperationResult.setMessage("XXX"); } else { baseOperationResult.setMessage("保留胜利!"); } } baseOperationResult.setId(script.getId()); processTenantRelation(script.getId(), tenantIdList, user.getLoginName()); if (!needCreateTestRunScript && needSubtypeMode && (StringUtil.equals(Enums.XX.COMMON, script.getScriptType()) || (StringUtil.equals(Enums.XX.SCRIPT, script.getScriptType())))) { JSONArray subtypetipsArray = JSON.parseArray(subtypeandtip); for (Object object : subtypetipsArray) { //省略局部内容XX } } } else { baseOperationResult.setSuccess(false); return baseOperationResult; }} catch (Exception e) { baseOperationResult.setMessage("XX");}return baseOperationResult;
}原代码大略400行以上,复杂度69,憋了一口长气才读完。如果让你来接手这段代码,是不是感觉很头疼?须要梳理外面各种分支逻辑,弄清楚骨干脉络。那么什么样的代码才容易读,容易上手呢?个别业界认为代码可读性,可测试,保护老本和圈复杂度有很大关系,具体如下:
我该怎么做1.【知己知彼,理解本人代码复杂度】这个比较简单,有以下几种形式:a.本人数下断定节点(if while for catch case and or等)大略就晓得圈复杂度是多大了,参考下面怎么计算圈复杂度章节。b.在蚂蚁外部应用的广目平台,也能够查看到新提交commit记录里,哪些办法圈复杂度比拟高。
c.在代码提交之前,本人用idea小插件(Metrics Reloaded插件),一次性扫描本人负责的零碎所有办法的复杂度。
红色局部标识圈复杂度,数字越大复杂度越高。2.【隔靴搔痒,升高复杂度】网上有很多办法,我总结了下,大略有以下几种办法一:抽取出独立逻辑的子办法,把简单逻辑拆分成几个独立模块,再去读代码,就会感觉清晰很多。以下面举例的复杂度69的办法为例,咱们做了如下的办法拆分,是不是感觉清晰了很多?public XXresult doSave( NewScriptDTO newScriptDTO) {
//0.结构后果 XXresult result=new XXresult() ; try{ //1.脚本名查看 scriptNameCheck(newScriptDTO); //2.脚本加载 loadScript(newScriptDTO); //3.脚本保留 saveScript(newScriptDTO); }catch(XXException e){ result.setSuccess(false) result.setMessage("XXX"); return result; }catch(Exception e){ result.setSuccess(false) result.setMessage("XXX"); return result; } //操作实现 result.setSuccess(true) result.setMessage("XXX"); return result;
}
/*查看脚本名/
private void scriptNameCheck(NewScriptDTO newScriptDTO){
xxx
}
/*加载脚本/
private void loadScript(NewScriptDTO newScriptDTO){
xxx
}
/*保留脚本/
private void saveScript(NewScriptDTO newScriptDTO){
xxx
}办法二:优化逻辑判断,通过提取频繁呈现的条件, 或者调整判断程序等形式达到简化代码目标。///////// 案例1,抽取频繁呈现的条件a/////////
//批改前
if (条件1)
{
if (条件a){ // 执行a逻辑}
}
else if(条件2)
{
if (条件a){ // 执行b逻辑}
}
if (条件a)
{
// 执行c逻辑
}
//批改后
if (条件a)
{
if (条件1) { // 执行a逻辑}else if(条件2){ // 执行b逻辑} // 执行c逻辑
}
///////// 案例2,优化逻辑判断程序/////////
//批改前
if((条件1 && 条件2)|| !条件1){
return true;
}else{
return false;
}
//批改后
if(条件1 && !条件2){
return false;
}
return true;办法三:适当应用java新个性,升高大量的if判断。上面是来自团队一淏同学的提供的优化案例//批改前
List list = XXX;
if (CollectionUtils.isEmpty(list)) {
for (XX item : list) {
if (item==null){ return; }else{ // 逻辑a }
}
//批改后
List list = XX;
list = Optional.ofNullable(list).orElse(new ArrayList<>());
list.stream().filter(Objects::nonNull).forEach(item->{
//逻辑a
});
}当然,只有用心钻研,升高复杂度还有很多办法,这里不一一列举了。总结下思路:一个办法/类不要写大段大段的代码,把内容封装在逻辑独立的子类和子办法里。采纳有意义的类名,办法名,让使用者见名思意,易于上手。逻辑表白上,优化判断逻辑成最简模式。适当应用编程技巧,合并判断形式。 结语作为蚂蚁工程师的咱们,开发代码也应该像创作一个艺术品,三思而行,粗制滥造,通过产品的一直降级迭代,依然可能放弃倔强的生命力,就像代码四层境界[注3]外面说的第四层,通过了工夫历练“我的代码还在用”。援用:[注1]对代码的领悟之-高质量代码有三要素:可读性、可维护性、可扩展性 :https://wenku.baidu.com/view/...[注2]详解圈复杂度:https://baike.baidu.com/item/...[注3]代码的四层境界:https://www.sohu.com/a/238434..., 第一层“我的代码写完了”,第二层“我的代码写好了”,第三层“我的代码能跑了”,第四层“我的代码还在用” 阿里云产品评测—阿里云容器镜像服务 ACR 收费试用体验面向容器镜像、Helm Chart 等合乎 OCI 规范的云原生制品平安托管及高效散发平台,公布你的评测更有机会取得千元机械键盘,限量定制礼品。点击这里,查看详情。原文链接:https://click.aliyun.com/m/10...本文为阿里云原创内容,未经容许不得转载。