一、前言
二、收银台存在的问题
2.1 可用性方面
2.2 效率方面
三、可用性及效率解决方案
3.1 可用性建设
3.2 效率问题解决
四、收益及展望
4.1 收益
4.2 展望
层级问题
设计问题
组件设计思考比较少,比如组件的内聚性不高,导致组件之间的耦合严重,造成组件的可维护性差,同时,组件的可测试性考虑不足,导致测试回归效率低。
针对以上问题,将从收银台的可用性和效率两个维度进行方案的阐述。
近些年,前端开发方式变迁如下:
原生Javascript开发,直接通过Javascript原生api操作dom进行开发,已经开始注重分层(MVC),将数据请求和逻辑从HTML文件中进行分离,那时Node.js诞生没几年,工程化工具还没有现在这么丰富和强大,资源压缩丑化一般通过在线工具进行压缩。特点是开发需要对原生javascript api熟练掌握,工程化程度低。
工具框架开始流行,尤其是jQuery,风光一时,基于jQuery的UI组件也是十分丰富,指令式和链式操作dom确实使代码更加简洁和有效率,几行代码就可以替换原来的大篇原生Javascript代码。后来,为了提升View层的开发,不用Javascript在中拼装字符串,开始出现各种Javascript Template库。特点是jQuery对原生Javascript api 的封装和Template库的引入对前端的开发效率大大提升。
随着模块化开发和前端工程化和的发展,国内出现了第一批优秀的打包工具,例如fis等,再后来Webpack出现,因其更好的插件扩展机制及生态,用户量越来越多。通过这些工程化工具可以进行优化配置,使项目的优化更加简易,使开发者更加专注业务的开发。
组件化开发时代的到来(Angular,Vue和React等),通过声明式标签进行分层组合,完成View层的搭建,开发者只需要考虑Model的设计,及更新时机。View层的数据更新,框架底层会优化渲染。组件化开发的特点就是Ui的展现都可以清晰的,可预测性的描述出来,比较利于维护和测试。组件化开发可以像积木一样进行拆分和组装。
基础组件是指和业务无关的组件,例如基础的UI组件(DJText,DJImage,图片上传组件等)。
业务组件就是实现具体业务UI的组件,例如项目中CashierPayTypeItem组件(收银台支付方式组件),包含了具体业务属性。
组件分层如下图:
分层逻辑:
基础组件层代表着基础组件这一类组件。
业务组件层代表着业务组件这一类组件,业务组件层和基础组件层关系如下图:
示例代码如下(react):
<View style={styles.bankBox} >
<DJTag
style={styles.bankTag}
type={'emptyTag'}
/>
<DJImage style={styles.logoUrl} source={item?.logoUrl} />
<DJText style={[styles.mainTitle]}>
{item?.mainTitle && item?.mainDesc ? `${item?.mainTitle},${item?.mainDesc}` : item?.mainTitle}
</DJText>
{ifShowArrow && <DJIconArrow size={13} style={styles.iconArrow} />}
</View>
业务粘合层
业务粘合层就是组件的控制层,负责组件的调度,向业务组件或基础组件提供数据和行为逻辑处理方法(事件回调)。层级关系如下图:
示例代码如下(react):
<View style={[styles.container, isLast && styles.isLast]}>
<View style={styles.wrap}>
<DJImage source={payMode?.iconUrl} style={styles.iconUrl} />
<View style={styles.box}>
<ItemHeader payMode={payMode} checked={checked} source={source}></ItemHeader>
{/* 主活动文案 */}
<ItemText payMode={payMode} />
{/* 微信免密开通 */}
{payMode?.payMode == 10 && this.state.showNoPass && <ItemNoSecrect payMode={payMode} applyContract={applyContract}/>}
{/* 银行活动文案 */}
<ItemActivityText source={source} argsData={argsData} payMode={payMode} token={token} cashierData={cashierData} selectedPayMode={selectedPayMode} onPressEv={this.onPressEv} />
</View>
</View>
</View
页面逻辑层
一个页面只有一个页面逻辑层,负责对其他层的控制和调度,可以这么理解页面逻辑层是最顶层的业务粘合层。层级关系如下图:
业务粘合层负责和状态管理器(redux|mobx)通信,负责将数据和事件通过props传递给展示型组件(展示型组件是指没有任何绑定及依赖的组件,通常是无状态组件,本文中的基础组件和业务组件理想状态下都是展示型组件)。
展示型组件可以通过事件触发上层传递回调来改变状态管理数据,达到数据的更新。
展示型组件只负责数据展现和回调注册,不直接和状态管理器通信,可以保证组件的复用性,正交性和可测试性。
组件中的props不是自己使用,而是传递给子孙级使用,就需要考虑添加业务粘合层来进行分层处理了,避免每次子级新增数据需要逐层添加。
如果页面逻辑层的子级业务组件超过10个,就可以考虑添加业务粘合层,比如老的收银台入口组件,由于没有添加业务粘合层,代码行数1000+,组件的可读性和维护性都比较差。可以根据页面布局和功能的特点,将逻辑层拆分成若干粘合组件。分层后,组件结构清晰,代码可读性增强。
在尝试将组件分层的初期,需要一些学习成本,例如需要识别分层条件,同时还需要对Model进行设计,让数据和粘合层更好的搭配。
分层不易过深,建议嵌套不超过三层,超过三层后数据处理就会变的复杂。
组件化分层没有终点,分层不是一成不变的,例如业务更新迭代,交互复杂度变高,原来的分层已经不能满足现在业务,那就需要继续分层,减少组件熵增。
单一职责原则(SRP:Single responsibility principle)又称单一功能原则,它规定一个类应该只有一个发生变化的原因。对于组件设计的指导就是组件不要承担过多的职责,避免修改组件内单个功能的代码,影响了组件内的其他功能。因为组件足够原子,组件的可组合性也随之提高,具体的操作可以参考以下方式:
(1)单个组件文件行数建议不要超过200行。
(2)组件参数不要过多,尽量控制在4个以内。
迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好。对于组件设计就是减少组件之间的耦合,避免组件和外部环境的关联,使组件的输入和输出可以保持一致,组件的可测试性比较高。具体的实践操作如下:
(1)最好使用函数组件,追求纯组件。
(2)尽量不要暴露组件的引用,避免直接操作dom。
(3)组件内部不要操作修改外部变量,避免影响其他组件。
收银台逻辑的抽离主要是各个端的支付方式逻辑抽离,抽离示意如下:
从上图可以看出,对不同的支付方式从组件逻辑中进行了抽离,可维护性更好,同时不会因为重构使RN包的文件变大。支付回调的抽离方式和支付方式类似。
// 入口函数 index.js
/**
* @param {Object} payData 支付完成必须参数
* @param {Object} payMode 支付类型
* @param {Object} currentPayMode 找人代付参数
* @param {Object} successCallBack 支付完成回调
* @param {Object} failCallBack 支付失败回调
* description: 支付封装SDK
*
*/
import payType form './payType/index.js'
// 采用策略落实,易于扩展维护
export default toPay(dataObj) {
payType[dataObj.payMode](dataObj);
}
// payType 入口函数 index.js
import JdPay from './JdPay';
import wxPay from './WxPay';
import CloudPay from './CloudPay';
import DaiPay from './DaiPay';
const payTypeObj = {
'10': wxPay,
'20': jdPay,
...
}
export default payTypeObj;
// JdPay 的入口函数 index.js 和 index.web.js
// index.js RN的京东支付逻辑
export default function jdPay (dataObj) {
const PayData = {
type: 'thirdPay',
param: data,
returnType: 'callJS',
success: data => {
dataObj.successCallBack(data)
};
JDPaySDK.jdPay(PayData);
}
// index.web.js H5的京东支付
重构后,收银台的可用性持续增强,良好的组件设计,让组件的可测试性提高,新增需求的bug率明显降低。重构后的组件为后面逐步接入单元测试打下了良好的基础。
重构后的收银台对于新支付方式的接入更加容易,更加有效率,重构完成后收银台的支付方式的接入工作量从原来的最少3天/人,减少到现在的1 天/人,能够更快的产生业务价值。
收银台组件层和逻辑层的代码抽离复用,符合预期效果,包文件大小比重构前包减少20KB,约占重构前的10%。页面文件大小的减少对RN包的升级或者H5资源的加载都是有益的。
组件分层和组件设计规范在团队的沉淀。通过组件分层和组件设计标准在收银台重构的实践,让大家切身体会到组件分层对项目维护性和扩展性的提高。组件的设计规范让大家意识到要想抽离一个高内聚,低耦合组件需要注意哪些原则。
善用一些设计模式,可以让你的代码的维护性和扩展性增强。通过对设计模式在特定场景下的练习,可以提高你对设计模式的使用和理解。
为了进一步提高收银台的可用性,后续还会通过以下几方面进行完善:
项目层面引入TS。由于TS的强类型特性,在编程阶段,TS就可以发现项目中的很多空指针问题,让问题提前暴露,增加项目线上稳定性。
组件单元测试全覆盖。收银台测试场景用例的持续补充和完善,减少测试场景遗漏,提高上线前的回归效率。
报警机制添加。在项目APM方案的基础上,我们将继续添加报警策略,让功能的不可用可以提前感知,使研发快速介入,减少支付不可用带来的损失。