研究思考|关于软件复杂度的困局
阿里妹导读
本文重点围绕软件复杂度进行剖析,希望能够帮助读者对软件复杂度成因和度量方式有所了解。
前言
大型系统的本质问题是复杂性问题。互联网软件,是典型的大型系统,如下图所示,数百个甚至更多的微服务相互调用/依赖,组成一个组件数量大、行为复杂、时刻在变动(发布、配置变更)当中的动态的、复杂的系统。而且,软件工程师们常常自嘲,“when things work, nobody knows why”。
导致软件复杂度的原因
修改扩散
@Override
public boolean isAllowed(Long accountId, Long personId, String featureName) {
boolean isPrivilegeCheckedPass = privilegeCheckService.isAllowed(
accountId, personId, featureName);
return isPrivilegeCheckedPass;
}
认知负担
int calculate(int v1, int v2);
不可知(Unknown Unknowns)
软件复杂度度量
Halstead 复杂度
为不同运算子(操作符)的个数。 为不同运算元(操作数)的个数。 为所有运算子合计出现的次数。 为所有运算元合计出现的次数。
上述的运算子包括传统的运算子及保留字,运算元包括变数及常数。
try {
DiversionRequest diversionRequest = new DiversionRequest();
diversionRequest.setDiversionKey(diversionKey);
if (MapUtils.isNotEmpty(params)) {
DiversionCondition condition = new DiversionCondition();
condition.setCustomConditions(params);
diversionRequest.setCondition(condition);
}
ABResult result = xsABTestClient.ab(testKey, diversionRequest);
if (result == null || !result.getSuccess()) {
return null;
}
return result.getDiversionResult();
} catch (Exception ex) {
log.error("abTest error, testKey:{}, diversionKey:{}", testKey, diversionKey, ex);
throw ex;
}
我们梳理这段代码中的预算子和运算元以及分别统计出其个数:
根据统计上面统计得到的对应的数据我们进行计算:
Halstead方法优点
圈复杂度
计算方法:
John Ousterhout的复杂度定义
如何避免复杂度问题
战略先于战术
成为一名优秀的软件设计师的第一步是认识到仅仅为了完成工作编写代码是不够的。为了更快地完成当前的任务而引入不必要的复杂性是不可接受的。最重要的是这个系统的长期结构。 --John Ousterhout(约翰欧斯特霍特),《A Philosophy of Software Design》
public void receiveMessage(Message message, MessageStatus status) {
// .....
if(StringUtils.equals(authType, OnetouchChangeTypeParam.IC_INFO_CHANGE.getType())
|| StringUtils.equals(authType, OnetouchChangeTypeParam.SUB_COMPANY_CHANGE.getType())){
if(StringUtils.equals("success", authStatus)){
oneTouchDomainContext.getOneTouchDomain().getOnetouchEnableChangeDomainService().notifySuccess(userId.toString(), authRequestId);
}
} else if(StringUtils.equals(authType,AUTH_TYPE_INTL_CGS_ONSITE)){
// XXXXXX
} else if(StringUtils.equals(authType,AUTH_TYPE_INTL_CGS_ONSITE_CHANGE)) {
// XXXXXX
} else if (AUTH_TYPE_VIDEO_SHOOTING.equals(authType)) {
if (AUTH_STATUS_SUCCESS.equals(authStatus)) {
// XXXXXX
} else if (AUTH_STATUS_PASS.equals(authStatus)) {
// XXXXXX
} else if (AUTH_STATUS_SUBMIT.equals(authStatus)) {
// XXXXXX
}
}
// .....
}
短期来看战略编程的成本会高于战术编程,但是从上面的案例长期来看,这样的成本是值得的,他能够有效的降低系统的复杂度,从而长期来看最终能降低后续投入的成本。开发同学在需求迭代的过程中应该先通过战略编程的思维进行设计和思考,然后再进行战术实现,所以我的观点是战略设计要优先于战术实现。
高内聚低耦合设计
简化接口设计
public boolean createProcess(StartProcessDto startProcessDto) {
// XXXXXXX
}
public HashMap createProcess(HashMap dataMap) {
// XXXXXXX
}
隐藏实现细节
public boolean createProcess(StartProcessDto startProcessDto) {
Validate.notNull(startProcessDto);
try {
HashMap<String, Object> dataMap = new HashMap<>(8);
dataMap.put(MEMBER_ID, startProcessDto.getMemberId());
dataMap.put(CUSTOMER_NAME, startProcessDto.getCustomerName());
dataMap.put(GLOBAL_ID, startProcessDto.getGlobalId());
dataMap.put(REQUEST_ID, startProcessDto.getAvRequestId());
String authType = startProcessDto.getAuthType();
String taskCode = getTaskCode(authType);
HashMap resultMap = esbCommonTaskService.createProcess(AV_ORIGIN_AV, taskCode, dataMap);
return (MapUtils.isNotEmpty(resultMap) && TRUE.equals(resultMap.get(IS_SUCCESSED)));
} catch (Exception e) {
LOGGER.error("createProcess error. startProcessDto:{}",
JSON.toJSONString(startProcessDto), e);
throw e;
}
}
public HashMap createProcess(HashMap dataMap) {
Validate.notNull(dataMap);
try {
HashMap process = esbCommonTaskService.createProcess(ORIGIN_AV, TASK_CODE, dataMap);
return process;
} catch (Exception e) {
LOGGER.error("createProcess error. dataMap:{}", JSON.toJSONString(dataMap), e);
throw e;
}
}
通用接口设计
public List<RightE> getRights(RightQueryParam rightQueryParam) {
// 参数校验
checkParam(rightQueryParam);
Locale locale = LocaleUtil.getLocale(rightQueryParam.getLocale());
// 查询商家权益
RightHandler rightHandler = rightHandlerConfig.getRightHandler(rightQueryParam.getMemberType());
if (rightHandler == null) {
log.error("getRightHandler error, not found handler, rightQueryParam:{}", rightQueryParam);
throw new BizException(ErrorCode.NOT_EXIST);
}
List<RightE> rightEList = rightHandler.getRights(rightQueryParam.getAliId(), locale);
return rightEList;
}
注释与文档
重构
重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。使用一系列重构手法,在不改变软件可观察行为的前提下,调整结构。傻瓜都能写出计算机可以理解的代码。唯有能写出人类容易理解的代码的,才是优秀的程序员。 -- Martin Fowler 《重构 改善既有代码的设计》
public ReportDetailDto getDetail(ReportQueryParam queryParam) {
if (null == queryParam) {
log.error("queryParam is null");
throw new BizException(PARAM_ERROR);
}
Long aliId = queryParam.getAliId();
if (null == aliId) {
if (StringUtils.isBlank(queryParam.getToken())) {
log.error("aliId and token are both null. queryParam: {}",
JSON.toJSONString(queryParam));
throw new BizException(PARAM_ERROR);
}
aliId = recommendAssistantServiceAdaptor.getAliIdByToken(queryParam.getToken());
if (null == aliId) {
log.error("cannot get aliId by token. queryParam: {}", JSON.toJSONString(queryParam));
throw new BizException("ALIID_NULL", "aliId is null");
}
}
// 获取同步数据
// 数据结构转换
return convertModel(itemEList);
}
总结
参考:
《A Philosophy of Software Design》:https://www.amazon.com/-/zh/dp/173210221X/ref=sr_1_1?qid=1636246895
《Clean Architecture》:https://detail.tmall.com/item.htm?spm=ata.21736010.0.0.2e637536hX3Gji&id=654392764249
微信扫码关注该文公众号作者
戳这里提交新闻线索和高质量文章给我们。
来源: qq
点击查看作者最近其他文章