您好!欢迎来到爱源码

爱源码

热门搜索: 抖音快手短视频下载   

阿里P8架构师教你杀祖石山代码重复/大量ifelse <免费源码>

  • 时间:2022-10-12 18:20 编辑: 来源: 阅读:318
  • 扫一扫,手机访问
摘要:阿里P8架构师教你杀祖石山代码重复/大量ifelse <免费源码>
文章收录在我的GitHub仓库里。欢迎星/叉:Java-面试-教程wasabi 1234/Java-面试-教程。本文将教你如何优雅地消除重复代码,改变你认为业务代码不是技术的想法。 1 crud工程师的“痛”很多CRUD工程师抱怨业务开发没有技术含量,不会使用设计模式或者高并发,就是堆砌CRUD。 每次面试都会被问到“说说常见的设计模式?”,只能说单实例掌握,其余的设计模式就算听说过也只会简单描述一下,因为没实际用过。 对于反射和注释,我只知道它们用在框架里,框架不是我自己写的,也不知道怎么用。 设计模式是世界级软件大师在大型项目中的经验,被证明有利于大型项目的维护。 反射、注释、泛型等高级特性。在框架中被广泛使用,因为框架经常需要同一套算法来处理不同的数据结构,而这些特性可以帮助减少重复代码,方便维护。 每个编码人员都必须注意提高项目的可维护性。一个很重要的手段就是减少代码重复。重复太多会导致:很容易修改一个地方,忘记修改另一个地方,导致不完全重复,但相似度高的bug代码量很小。修改这些相似的代码很容易出现change (cv)错误,把原来的差异改成同样的2 factory+template method模式。消除多重if和重复代码。2.1需要开发购物车订单,针对不同的客户可以有不同的解决方法:普通客户需要收取运费,是商品价格的10%,没有商品折扣的VIP客户也需要收取商品价格的10%的快递费用。但是,当购买两个以上相同的商品时,第三个开始享受正折扣。内部客户可免运费,无商品折扣可实现三类购物车业务逻辑。地图对象(K:商品ID,V:) 2.2菜鸟实现购物车镜像2.2.1普通客户镜像2.2.2 VIP客户VIP客户可以享受购买镜像同类产品的优惠。 只要多买折扣的多余部分解决了 图2.2.3内部客户免运费不打折,只解决商品折扣和运费的逻辑差异。 图片三个购物车超过一半的代码是重复的。 虽然不同类型的客户计算运费和折扣的方式不同,但是整个购物车的初始化、总价统计、总运费、总折扣、支付价格的逻辑是一样的。 代码重复本身并不可怕,可怕的是遗漏或者纠错。 比如给VIP客户写购物车的同学,发现商品总价计算有Bug。他们应该将所有商品的价格*数量相加,而不是将所有商品的价格相加。 他可能只修复了VIP客户购物车的代码,错过了普通客户和内部客户购物车中同样的重复逻辑实现的Bug。 三个购物车,需要根据不同的客户类型使用不同的购物车。 用多if实现不同类型的客户调用不同的购物车processimage只能多加购物车类,写重复的购物车逻辑多写if逻辑?不,当然,相同的代码应该只出现在一个地方!2.3重构技术——模板方法模式可以在通用类中定义重复的逻辑,三个购物车只需要分别实现逻辑的不同部分。 这实际上是模板方法模式。 在父类中实现购物车解决方案的流程模板,然后定义需要特殊解决方案的通用方法,让子类实现。 因为父类逻辑不能单独工作,所以需要定义为通用类。 如下面的代码所示,AbstractCart通用类实现了购物车的通用逻辑,并定义了两个附加的通用方法供子类实现。 其中,processCouponPrice方法用于计算商品折扣,processDeliveryPrice方法用于计算运费。 Image有一个通用类,三个子类的实现很简单。 普通客户的购物车NormalUserCart,实现0折扣,10%运费。imageVIP客户的购物车VipUserCart直接继承NormalUserCart。修改买多多折扣策略图片内部客户购物车最简单,设置直接0运费和0折扣图片通用类和三个子类的实现关系图。图2.4重构秘技工厂模式——消除多if。由于三个购物车都叫XXXUserCart,所以可以用UserCart拼接客户类型字符串,形成购物车Bean的名称,然后使用IoC容器直接通过Bean名称获取AbstractCart,通过调用其process方法实现通用目的。 这是工厂模式,借助Spring容器实现:如果image有新的客户类型和客户逻辑,只需添加一个新的XXXUserCart类继承AbstractCart,实现特殊的折扣和运费结算逻辑。 工厂+模板方法模式消除了重复代码,避免修改现有代码。 这就是设计模式中的开闭原则:关闭用于修改,打开用于扩展。 注意+反射消除重复代码3.1需求银行提供少量API接口。参数的序列化不使用JSON,而是需要我们把参数依次拼在一起,形成一个大字符串。 根据银行提供的API文档顺序,将所有参数做成定长数据,然后拼接在一起形成整个字符串。由于每个参数的长度都是固定的,所以没有达到长度时需要填充:字符串中小于参数长度的部分需要用下划线向右填充,即字符串中小于数字类型的参数长度的部分需要用0向左填充,即, 实际数字由右边的货币类型显示,金额需要向下取2位数到分,才能分单位。 作为数字类型,它需要 对所有参数做MD5运算作为签名(为了便于理解,演示中不涉及加盐) 比如客户创建方式和支付方式的定义如下:imageimage3.2菜鸟实现直接根据接口定义填充、签名和请求调用:public类bank service {//Create customer public static String Create user(String name,String identity,String mobile,int age)throws io exception { StringBuilder StringBuilder = new StringBuilder();//字符串左边,多余的地方用_ stringbuilder . append(string . format(" %-10s ",name)填充。替换(',' _ ');stringbuilder . append(string . format(" %-18s ",identity)。替换(',' _ ');//数字在右边,多余的地方为stringbuilder.append填充0(string . format(" % 05d ",age));//String是left stringbuilder . append(String . format(" %-11s ",mobile)。替换(',' _ ');// MD5签名stringbuilder . append(digestutils . MD 2 hex(stringbuilder . tostring()));退货请求。post(" http://localhost:45678/reflection/bank/create user ")。body string(stringbuilder . tostring(),ContentType。APPLICATION_JSON)。执行()。returnContent()。asString();}//Pay公共静态字符串pay (long userid,bigdecimal amount)ThrowsioException { stringbuilder stringbuilder = newstringbuilder();//编号为right stringbuilder . append(string . format(" % 020d ",userid));//金额向下取2位数到分钟,分单位。数字靠右,多余的地方用0填充,stringbuilder . append(string . format(" % 010d "),amount.setscale (2,rounding mode.down)。Multiply (new bigdecimal ("100 "))。长值()。// MD5签名stringbuilder . append(digestutils . MD 2 hex(stringbuilder . tostring()));退货请求。post(" http://localhost:45678/reflection/bank/pay ")。body string(stringbuilder . tostring(),ContentType。APPLICATION_JSON)。执行()。returnContent()。asString();}}这段代码的重复粒度更细:三种标准数据类型的解析逻辑包括重复解析过程中的字符串拼接、签名和发送请求的逻辑。实际方法的参数类型和顺序在所有方法中都是重复的,不一定与接口要求一致。容易出错的代码级别是为每个参数硬编码的,因此无法清楚地检查。如果有几十个或者上百个参数,错误概率极高。 3.3关于重构技术的注释&所有反映银行请求的逻辑由一组代码实现,没有任何重复。 要实现接口逻辑和逻辑实现的分离,首先要用POJO类定义所有的接口参数。 parameter @ data public class create user API { privatestringname;私有字符串标识;私有字符串移动;私人年龄;}有了接口参数定义,就可以通过设置自己的标注,为接口和所有参数添加少量元数据。 定义接口API的如下注释BankAPI,包括接口URL地址和接口描述。image定义了一个自己设置的注释@BankAPIField,描述了接口的各个字段规范,包括参数顺序、类型、长度三个属性:image定义了CreateUserAPI类来描述创建客户接口的信息,在接口中加入@BankAPI注释,补充接口的URL、描述等元数据;为每个字段添加@BankAPIField注释,补充参数的顺序、类型、长度等元数据:image类似于PayAPI类image。这两个类继承的AbstractAPI类是一个空实现,因为在这种情况下接口没有公共数据。 通过这两个类,你可以在几秒钟内完成对API列表表单的验证。 如果我们的核心翻译过程(即把注释和接口API序列化成请求所需的字符串的过程)没问题,只要注释和表一致,API请求翻译就没问题。 API参数的描述是通过注释实现的。 看反射如何配合注释实现动态接口参数组装:私有静态字符串remotecall (abstractAPI)抛出io异常{//从类中获取BankAPI注释,然后获取其URL属性,再远程调用BankAPI BankAPI = api.getclass()。get annotation(bankapi . class);bankapi . URL();StringBuilder StringBuilder = new StringBuilder();//使用stream快速实现获取类中所有带有BankAPIField注释的字段,按照order属性对字段进行排序,然后将私有字段反射设置为可访问。 Arrays.stream (api.getclass()。getDeclaredFields())//获取所有字段//找到标有comments.filter的字段(field->;field . isanotationpresent(bankapifield . class))//根据注释中的顺序对字段进行排序。sorted(comparator . comparingint(a-->;a . get annotation(bankapifield . class)。order()))。peek(字段->;Field.setAccessible(true)) //设置私有字段。foreach(field->:{//实现反射得到注释值,然后根据BankAPIField得到的参数类型按照三个标准进行格式化,把所有参数的格式化逻辑集中在这个地方。//Get批注Bankapifield Bankapifield = field . Get annotation(Bankapifield . class);对象值= " ";Try {// Reflect获取字段值value = field . get(API);} catch(IllegalAccessException e){ e . printstacktrace();}//根据字段类型用正确的填充设置字符串开关(bankAPIField.type())的格式。{ case " S ":{ stringbuilder . append(string . format(" %-"+bankapifield . length()+" S ",value.toString())。替换(',' _ ');打破;} case " N ":{ stringbuilder . append(string . format(" % "+bankapifield . length()+" s ",value.toString())。replace(',' 0 ');打破;} case“M”:{ if(!(bigdecimal的valueinstance))抛出新的运行时异常(string . format(“{0}的{ 0 }必须是BigDecimal”,api,field));stringbuilder . append(string . format(" % 0 "+bankapifield . length()+" d ",((BigDecimal)值))。setScale(2,RoundingMode。向下)。multiply(new BigDecimal("100 "))。long value()));打破;}默认:break} });//实现参数签名并请求调用//签名逻辑stringbuilder . append(digest utils . MD 2 hex(stringbuilder . tostring()));string param = string builder . tostring();long begin = system . current time millis();//发送请求字符串result = request . post(" http://localhost:45678/reflection "+bankapi . URL())。bodystring (param,ContentType。APPLICATION_JSON)。执行()。returnContent()。asString();Log.info("调用银行API {} url:{}参数:{}耗时:{} ms ",bankapi.desc(),bankapi.url(),param,system . current Time millis()-begin);返回结果;}所有解决参数排序、填充、签名、请求调用的核心逻辑都聚集在remoteCall中。 用这种方法,BankService中每个接口的实现都很简单,只需要参数的组装,然后调用remoteCall即可。 任何涉及图像类结构的通用解决方案都可以根据这种模式减少重复代码。 当我们不知道类的结构时,反射可以按照固定的逻辑解决类成员的标注,并赋予我们为这些成员补充元数据的能力。当我们使用反射来实现一般逻辑时,我们可以从外部获得更多我们关心的data 4属性的副本。对于三层架构的系统,层间解耦,每一层的数据需求不同,每一层都会有自己的POJO实体。 在这些实体之间手工编写分配代码很容易出错。 对于复杂的业务系统,实体有几十个甚至上百个属性是很正常的。 例如,ComplicatedOrderDTO描述了一个订单中的几十个属性。 如果转换成类似的DO,复制大部分字段,然后将数据放入仓库,就必须执行许多属性映射和赋值操作。 就这样,密密麻麻的代码有没有让你头晕?ComplicatedOrderDTO order dto = new ComplicatedOrderDTO();ComplicatedOrderDO orderDO = new ComplicatedOrderDO();order do . set acceptdate(order to . getacceptdate());order do . set address(order dto . get address());order do . set addressid(order to . get addressid());order do . setcancelable(order to . is cancelable());order do . setcommentable(order to . is complainable());//属性错误order do . setcomplaineable(order dto . is commmatable());//属性错误order do . setcancelable(order to . iscancelable());order do . setcouponamount(order to . getcouponamount());order do . set couponid(order to . get couponid());order do . set createdate(order to . get createdate());order do . setdirectcancelable(order to . isdirectcancelable());order do . setdeliverdate(order to . getdeliverdate());order do . setdelivergroup(order to . getdelivergroup());order do . setdelivergrouporderstatus(order to . getdelivergrouporderstatus());order do . setdelivermethod(order to . getdelivermethod());order do . setdeliverprise(order to . getdeliverprise());order do . setdeliverymanid(order to . getdeliverymanid());order do . setdeliverymanmobile(order do . getdeliverymanmobile());//对象错误order do . setdeliverymanname(order to . getdeliverymanname());order do . set distance(order to . get distance());order do . set expectdate(order to . get expectdate());order do . set first deal(order to . isfirstdeal());order do . sethaspayd(order to . ishasp aid());order do . setheadpic(order to . getheadpic());order do . set longitude(order dto . get longitude());order do . set latitude(order dto . get longitude());//属性赋值错误order do . set merchant address(order dto . get merchant address());order do . setmerchantheadpic(order to . getmerchantheadpic());order do . setmerchantid(order to . getmerchantid());order do . setmerchantaddress(order to . getmerchantaddress());order do . set merchant name(order to . get merchant name());order do . set merchant phone(order to . getmerchant phone());order do . setorderno(order to . getorderno());order do . set outdate(order dto . get outdate());order do . set payable(order to . is payable());order do . setpaymentamount(order to . getpaymentamount());order do . setpaymentdate(order to . getpaymentdate());order do . setpaymentmethod(order to . getpaymentmethod());order do . setpaymenttimelimit(order to . getpaymenttimelimit());order do . set phone(order dto . get phone());order do . set referrable(order dto . is refundable());order do . set remark(order to . get remark());order do . set status(order dto . get status());order do . settotalquantity(order dto . gettotalquantity());order do . setupdatetime(order to . getupdatetime());order do . setname(order to . getname());order do . setuid(order dto . getuid());如果原始DTO有100个字段,我们需要将90个字段复制到DO中,并保留10个字段而不赋值。最后,应该如何检查正确性?数数?就算算出来90行代码,也不确定是正确的。因为属性可能会被重复赋值,有时字段名是相似的,比如complainable和commentable,所以很容易出现这样的情况:两个目标字段被重复赋值给同一个源字段,这显然要将d to的值赋给DO,但在设置时从DO本身取值。导致无效分配。使用BeanUtils这样的映射工具对Bean进行转换,copyProperties方法也允许我们提供需要忽略的属性:image5总结,重复代码太多,总有一天会出错。 有多个实现类似代码逻辑的并行类。考虑提取相同的逻辑在父类中实现,通过通用方法把差异逻辑留给子类。 使用类似的模板方法,将相同的流程和逻辑固定到一个模板中,保留差异的同时尽可能避免代码重复。 同时可以利用Spring的IoC特性注入相应的子类,避免实例化子类时大量的if…else代码。 考虑通过硬编码重复实现相同的数据求解算法。将规则转换成自己设置的注释,作为元数据来描述类或字段和方法,然后通过反射动态读取这些元数据、字段或方法,实现规则参数和规则定义的分离。 也就是说,改变的部分,也就是规则的参数,放入注释,规则的定义统一求解。 业务中常见的DO、DTO、VO转换过程中大量字段的手工赋值,在遇到有上百个属性的复杂类型时,是非常非常容易出错的。不要手动赋值,考虑使用Bean映射工具。 此外,还可以考虑使用单元测试来检查所有字段赋值的正确性。 代码重复是评价一个项目质量的重要指标。如果一个项目几乎没有重复代码,那里面一定很一般。 重构时,首要任务是消除重复。 参考重构,代码重复的三招,https://blog.csdn.net/qq_32447301/article/details/107774036


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【技术支持|常见问题】1556原创ng8文章搜索页面不齐(2024-05-01 14:43)
【技术支持|常见问题】1502企业站群-多域名跳转-多模板切换(2024-04-09 12:19)
【技术支持|常见问题】1126完美滑屏版视频只能显示10个(2024-03-29 13:37)
【技术支持|常见问题】响应式自适应代码(2024-03-24 14:23)
【技术支持|常见问题】1126完美滑屏版百度未授权使用地图api怎么办(2024-03-15 07:21)
【技术支持|常见问题】如何集成阿里通信短信接口(2024-02-19 21:48)
【技术支持|常见问题】算命网微信支付宝产品名称年份在哪修改?风水姻缘合婚配对_公司起名占卜八字算命算财运查吉凶源码(2024-01-07 12:27)
【域名/主机/服务器|】帝国CMS安装(2023-08-20 11:31)
【技术支持|常见问题】通过HTTPs测试Mozilla DNS {免费源码}(2022-11-04 10:37)
【技术支持|常见问题】别告诉我你没看过邰方这两则有思想的创意广告! (2022-11-04 10:37)

联系我们
Q Q:375457086
Q Q:526665408
电话:0755-84666665
微信:15999668636
联系客服
企业客服1 企业客服2 联系客服
86-755-84666665
手机版
手机版
扫一扫进手机版
返回顶部