前言
Aliware
大型系统的本质问题是复杂性问题。互联网软件,是典型的大型系统,如下图所示,数百个甚至更多的微服务相互调用/依赖,组成一个组件数量大、行为复杂、时刻在变动(发布、配置变更)当中的动态的、复杂的系统。而且,软件工程师们常常自嘲,“when things work, nobody knows why”。
导致软件复杂度的原因
Aliware
导致软件复杂度的原因是多种多样的。
修改扩散
@Overridepublic 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)
软件复杂度度量
Aliware
Manny Lehman 教授在软件演进法则中首次系统性提出了软件复杂度:
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 的复杂度定义

如何避免复杂度问题
Aliware
软件复杂度问题可以完全避免么?我觉得不可能,但是这并不能成为我们忽视软件复杂度的理由,有很多措施可以帮助我们尽量避免自身的需求开发或工作中引入问题代码而导致软件复杂。这里结合日常的开发理解谈一下自己的认知:
战略先于战术
成为一名优秀的软件设计师的第一步是认识到仅仅为了完成工作编写代码是不够的。为了更快地完成当前的任务而引入不必要的复杂性是不可接受的。最重要的是这个系统的长期结构。 --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}}// .....}
短期来看战略编程的成本会高于战术编程,但是从上面的案例长期来看,这样的成本是值得的,他能够有效的降低系统的复杂度,从而长期来看最终能降低后续投入的成本。开发同学在需求迭代的过程中应该先通过战略编程的思维进行设计和思考,然后再进行战术实现,所以我的观点是战略设计要优先于战术实现。

高内聚低耦合设计
简化接口设计
@Overridepublic boolean createProcess(StartProcessDto startProcessDto) {// XXXXXXX}@Overridepublic HashMap createProcess(HashMap dataMap) {// XXXXXXX}
隐藏实现细节
@Overridepublic 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;}}@Overridepublic 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);}
总结
Aliware
本文主要阐述了个人对软件复杂度的思考,分析了导致软件复杂度的原因、软件复杂度的度量方式以及阐述了自我理解的如何避免软件复杂度的问题。
参考:
《Clean Architecture》:
文章转载自阿里巴巴中间件,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。





