当前位置: 首页 > 科技观察

京东资深架构师CodeReview错诗

时间:2023-03-12 10:02:48 科技观察

贾岩架构师说,用20个字来形容CodeReview的内容,就是自省、自省。因为每个词都有一个意思,所以不连贯。为了增加趣味性,每个句子都有相应的变形。这只是对常见评论的描述。如有不详欢迎补充!越重的测试越重要——严焕空MoonBug测试:公共方法必须检查参数,如果参数检查失败,则显式抛出异常或相应的响应代码。Javabeanvalidation已经是很古老的技术了,它会为我们避免很多问题,请参考:http://beanvalidation.org/http://www.infoq.com/cn/news/2010/03/javaee6-validationhttps://www.sitepoint.com/using-java-bean-validation-method-parameters-return-values/接口中,也显式使用validation注解修改参数和返回值。作为一个协议,调用者需要按照validationannotations约束参数传递,返回值校验AnnotationConstraintprovider根据注解要求返回参数Magic:代码中避免magicnumbers,magicnumbers可以定义为枚举或者常量来增强其可读性a.equals(b)应该把左边的常量aInteger==10。如果aInteger为空,会抛出空指针异常。如果不确定返回的集合是否为空,则必须进行非空判断,然后for循环使用empty对象方式,约定返回一个空集合,而不是null。使用StringUtils判断一个字符串是否不为空:如果该方法传入一个数组下标作为参数,一开始就检查下标是否越界,避免下标越界异常:不要写重复代码,使用重构工具提取重构工具重构代码。增加可读性,也可以引导我们在命名过程中思考方法/变量/类的职责是否恰当和有意义。典型的无意义命名:publicstaticfinalIntegerCODE_39120=39120;publicstaticfinalStringMESSAGE_39120="【包裹】与【仓号】不符,确定要打包吗?";publicstaticfinalIntegerCODE_39121=39121;publicstaticfinalStringMESSAGE_39121="【包裹】与【箱号】发货不同types,areyousureyouwanttopack?";Rulerule1=request.getRuleMap().get("1050");CODE_39120这个名字和幻数没有太大区别Follow:不要在循环中调用服务,不要在循环中做数据库等跨网络操作可能达到多少,如果调用频率高,必须考虑性能指标,考虑是否会不会破坏数据库,是否会破坏缓存区别:异常处理是程序员最基本的素质,不要处处捕获异常,对于只写日志的捕获,有没有对于任何处理catch,问问自己这样吃异常是否合理这样用户看不到自己真正想要的是什么,研发只能通过查看日志来发现错误,而“读日志”通常只有在业务方反馈问题时才会去读,这会导致研发人员比现场人员更快地发现错误。会晚了。@RequestMapping(value="/export")publicvoidexport(CityRelationDomaincondition,HttpServletResponseresponse){ZipOutputStreamzos=null;BufferedWriterbufferedWriter=null;try{condition.setStart(0);condition.setSize(MAX_EXPORTityServicegetder_LINES);Listlist=(条件);response.setCharacterEncoding("GBK");response.setContentType("multipart/form-data");response.setHeader("Content-Disposition","attachment;fileName=export.zip");zos=newZipOutputStream(response.getOutputStream());bufferedWriter=newBufferedWriter(newOutputStreamWriter(zos,"GBK"));bufferedWriter.write("订单类型代码,始发市-省,始发市-市,目的市-省,目的市-市");ZipEntryzipEntry=newZipEntry("export.csv");zos.putNextEntry(zipEntry);for(CityRelationDomaindomain:list){try{bufferedWriter.newLine();bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getOrderCode()));bufferedWriter.write(',');bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getProvinceNameFrom()));bufferedWriter.write(',');bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getCityNameFrom()));bufferedWriter.write(',');bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getProvinceNameTo()));bufferedWriter.write(',');bufferedWriter.write(CSVExportUtil.trans2CSV(domain.getCityNameTo()));}catch(Exceptione){e.printStackTrace();}}bufferedWriter.newLine();bufferedWriter.flush();zos.closeEntry();bufferedWriter.close();}catch(Exceptione){e.printStackTrace();logger.error("导出CSV文件异常");}finally{try{if(zos!=null){zos.close();}if(bufferedWriter!=null){bufferedWriter.close();}}catch(IOExceptione){e.printStackTrace();}}}Long:如果一行代码太长,需要分解;如果方法太长,则需要重构;当调用外部依赖时,需要弄清楚外部依赖能够提供的性能指标。最好就SLA轮子达成一致:不要重新发明轮子。如果已经有实现类似功能的成熟类库,则优先使用成熟类库的方法。这是因为成熟的类库中的方法都经过了很多人的测试和验证。通常,我们自己实现的质量绝对等同于成熟类库的质量。行:要注意我们的jsf服务,web应用,消费消息的worker都是多线程环境。一定要注意线程安全问题。最典型的HashMap、SimpleDateFormat、ArrayList都不是线程安全的。另外,如果你使用了Spring的自动扫描服务,那么这个服务默认是单例的,它的内部成员是多线程共享的。如果直接使用成员变量,是线程不安全的。两个典型的错误代码片段:IgnoreSimpleDateFormatNon-thread-safe@ServicepublicclassAService{privatestaticfinalSimpleDateFormatFORMAT=newSimpleDateFormat("yyyy-MM-dd");publicvoiddoSomething(){//useFORMAT}}使用Service成员变量@ServicepublicclassBService{privatePojob;publicvoiddoB(){b=getB();process(b);}}Day:打印日志,设置合理的日志级别。如果需要,可以添加一个if条件来限制是否打印日志。日志中使用JSON序列化生成长字符串toString()必须带if限制打印,否则达不到配置的日志级别,会拼接大量字符串,占用gc年轻代内存.另外需要通过log4j打印日志,而不是直接打印日志到控制台。典型错误示例:@ServicepublicclassFooService{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(FooService.class);publicvoiddoFooThing(Foofoo){LOGGER.debug("getparameterfoo{}",JSONObject.toString(foo));try{/*dosomething*/}catch(Exceptionex){ex.printStackTrace();}}}简单:保持整体设计尽可能简单,方法实现尽可能简单。根据情况使用内存缓存、redis缓存、jmq异步处理。这里的简需要保持良好的分寸。耦合分为强-洁和正粉妆连接:接口用于隔离变化。如果一个业务有多个不同的表单,但是它们都有相同的处理,那么可以定义接口来隔离不同的业务表单。在服务调用处,可以通过业务类型字段获取不同的服务类。不要实现一个类,然后在类的每个方法中根据业务类型做ifelse或者更复杂的判断。典型示例:做法1:publicinterfaceBarService{voiddoBarThing(Barb);voiddoBarFatherThing(Barb);}publicclassBarServiceImplimplementBarService{publicvoiddoBarThing(Barb){if(b.getType()==BarType.A){//dosomelogic}else(b.getType()==BarType.B){//dosomeBtypelogic}//dootherdoBarThinglogic}publicvoiddoBarFatherThing(Barb){if(b.getType()==BarType.A){//dosomelogic}else(b.getType()==BarType.B){//dosomeBtypelogic}//dootherdoBarFatherThinglogic}}做法2:publicinterfaceBarService{voiddoBarThing(Barb);voiddoBarFatherThing(Barb);}publicclassBarServiceFactory{publicBarServicegetBarService(BarTypetype){//getbarservicelogic}}//如果有公共存在,就定容就不确定publicclassBaseBarServiceimplementBarService{publicvoiddoBarThing(Barb){//dootherdoBarThinglogic}publicvoiddoBarFatherThing(Barb){//dootherdoBarFatherThinglogic}}publicclassTypeABarServiceextendsBaseBarServiceimplementBarService{publicvoiddoBarThing(Barb){//doATypeThingsuper.doBarvoid(doB);}//dobartypeAservicesuper.doBarFatherThing(b);//有必要就调用,不需要就不要调用父类}}方法2的好处是解耦了不同类型的逻辑,分别开发,互不影响。不需要影响现有类型的逻辑耦合:识别系统之间的耦合关系,两个系统之间通过同步数据进行交互是强耦合关系,这会使数据接收方依赖于数据发送方的数据库定义,如果发送端想改变数据结构,必须要求下游接收端一起修改;通过接口调用是一种常见的系统耦合关系,接口的提供者必须保证接口的可用性,接口的调用者必须考虑接口不可用时的响应解决方案;mq消息是一种解耦方式,两个系统之间不存在实时耦合关系。但是不能滥用mq解耦的方法。不建议在同一个系统中使用过多的mq消息进行异步。需要尽可能保证接口的性能,而不是使用mq来防止出现问题后重新消费。正向:模块之间的依赖必须是前向依赖,底层模块不能依赖上层模块;数据层不能依赖服务层,服务层不能依赖UI层;模块之间也不能形成循环依赖。分:分而治之。复杂的问题应该分解成几个相对简单的问题来解决。先分析核心问题,再分析核心输入参数是什么,结果是什么。只需几步即可更改输入参数以获得结果。强:时刻关注程序的健壮性,从两个方面提高健壮性:契约,设计接口时定义协议参数,实现时第一时间验证参数,如果参数有问题,直接返回给调用者如果有异常情况,还需要根据异常情况约定的响应策略考虑各种边界条件的输出,比如运单号查询服务,用户输入错误运单时如何返回,有边界查询条件,如果用户查询条件超出了边界,应该返回什么给设计失败,如果出了问题,有降级的解决方案。作者:赵玉凯,拥有十余年互联网研发经验,2013年加入京东,任运营研发部架构师。物流系统业务和架构经验。在此之前,他在和讯网负责股票基金行情系统的研发,有高并发、高可用互联网应用的研发经验。【本文来自专栏作者张凯涛微信公众号(凯涛的博客)公众号id:kaitao-1234567】点此查看作者更多好文