10年程序员的经验

6 篇文章 0 订阅

一、分享目的

1.1 统一代码规范

1.2 提高编程技巧

二、分享内容

2.1 代码规范

2.1.1 包名划分

2.1.2 命名规范

2.1.2.1 领域对象命名

包名

命名规范

属性规范

备注

constants

xxxConstants

根据常量的功能进行划分

xxx代表功能模块名称,例如:AnswerConstants、SystemConstants等

entity

xxxEntity

所有属性和DB表数据类型一致

xxx代表表名的驼峰字符串

model

xxxModel

和页面展示用的数据结构一致

xxx代表页面模块的名称

param

xxxParam/TXxxParam

接口请求参数对象

T开头代表thrift接口

result

xxxResult/TXxxResult

接口返回参数对象

T开头代表thrift接口

convert

xxxConvert

对象转换用的工具类,静态方法

xxx代表需要转换的对象名称

2.1.2.2 对外接口命名

接口协议

HTTP

Thrift

命名规则

XxxxController

XxxxThriftService

2.1.2.3 接口方法命名

方法内容最好不要超过30行,不然很难看得懂,利用继承、多态等拆分方法,实在不行也可以用私有方法。

方法功能

命名规则

含义

保存数据

save(Object entity)

写入数据到数据库,有id更新,无id插入

插入数据

insert(Object entity)

插入数据到数据库

更新数据

update(Object entity)

更新数据库中数据

删除数据

delete(Object entity)

删除数据库中数据

获取单条记录

get(Long id)

根据id查询数据库记录,不用写getById(),从参数名我们已经知道含义了

查询多条记录(不分页)

list(Object entity)

根据查询条件查询数据库记录列表,不带分页,动态sql全部涵盖

查询多条记录(分页)

find(Object entity)

根据查询条件查询数据库总记录数,同时查询分页内的记录,最后组合返回

2.1.2.4 配置文件命名

采用spring-boot构建项目,统一配置文件名称:

目录

名称

含义

/resources

application.properties

springboot的默认配置文件,公共的配置放在这里

/resources/config

application-prod.properties

application-test.properties

springboot区分环境的配置文件,不同环境的配置放在这里,启动项目时指定-Dspring.profiles.active=[prod|test]

2.1.3 接口规范

2.1.3.1 HTTP接口

统一返回值类型,内容如下:

代码块

Java

 
public class HttpBaseResult {
    private Integer code;//统一HTTP协议code码,例如:200为成功,500为服务端错误,400为客户端错误
    private String message;  //错误提示信息,例如:请求参数xxx不能为空等
    private Object data;  //具体数据
}

2.1.3.2 Thrift接口

统一RPC接口返回值类型,如下:

代码块

Java

 
enum TBaseResultCode {
    SUCCESS = 200, //成功
    CLIENT_ERROR = 400, //客户端异常
    SERVER_ERROR = 500 //服务端异常
}
//以下为接口的内容
include "TBaseResult.thrift"
// 意图说法数据
struct TIntentGrammarResult {
    1: optional TBaseResult.TBaseResultCode code; //接口返回状态码
    2: optional string message; //接口返回消息
    3: optional string grammar; //接口返回数据
}

2.1.3.3 分页接口

代码块

Java

 
//请求参数
@Data
public class PagerParam {
    private Integer page = 1;          //当前第几页
    private Integer pageSize = 20;     //每页多少行
    private String sort;               //排序字段
    private String order = "asc";      //排序规则 asc || desc
    private Integer pageIndex;         //分页索引位置,根据page和pageSize计算出来的,前端不用传
}
//返回结果
@Data
public class PagerResult<T> {
    private Integer page;       //当前第几页
    private Integer pageSize;   //每页多少行
    private Integer totalPage;  //总共多少页
    private String sort;        //排序字段
    private String order;       //排序规则
    private Long total;         //总共多少条记录
    private List<T> records;    //当前页数据
}
 
//帮助类
public class PagerResultHelper {
    /**
     * 获取分页的起始索引位置
     *
     * @param page
     * @param pageSize
     * @return
     */
    public static Integer getPageIndex(Integer page, Integer pageSize) {
        return (page - 1) * pageSize;
    }
    /**
     * 获取总页数
     *
     * @param total
     * @param pageSize
     * @return
     */
    private static Integer getTotalPage(Long total, Integer pageSize) {
        return total == 0L ? 0 : (int) ((total - 1) / pageSize + 1);
    }
    /**
     * 转换查询结果为分页查询结果
     *
     * @param param   分页对象
     * @param records 记录行转换后的列表
     * @return
     */
    public static <T> PagerResult<T> getPageResult(PagerParam param, Long total, List<T> records) {
        PagerResult<T> result = new PagerResult<>();
        result.setPage(param.getPage());
        result.setPageSize(param.getPageSize());
        result.setTotal(total);
        result.setTotalPage(getTotalPage(total, param.getPageSize()));
        result.setSort(param.getSort());
        result.setOrder(param.getOrder());
        result.setRecords(records);
        return result;
    }
}

2.2 高级编程

2.2.1 统一接口切面

所有对外暴露的接口,一般都需要打印请求参数列表返回参数列表耗时异常等信息,但是你会在每个方法中都写如下一段代码吗?

代码块

Java

 
public HttpBaseResult save(@RequestBody IntentAnswerParam param) {
    try {
        LOGGER.info("当前方法是:{},请求参数为:{}", "saveIntent", param.toString());
        Long id = intentAnswerService.save(param);
        HttpBaseResult result =  HttpResultHelper.buildSuccess(id);
        LOGGER.info("当前方法是:{},请求参数为:{},返回结果为:{}", "saveIntent", param.toString(), result.toString());
        return result;
    } catch (Exception e) {
        LOGGER.error("当前方法是:{},请求参数为:{},异常参数为:{}", "saveIntent", param.toString(), e);
        return HttpResultHelper.buildFailure(HttpCodeEnum.SERVER_ERROR);
    }
}

当然不是,学习过面向切面编程的同学肯定会想到的是用切面统一处理,具体代码如下:

代码块

Java

 
private static final Logger LOGGER = LoggerFactory.getLogger(DialogPortalAspect.class);
private static final String LOG_NOT_PARAM_TEMPLATE = "当前类:{},当前方法:{},返回结果:{},耗时:{}ms";
private static final String LOG_THROWABLE_TEMPLATE = "当前类:{},当前方法:{},请求参数:{},异常:{}";
private static final String LOG_ALL_TEMPLATE = "当前类:{},当前方法:{},请求参数:{},返回结果:{},耗时:{}ms";
/**
 * 统一使用该环绕通知切面来处理API接口的请求参数日志、返回结果日志、耗时日志、异常处理等
 *
 * @param point 切点
 * @return 接口返回的实际数据类型

2.2.2 使用设计模式

2.2.2.1 单例模式

使用了spring之后,单例、工厂模式貌似变成了理所当然的事情,可是一旦脱离了spring,还是要考虑在合适的地方使用单例。

代码块

Java

 
/**
 * 解析XML、转换XML、生成XML的代理对象
 */
public class LoadXmlDelegate {
    private static final Logger LOGGER = LoggerFactory.getLogger(LoadXmlDelegate.class);
    private static LoadXmlDelegate delegate = null;
 
    private LoadXmlDelegate() {
    }
    //简单的一个单例
    public static synchronized LoadXmlDelegate getInstance() {
        if (delegate == null) {
            delegate = new LoadXmlDelegate();
        }
        return delegate;
    }
}

2.2.2.2 策略模式

假如你的代码接入方有两个业务方,内部逻辑不同,但是接口方法相同,你还会用if-else区分代码吗?为什么不用策略模式区分呢?

代码块

Java

 
/**
 * 转换BPMN为OpenDial的格式
 *
 * @param process
 * @param sourceType
 * @param configs
 */
public Document handle(BpmnProcessDomain process, BpmnSourceEnum sourceType, String configs) {
    Document document = this.writeDocument();
    //策略模式
    AbstractBpmnHandler handler;
    if (sourceType == BpmnSourceEnum.CSC) {
        handler = new CscBpmnBaseHandler(document, process, configs);
    } else if (sourceType == BpmnSourceEnum.APP) {
        handler = new CscBaseHandler(document, process, configs);
    } else {
        handler = new MosesBpmnBaseHandler(document, process, configs);
    }
    handler.init();
    handler.buildTree(process.getStartEvent());
    handler.handle(process.getStartEvent());
    return document;
}

2.2.2.3 模板方法模式

总感觉有段代码像一个模板,一段逻辑在控制:先干什么,在干什么,最后干什么,但是每一段的具体逻辑又不同,怎么办呢?

代码块

Java

 
/**
 * 处理某个节点
 *
 * @param node
 * @return
 */
default Optional<List<RuleModel>> buildRule(TreeNodeModel node, ParseContextModel context) {
    List<RuleModel> rule;
    if (node instanceof StartEventModel) {
        rule = this.buildStartEventRule((StartEventModel) node);
    } else if (CollectionUtils.isEmpty(node.getChildren())) {
        RuleModel endEventRule = this.buildEndRule((BpmnComponentModel) node, context);
        rule = endEventRule == null ? new ArrayList<>() : Collections.singletonList(endEventRule);
    } else if (node instanceof ExclusiveGatewayModel) {
        rule = this.buildGatewayRule((ExclusiveGatewayModel) node, context);
    } else {
        RuleModel callActivityRule = this.buildCallActivityRule((CallActivityModel) node, context);
        rule = callActivityRule == null ? new ArrayList<>() : Lists.newArrayList(callActivityRule);
    }
    return Optional.ofNullable(rule);
}
List<RuleModel> buildStartEventRule(StartEventModel node);
RuleModel buildEndRule(BpmnComponentModel node, ParseContextModel context);
List<RuleModel> buildGatewayRule(ExclusiveGatewayModel node, ParseContextModel context);
RuleModel buildCallActivityRule(CallActivityModel node, ParseContextModel context);

2.2.2.4 责任链模式

有没有经历过一段代码,是先调接口A,再调接口B,再调接口C,等等,像是一个链路,你还在一个方法里面写吗?你不觉得方法片段很长吗?

代码块

Java

 
/**
 * 用责任链模式解耦代码
 * 请不要全部写在一个方法中!!!!!!!
 * Created by kangxiongwei on 2019-04-15 17:17.
 */
public abstract class AbstractDmTaskService {
    //当前类的下一级处理器
    protected AbstractDmTaskService handler;
    /**
     * 执行下一级任务
     *
     * @param params 请求参数
     */
    public abstract void doTask(Object params);
}
public class DmTaskVersionServiceImpl extends AbstractDmTaskService implements DmTaskVersionService {
    /**
     * 更新TaskInfo的Trigger信息
     */
    @Override
    public void doTask(Object param) {
        this.handler = dmTaskInfoService;  //设置下一级责任链
        DmTaskVersionParam version = (DmTaskVersionParam) param;
        Long taskId = dmTaskVersionMapper.getTaskId(version.getId());
        DmTaskInfoEntity task = new DmTaskInfoEntity();
        task.setId(taskId);
        task.setTaskTrigger(version.getTrigger() == null ? null : JSON.toJSONString(version.getTrigger()));
        task.setUpdateTime(new Date());
        User user = UserUtils.getUser();
        task.setModifier(user == null ? SystemConstants.SYSTEM : user.getLogin());
        dmTaskInfoMapper.updateByPrimaryKeySelective(task);
        if (this.handler == null) return;
        handler.doTask(task);      //下一级责任链开始处理逻辑
    }
}

2.2.2.5 其他设计模式

以上是我简单举得几个例子,个人认为首先需要掌握常用的设计模式的应用场景,然后在编码过程中多寻思自己的代码写的好不好,哪里还有优化空间?是否需要某种设计模式来解决?以下为GoF提出的23种设计模式:

其中个人认为最常用的设计模式有以下几个:

单例、工厂、适配器、代理、责任链、观察者、策略、模板方法

各种设计模式的实现在网站上很容易找到,我的github上也有一部分实现:JavaInterview/JavaInterview/src/main/java/com/kxw/pattern at master · kangxiongwei/JavaInterview · GitHub),这里不再详细介绍。

2.2.3 DB操作

对于数据库操作频繁的SQL,一定要做好SQL优化,SQL优化分为两步:1.定位问题 2.优化问题

2.2.3.1 定位问题

sql

作用

备注

show [session|global] status [like 'Com_%'];

查看sql执行频率,默认为session

关注:Com_select,Com_update,Com_insert,Com_delete,Innodb_rows_read, Innodb_rows_inserted, Innodb_rows_updated, Innodb_rows_deletedConnections, Uptime, Slow_queries

启动mysql用--log-slow-queries [=file_name]

指定慢查询日志,根据日志定位执行效率较慢的sql

show [full] processlist

查看mysql进程,查看是否锁表等

explain select * from a;
explain extended select * from a;

explain partitions ;

show warnings;

根据执行计划查看效率

select_type: simple、primary、union、subquery

type: [all|index|range|ref|eq_ref|const|system|null]性能由差到好

explain extended: 可以看到mysql在执行sql前,对sql做了哪些优化

type=all:全表扫描

type=index:索引全扫描

type=range:索引范围扫描

type=ref:使用非唯一索引扫描或唯一索引的前缀扫描

type=eq_ref:类似于ref,但是索引为唯一索引,多表中唯一key所为关联条件

type=const/system:单表中最多一个匹配行,主键ID或者唯一索引查询

type=null:不用访问表就能得到结果

show profile[s]

show profile for query 4; //查看各状态消耗时间

select @@have_profiling; //查看是否支持profile

select @@profiling;

通过profile可以知道sql执行耗时主要消耗到了哪里

开启profile:set profiling = 1;

其他用法:

show profile cpu for query 4;

show profile source for query 4; //查看源码

set optimizer_trace="enabled=on",end_markers_in_json=on;

set optimizer_trace_max_mem_size=10000000;

select * from aaa;

select * from information_schema.optimizer_trace;

通过跟踪trace,分析优化器为什么选择了A计划而不是B计划

2.2.3.2 解决问题

使用索引

避免有索引,但是使用不到索引的情况

a. like左边有%号

b. or语句有一部分用不到索引

c. 复合索引不满足最左原则

d. sql中存在隐式转换

e. 使用索引比全表扫描更慢

定期分析、检查、优化表

analyze table aaa;

check table aaa;

optimize table aaa;

表优化,会锁表

load data 'aaa.txt' into table aaa;

alter table aaa [disable|enable] keys; //只对MyISAM有效

大批量插入数据,在前后禁用和启用索引

对于InnoDB,需要用以下方式:

1. 文件按照主键顺序排序

2. 在导入前,set unique_checks = 0; 导入后,再设为1

3. 在导入前,set autocommit = 0; 导入后,再设为1

insert into test values (1, 2), (3,4) .....

优化insert语句

order by

1. where和order by使用相同字段,如果字段有索引

2. 排序字段ASC还是DESC尽量一致

3. 适当增大buffer_sort_size参数

group by

group by后可以指定order by null

子查询

尽量使用join代替子查询

or

两个子句必须都有索引,考虑使用union

分页查询

1. 用带索引子查询返回排序的数据,然后关联表查询

2. 记录上次查询的位置,然后利用位置查询

1. select a.id, a.name from aaa a inner join (select id from aaa order by id limit 50,10) b on (a.id = b.id)

2. select a.id, a.name from aaa order by id desc limit 400, 10  

//记录此次执行完后最后一行的id号,比如1560

   select a.id, a.name from aaa where a.id < 1560 order by id desc limit 410, 10;  //利用上次的查询结果查询

SQL提示

select count(*) from aaa use index (idx_id);

select count(*) from aaa ingnore index(idx_id);

select count(*) from aaa force index(idx_id);

让数据库不考虑其他索引

善于正则表达式,替换like

使用group by with rollup子句

分组后不仅可以获取想要的数据,还有其他的数据,如总金额等

和分组字段的顺序有关

和limit互斥的

2.2.4 异步调用

代码中为了更好地效率,经常需要将有些方法设置为异步调用,你还在用new Thread(........).start()或者线程池中创建线程异步调用吗?其实spring-boot提供了异步方法,如下:

代码块

Java

 
/**
 * 异步任务线程池配置
 * Created by kangxiongwei on 2019-01-29 14:43.
 */
@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
    /**
     * The {@link Executor} instance to be used when processing async
     * method invocations.
     */
    @Override
    public Executor getAsyncExecutor() {
        int corePoolSize = Runtime.getRuntime().availableProcessors();
        int maxPoolSize = 100;
        int keepAliveTime = 1;
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10000);
        return new ThreadPoolExecutor(corePoolSize * 2, maxPoolSize, keepAliveTime, TimeUnit.MINUTES, queue);
    }
}
/**
 * 异步增加操作日志
 */
@Async
@Override
public void insert(ActionLogEntity entity) {
    actionLogMapper.insert(entity);
}

2.2.4 单元测试

最简单的单元测试,当属测试一段代码,查看代码是否有异常,人工检测数据库数据是否符合预期

代码块

Java

 
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)  //随机端口,防止和springboot的默认端口冲突
public class DmTaskServiceTest {
    @Resource
    private DmTaskInfoService dmTaskInfoService;
    @Test
    public void testListTask() {
        List<TTaskInfoModel> list = dmTaskInfoService.list("1bfe8f02-cd0a-48aa-84c6-1dc1a2bab8da");
        System.out.println(list);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

博士通

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值