当前位置: 首页 新闻详细

csgo转圈圈代码怎么取消|代码写的烂,我被开除了!

专业编程培训机构——完成蜕变以后轻松拿高薪

电话+V:159999-78052 ,欢迎咨询写完代码 头晕,[python实用课程],[C++单片机原理],[C#网站搭建],[Nodejs小程序开发],[ios游戏开发],[安卓游戏开发],[教会用大脑用想法赚钱实现阶层跨越]

一、csgo转圈圈代码怎么取消

  CSGO转圈圈代码是指在CSGO游戏中,玩家使用鼠标进行快速旋转的技巧,从而更容易杀死敌人。但是,这种技巧有时会使玩家感到非常头晕和眩晕。如果您想取消这个技巧,有几种方法可以尝试。

  首先,您可以尝试减少鼠标移动的速度和幅度。这意味着您需要在游戏中慢慢移动鼠标,而不是快速移动。这可以减少头晕和眩晕的风险。

  另一种方法是使用游戏设置中的“禁用自动旋转”选项。这将禁用自动旋转功能,从而减少头晕和眩晕的风险。这个选项在游戏设置中很容易找到,您只需要找到“附加设置”选项,然后找到“游戏设置”选项。

  最后,您可以尝试使用一些特殊的软件来帮助您取消这种技巧。这些软件可以帮助您控制鼠标移动的速度和幅度,从而减少头晕和眩晕的风险。但是,请注意,使用这些软件可能会影响您的游戏体验,并且可能会被认为是作弊行为。

二、三种人学不会编程(初学编程100个代码)

三种人学不会cnc

学不会编程的3种人。分别是

1、不爱接受新事物、不爱动脑筋。

2、学历起点低、基础薄弱。

3、喜欢安逸、缺乏坚持。cnc就是cnc编程。

为什么我就是学不会编程

我也不会编程啊。我是19岁开始学的。现在26了。

跟你聊聊这些年的编程生涯。

第一次接触编程是因为没有工作,没有收入。然后我就想通过学习一门计算机技能,解决我的工作和收入问题。有的人以为我得了网瘾,天天在网吧玩游戏。其实我会玩的游戏,除了飞车就是玩个cs。然后,我跟着别人做网站。但是代码是一点都不懂得。但是还是在电脑前面敲一些代码。我当时都不知道那是什么代码。后来才知道,我敲得那些是javascript。然后,自己的目标很简单,就是先做一个网站。慢慢地就了解到PS。flash,。还有html。等等。但是我只是在了解,其实我一行代码都不会写。连html都不会。任务很紧,我浏览了所有的net硅谷教程,几乎是没有一套能够看懂。连ps教程,都看了十多个,但是水平还是初级。w3c也被我翻烂了,但是还是没有写出一些有用的东西。

慢慢地我终于熬到了2015年,这是一个人人编程年。然后,我又找了一些教程,慢慢地跟着敲代码。最后,在几个月的时间里,我总结了以前的的学习。大部分就是上网看教程了,自己做的东西很少。根本看不懂算法导论。基本的算法都理解不了。一旦代码过长,就有点头晕。

终于,到2016年,注册了玉米,开通了自己的网站。但是还有一个月就到期了。我只是尝试了下做站长的感觉。但是,自己的能力有限,基本的编程能力都没有。

现在,编程能力几乎为零。而我学编程已经有8年了。现在唯一会的东西就是,安装一些现成的开源程序,给自己的电脑重装系统。

基本上,我什么都不会。从来都不写技术博客,从来也不爱写文章。有时候,自己一个人默默地待着就是一整天。

但是我学会了怎么搜资料。怎么搜问题。然后,我找到了一些好书,比如大话java,让我进入java的门槛。

我也找到了w3c,一有记不住的都可以翻开看一看。我也找到了很多免费视频教程,没事就看看。

现在是2017年。我的目标就是跟着时代前进。

虽然我还不会编程,但是从我的进步来看,我已经知道了一些东西。我想通过我的不停琢磨,总有一日,我要获得计算机博士学位。

为什么有的人学不会C语言?

相对于其他编程语言,C语言还是比较难的。初学者需要注意一下几点:

一是学习顺序

先从熟悉简单的C语言语法开始入门,然后再循序渐进,学习C++语法,WIN32、MFC、QT、网络编程,数据库、数据结构、算法、COM、STL等。构建一个完整的C语言知识体系。这需要一个比较漫长的学习积累的过程。语法入门部分大概2-3个月,其他部分需要学习和工作中慢慢理解和消化了。

二是学习方法

人的知识80%是通过眼睛获取的,但是学习编程有所不同,除了看书、看视频之外,关键是要勤动手,勤动脑。通过做大量的练习、项目实战不断积累代码量。只有代码量足够多了,项目做的多了,才能算是真正学会了。项目能否完成,就是衡量是否学会的唯一标准。后期就是代码的质量和优化问题了,这个只能在项目工作中慢慢积累经验了。

最后强调一点,很多人学不会编程是因为掉坑里了。就是教程或者书上的知识点之间跨越太大,作为一个初学者很难自己摸索出来,前面的知识点没有掌握,接着学习后面的知识,肯定是学不会了。目前绝大多数编程书籍和教程或多或少都有这样的弊端。自学能力比较强的人可以通过各种方法,参考各种网上的资料自己解决。但是大多数自学能力不是很强的人,只能依赖老师、同学、同事或者朋友帮忙指导,或者报名培训机构,老师指导完成了。学习编程通常需要一些好的学习资料,包括纸质的书籍,视频教程,课件,项目练习,代码。零基础入门的书籍推荐《明解C语言》、《CPrimerPlus》,还有一本非常特别的汇编和C语言正向逆向结合的书编程达人内部教材《汇编、C语言基础教程》也非常不错,讲解汇编和C语言的本质非常透彻,非常细致。视频资料也是特别多了,各种视频网站、论坛、自媒体都有,比如网易课堂、腾讯课堂、慕客网这些。还有一些论坛,比如CSDN、编程中国等。最重要的一点就是答疑服务,推荐爱达人的网站也很不错,从零基础入门到应用课程,配套的视频、课件、代码、项目、答疑服务都有,还可以兼职接单,学以致用。

为什么我死活学不会编程?

死活学不会编程主要是犯了以下的错误:

1、专注力不够,没有恒心

有很多人开始学编程热度很强,刚刚入门时候相对比较简单觉得写代码这就是这么回事,开始有点小浮躁,到了后边学到难理解的,又开始怀疑人生。基本上来回几下就被弄得焦头烂额了,慢慢也就距离放弃不远了,编程对定力和耐心的要求很高,正在自学的或者想学编程的准备好这份耐心了没。

2、不讲究学习方法

大多数学编程的基本上学习之初都会下载很多的资料,大量的视频和电子书,很多很多个G。然后搞一个视频就开始了自己编程学习之路,看了一段时间有点听不懂了,换个视频继续,又遇到不懂得,开始电子书的学习之路。这样下去基本上几个月过去了,弄得自己思维都乱了方寸。

学习编程基本上需要制定一个大纲,这个需要大纲需要限定时间和目标,建议以书本为主线,视频为辅助,这样更容易建立自己的知识体系。

3、不懂得实践

编程是一个熟练工种,不动手写代码啥也搞不定,也就别异想天开做属于自己的产品了,实践不是对着视频得代码在电脑上敲一遍就万事大吉了,需要自己独立去完成书本或者视频上的代码,还要举一反三。

4、不打造自己的知识体系

学习过程中难免枯燥乏味,就要讲究点策略,或者找点什么能够激励下自己。其实可以找点榜样得力量,这就是为什么要找个带自己的,起码在心理有个期盼会觉得什么时候能达到像谁一样厉害,有时候榜样的力量是很强大的,所以在学习过程中要设置这样的榜样。

代码写的烂,我被开除了!

首发2024-07-0215:47·小信先生昨天,一位朋友和我反馈说自己需要模拟面试和建立深度优化,可是我记忆中他上个月才入职,怎么又开始找工作?和他聊了一会,才知道被开除了,写的代码跟一坨shi一样。害,可惜了,好不容易找到工作。

其实,软件工程师和码农最大的区别就是平时写代码时习惯问题,码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。

业务同学抱怨业务开发没有技术含量,用不到设计模式、Java高级特性、OOP,平时写代码都在堆CRUD,个人成长无从谈起。

其实,我认为不是这样的。设计模式、OOP是前辈们在大型项目中积累下来的经验,通过这些方法论来改善大型项目的可维护性。反射、注解、泛型等高级特性在框架中大量使用的原因是,框架往往需要以同一套算法来应对不同的数据结构,而这些特性可以帮助减少重复代码,提升项目可维护性。

在我看来,可维护性是大型项目成熟度的一个重要指标,而提升可维护性非常重要的一个手段就是减少代码重复。那为什么这样说呢?

如果多处重复代码实现完全相同的功能,很容易修改一处忘记修改另一处,造成Bug有一些代码并不是完全重复,而是相似度很高,修改这些类似的代码容易改(复制粘贴)错,把原本有区别的地方改为了一样。今天,我就从业务代码中最常见的三个需求展开,聊聊如何使用Java中的一些高级特性、设计模式,以及一些工具消除重复代码,才能既优雅又高端。通过今天的学习,也希望改变你对业务代码没有技术含量的看法。

1.利用工厂模式+模板方法模式,消除if…else和重复代码假设要开发一个购物车下单的功能,针对不同用户进行不同处理:

普通用户需要收取运费,运费是商品价格的10%,无商品折扣;VIP用户同样需要收取商品价格10%的快递费,但购买两件以上相同商品时,第三件开始享受一定折扣;内部用户可以免运费,无商品折扣。我们的目标是实现三种类型的购物车业务逻辑,把入参Map对象(Key是商品ID,Value是商品数量),转换为出参购物车类型Cart。

先实现针对普通用户的购物车处理逻辑:

//购物车@DatapublicclassCart{//商品清单privateList<Item>items=newArrayList<>();//总优惠privateBigDecimaltotalDiscount;//商品总价privateBigDecimaltotalItemPrice;//总运费privateBigDecimaltotalDeliveryPrice;//应付总价privateBigDecimalpayPrice;}//购物车中的商品@DatapublicclassItem{//商品IDprivatelongid;//商品数量privateintquantity;//商品单价privateBigDecimalprice;//商品优惠privateBigDecimalcouponPrice;//商品运费privateBigDecimaldeliveryPrice;}//普通用户购物车处理publicclassNormalUserCart{publicCartprocess(longuserId,Map<Long,Integer>items){Cartcart=newCart();//把Map的购物车转换为Item列表List<Item>itemList=newArrayList<>();items.entrySet().stream().forEach(entry->{Itemitem=newItem();item.setId(entry.getKey());item.setPrice(Db.getItemPrice(entry.getKey()));item.setQuantity(entry.getValue());itemList.add(item);});cart.setItems(itemList);//处理运费和商品优惠itemList.stream().forEach(item->{//运费为商品总价的10%item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(newBigDecimal("0.1")));//无优惠item.setCouponPrice(BigDecimal.ZERO);});//计算商品总价cart.setTotalItemPrice(cart.getItems().stream().map(item->item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO,BigDecimal::add));//计算运费总价cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO,BigDecimal::add));//计算总优惠cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO,BigDecimal::add));//应付总价=商品总价+运费总价-总优惠cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount()));returncart;}}然后实现针对VIP用户的购物车逻辑。与普通用户购物车逻辑的不同在于,VIP用户能享受同类商品多买的折扣。所以,这部分代码只需要额外处理多买折扣部分:

publicclassVipUserCart{publicCartprocess(longuserId,Map<Long,Integer>items){...itemList.stream().forEach(item->{//运费为商品总价的10%item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(newBigDecimal("0.1")));//购买两件以上相同商品,第三件开始享受一定折扣if(item.getQuantity()>2){item.setCouponPrice(item.getPrice().multiply(BigDecimal.valueOf(100-Db.getUserCouponPercent(userId)).divide(newBigDecimal("100"))).multiply(BigDecimal.valueOf(item.getQuantity()-2)));}else{item.setCouponPrice(BigDecimal.ZERO);}});...returncart;}}最后是免运费、无折扣的内部用户,同样只是处理商品折扣和运费时的逻辑差异:

publicclassInternalUserCart{publicCartprocess(longuserId,Map<Long,Integer>items){...itemList.stream().forEach(item->{//免运费item.setDeliveryPrice(BigDecimal.ZERO);//无优惠item.setCouponPrice(BigDecimal.ZERO);});...returncart;}}对比一下代码量可以发现,三种购物车70%的代码是重复的。原因很简单,虽然不同类型用户计算运费和优惠的方式不同,但整个购物车的初始化、统计总价、总运费、总优惠和支付价格的逻辑都是一样的。

正如我们开始时提到的,代码重复本身不可怕,可怕的是漏改或改错。比如,写VIP用户购物车的同学发现商品总价计算有Bug,不应该是把所有Item的price加在一起,而是应该把所有Item的price*quantity加在一起。

这时,他可能会只修改VIP用户购物车的代码,而忽略了普通用户、内部用户的购物车中,重复的逻辑实现也有相同的Bug。

有了三个购物车后,我们就需要根据不同的用户类型使用不同的购物车了。如下代码所示,使用三个if实现不同类型用户调用不同购物车的process方法:

@GetMapping("wrong")publicCartwrong(@RequestParam("userId")intuserId){//根据用户ID获得用户类型StringuserCategory=Db.getUserCategory(userId);//普通用户处理逻辑if(userCategory.equals("Normal")){NormalUserCartnormalUserCart=newNormalUserCart();returnnormalUserCart.process(userId,items);}//VIP用户处理逻辑if(userCategory.equals("Vip")){VipUserCartvipUserCart=newVipUserCart();returnvipUserCart.process(userId,items);}//内部用户处理逻辑if(userCategory.equals("Internal")){InternalUserCartinternalUserCart=newInternalUserCart();returninternalUserCart.process(userId,items);}returnnull;}电商的营销玩法是多样的,以后势必还会有更多用户类型,需要更多的购物车。我们就只能不断增加更多的购物车类,一遍一遍地写重复的购物车逻辑、写更多的if逻辑吗?

当然不是,相同的代码应该只在一处出现!

如果我们熟记抽象类和抽象方法的定义的话,这时或许就会想到,是否可以把重复的逻辑定义在抽象类中,三个购物车只要分别实现不同的那份逻辑呢?

其实,这个模式就是模板方法模式。我们在父类中实现了购物车处理的流程模板,然后把需要特殊处理的地方留空白也就是留抽象方法定义,让子类去实现其中的逻辑。由于父类的逻辑不完整无法单独工作,因此需要定义为抽象类。

如下代码所示,AbstractCart抽象类实现了购物车通用的逻辑,额外定义了两个抽象方法让子类去实现。其中,processCouponPrice方法用于计算商品折扣,processDeliveryPrice方法用于计算运费。

publicabstractclassAbstractCart{//处理购物车的大量重复逻辑在父类实现publicCartprocess(longuserId,Map<Long,Integer>items){Cartcart=newCart();List<Item>itemList=newArrayList<>();items.entrySet().stream().forEach(entry->{Itemitem=newItem();item.setId(entry.getKey());item.setPrice(Db.getItemPrice(entry.getKey()));item.setQuantity(entry.getValue());itemList.add(item);});cart.setItems(itemList);//让子类处理每一个商品的优惠itemList.stream().forEach(item->{processCouponPrice(userId,item);processDeliveryPrice(userId,item);});//计算商品总价cart.setTotalItemPrice(cart.getItems().stream().map(item->item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO,BigDecimal::add));//计算总运费cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO,BigDecimal::add));//计算总折扣cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO,BigDecimal::add));//计算应付价格cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount()));returncart;}//处理商品优惠的逻辑留给子类实现protectedabstractvoidprocessCouponPrice(longuserId,Itemitem);//处理配送费的逻辑留给子类实现protectedabstractvoidprocessDeliveryPrice(longuserId,Itemitem);}有了这个抽象类,三个子类的实现就非常简单了。普通用户的购物车NormalUserCart,实现的是0优惠和10%运费的逻辑:

@Service(value="NormalUserCart")publicclassNormalUserCartextendsAbstractCart{@OverrideprotectedvoidprocessCouponPrice(longuserId,Itemitem){item.setCouponPrice(BigDecimal.ZERO);}@OverrideprotectedvoidprocessDeliveryPrice(longuserId,Itemitem){item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(newBigDecimal("0.1")));}}VIP用户的购物车VipUserCart,直接继承了NormalUserCart,只需要修改多买优惠策略:

@Service(value="VipUserCart")publicclassVipUserCartextendsNormalUserCart{@OverrideprotectedvoidprocessCouponPrice(longuserId,Itemitem){if(item.getQuantity()>2){item.setCouponPrice(item.getPrice().multiply(BigDecimal.valueOf(100-Db.getUserCouponPercent(userId)).divide(newBigDecimal("100"))).multiply(BigDecimal.valueOf(item.getQuantity()-2)));}else{item.setCouponPrice(BigDecimal.ZERO);}}}内部用户购物车InternalUserCart是最简单的,直接设置0运费和0折扣即可:

@Service(value="InternalUserCart")publicclassInternalUserCartextendsAbstractCart{@OverrideprotectedvoidprocessCouponPrice(longuserId,Itemitem){item.setCouponPrice(BigDecimal.ZERO);}@OverrideprotectedvoidprocessDeliveryPrice(longuserId,Itemitem){item.setDeliveryPrice(BigDecimal.ZERO);}}抽象类和三个子类的实现关系图,如下所示:

是不是比三个独立的购物车程序简单了很多呢?接下来,我们再看看如何能避免三个if逻辑。

或许你已经注意到了,定义三个购物车子类时,我们在@Service注解中对Bean进行了命名。既然三个购物车都叫XXXUserCart,那我们就可以把用户类型字符串拼接UserCart构成购物车Bean的名称,然后利用Spring的IoC容器,通过Bean的名称直接获取到AbstractCart,调用其process方法即可实现通用。

其实,这就是工厂模式,只不过是借助Spring容器实现罢了:

@GetMapping("right")publicCartright(@RequestParam("userId")intuserId){StringuserCategory=Db.getUserCategory(userId);AbstractCartcart=(AbstractCart)applicationContext.getBean(userCategory+"UserCart");returncart.process(userId,items);}试想,之后如果有了新的用户类型、新的用户逻辑,是不是完全不用对代码做任何修改,只要新增一个XXXUserCart类继承AbstractCart,实现特殊的优惠和运费处理逻辑就可以了?

这样一来,我们就利用工厂模式+模板方法模式,不仅消除了重复代码,还避免了修改既有代码的风险。这就是设计模式中的开闭原则:对修改关闭,对扩展开放。

2.利用注解+反射消除重复代码是不是有点兴奋了,业务代码居然也能OOP了。我们再看一个三方接口的调用案例,同样也是一个普通的业务逻辑。

假设银行提供了一些API接口,对参数的序列化有点特殊,不使用JSON,而是需要我们把参数依次拼在一起构成一个大字符串。

按照银行提供的API文档的顺序,把所有参数构成定长的数据,然后拼接在一起作为整个字符串。因为每一种参数都有固定长度,未达到长度时需要做填充处理:
字符串类型的参数不满长度部分需要以下划线右填充,也就是字符串内容靠左;数字类型的参数不满长度部分以0左填充,也就是实际数字靠右;货币类型的表示需要把金额向下舍入2位到分,以分为单位,作为数字类型同样进行左填充。对所有参数做MD5操作作为签名(为了方便理解,Demo中不涉及加盐处理)。比如,创建用户方法和支付方法的定义是这样的:

代码很容易实现,直接根据接口定义实现填充操作、加签名、请求调用操作即可:

publicclassBankService{//创建用户方法publicstaticStringcreateUser(Stringname,Stringidentity,Stringmobile,intage)throwsIOException{StringBuilderstringBuilder=newStringBuilder();//字符串靠左,多余的地方填充_stringBuilder.append(String.format("%-10s",name).replace('','_'));//字符串靠左,多余的地方填充_stringBuilder.append(String.format("%-18s",identity).replace('','_'));//数字靠右,多余的地方用0填充stringBuilder.append(String.format("%05d",age));//字符串靠左,多余的地方用_填充stringBuilder.append(String.format("%-11s",mobile).replace('','_'));//最后加上MD5作为签名stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));returnRequest.Post("http://localhost:45678/reflection/bank/createUser").bodyString(stringBuilder.toString(),ContentType.APPLICATION_JSON).execute().returnContent().asString();}//支付方法publicstaticStringpay(longuserId,BigDecimalamount)throwsIOException{StringBuilderstringBuilder=newStringBuilder();//数字靠右,多余的地方用0填充stringBuilder.append(String.format("%020d",userId));//金额向下舍入2位到分,以分为单位,作为数字靠右,多余的地方用0填充stringBuilder.append(String.format("%010d",amount.setScale(2,RoundingMode.DOWN).multiply(newBigDecimal("100")).longValue()));//最后加上MD5作为签名stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));returnRequest.Post("http://localhost:45678/reflection/bank/pay").bodyString(stringBuilder.toString(),ContentType.APPLICATION_JSON).execute().returnContent().asString();}}可以看到,这段代码的重复粒度更细:

三种标准数据类型的处理逻辑有重复,稍有不慎就会出现Bug;处理流程中字符串拼接、加签和发请求的逻辑,在所有方法重复;实际方法的入参的参数类型和顺序,不一定和接口要求一致,容易出错;代码层面针对每一个参数硬编码,无法清晰地进行核对,如果参数达到几十个、上百个,出错的概率极大。那应该如何改造这段代码呢?没错,就是要用注解和反射!

使用注解和反射这两个武器,就可以针对银行请求的所有逻辑均使用一套代码实现,不会出现任何重复。

要实现接口逻辑和逻辑实现的剥离,首先需要以POJO类(只有属性没有任何业务逻辑的数据类)的方式定义所有的接口参数。比如,下面这个创建用户API的参数:

@DatapublicclassCreateUserAPI{privateStringname;privateStringidentity;privateStringmobile;privateintage;}有了接口参数定义,我们就能通过自定义注解为接口和所有参数增加一些元数据。如下所示,我们定义一个接口API的注解BankAPI,包含接口URL地址和接口说明:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Inheritedpublic@interfaceBankAPI{Stringdesc()default"";Stringurl()default"";}然后,我们再定义一个自定义注解@BankAPIField,用于描述接口的每一个字段规范,包含参数的次序、类型和长度三个属性:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)@Documented@Inheritedpublic@interfaceBankAPIField{intorder()default-1;intlength()default-1;Stringtype()default"";}接下来,注解就可以发挥威力了。

如下所示,我们定义了CreateUserAPI类描述创建用户接口的信息,通过为接口增加@BankAPI注解,来补充接口的URL和描述等元数据;通过为每一个字段增加@BankAPIField注解,来补充参数的顺序、类型和长度等元数据:

@BankAPI(url="/bank/createUser",desc="创建用户接口")@DatapublicclassCreateUserAPIextendsAbstractAPI{@BankAPIField(order=1,type="S",length=10)privateStringname;@BankAPIField(order=2,type="S",length=18)privateStringidentity;@BankAPIField(order=4,type="S",length=11)//注意这里的order需要按照API表格中的顺序privateStringmobile;@BankAPIField(order=3,type="N",length=5)privateintage;}另一个PayAPI类也是类似的实现:

@BankAPI(url="/bank/pay",desc="支付接口")@DatapublicclassPayAPIextendsAbstractAPI{@BankAPIField(order=1,type="N",length=20)privatelonguserId;@BankAPIField(order=2,type="M",length=10)privateBigDecimalamount;}这2个类继承的AbstractAPI类是一个空实现,因为这个案例中的接口并没有公共数据可以抽象放到基类

通过这2个类,我们可以在几秒钟内完成和API清单表格的核对。理论上,如果我们的核心翻译过程(也就是把注解和接口API序列化为请求需要的字符串的过程)没问题,只要注解和表格一致,API请求的翻译就不会有任何问题。

以上,我们通过注解实现了对API参数的描述。接下来,我们再看看反射如何配合注解实现动态的接口参数组装:

第3行代码中,我们从类上获得了BankAPI注解,然后拿到其URL属性,后续进行远程调用。第6~9行代码,使用stream快速实现了获取类中所有带BankAPIField注解的字段,并把字段按order属性排序,然后设置私有字段反射可访问。第12~38行代码,实现了反射获取注解的值,然后根据BankAPIField拿到的参数类型,按照三种标准进行格式化,将所有参数的格式化逻辑集中在了这一处。第41~48行代码,实现了参数加签和请求调用。privatestaticStringremoteCall(AbstractAPIapi)throwsIOException{//从BankAPI注解获取请求地址BankAPIbankAPI=api.getClass().getAnnotation(BankAPI.class);bankAPI.url();StringBuilderstringBuilder=newStringBuilder();Arrays.stream(api.getClass().getDeclaredFields())//获得所有字段.filter(field->field.isAnnotationPresent(BankAPIField.class))//查找标记了注解的字段.sorted(Comparator.comparingInt(a->a.getAnnotation(BankAPIField.class).order()))//根据注解中的order对字段排序.peek(field->field.setAccessible(true))//设置可以访问私有字段.forEach(field->{//获得注解BankAPIFieldbankAPIField=field.getAnnotation(BankAPIField.class);Objectvalue="";try{//反射获取字段值value=field.get(api);}catch(IllegalAccessExceptione){e.printStackTrace();}//根据字段类型以正确的填充方式格式化字符串switch(bankAPIField.type()){case"S":{stringBuilder.append(String.format("%-"+bankAPIField.length()+"s",value.toString()).replace('','_'));break;}case"N":{stringBuilder.append(String.format("%"+bankAPIField.length()+"s",value.toString()).replace('','0'));break;}case"M":{if(!(valueinstanceofBigDecimal))thrownewRuntimeException(String.format("{}的{}必须是BigDecimal",api,field));stringBuilder.append(String.format("%0"+bankAPIField.length()+"d",((BigDecimal)value).setScale(2,RoundingMode.DOWN).multiply(newBigDecimal("100")).longValue()));break;}default:break;}});//签名逻辑stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));Stringparam=stringBuilder.toString();longbegin=System.currentTimeMillis();//发请求Stringresult=Request.Post("http://localhost:45678/reflection"+bankAPI.url()).bodyString(param,ContentType.APPLICATION_JSON).execute().returnContent().asString();log.info("调用银行API{}url:{}参数:{}耗时:{}ms",bankAPI.desc(),bankAPI.url(),param,System.currentTimeMillis()-begin);returnresult;}可以看到,所有处理参数排序、填充、加签、请求调用的核心逻辑,都汇聚在了remoteCall方法中。有了这个核心方法,BankService中每一个接口的实现就非常简单了,只是参数的组装,然后调用remoteCall即可。

//创建用户方法publicstaticStringcreateUser(Stringname,Stringidentity,Stringmobile,intage)throwsIOException{CreateUserAPIcreateUserAPI=newCreateUserAPI();createUserAPI.setName(name);createUserAPI.setIdentity(identity);createUserAPI.setAge(age);createUserAPI.setMobile(mobile);returnremoteCall(createUserAPI);}//支付方法publicstaticStringpay(longuserId,BigDecimalamount)throwsIOException{PayAPIpayAPI=newPayAPI();payAPI.setUserId(userId);payAPI.setAmount(amount);returnremoteCall(payAPI);}其实,许多涉及类结构性的通用处理,都可以按照这个模式来减少重复代码。

反射给予了我们在不知晓类结构的时候,按照固定的逻辑处理类的成员;而注解给了我们为这些成员补充元数据的能力,使得我们利用反射实现通用逻辑的时候,可以从外部获得更多我们关心的数据。

3.利用属性拷贝工具消除重复代码最后,我们再来看一种业务代码中经常出现的代码逻辑,实体之间的转换复制。

对于三层架构的系统,考虑到层之间的解耦隔离以及每一层对数据的不同需求,通常每一层都会有自己的POJO作为数据实体。比如,数据访问层的实体一般叫作DataObject或DO,业务逻辑层的实体一般叫作Domain,表现层的实体一般叫作DataTransferObject或DTO。

这里我们需要注意的是,如果手动写这些实体之间的赋值代码,同样容易出错。

对于复杂的业务系统,实体有几十甚至几百个属性也很正常。就比如ComplicatedOrderDTO这个数据传输对象,描述的是一个订单中的几十个属性。如果我们要把这个DTO转换为一个类似的DO,复制其中大部分的字段,然后把数据入库,势必需要进行很多属性映射赋值操作。就像这样,密密麻麻的代码是不是已经让你头晕了?

ComplicatedOrderDTOorderDTO=newComplicatedOrderDTO();ComplicatedOrderDOorderDO=newComplicatedOrderDO();orderDO.setAcceptDate(orderDTO.getAcceptDate());orderDO.setAddress(orderDTO.getAddress());orderDO.setAddressId(orderDTO.getAddressId());orderDO.setCancelable(orderDTO.isCancelable());orderDO.setCommentable(orderDTO.isComplainable());//属性错误orderDO.setComplainable(orderDTO.isCommentable());//属性错误orderDO.setCancelable(orderDTO.isCancelable());orderDO.setCouponAmount(orderDTO.getCouponAmount());orderDO.setCouponId(orderDTO.getCouponId());orderDO.setCreateDate(orderDTO.getCreateDate());orderDO.setDirectCancelable(orderDTO.isDirectCancelable());orderDO.setDeliverDate(orderDTO.getDeliverDate());orderDO.setDeliverGroup(orderDTO.getDeliverGroup());orderDO.setDeliverGroupOrderStatus(orderDTO.getDeliverGroupOrderStatus());orderDO.setDeliverMethod(orderDTO.getDeliverMethod());orderDO.setDeliverPrice(orderDTO.getDeliverPrice());orderDO.setDeliveryManId(orderDTO.getDeliveryManId());orderDO.setDeliveryManMobile(orderDO.getDeliveryManMobile());//对象错误如果不是代码中有注释,你能看出其中的诸多问题吗?

如果原始的DTO有100个字段,我们需要复制90个字段到DO中,保留10个不赋值,最后应该如何校验正确性呢?数数吗?即使数出有90行代码,也不一定正确,因为属性可能重复赋值。

有的时候字段命名相近,比如complainable和commentable,容易搞反(第7和第8行),或者对两个目标字段重复赋值相同的来源字段(比如第28行)

明明要把DTO的值赋值到DO中,却在set的时候从DO自己取值(比如第20行),导致赋值无效。

这段代码并不是我随手写出来的,而是一个真实案例。有位同学就像代码中那样把经纬度赋值反了,因为落库的字段实在太多了。这个Bug很久都没发现,直到真正用到数据库中的经纬度做计算时,才发现一直以来都存错了。

修改方法很简单,可以使用类似BeanUtils这种Mapping工具来做Bean的转换,copyProperties方法还允许我们提供需要忽略的属性:

ComplicatedOrderDTOorderDTO=newComplicatedOrderDTO();ComplicatedOrderDOorderDO=newComplicatedOrderDO();BeanUtils.copyProperties(orderDTO,orderDO,"id");returnorderDO;总结第一种代码重复是,有多个并行的类实现相似的代码逻辑。我们可以考虑提取相同逻辑在父类中实现,差异逻辑通过抽象方法留给子类实现。使用类似的模板方法把相同的流程和逻辑固定成模板,保留差异的同时尽可能避免代码重复。同时,可以使用Spring的IoC特性注入相应的子类,来避免实例化子类时的大量if…else代码。

第二种代码重复是,使用硬编码的方式重复实现相同的数据处理算法。我们可以考虑把规则转换为自定义注解,作为元数据对类或对字段、方法进行描述,然后通过反射动态读取这些元数据、字段或调用方法,实现规则参数和规则定义的分离。也就是说,把变化的部分也就是规则的参数放入注解,规则的定义统一处理。

第三种代码重复是,业务代码中常见的DO、DTO、VO转换时大量字段的手动赋值,遇到有上百个属性的复杂类型,非常非常容易出错。我的建议是,不要手动进行赋值,考虑使用Bean映射工具进行。此外,还可以考虑采用单元测试对所有字段进行赋值正确性校验。

【WINDRISES EMPLOYMENT PROGRAMMING】尊享对接老板

电话+V:159999-78052

机构由一批拥有10年以上开发管理经验,且来自互联网或研究机构的IT精英组成,负责研究、开发教学模式和课程内容。公司具有完善的课程研发体系,一直走在整个行业发展的前端,在行业内竖立起了良好的品质口碑。

写完代码 头晕
发布人:q511973469 发布时间:2024-10-08