看了很多代码,也写了很多代码,好代码还是太少了。
回想起自己如果从开始学习编程(大一)到参加工作,到现在,和代码打交道的时间也接近10年了。
回顾看过的代码和写过的代码,让自己满意、有成就感的不算多。
一方面也是和职业前期做的事情有关,没定下来自己想从事的领域(都是后端开发分很多种),方向一直在换,大部分都是快速实现业务需求,效率优先,写过的代码没有经过仔细的领域划分、架构设计,按时交付功能是最大目标,从流程上来说,很多项目都没有代码CR、评审、单测。
我认为技术人最好的状态不是一直去追求新的技术、新的业务,而是选择一个喜欢且有挑战的方向或产品,用代码实现它,20%的时间实现从0到1,80%时间不断打磨优化你的产品或系统,因为大部分人都在做那20%的事情,愿意不浮躁坚持做那80%的才会走的更远,做好一件事比做10件平庸的事更有意义。
职业生涯里面,给予我帮助的人很多,其中一位我内心很感谢的是我刚进蚂蚁时的TL,在阿里还有个称谓:师兄。在阿里入职都会有一位师兄,一般师兄都是负责带师弟熟悉业务、研发流程、技术系统。师兄对我帮助最大的是在代码规范,在我没有遇到他之前,我的代码是没有CR 流程的,我相信国内很多公司都没有这个流程,甚至写代码前没有系统设计文档,即使是蚂蚁内部,代码CR 和系统设计文档的重视程度和水平也会随着不同bu、不同团队相差很大(可能和负责的系统有关系)。
师兄code review我的代码非常细致,小到 if 、 ()
、{}
之间应该有空格、变量命名需要准确表达,方法名清晰体现方法内做的事,需要保持一致,日志打印不多不少,格式规范,诸如此类,还有异常case和边界路径,在一个用户并发量很大,业务复杂度高的系统,非常边缘的分支链路都需要cover到,不然小流量也能引发大事故。至此之后我每次提交代码前会自己仔细检查,代码结构和逻辑是否通畅,类、变量、方法命名是否有更合适、表达力更强的?所有边缘case 是否有考虑到?到后面师兄提的cr意见也越来越少了,但是还是能提出意见来,我每次都会惊叹,对哦,这个我怎么都没想到。
关于师兄的故事以后有机会再跟大家分享,接下来我会跟大家说一些编码方面的心得。
方法和方法的命名我认为真的真的非常重要,尤其是方法。
我经常阅读代码的时候觉得方法做的事情和方法名不匹配,想到一个更合适的命名会自嗨一下。当你觉得不舒服的时候,证明这个地方应该重构了,当然重构前提是你对系统已经了如指掌了,知道所有的犄角旮旯的执行路径。
命名最重要的是名副其实,比如下面这个命令:
long time;
这个变量本来是想表达经过的时间,但是命名基本没有意义,换成这个:
long elapsedTimeInMinisecond
//经过的时间,单位: 毫秒
方法名要能表达做的事情, 看下面这个示例,只看getHero 方法名你知道它是做什么的吗?
//朝被标记的英雄开火
public void fire(String targerHeroName) {
List<Hero> heros = getCurrentLiveHeros();
Hero targetHero = getHero(heros);
doFire(targetHero);
}
public Hero getHero(List<Hero> heros) {
//1.入参检查
if (CollectionUtil.isEmpty(heros) || StringUtils.isEmpty(name)) {
return null;
}
//2.查找被标记的英雄
for (Hero hero: heros) {
if (hero.getStatus() == HeroStatusEnum.MARKED) {
return hero;
}
}
return null;
}
改成 getMarkedHero() //获取被标记的英雄
表达更准确。
尽量让你写的代码阅读起来像阅读新闻文章一样通畅,以上面 getMarkedHero 代码为例,我们可以把 hero.getStatus() == HeroStatusEnum.MARKED
可以直接抽成一个方法,这个方法放在 Hero 类里面。
public class Hero {
private String name;
private HeroStatusEnum status;
//其他属性
//是否被标记
public boolean isMarked() {
return status == HeroStatusEnum.MARKED;
}
}
这样代码就能调整成
for (Hero hero: heros) {
if (hero.isMarked()) {
return hero;
}
}
是不是更通畅了,每次读这段代码的时候都就看一段话:英雄是否被标记。
另外这个方法放在Hero 类的内部,看到DDD 领域知识的都知道,如果一个方法只依赖Bean对象内部域的数据(这里的status),那这个方法就应该放在对象内部,避免贫血模型。
这个在系统设计之初就应该做的,比如我们在做包的设计,蚂蚁 sofa里面是bundle,一定要有模块化的概念,比如我们经常会依赖第三方的jar,但是只是依赖部分API,而且第三方的jar 有很多定制化的逻辑,我们可以做适配层,在DDD中叫防腐层,把自己的系统和具体依赖隔离开,内部系统接口设计不依赖具体实现。如果未来要升级包或换依赖jar,可以替换依赖jar,只需要修改适配层代码,不需要修改业务代码。
我下面举个设计层次的例子。
这个是之前文章提到过的第一个项目部分的模块划分,这里面的每一层都可以内部水平扩展,举个我之前项目里面的例子,我们应用需要接入流程平台,需要有审批流的功能,这个属于第三方服务,在三方接口接入这里引入三方包,因为不希望和具体流程平台绑定,所以在数据转化层做数据的标准化,你可以理解成把自己系统的领域数据模型和第三方的数据模型隔离开,比如创建流程工单,工单id内部有一套独立于具体平台的唯一id生成器,生成好之后在调用工单系统之前将自己的模型做一次转化,转为第三方流程平台的模型,然后发起rpc请求。
为什么是单元测试,因为这是开发最应该关注的,其他类型的有专门的测试负责。
单元测试有个很大的作用就是方便重构优化代码。只有良好的单测覆盖所有代码执行路径和测试用例,才能放心大胆的重构代码。
我们目前项目单测都是core-service 的方法级别,测试框架的核心逻辑应该有这些模块:测试前清理数据、测试前准入数据、测试后数据比对和方法执行结果比对。
类和方法都应该控制在一定范围,一个方法超过100行表达力就会逐步递减,所以一个方法最好只做一件事,保持逻辑清晰。
方法的参数尽量不超过三个,参数多了会降低方法的可读性,读者每次读到你的方法时需要理解你方法参数的含义,超过三个你就应该重新审视方法设计是否合理,参数之间的关系是什么,是否可以用类来封装?
单一职责很重要,同时兼顾开闭原则。我们写一段示例代码看一看:
//团战
public BattleResult teamBattle(HeroRequest request) {
BattleResult result = BattleResult.init();
try{
//1.校验请求入参
validateRequest(request);
//2.查询用户信息
UserInfo userInfo = userService.queryUserInfo(request.getUserId());
//3.查询队友信息
List<UserInfo> teammateUserInfos = userService.queryTeammates(request.getUserId());
//4.初始化执行上下文
TeamBattleContext temBattleContext = initExecutorContext(userInfo, teammateUserInfos);
//5.执行具体业务
result = doTeamBattle(temBattleContext);
} catch (Throwable ex) {
//6.封装失败结果
result = assembleFailureResult(BattleResult.class, ex);
LogUtils.printErrorLog('请求英雄信息错误', ex);
} finally {
//7.打印日志 标准格式:类名#方法名,是否成功,耗时,成功或失败详细信息,附加埋点信息
LogUtils.printInfoLog(method, result, elapsedTime, info, extraInfo);
}
retutn result;
}
我见过几个普通的业务系统写成了流程引擎,本来业务系统内部可以模块化拆解的非常清晰,但是所有模型都抽象到无差别任务这一层,对于代码的阅读和维护都困难重重,尤其对于新人接受,不知道从何入手,这就是典型的抽象层次太高的体现。
当然如果抽象层次太低,就会发生很多地方充斥着逻辑相似的重复代码,即使不完全重复,可能代码很多逻辑也是相似的,你想象一下如何 java 的集合工具类 CollectionUtils 如果不做抽象,需要对每种数据类型都单独实现一个,维护起来难度很大。
抛出异常比错误码更有表现力,错误码会让整个方法逻辑不通畅,直接在最外部顶层做异常集中处理,内部只需要关注具体业务。
很多开发经常是写完代码就丢到一旁,不再理会之前的代码。但是有追求的开发应该经常看自己负责系统的代码,检查方法是否通畅,抽象层次合理,封装满足开闭原则,这样在业务需求来的时候能快速轻装上阵。
比如你是做营销系统、会员系统、支付系统、收单、资金、风控、CRM、监控、行业软件的,多去看看这个领域的业务知识,目前的现状和未来的发展前景,这样你在设计系统时能提前布局,做面向未来的事情,从整个行业看你现在做的事情,会知道自己还能在哪些地方做的更好。
人往往会因为自己的性格和爱好选择认为适合的类型的工作,同时工作的性质也会潜移默化的改变人的性格。
很多时候会因为所负责的系统复杂度高,养成了思维缜密、逻辑严谨的思维方式。
最后想给大家推荐二本书:《代码整洁之道》和《重构》,本章部分示例取材自第一本书。
<END>
联系客服