前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >开发注意事项

开发注意事项

原创
作者头像
用户9704972
发布2022-04-27 00:40:22
8370
发布2022-04-27 00:40:22
举报
文章被收录于专栏:用户9704972的专栏

目录

代码语言:javascript
复制
ArrayList<Object> objects = Lists.newArrayList();
HashMap<Object, Object> objectObjectHashMap = Maps.newHashMap();
HashSet<Object> objects1 = Sets.newHashSet();
Map<String, Object> map = new HashMap<>(16);
HashMap<Object, Object> objectObjectHashMap = Maps.newHashMapWithExpectedSize(16);
StringUtils  org.apache.commons.lang3
CollectionUtils  org.apache.commons.collections4.CollectionUtils
MapUtils  org.apache.commons.collections4.CollectionUtils
Collections.emptyList();
Collections.emptySet();
Collections.singletonList();
安全容器
List<SupplierTodoDTO> result = Collections.synchronizedList(new LinkedList<>());
CopyOnWriteArrayList<Object> objects = new CopyOnWriteArrayList<>();
代码语言:javascript
复制
@NotNull(message = "操作人misId不合法")  用于判断基础数据类 对象等
@NotBlank(message = "配置名称不能为空")  判断String
@NotEmpty(message = "配置列表不能为空") 集合类
@Length(min = 1, max = 100, message = "标题不合法") String类型长度
@Range(min = 0, max = 1, message = "force字段不合法")   int等的范围
@Valid
@NotNull(message = "分页信息不能为空")
private Page paging;
代码语言:javascript
复制
List<AnnouncementFirstListResponseVO> sortedUnRead = unReadList.stream().map(item -> Pair.of(item.getAnnouncementVO().getId(), item))
                .sorted(Comparator.comparing(Pair::getLeft))
                .map(Pair::getRight)
                .collect(Collectors.toList());
sortedSupplierTodoDTOList = sortedSupplierTodoDTOList.stream()
                    .filter(Objects::nonNull)
                    .sorted(Comparator.comparing(SupplierTodoDTO::getOrderId, Comparator.reverseOrder()).thenComparingInt(SupplierTodoDTO::getTaskId))
                    .collect(Collectors.toList());

1.遇到排查没有结论的问题,首先怀疑主从延迟

2.任何书,要介绍某东西,都是先说认知,背景知识,在说怎么做。

3.在和他人沟通时,要达成一致,统一认知

4.要从全局去思考,比如这次对于成功率的定义,就是成功的/总量,拆的太散显然是不合理的。稍微有变动就会导致成功率不准确。

常用事务,重试,缓存注解

@Retryable(maxAttempts = 15, backoff = @Backoff(delay = 100L)) @Transactional(rollbackFor = Exception.class)

@Cacheable

乐观锁,事务失效

https://km.sankuai.com/page/1285988895

并发问题注意:代码逻辑和并发要分开(不要因为并发修改代码逻辑,在逻辑之外加代码去解决技术实现问题)

1 工作注意

1.事务注解必须要跨类才能生效,原理是通过aop。@EnableAspectJAutoProxy本类也可以生效,注意必须是public方法,private代理失败

代码语言:javascript
复制
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
((MessageSendServiceImpl)AopContext.currentProxy()).saveTaskAndDetail(batchTask, batchMessageDetails);
@Transactional(rollbackFor = Exception.class)
    public void saveTaskAndDetail(BatchTask batchTask, List<BatchMessageDetail> batchMessageDetails) {
        batchTaskService.createBatchTask(batchTask);
        batchMessageDetailRepository.add(batchMessageDetails);
    }

2.注意慢sql,要走索引

3.并发问题:多个线程操作同一份资源就会产生。不要去管这块会不会产生并发问题。要抓住本质就是可能会有多个线程操作同一份资源。杜绝隐藏问题的发生

4.代码逻辑和并发要分开(不要因为并发修改代码逻辑,在逻辑之外加代码去解决技术实现问题)

5.命名问题,代码逻辑性问题,站在业务角度去理解优化代码。

6.要有目标和问题,去思考和检验自己。项目周期各个节点

7.thrift接口记得加@ThriftField注解

8、上线时间变动在群里通知,手头事项安排,不能按照预期完成及时给TL通报

9、重试注解,事务注解启动类

代码语言:javascript
复制
@EnableTransactionManagement(proxyTargetClass = true)
@EnableRetry
类上面的注解
@Retryable(backoff = @Backoff(delay = 100))

1.1 git注意不要commit多次

https://km.sankuai.com/page/1273326292

1.2 事务的使用

事务注解必须要跨类才能生效,原理是通过aop。@EnableAspectJAutoProxy本类也可以生效,注意必须是public方法,private代理失败。

事务本类生效三个要点:1.使用注解@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)开启代理

2.((ResultNotifyServiceImpl) AopContext.currentProxy()) 手动获取当前类的代理

3.必须是public方法,private代理失败。

1.3 代码review

代码语言:javascript
复制
1.出入参加日志
2.重点:数据库:性能和sql的正确性
3.profile:线上线下哪里应该一致,哪里应该不同
4.新增文件看性能。修改原来的文件看影响 try catch
5.每一个文件,每一行代码都要过到
6.数据库修改 加代码,,出入对称(新增字段)
7.早判断,早结束。避免多层if else循环嵌套。一个if不通过,直接return,避免里面继续嵌套if else
8.if判断常量放前面
9.需要有默认值,默认值以default开头,StringUtils.defaultString(业务逻辑值可能为空,默认值)
10.但凡是状态,必须用枚举

1.4 代码质量

(不要老想着当前场景这样做没有问题。要从整个事的影响来考虑,会不会影响到这个地方。单从目前情况来看,可能没有问题,但是一旦发生问题确实会有影响。比如本次监控大盘更新消息发送结果的时候,之前代码逻辑是new了MessageRecordPO然后去更新,但是更改了逻辑,用了select先从库里查出来。但从功能上来看没有什么问题,但是确实造成了影响,后续迭代发展这里可能埋下隐患)

代码语言:javascript
复制
类名,方法名,常量,异常处理
参数校验尽量提取能抽到一个方法抽取到一个方法中
方法和类上一定要有注释
清楚职能
a调b,b调c,c调d,d调f  特别糟糕
应该是有一个主方法:
a   调  b,c,d,f
提供代码质量:一开始进行类设计的时候没有梳理好,没有想清楚每个类和方法的职能,
就是一条线顺着写下去,a方法调b,b调c,c调d,光想着去实现功能。如何解决:想清楚类的职能,
先用一句话去给同事讲明白这个类要做什么,以及方法是干什么,然后再用三个英文单词做总结提炼。
方法的命名过长可以根据返回值和入参这些信息对命名做精简。想清楚这些,代码总体结构就会好很多。

细节点优化:

1.代码中避免出现魔法字符串,用常量代替

2.代码中的状态码使用枚举类

3.参数校验尽量提到前面,校验不通过早结束

4.使用lambda表达式可以使代码更精简

5.使用Assert做校验

6.bean注入属性值的时候,可以用构造器,避免多个set

7.三元运算符可以使代码更精简

8.注意命名,思考命名的本质,命名是有确切的业务含义的(比如监控大盘打点的目的是为了上报数据,方法名叫上报就比叫打点好很多)

9.避免多个if else嵌套太深。 可以使用三元运算符减少if else。if判断可以一个一个进行判断,一个不通过直接return,避免嵌套太深。

(这里和批量个性化触达需求场景不一样,业务需求导致判断完一个接着判断下面的,判断完所有才return。正常都是校验不通过直接return,不会接着往下判断,不要被此影响)

10.避免重复代码

11.if判断常量放前面,避免可能为空的情况

12.set,map,list等使用工具类去创建,haspmap需要指定初始化大小。

13.避免在定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。(反例:POJO 类的 createTime 默认值为 new Date(),但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。)

14.包装类和基本数据类型的使用。包装类:所有的 POJO 类属性必须使用包装数据类型。RPC 方法的返回值和参数必须使用包装数据类型。基本数据类型:所有的局部变量使用基本数据类型。详情见3.3

代码语言:javascript
复制
ArrayList<Object> objects = Lists.newArrayList();
HashMap<Object, Object> objectObjectHashMap = Maps.newHashMap();
HashSet<Object> objects1 = Sets.newHashSet();
Map<String, Object> map = new HashMap<>(16);
HashMap<Object, Object> objectObjectHashMap = Maps.newHashMapWithExpectedSize(16);

15.ctime,utime在仓库层赋值(只做sql的插入时间,更新时间,如果业务上确实有用到时间字段,推荐新建一列begin_time等去做业务逻辑)。数据库时间列使用ddatetime类型,程序中使用 java.util.Date 。

1.5 事项安排,上线时间

1、上线时间变动在群里通知

2、手头事项安排,不能按照预期完成及时给龙哥通报

1.6 多数据源配置

https://km.sankuai.com/page/1295532911

1.7 工具类

https://km.sankuai.com/page/306914235

2 业务积累

2.1 通用已读功能梳理

1.https://km.sankuai.com/page/1293004195

2.管控梳理 todo

3.触达任务拆分管控 todo

2.2 触达的本质

触达的本质是在什么样的场景下,通过什么通道发送什么内容给什么对象。最后达到什么样的目标

3 个人成长

1.从全局整体进行思考,比如消息结果异步通知,crane扫描到达终止状态的数据,发送通知后更新标识为发送过,从技术实现上当然可以,但是不合理。为什么会把已经终态的数据从新扫描出来。

2.现阶段其实学习太多没有呢么重要,重要的是多思考,把事情想明白和透彻。总结和输出文档的时候,把事情想明白说明白的时候都是思考的过程。设计文档的目标,解决什么问题。

3.在消息未进入触达系统之前,由mafka死信保证,消息一定会进入触达系统。在落库到触达系统后,由crane保证消息的状态一直向前推进。调用message成功通知由message触发,调用失败会重试,明确返回失败或者名字管控并且拒绝下发由processor保证。

4.做事一定要有逻辑,1,2,3步,要带着目标和问题

5.spring加载配置类ConfigurationClassParser

6.阿里规约,设计模式,mysql优化,锁,jvm,springcloud,谷粒商城,spring源码

设计模式:https://km.sankuai.com/page/1288809237 mysql:https://km.sankuai.com/page/1214623209

4 索引设计

区分度高的放前面。即使是唯一索引,不用根据业务去层级递进,直接区分度高的放前就行

对于有关时间字段的复合索引的设计。比如status ctime 如果status的区分度足够高,是不需要给ctime加索引的。比如status = x唯一个异常状态,一千条数据只会出现一两次,区分度很高就不需要加索引

如果status 的区分度不高,呢么就可以给ctime加索引。比如触达发消息,发送成功的消息状态为2 如果有业务需求需要查询状态为2的数据,就需要加索引

5 其他

5.1 mybatis select和selective的区别

代码语言:javascript
复制
如果选择insert 只设置了部分值,比如id,name,age  只设置name

insert into tb_user (id,name,age) value (null,"张三",null);

选择insertSelective

insert into tb_user (id,name) value (null,"张三");  主键通常自增

如果age有默认值,设置为默认值,没有默认值允许为null,则为null,不允许为null,还没有设置报错

updateSelective和update同理

代码语言:javascript
复制
update user
    <set>
      <if test="name != null">
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="pwd != null">
        pwd = #{pwd,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
代码语言:javascript
复制
 update user
    set name = #{name,jdbcType=VARCHAR},
      pwd = #{pwd,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}

updateByPrimaryKeySelective会对字段进行判断再更新(动态sql)(如果为Null就忽略更新)

updateByPrimaryKey对你注入的字段全部更新,如果为字段不更新,数据库的值就为默认值。如果更新还设置null 就会报错,即使数据库默认值为null,因为sql检查过不去

insertSelective同理 如果数据库设置非空并设置了默认值,用了insert 插入会报错,但是用insertSelective 插入会是默认值

selective可以实现动态的查询

代码语言:javascript
复制
    <select id="countByParam" parameterType="com.meituan.groceryscm.sqm.notification.os.service.adapter.dal4vss.param.QueryAnnouncementListParam"
            resultType="java.lang.Integer">
        select count(*) from announcement_info a
        <where>
            <if test="creator !=null and creator !=''">
                AND a.creator =#{creator}
            </if>
            <if test="title !=null and title !='' ">
                AND a.title like concat(#{title},'%')
            </if>
            <if test="receivers !=null and receivers.size() > 0">
                AND a.receiver IN
                <foreach collection="receivers"  item="receiver" open="(" separator="," close=")">
                    #{receiver}
                </foreach>
            </if>
            <if test="beginTime != null and beginTime > 0 and endTime != null and endTime>0">
                and a.begin_time >= #{beginTime,jdbcType=BIGINT}
                and #{endTime,jdbcType=BIGINT} >= a.begin_time
            </if>
            <if test="announcementType != null and announcementType > 0">
                and a.announcement_type = #{announcementType,jdbcType=BIGINT}
            </if>
            <if test="tag != null and tag > 0">
                AND a.tag =#{tag}
            </if>
            AND valid = 1
        </where>
    </select>

比如根据id查询 name 和age 可以不传为null

可以根据 id 和 name 查询 age为null,任意的组合

目前我们的系统 processor使用mybatis 且是select和update的方式没有使用动态sql。os系统使用mybatis且动态sql的方式

message系统使用 mybatisplus 的方式本质也是动态sql

5.2 cookie和session

https://www.cnblogs.com/ityouknow/p/10856177.html

5.3 基本数据类型和包装类型的使用

关于基本数据类型与包装数据类型的使用标准如下:

避免在定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。(反例:POJO 类的 createTime 默认值为 new Date(),但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。)

包装类和基本数据类型的使用。包装类:所有的 POJO 类属性必须使用包装数据类型。RPC 方法的返回值和参数必须使用包装数据类型。基本数据类型:所有的局部变量使用基本数据类型。

说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。

正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。

反例:某业务的交易报表上显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线-。所以包装数据

类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。

5.4 聚簇索引和非聚簇索引的区别

https://zhuanlan.zhihu.com/p/366972218

5.5 强制查主库

https://km.sankuai.com/page/1071229389

代码语言:javascript
复制
        // 获取品退商家
        List<Long> refundSupplierByCalculateDateList;
        try {
            // 强制走主库
            ZebraForceMasterHelper.forceMasterInLocalContext();
            refundSupplierByCalculateDateList = refundSupplierRepository.searchRefundSupplierByCalculateDate(calculateDate, taskId);
            log.info("获取到品退商家:refundSupplierByCalculateDateList:{} size:{}", JsonTool.writeToString(refundSupplierByCalculateDateList), refundSupplierByCalculateDateList.size());
        } finally {
            ZebraForceMasterHelper.clearLocalContext();
        }

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常用事务,重试,缓存注解
  • 乐观锁,事务失效
  • 1 工作注意
    • 1.1 git注意不要commit多次
      • 1.2 事务的使用
        • 1.3 代码review
          • 1.4 代码质量
            • 1.5 事项安排,上线时间
              • 1.6 多数据源配置
                • 1.7 工具类
                • 2 业务积累
                  • 2.1 通用已读功能梳理
                    • 2.2 触达的本质
                    • 3 个人成长
                    • 4 索引设计
                    • 5 其他
                      • 5.1 mybatis select和selective的区别
                        • 5.2 cookie和session
                          • 5.3 基本数据类型和包装类型的使用
                            • 5.4 聚簇索引和非聚簇索引的区别
                              • 5.5 强制查主库
                              相关产品与服务
                              云数据库 SQL Server
                              腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档