作为程序猿,工程质量是我们逃不开的一个话题,工程质量高带来的好处多多,我在写这篇文章的时候问了一下CHATGPT,就当娱乐一下,以下是ChatGPT的回答:
1、提高产品或服务的可靠性和稳定性。高质量的系统工程能够确保产品或服务的稳定性和可靠性,使其更加符合客户的需求和期望。
2、减少成本和节约时间。高质量的系统工程可以减少错误和缺陷的出现,从而减少修复错误和缺陷所需的时间和成本。
3、提高客户满意度。高质量的系统工程可以提高客户满意度,并增加客户的忠诚度和口碑。
除了这些,对于我们程序猿而言,如果工程质量做得好,那么线上出现问题的机率会低很多,随之而来就是运维的工作也会不断减少,这样的话我们可以将更多的精力投入到其它更重要的项目中去,所以我觉得工程质量这一块非常重要,它决定一个团队是否有更多产出的关键,所以这一篇文章主要讲讲工程质量需要关注的一些点,特别是研发过程的管理,最为重要。
我们的系统发展主要分为三个阶段,如下图:
为了保证系统能够稳定地对外提供服务,我们需要关注系统发展的三个阶段,也就是事前、事中、事后,对于每个阶段必须有对应的措施及系统工具来支持,来帮助我们提升系统的稳定性,快速定位问题并将影响尽量控制在一定范围内。
影响工程质量的因素,主要是人为因素、软件因素和硬件因素,所谓人为因素主要是一些不合理的变更和外部攻击等,比如去年我们安全做了一个变更,将测试环境变更误变更到生产,导致了重大故障。而软件因素大部分责任应该归在我们产研上,像我们技术方案没有考虑周全,存在漏洞,还有就是代码写得不够好,线上系统经常出问题,像FULLGC、空指针异常等等。硬件因素则不可预测,只能出了问题有对应的措施来快速恢复,像双活、灾备,某台机器硬件出现问题能生成新的容器将故障转移。
那我们要提升工程质量,让我们的系统SLA达到三个9甚至4个9,需要具备哪些条件呢,我想也是三个,分别是组织文化、研发流程管控、技术支撑三个方面,我认为组织文化和研发过程管理最为重要,而技术支撑在大厂这一块基本比较完善了,都会有CI/CD、iaas/paas。
组织文化上,我们要打造一支重视技术和质量的团队,一定要在团队营造氛围。其实在软件系统的开发和运维的过程中,我们有很多手段可以发现问题,如监控报警、会议回顾、业务反馈等等,但要从根本上解决问题其实还是比较困难,在大多数情况下是头痛医头,到最后结果就是技术债务比较重,线上故障频发。究其原因可能比较复杂,但从我的视角来看,通常是团队对待技术并没有那么严谨,对待生产环境也没有那么敬畏,对待自己的代码也并不严苛。所以要提升系统的可用性,必须要有一支重视技术的团队,自上而下崇尚技术,尊重技术的氛围,不会为了短期的业务目标而在技术上做够多的妥协,即使有也要让我们技术债务可见,事后抽时间完善这一块,其实我很不赞成经常玩短期方案,虽然能享受快速上线带来的快感,但是这种快感的背后存在不少的弊端,就是后期运维的工作极大,故障频出,所以团队必须要重视工程质量,只有大家都重视起来,我们的工程质量才能真正做上去。除了这个外,跟我们研发相关的就是要重视研发过程管理。
研发过程管理是控制质量非常重要的一环,需要考虑到从需求的产生到最终功能上线,以及后续的运维的工作甚至是故障的处理,各个环节都需要做好把关,才能构建我们的质量控制体系。
技术支撑这一块我就不详细说了,一般大厂在这一块相对较为完善基础设施,比如CI/CD、iaas/paas等,一般小厂相对较差一些,但我们要做好工程质量,持续集成、持续交付,将研发过程数字化还是挺重要的。
如果我们的过程管理不好或管理较为粗糙,往往会带来不少的问题,像FULLGC\空指针\资源使用超标告警,还有一些是我们做设计方案的时候没有考虑周全,导致业务流程阻断或数据出错,如果这种问题一多,产品和业务会有不少抱怨和投诉,我们开发就会天天加班,每天看上去很忙碌但实际开发效率低,我之前刚接手线下店就面临这种情况,像调拨、商品池一天不FULLGC就不正常,系统流单经常卡单,天天有业务投诉,前半年基本60%的时间都花在运维上面,而且天天晚上加班,白天在公司搞,晚上在家里搞,那半年团队的人都非常辛苦,我自己也瘦了10多斤,非常痛苦,所以在这几年我一直在强调团队主要骨干一定要做好研发过程管理。
那么研发过程到底包含哪些呢,无非就是需求、开发、测试、上线部署和运维5个阶段,而我们研发重点关注开发、上线和运维三个阶段。
如何做好技术方案,在说这个之前我们技术为什么要做技术方案,我觉得写技术方案根本目的是提高团队的沟通效率、提高研发和质量效率。
那么沟通效率具体体现在:对于产品经理可以审查技术方案是否与产品设计有偏差,是否满足当前产品需求和后续产品设计规划,反过来我们研发通过技术方案来完善产品方案,这个是一个相补的过程。对于研发可以把控技术方案是否满足技术要求,包括技术规范,性能要求,实现复杂度,可拓展性等。对于测试同学可以掌握需求技术实现原理,改动点,影响点,再做针对性测试用例设计。另外还有一点就是后续接手项目的同学可以通过技术方案文档熟悉系统。
通过写技术方案,把需求和实现提前梳理一遍,减少等到编码阶段返工的情况,并且写好技术方案再编码,使得编码时思维更加清晰,提高编码效率和质量。
技术方案确实能带来不少好处,那么我们如何才能写好技术方案呢?其实好的技术方案其实就相当于一个工程施工图纸,研发人员拿到图纸就能按部就班实施,只是我们平时项目都是小步快跑,采用敏捷的方式,所以很多时候做了一些简化,目前我们大部分团队写概要设计更多一些,以下是概要设计包含部分,仅供参考。
想成为优秀的开发人员,除了有过硬的技术功底外,必须要有跨界的意识,包括产品意识、测试意识和运维意识。
我接下来讲讲编写高质量代码的一些标准和原则,之前我们也经常讲作为程序员,衡量职业素养高低,代码就是其中之一,因为我们日常工作中接触最多就是写代码,代码的好坏就代表了个人的名片,那么如何评价代码的好坏,其实业界有了明确的原则,主要是评判原则和指导原则。
我们先来看一下评判原则,主要涉及可读性、可维护性、可扩展性、可复用性、灵活性等七个点,我重点讲我最关注的三点:
public StoreListPageDTO getStoreList(String companyCode, int currentPage, int pageSize, SearchInputDTO searchInputDTO) throws OspException {
Example example = new Example(StoreInfo.class);
Example.Criteria criteria = example.createCriteria();
if (StringUtils.isNotBlank(companyCode)) {
criteria.andEqualTo("companyCode", companyCode);
}
StoreListPageDTO storeListPageDTO = new StoreListPageDTO();
storeListPageDTO.setPageSize(pageSize);
storeListPageDTO.setCurrentPage(currentPage);
if (null != searchInputDTO) {
if (StringUtils.isNotBlank(searchInputDTO.getStoreCode())) {
criteria.andEqualTo("storeCode", searchInputDTO.getStoreCode());
}
if (StringUtils.isNotBlank(searchInputDTO.getWarehouseCode())) {
criteria.andEqualTo("warehouseCode", searchInputDTO.getWarehouseCode());
}
if (StringUtils.isNotBlank(searchInputDTO.getStoreName())) {
criteria.andLike("storeName", "%" + searchInputDTO.getStoreName() + "%");
}
if (StringUtils.isNotBlank(searchInputDTO.getStoreLevelCode())) {
criteria.andEqualTo("storeLevelCode", searchInputDTO.getStoreLevelCode());
}
if (null != searchInputDTO.getStoreClass()) {
criteria.andEqualTo("storeClass", searchInputDTO.getStoreClass());
}
if (null != searchInputDTO.getStoreTypeCode()) {
criteria.andEqualTo("storeTypeCode", searchInputDTO.getStoreTypeCode());
}
if (StringUtils.isNotBlank(searchInputDTO.getZoneCode())) {
criteria.andEqualTo("zoneCode", searchInputDTO.getZoneCode());
}
if (StringUtils.isNotBlank(searchInputDTO.getProvince())) {
criteria.andEqualTo("provinceCode", searchInputDTO.getProvince());
}
if (StringUtils.isNotBlank(searchInputDTO.getCity())) {
criteria.andEqualTo("cityCode", searchInputDTO.getCity());
}
if (StringUtils.isNotBlank(searchInputDTO.getContact())) {
criteria.andLike("contact", "%" + searchInputDTO.getContact() + "%");
}
if (StringUtils.isNotBlank(searchInputDTO.getMobie())) {
criteria.andEqualTo("mobie", searchInputDTO.getMobie());
}
if (null != searchInputDTO.getStartOpenDate()) {
criteria.andGreaterThanOrEqualTo("openDate", searchInputDTO.getStartOpenDate());
}
if (null != searchInputDTO.getEndOpenDate()) {
criteria.andLessThanOrEqualTo("openDate", searchInputDTO.getEndOpenDate());
}
//城市等级如果传参了,需要先根据城市等级查询对应的城市表,再把城市codeList作为传参
if (null != searchInputDTO.getCityLevel()) {
List<VipXstoreCityLevelCity> cityLevelCityList = cityLevelRepository.selectByCityLevelIdSet(Sets.newHashSet(searchInputDTO.getCityLevel()));
//如果城市等级没有关联到任何城市,则直接返回空列表
if (CollectionUtils.isEmpty(cityLevelCityList)) {
storeListPageDTO.setTotalCount(0);
storeListPageDTO.setStoreDTOList(new ArrayList<>());
return storeListPageDTO;
}
if (CollectionUtils.isNotEmpty(cityLevelCityList)) {
Set<String> cityCodeSet = new HashSet<>();
cityLevelCityList.stream().forEach(x -> cityCodeSet.add(x.getCityCode()));
//如果城市等级没有关联到任何城市,则直接返回空列表
if (CollectionUtils.isEmpty(cityCodeSet)) {
storeListPageDTO.setTotalCount(0);
storeListPageDTO.setStoreDTOList(new ArrayList<>());
return storeListPageDTO;
}
criteria.andIn("cityCode", cityCodeSet);
}
}
if (StringUtils.isNotBlank(searchInputDTO.getSmallZoneName())) {
criteria.andLike("smallZoneName", "%" + searchInputDTO.getSmallZoneName() + "%");
}
if (CollectionUtils.isNotEmpty(searchInputDTO.getSmallZoneCodeList())) {
Set<String> smallZoneCodeSet = searchInputDTO.getSmallZoneCodeList().stream().filter(x -> StringUtils.isNotBlank(x)).collect(Collectors.toSet());
if (CollectionUtils.isNotEmpty(smallZoneCodeSet)) {
criteria.andIn("smallZoneCode", smallZoneCodeSet);
}
}
if (searchInputDTO.getStoreStatus() != null) {
criteria.andEqualTo("storeStatus", searchInputDTO.getStoreStatus());
}
}
criteria.andNotEqualTo("storeStatus", StoreStatusEnum.DELETED.getValue());
example.setOrderByClause("update_time DESC");
BaseLogic.PageInfo<VipXstoreStore> pageInfo = storeRepository.selectPageInfoWithCnt(example, currentPage, pageSize);
int total = (int) pageInfo.getTotal();
storeListPageDTO.setTotalCount(total);
List<VipXstoreStore> storeList = pageInfo.getList();
List<StoreDTO> storeDTOList = new ArrayList<>(total);
Map<String, SimpleUserModel> userMapModels = new HashMap<>();
if (CollectionUtils.isNotEmpty(storeList)) {
List<String> userIds = storeList.stream().map(s -> s.getContactUserNumber()).collect(Collectors.toList());
userMapModels = userService.getSimpleUserByUserIds(userIds).stream().collect(Collectors.toMap(u -> u.getUserNumber(), u -> u));
}
for (VipXstoreStore store : storeList) {
if (Objects.equals(store.getStreetCode(), "")) {
store.setStreetCode(null);
store.setStreet(null);
}
StoreDTO storeDTO = convert(store);
storeDTO.setOperator(store.getUpdateBy());
//填充城市等级
VipXstoreCityLevel vipXstoreCityLevel = cityLevelRepository.selectByCityCode(storeDTO.getCompanyCode(), storeDTO.getCityCode());
storeDTO.setCityLevel(vipXstoreCityLevel == null ? null : vipXstoreCityLevel.getCityLevel());
storeDTO.setCityLevelId(vipXstoreCityLevel == null ? null : vipXstoreCityLevel.getId());
if((store.getCompanyCode().equalsIgnoreCase(CompanyCodeEnum.CITYOUTLETS.getCode())
||store.getCompanyCode().equalsIgnoreCase(CompanyCodeEnum.OUTLETS.getCode()))
&&(store.getStoreClass()==StoreClassEnum.ADD.getValue() ||store.getStoreClass()==StoreClassEnum.RENT.getValue())
){
storeDTO.setContact(store.getContact());
storeDTO.setMobie(store.getMobie());
}else{
SimpleUserModel user = userMapModels.get(store.getContactUserNumber());
if (user != null) {
storeDTO.setContact(user.getUserName());
storeDTO.setMobie(user.getUserPhone());
}
}
storeDTOList.add(storeDTO);
}
storeListPageDTO.setStoreDTOList(storeDTOList);
return storeListPageDTO;
}
分层后代码优化:
public StoreQueryResp getStoreInfo(StoreQueryRequest req) throws OspException {
StoreQuery storeQuery = StoreConverter.toStoreQuery(req);
matchZoneByZoneName(req, storeQuery);
matchSmallZoneByZoneName(req, storeQuery);
PageInfo<StoreInfo> storePage = storeRepo.listByExample(storeQuery);
return StoreConverter.toStoreQueryResp(storePage);
}
要想让我们的代码具备可读性、可复用性等,那么我们首先要对工程的代码进行分层,而且每层要有严格要求和规范,每层由多个模块组成,每层有自己独立的职责,多个层次协同提供完整的功能,带来的好处是:
随着软件复杂度越来越高,所以分层也越来越细,目前大部分都采用四层分层结构,具体如下:
我相信每个工程师都想写出高质量的代码,不想一直写没有成长、被人吐槽的烂代码。要写好代码,最佳的路径是学习、反复练习、多总结,养成良好的习惯非常重要,我先给大家讲一个小故事,前一段时间看了一本书叫《优势成长》,里面有一章节讲到了新东方,我们都知道新东方的老师非常牛,那么他们是如何从一个普通老师变成大家都喜欢的老师,其核心就是正确的姿势重复做,在新东方有一个试讲环节,应聘老师得对着台下那些老教师们去讲,这些老师也不好好听,干什么的都有,当讲着讲着,他们还会不断打断你,然后告诉你前面40分钟讲的都不好,重来一遍。于是1个小时的课,你得反复讲,反复调整,有时候一节课,要被老师们批评半年时间,打磨半年之后才真正的讲好一堂课。其实在这个过程非常的煎熬,中途很多应聘的老师被气哭、被骂走了,一开始100人最后剩下20人都不到了。在新东方他们内部有句口号,通过把自己逼疯从而把对手逼死。所以我们看到这些新东方的老师各个出口成章,能言会道,都是在极端苛刻的条件下训练而成。经过千百次的刻意练习后形成肌肉记忆,从知道,再到熟练,进而精通。所以我们很多人都说要写好代码,知道高内聚低耦合,并不是我们讲讲口号就行,这中间还差了千百次的刻意练习才能转化成你的技能,所以学习、反复实践并总结才能成为一个代码技术达人。
抽象思维是我们工程师最重要的思维能力,因为软件技术本质上就是一门抽象的艺术。我们工程师每天都要动用抽象思维,对问题域进行分析、归纳、综合、判断、推理,从而抽象出各种概念,挖掘概念和概念之间的关系,然后通过编程语言实现业务功能,所以,我们大部分的时间并不是在写代码,而是在梳理需求,理清概念,对需求有一个全局的认知。而抽像能力让我及团队切身感受到,它给我们在编码和设计上带来的质的变化。
其实导出Excel功能在我们工程里随处可见,特别是咱们的运营希望一次性导出越多数据越好,为了不给我们系统带来太大压力,对于大数据量的导出一般异步进行,针对于这样一个简单的功能,那么应该如何抽像呢?
public String exportXXX(参数) throws Exception {
//业务实现
}
public String exportXXX2(参数) throws Exception {
//业务实现
}
我们其实可以把每个异步导出看作是一个异步任务,而每个任务可导出的内容是不一样的,因此完全可以把导出抽像一个方法,由每个具体实现类去实现导出不同的内容,具体如下:
// export excel
public interface IExcelExportTask {
String export(BizCommonExportTask exportTask) throws Exception;
}
//样例实现类
XXXXExportTask implements IExcelExportTask {
String export(BizCommonExportTask exportTask) throws Exception{
public String export(BizCommonExportTask exportTask) throws Exception {
//组织数据筛选条件
TestReq queryReq = GsonUtils.toObject(exportTask.getInputParams(),TestReq.class);
String fileName = String.format("%s%s%s", exportTask.getUploadFileName(),System.currentTimeMillis(),".xlsx");
String downUrl = excelService.uploadExcel(fileName, null, new Fetcher<PreOccupyModel>(PreOccupyModel.class) {
//循环获取数据
@Override
public List<TestModel> fetch(int pageNo, int pageSize) throws OspException{
TestQueryResp resp = testFethchLogic.fetchRecord(queryReq);
return pageNo > resp.getPageNum() ? Collections.emptyList() :toExcelModel(resp);
}
});
return downUrl;
}
}
public class XXXXExportTask1 implements IExcelExportTask {
@Override
public String export(BizCommonExportTask exportTask) throws OspException {
TestQuery query = GsonUtils.toObject(exportTask.getInputParams(), TestQuery .class);
String fileName = String.format("%s%s%s", exportTask.getUploadFileName(), System.currentTimeMillis(), ".xlsx");
return excelService.uploadExcel(fileName, null, new Fetcher<ExportItemModel>(TestModel.class) {
@Override
public List<TestModel> fetch(int pageNo, int pageSize) throws OspException {
return XXXXLogic.queryExportItem(query, pageNo, pageSize);
}
});
}
}
//导出任务分发器
public class ExcelTaskDispacther extends ApplicationObjectSupport {
public boolean dispacthTask(Long taskId) throws OspException {
updateTaskStatus(exportTask,CommonExportStatus.CREATING,TransferExportStatus.CREATING,StringUtils.EMPTY);
try {
String beanName = getBeanName();
ExportTaskHandler exportTaskHandler = getApplicationContext().getBean(beanName , IExcelExportTask .class);
if(exportTaskHandler == null) {
log.warn(String.format("任务ID[%s]写入配置错误!", taskId));
return false;
}
updateTaskStatus(exportTask,CommonExportStatus.CREATE_SUCCESS,TransferExportStatus.CREATE_SUCCESS,StringUtils.EMPTY);
log.info(String.format("任务ID[%s]RFID为[%s]处理成功", exportTask.getId(),rfid));
return true;
} catch(BusiException ex) {
log.info("任务ID[{}]失败,原因:{}", exportTask.getId(),ex.getMessage(),ex);
updateTaskResult();
} catch(Exception ex) {
log.info("任务ID[{}]失败,原因:{}", exportTask.getId(),ex.getMessage(),ex);
updateTaskResult();
}
return false;
}
}
在微服务化流行的今天,为了提升系统吞吐量,系统职责越来越细,各系统模块需要频繁交互数据,那么对于复杂的数据交互场景,比如我们调拨单,调拨单在扭转的过程中需要与很多系统交互,跟门店、仓库、库存模块有非常多的交互,我们又该如何抽像呢,以下是调拨与各系统交互的代码示例
//接口定义
public interface BizNotificationHandler {
/**
* 抛异常会当失败处理
* 是否需要重试由BizNotificationStatus返回状态来决定
* @param bizNotification
* @return
* @throws OspException
*/
BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException;
}
//推送调拨差异数据给库存系统
public class SyncDiffToSimsAndBackQuotaHandler implements BizNotificationHandler {
@Override
public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException {
//业务逻辑实现
return BizNotificationStatus.PROCESS_SUCCESS;
}
}
//占用库存
public class TransferOccupyInventoryHandler implements BizNotificationHandler {
@Override
public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException {
//业务实现
}
}
//在GPDC生成新条码
public class GpdcGenerateNewBarcodeHandler implements BizNotificationHandler {
@Override
public BizNotificationStatus handleNotification(BizNotification bizNotification) throws OspException {
//业务代码实现
}
}
其实我们在与其它系统交互的时候,我们可以把每一个交互动作抽像成一个通知事件,每次交互的时候,写一个事件通知事件即可。
关于组合/聚合复用原则,其实我们在项目过程会经常遇到,比如项目里会经常管理各种单据,像采购单、调拨单、收货单等,而对于每种单据都会有各种各样的较验,我们先来看一段建调拨单代码,具体如何下:
//接口定义
public interface TransferValidator {
boolean validator(CreateTransferCtx ctx) throws OspException;
}
//接口实现1
public class W2sCrossPoQtyValidator implements TransferValidator {
@Override
public boolean validator(CreateTransferCtx ctx) throws OspException {
//较验器代码实现
}
//接口实现2
public class W2sStoreBarcodeSaleLimitValidator implements TransferValidator {
@Override
public boolean validator(CreateTransferCtx ctx) throws OspException {
//较验器代码实现
}
}
//较验器组装
public class TransferValidators {
public ValidatorChain newChain() {
return new ValidatorChain();
}
public class ValidatorChain {
private final List<TransferValidator> validators = new ArrayList<>();
public ValidatorChain qtyValidator() {
validators.add(qtyValidator);
return this;
}
public ValidatorChain transferRouteCfgValidator() {
validators.add(transferRouteCfgValidator);
return this;
}
public ValidatorChain prodValidator() {
validators.add(prodValidator);
return this;
}
public ValidatorChain w2sWarehouseStoreValidator() {
validators.add(w2sWarehouseStoreValidator);
return this;
}
public ValidatorChain w2sStoreBarcodeSaleLimitValidator() {
validators.add(w2sStoreBarcodeSaleLimitValidator);
return this;
}
public ValidatorChain w2sAssignPoValidator() {
validators.add(w2sAssignPoValidator);
return this;
}
public ValidatorChain w2sCrossPoValidator() {
validators.add(w2sCrossPoValidator);
return this;
}
public ValidatorChain w2sCrossPoQtyValidator() {
validators.add(w2sCrossPoQtyValidator);
return this;
}
public ValidatorChain w2sCross4XupValidator() {
validators.add(w2sCross4XupValidator);
return this;
}
public ValidatorChain repeatLineValidator() {
validators.add(repeatLineValidator);
return this;
}
public ValidatorChain sstradeBarcodeValidator() {
validators.add(sstradeBarcodeValidator);
return this;
}
public ValidatorChain s2wWarehouseStoreValidator() {
validators.add(s2wWarehouseStoreValidator);
return this;
}
public boolean validator(CreateTransferCtx ctx) throws OspException {
for (TransferValidator validator : validators) {
if (!validator.validator(ctx)) {
return false;
}
}
return true;
}
}
}
//业务代码使用
public interface TransferCreator {
boolean createOrder(CreateTransferCtx ctx) throws OspException;
}
public abstract class DefaultTransferCreator implements TransferCreator {
@Override
public boolean createOrder(CreateTransferCtx ctx) throws OspException {
validator(ctx)
//实现业务逻辑
}
protected abstract boolean validator(CreateTransferCtx ctx) throws OspException;
}
//店仓调拨单
public class S2wRefundCreator extends DefaultTransferCreator {
//较验器自由组装
@Override
protected boolean validator(CreateTransferCtx ctx) throws OspException {
return transferValidators.newChain()
.qtyValidator()
.transferRouteCfgValidator()
.prodValidator()
.validator(ctx);
}
}
通过上面的示例,其实抽像并不难,难的是我们要花时间去思考,去理解,只有自己花足够的多时间,反复训练我相信比较容易做到。
写出满足这些评价标准的高质量代码,我们需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。而所有这些编程方法论的最终目的都是为了编写出高质量的代码。比如,面向对象中的继承、多态能让我们写出可复用的代码;编码规范能让我们写出可读性好的代码;设计原则中的单一职责、DRY、基于接口而非实现、里式替换原则等,可以让我们写出可复用、灵活、可读性好、易扩展、易维护的代码;设计模式可以让我们写出易扩展的代码;持续重构可以时刻保持代码的可维护性等等。
研发过程我们不刚要写好技术方案、写好代码,但仅做好这两点还不够,我们需要善用于一些工具,将过程管理数字化,我们平常工作中很多人只管理写好代码,但代码是否符合预期,认为是测试的事情,这个观点是错误的,所以一定要善用工具sonar、测试覆盖率、流量回放等工具,将过程管理数字化,同时要与我们的合作伙伴产品、测试、研发形成良好的互动,如果有条件的话跟我们的业务做一些沟通也是非常有必要的。
要想让我们的系统能够平稳运行,我们需要在抓好人、工具、预案、目标这几个点,只有抓好这些点,我们才能真正构建高性能、高可用、可降级的业务系统。