最近在Java上踩的坑

前言

换工作也有两个多月了,在新环境中无论说是站着前人的肩膀上也好,还是说踩前人挖的坑也罢,总结一下总是没错的。

项目规范

README

作为一个新人,刚进项目拿到代码,特别是当下流行的微服务体系,一个项目十几个微服务。也不可能有人有耐心告诉你每个微服务是干嘛的,核心功能是什么,是如何与其他微服务或第三方交互的。流水的程序员,铁打的项目。所以这时候READEME就显得特别重要了。README里面可以描述项目的核心业务、编码规范、工具类、启动参数等等,让新人拿到项目起码有个宏观的认知,而不是看到的只有一堆冰冷的代码,甚至连注释都没。
第二个月时,我负责一个新项目的开发,处理好项目结构后,第一件事就把README写了,当然README也是需要时时更新维护的。
在这里插入图片描述

MVC

SpringBoot对于MVC没有显性的配置,通过spring-boot-starter-web完成自动配置。但并不代表在编码中不需要遵守MVC的编码规范。以下是错误示范

@Api(description = "客户合同管理", value = "客户合同管理")
@RequestMapping("/customerContract")
public interface CustomerContractService extends IService<CustomerContract> {
    
    
    /**
     * 新增
     *
     * @param addCustomerContractDTO
     * @return
     */
    @PostMapping(value = "/saveCustomerContract")
    JsonResult<Object> saveCustomerContract(@Valid @RequestBody AddCustomerContractDTO addCustomerContractDTO);
}
---------------------------------------------------------------------------------
@Api(description = "客户合同管理", value = "客户合同管理")
@Slf4j
@RestController
public class CustomerContractServiceImpl extends CommonService<CustomerContractDao, CustomerContract>
        implements CustomerContractService, CustomerContractRemoteService {
    
    
    @Override
    public JsonResult<Object> saveCustomerContract(AddCustomerContractDTO addCustomerContractDTO) {
    
    
    	......
    }
}

在接口中通过@RequestMapping来指定路径,然后把实现类当成@RestController,简便了开发但混淆了MVC的传统分层,我认为是得不偿失的。

Feign的本地熔断

大型微服务项目中,如果没有处理好熔断和限流机制,容易产生服务的雪崩,最后导致宕机。FeignClient提供了FallbackFactory,当远程调用出现超时或错误时,会执行本地的fallback方法进行兜底,防止出现长时间的等待从而拖垮服务器的性能。在新项目中缺失了FallbackFactory,所有的Feign接口都没有做调用失败的处理。这一块的优化必须排上日程了。

代码规范

访问修饰符

对变量使用的访问修饰符不严谨或者缺少访问修饰符,比如自动注入的接口,只在本类使用,应该再上private修饰符,下面是错误示范
在这里插入图片描述

日志的打印

对外的接口,入参的报文是一定要完整打印或者存储的,方便问题的排查。对于日志的拼接,使用占位符的方式会使得代码比较简洁,不推荐直接使用字符串拼接。

// 正例
log.info("中移动下单请求参数 = {}", JSON.toJSONString(string));
// 反例
log.info("中移动下单请求参数 = " + JSON.toJSONString(string));

有很多人觉得,用占位符可以减少字符串拼接的性能损耗,但并不是的。来看一下使用占位符的日志拼接源码。

public static final FormattingTuple arrayFormat(String messagePattern, Object[] argArray, Throwable throwable) {
    
    
        if (messagePattern == null) {
    
    
            return new FormattingTuple((String)null, argArray, throwable);
        } else if (argArray == null) {
    
    
            return new FormattingTuple(messagePattern);
        } else {
    
    
            int i = 0;
            StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);

            for(int L = 0; L < argArray.length; ++L) {
    
    
                int j = messagePattern.indexOf("{}", i);
                if (j == -1) {
    
    
                    if (i == 0) {
    
    
                        return new FormattingTuple(messagePattern, argArray, throwable);
                    }

                    sbuf.append(messagePattern, i, messagePattern.length());
                    return new FormattingTuple(sbuf.toString(), argArray, throwable);
                }

                if (isEscapedDelimeter(messagePattern, j)) {
    
    
                    if (!isDoubleEscaped(messagePattern, j)) {
    
    
                        --L;
                        sbuf.append(messagePattern, i, j - 1);
                        sbuf.append('{');
                        i = j + 1;
                    } else {
    
    
                        sbuf.append(messagePattern, i, j - 1);
                        deeplyAppendParameter(sbuf, argArray[L], new HashMap());
                        i = j + 2;
                    }
                } else {
    
    
                    sbuf.append(messagePattern, i, j);
                    deeplyAppendParameter(sbuf, argArray[L], new HashMap());
                    i = j + 2;
                }
            }

            sbuf.append(messagePattern, i, messagePattern.length());
            return new FormattingTuple(sbuf.toString(), argArray, throwable);
        }
    }

从源码中可以发现,对于使用占位符加可变参数的方式进行日志拼接,源码的处理是遍历日志模板,取出占位符来进行替换,看到这里我觉得在性能上不一定会比直接拼接字符串的性能高,于是做了一个测试。

    @Test
    public void testStringLog() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            log.info("测试" + "字符串" + "拼接");
        }
    }

    @Test
    public void testPlaceholder() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            log.info("测试{}拼接", "字符串");
        }
    }

执行结果如下
在这里插入图片描述
在这里插入图片描述
时间实际上是差不多的,甚至多次执行的结果,字符串拼接由于String缓存池的存在,速度还会比占位符更快一些。但出于项目代码的统一规范,还是推荐使用占位符来进行日志的打印。最后说一下日志级别,根据不同环境的日志级别,对于代码中的日志输出也要有相应的处理,比如业务流程出现异常,日志级别要满足在生产也能输出的条件。

数据库规范

表规范

如果项目是微服务体系,建议在表名前加上服务名,如OMS的订单表ORDER,建议命名为OMS_ORDER。一是见名知意,看到实体就能知道对应的服务,二是可能其他服务也存在订单表,容易发生歧义。

表字段规范

1.主键: 不建议直接使用ID作为主键,而是使用表名加ID,如果订单表ORDER的主键为ORDER_ID。因为当主键作为其他表外键时,在涉及BEAN COPY时,ID字段的值会赋值错误,比如订单表ORDER的主键为ID,在订单详情表中主键也为ID,外键为ORDER_ID,那么BEAN COPY是,ID的赋值就出现错误了。主键的值不建议使用数据库自增,推荐雪花算法,如果使用数据库自增,那么数据库主键的类型必须为bigint,如果只是int,在业务量飞速增长的年代,很有可能出现超过21亿的数据,到时候再来重构,就很头疼了。
2.金额: 首先数据库所有的金额最好是统一单位,出现过有的表存储的是分,有的表存储的厘,在开发过程中简直是折磨人。金额的存储比较主流的有两种方式,一是直接存储小数,类型是BigDecimal,二是存储最小单位,比如厘,可以避免在计算过程中的精度丢失。存储小数的优点是返回给前端可以直接展示,不需要做转换。存储最小单位使用与需要大量计算的场景,可以提高数据的准确性。
3.注释: 数据库的注释一定要准确,如有修改也必须时时更新,包括代码实体中的注解,特别是数据字典类型的属性,否则在开发中难免会踩坑。

职业素养

写好每一行代码,写好每一句注解。我觉得程序员要对生产环境抱有敬畏之心,别让自己的BUG给别人带来麻烦。注解和代码一样重要,注解不仅是给下一个接手的人看的,也是给自己看的,在排查问题的时候,几行注解能帮助快速定位问题,所以代码的优化也包括补齐关键节点、里程碑节点的注解。最后说一下代码重构,这是一件我很热衷的事。程序员多多少少都有点代码的洁癖,觉得别人的代码写的看不惯,不符合自己的风格和逻辑,那既然接手了,为何只是抱怨而不去重构呢?未上生产的代码,大胆重构,已经在生产的代码,小心重构,充分测试。当然,风险是有,我也因为重构生产代码发生过生产事故,但这不是你拒绝重构代码的理由,而是作为下一次重构代码的宝贵经验,哪些地方没有考虑到,哪些地方需要更充分的测试。

猜你喜欢

转载自blog.csdn.net/weixin_43776741/article/details/108068842