技术规范,严谨点
技术规范,严谨点
技术规范一般都是很严谨,正统.
作为规范,一定要能经得起推敲和质疑.
前段时间,去浙B走了一圈,某浙B大行的技术规范,不是一般的搞笑,这边提炼一下,让大家开心开心.
全程贯彻DDD,但是只用了个命名
听了听技术介绍,吓死人,全程贯彻领域模型.
看到代码,笑死了,基本就用了个命名.
说说领域模型
说到DDD,正好撞到我枪口上了,我还真研究过领域模型.
说到领域模型,这个东西还真有用,但是有用的地方是在设计里面,而不是在开发上面.
领域模型中有很多理念都是非常厉害的,比如,"模型是一种通用语言",这个理念是无敌的存在,特别是真正经历过和不懂技术的产品经理干架以后,顺便通化产品经理的过程后,你会更加有感触.
但是,这边有个问题,你知道有哪些东西是使用领域驱动开发(DDD)的模式实现的么???
如果从编程模型上来看,编程模型一般可以分为贫血模型和充血模型.
我们来举个栗子,我们要在程序里面设计一只狗,狗有一个动作"看门".
贫血模型的设计方法
class 狗{
属性1,
属性2
}class 看门服务{
看门(狗,门){
do(狗 , 门);
}
}充血模型会这么设计呢?
class 狗{
属性1,
属性2,
看门(门){
do(this,门);
}
}看明白了么,充血模型的"狗"就是领域模型,在设计领域模型的时候,"看门"只是狗的一个方法.
而在贫血模型里面,"狗"是没有方法的,是由外面的"看门服务"用到了"狗".
你可能就要说了,就这么点差别么??
也不是很大啊.... 但是,我们知道,我们现在的编码已经很难离开spring了,spring在设计的时候,默认已经选好了编程模型,那就是贫血模型.
如果在使用spring的前提下,使用了充血模型会怎么样?所有的领域对象都要new出来,导致spring的IOC就像多余的一样,或者类似于shiro,把realm对象配置在spring里面,spring默认是单例模式,那些属性怎么办???
这边不是一个方法写在哪的问题,而是一种思维习惯的问题.而这种思维习惯已经在强大的spring的指引下,我们绝大部分的开发人员已经无法回头了.
如果一定要把领域模型应用到编程模型,需要承受思维模式无法和spring兼容的代价.
代码分层
刚看示例代码的时候,我滴个乖,那么厉害.
有api层,domain层,infrastructure层,还有repository层,好规范.
但是仔细一看domain里面没有属性,每一层都有自己的"领域对象",也就是说,如果要写一个controller去访问数据库,本来request,response再加上数据库的POJO就能满足,现在额外需要定义domain的输入和输出,infrastructure的输入和输出,每一层的对象都要手动转换
本质上还是贫血模型的使用方式,却用领域模型推荐的名词来包装,牛逼! 这边的分层,问题不小.再来说说分层吧.
我们的代码为什么要分层??? 我这边写点自己的体会.
我最早写代码的时候,压根没有分层,JSP+Bean就直接写了,后面有了struts,把controller分离出来.
后面引入spring,那东西太强大了,现在还在用.
最早的spring被大量使用,就是因为spring的AOP满足了数据库访问层的需求,比如DAO的注入,事务管理.
但是早期的spring的AOP是配置式的,为了满足方便的使用spring的功能,我们剥离了DAO和service层,把数据库访问和业务逻辑做了分离,在service上配置了事务. 这就是分层的由来.
提示
没有spring的年代,我们使用DAOHelper,把所有的DAO都通过一个类来初始化.在开发过程中,一个团队十几个人,每天都有不同的开发来改一下DAOHelper,然后就能体会到spring巨大的优越性.
后面我们逐渐认同spring的设计理念,我们同时认同,DAO和service的分离能更好的把数据库访问和业务逻辑进行分离.
所以action(controller)->service->dao的分层就这么保留下来.
浙B大行的分层,除了增加额外的转换开销,我没有看出有任何存在的意义.
这个时候,有个同学举手提问,我们不是有POJO对象不能暴露到action(controller)的规范么??
确实有!早期的时候,数据库的访问当时还在采用hibernate,使用hibernate的数据库生成会在POJO类上面生成特定的表名,列名以及表关系.
然后我们认为,如果直接在webservice中使用这些POJO会把应用内部的数据库结构暴露出去,非常不安全,这个webservice也可以类推到action(controller),所以我们又规定的DTO,如果一个POJO要对外暴露,必须使用DTO暴露.
就算中间层有各种对象的转换,也应该明确POJO/DO和DTO/VO的关系,要转换也只有一个层级的转换,而不是在内部不断的相互转换.
单元测试覆盖率100%
开始听到这个话的时候,我以为我听错了,单元测试能到100%???
后面看到实现,哦,原来是使用Karate,对spring的controller进行测试.
所谓的覆盖率100%,是指,一共有N个controller的方法,必须每个方法都使用Karate进行测试,都必须覆盖到.
引用我前文软件质量体系建设--说说测试中的单元测试的示例来介绍一下,我们就会发现其中的悖论.
如果一个controller内部足够简单,那样的测试根本没必要.
如果一个controller内部的结构很复杂,那样的测试也根本没效果,根本没有覆盖率可以看,只知道这个请求测试过了,有问题也不知道哪里有问题.
这种框架本质上是模拟了Postman的请求发送,唯一对开发有利的就是可以比较好的本地调试.
可以对测试覆盖率有误解,但是也不能这么解释.
测试覆盖率这个名词已经是有明确的定义
测试覆盖率=测试跑过的代码/总的代码行数
如果一段代码被if所包围,那么这个if如果只有一个条件,跑到这段代码能算100%,如果if有2个条件,跑过这段代码就只能算50%,当然,还有else部分的覆盖
根据上面的公式,怎么可能会出现测试覆盖率=100%的情况呢???
按照10年前,阿里内部的指标,拿来分享下
| 覆盖率 | 代码量 | 用途 |
|---|---|---|
| 60% | 测试代码=被测试代码*3 | 普通应用 |
| 80% | 测试代码=被测试代码*5,甚至更多 | 中间件 |
另外一个指标也拿来分享下,那就是圈复杂度(Cyclomatic complexity),一般一个方法的圈复杂度最多为30(sonar默认值设置为15),一个类的圈复杂度最多为300(sonar默认值为200).
提示
圈复杂度为什么很重要?
因为直接影响到代码的可读性,一段好的代码,必须具备可读性.
而圈复杂度过高,基本不可读.(这就是代码即文档的含义) 圈复杂度间接影响了测试覆盖率,一个方法很复杂,测试覆盖率一定不高.
写在最后
一个技术的规范,其指定必须非常严谨.不但需要考虑现状,还要参考形成的历史.如此规范,不是笑话么??
