高质量代码日常总结
作者:互联网
代码质量提升日常总结
1. 开发规范
1.1 合适的命名
- 合适的命名是头等大事,不合适的命名通常是词不达意、误导观众、过度缩写等,由于英文并非我们的母语,找个合适的单词命名似乎真的很难
- 建议是先把业务弄清楚,组织会议定下常用业务领域的单词,禁止组员各自发明
1.2 不破坏规则,不引入混乱
- 如果团队已经制定了代码规范,比如类名必须有子系统前缀比如
BiOrderService
(Bi指BI业务部门),就继续遵循下去 - 再比如,团队已经提供了公共库比如MD5的加密,那就不要再次引入新的MD5库
- 很多程序员接活儿后,看到不喜欢的规范就另起炉灶,需要某些工具类也不询问老司机公共库有没有,直接引入自己熟悉的库,造成兼容性或者其他问题
1.3 代码是程序员的重要沟通方式之一
- 用代码实现需求,必须让代码表达自己的设计思想
- 如果你的代码结构清晰、注释合理,其它同事就不用频繁的询问代码疑点,不用打断你的工作
- 编写代码的时候,应该考虑到别人的阅读感受,减少阅读障碍,为整个团队创造代码,而不是为你自己
2. 编码技巧
2.1 利用try-with-resource
- 减少代码数量,减少条件判断,减少异常捕获
反例:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("cities.csv"));
String line;
while ((line = reader.readLine()) != null) {
// TODO: 处理line
}
} catch (IOException e) {
log.error("读取文件异常", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("关闭文件异常", e);
}
}
}
正例:
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
// TODO: 处理line
}
reader.close();
} catch (IOException e) {
log.error("读取文件异常{}", ExceptionUtil.stacktraceToString(e));
}
2.2 利用return
- 建议函数代码层级最多3层
- 代码层级减少,代码缩进减少
- 模块划分清晰,方便阅读维护
反例:
public boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
if (ObjectUtil.isNull(userId) && StrUtil.isNotBlank(newPassword) && StrUtil.isNotBlank(oldPassword)) {
User user = getUserById(userId);
if(ObjectUtil.isNotNull(user)) {
if(StrUtil.equals(user.getPassword(),oldPassword) ){
return updatePassword(userId, newPassword);
}
}
}
}
正例:
public Boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
if (ObjectUtil.isNull(userId) || StrUtil.isBlank(newPassword) || StrUtil.isBlank(oldPassword)) {
return false;
}
User user = getUserById(userId);
if(ObjectUtil.isNull(user)) {
return false;
}
if(!StrUtil.equals(user.getPassword(),oldPassword)) {
return false;
}
return updatePassword(userId, newPassword);
}
2.3 封装条件表达式(简单条件,复杂条件都要封装)
- 把条件表达式从业务函数中独立,使业务逻辑更清晰
- 封装的条件表达式为独立函数,可以在代码中重复使用
反例:
public double getTicketPrice(Date currDate) {
if (ObjectUtil.isNotNull(currDate) && currDate.after(DISCOUNT_BEGIN_DATE)
&& currDate.before(DISCOUNT_END_DATE)) {
return TICKET_PRICE * DISCOUNT_RATE;
}
return TICKET_PRICE;
}
正例:
public double getTicketPrice(Date currDate) {
return isDiscountDate(currDate)?TICKET_PRICE * DISCOUNT_RATE:TICKET_PRICE;
}
// 封装函数
private static boolean isDiscountDate(Date currDate) {
return ObjectUtil.isNotNull(currDate) && currDate.after(DISCOUNT_BEGIN_DATE)&& currDate.before(DISCOUNT_END_DATE);
}
2.4 拆分超大函数(当一个函数超过80行就要进行拆分)
- 函数越短小精悍,功能就越单一,往往生命周期较长
- 一个函数越长,就越不容易理解和维护,维护人员不敢轻易修改
- 在过长函数中,往往含有难以发现的重复代码
反例:
//获取个人信息
private UserDTO getUserDTO(Integer userId){
try {
//获取基本信息
...此处写了10行
//获取最近的一次订单信息.
...此处写了20行
//获取用户可用余额等
...此处写了20行
//获取用户可用优惠券等
...此处写了20行
//获取用户奖励金等
...此处写了20行
}catch (Exception e) {
log.error("{}获取个人信息异常{}",userId,ExceptionUtil.stacktraceToString(e));
return null;
}
return userDTO;
}
正例:
// 获取个人信息
private UserDTO getUserDTO(Integer userId) {
//获取基本信息
UserDTO userDTO = getUserBasicInfo(userId);
//获取最近的一次订单信息
userDTO.setUserLastOrder(getUserLastOrder(userId));
//获取用户可用余额等
userDTO.setUserBalance(getUserBalance(userId));
//获取用户可用优惠券等
userDTO.setUserCoupons(getUserCoupons(userId));
//获取用户奖励金等
userDTO.setUserReward(getUserRewards(userId));
return userDTO;
}
private UserDTO getUserBasicInfo(userId) { //TODO}
private UserLastOrder getUserLastOrder(userId) {//TODO}
private UserBalance UserBalance(userId){
try{// TODO} catch(Exception e){//TODO}
}
private UserCoupons getUserCoupons(userId){
try{// TODO} catch(Exception e){//TODO}
}
private UserReward getUserRewards(userId){// TODO}
2.5 同一函数内代码块级别尽量一致
- 函数调用表明用途,函数实现表达逻辑,层次分明便于理解
- 不用层次的代码块放在一个函数中,容易让人觉得代码头重脚轻
反例:
public void auditById(){
//组装条件获取数据
... 十几行代码
//处理数据
processData();
//发送数据
sendData();
// 结果数据保存
... 十几行代码
}
正例:
public void auditById(){
//组装条件获取数据
getDataById();
//处理数据
processData();
//发送数据
sendData();
// 结果数据保存
saveData();
}
2.6 封装代码(相同代码直接封装,相似代码通过参数区分 )
-
封装公共函数,减少代码行数,提高代码质量
-
封装公共函数,使业务代码更精炼,可读性可维护性更强
反例:
//取消订单
public void cancelTicket(){
Ticket ticket = new Ticket();
ticket.setId(id);
ticket.setStatus(CANCEL);
ticket.setModifyBy(user);
ticket.setModifyTime(now());
ticketDao.update(ticket);
}
//订单作废
public void voidTicket(){
Ticket ticket = new Ticket();
ticket.setId(id);
ticket.setStatus(VOID);
ticket.setModifyBy(system);
ticket.setModifyTime(now());
ticketDao.update(ticket);
}
正例:
//取消订单
public void cancelTicket(){
modifyTicket(id,CANCEL,user);
}
//订单作废
public void voidTicket(){
modifyTicket(id,VOID,system);
}
//修改订单
private void modifyTicket(Stirng id,String status,String operater){
Ticket ticket = new Ticket();
ticket.setId(id);
ticket.setStatus(status);
ticket.setModifyBy(operater);
ticket.setModifyTime(now());
ticketDao.update(ticket);
}
2.7 尽量避免不必要的空指针判断
-
调用函数保证参数不为NULL,被调用函数保证返回值不为NULL(尤其是返回数组跟列表),赋值逻辑保证列表数据项不为空
-
避免不必要的空指针判断,精简业务代码处理逻辑,提高业务代码运行效率
-
这些不必要的空指针判断,基本属于永远不执行的Death代码,删除有助于代码维护
反例:
// 保存用户函数
public void saveUser(String id,String name){
// 构建用户信息
User user = buildUser(id, name);
if (ObjectUtil.isNull(user)) {
throw new BizRuntimeException("构建用户信息为空");
}
// 创建用户信息
userDAO.insert(user);
userRedis.save(user);
}
// 构建用户函数
private User buildUser(String id, String name) {
User user = new User();
user.setId(id);
user.setName(name);
return user;
}
正例:
// 保存用户函数
public void saveUser(String id,String name){
// 构建用户信息
User user = buildUser(id, name);
// 创建用户信息
userDAO.insert(user);
userRedis.save(user);
}
// 构建用户函数
private User buildUser(String id, String name) {
User user = new User();
user.setId(id);
user.setName(name);
return user;
}
2.8 内部变量尽量使用基础数据类型
-
内部函数尽量使用基础类型,避免了隐式封装类型的打包和拆包
-
内部函数参数使用基础类型,用语法上避免了内部函数的参数空指针判断
-
内部函数返回值使用基础类型,用语法上避免了调用函数的返回值空指针判断
反例:
// 调用代码
double price = 5.1D;
int number = 9;
Double total = calculate(price, number);
// 计算金额函数
private Double calculate(Double price, Integer number) {
return price * number;
}
正例:
// 调用代码
double price = 5.1D;
int number = 9;
double total = calculate(price, number);
// 计算金额函数
private double calculate(double price, int number) {
return price * number;
}
2.9 基础规范
-
重复的代码一定要找个地方隔离,包扩变量,常量,工具类
-
函数式编程不要满天飞,例如stream后面的调用超过5个或超过10行代码就要进行拆分
-
多个return 语句,概率高的一定先进行判定
-
当传入参数过多时(超过三个参数),参数成组出现时,要封装为参数类
反例:
// 获取距离函数 public double getDistance(double x1, double y1, double x2, double y2) { // 具体实现逻辑 }
正例:
// 获取距离函数 public double getDistance(Point point1, Point point2) { // 具体实现逻辑 } // 点类 @Getter @Setter @ToString private class Point{ private double x; private double y; }
-
当调用函数只使用参数对象的个别属性时,则只使用属性值,不要传参数对象
-
局部变量在需要时才定义
-
判断罗辑太复杂时,使用临时变量增加可读性
反例:
// 是否土豪用户函数 private boolean isRichUser(User user) { return ObjectUtil.isNotNull(user.getAccount()) && ObjectUtil.isNotNull(user.getAccount().getBalance()) && user.getAccount().getBalance().compareTo(RICH_THRESHOLD) >= 0; }
正例:
// 是否土豪用户函数 private boolean isRichUser(User user) { // 获取用户账户 UserAccount account = user.getAccount(); if (ObjectUtil.isNull(account)) { return false; } // 获取用户余额 Double balance = account.getBalance(); if (ObjectUtil.isNull(balance)) { return false; } // 比较用户余额 return balance.compareTo(RICH_THRESHOLD) >= 0; }
3. 流程控制
3.1 日志和异常
- 良好的日志和异常机制,是不应该出现调试的。打日志和抛异常,一定要把上下文给出来,否则,等于把后边处理问题的人,往歪路上带
- 别人传一个参数进来,发现是 null ,立马抛出来一个参数异常提示,然后也不返回哪一个参数是 null ,这在调用参数很多的情况下,简直就是字谜游戏一样
- 没有充足的理由,不要乱抛受检异常。异常抛出时,一定要自己消化干净,告诉别人说我的方法签名抛的是 AbcException ,实际运行中,代码某个地方直接抛出 EfgException ,这也是不负责任的
- 用户视角的对与错 :用户不需要系统出错的信息,只需要提示信息。后台承包错误码,错误信息,用户提示信息三者的联动关系,封装成json推送给前端,前端拿来主义即可
3.2 空行
-
在方法的 return、break、continue、这样断开性语句后必须是空行
-
在不同语义块之间
-
循环之前和之后一般有空行。另外,方法和类定义下方就不需要空行了吧
3.3 注释
-
先写注释,再写代码(注释是背景是业务罗辑,先写注释避免写代码时是一脸懵逼的)
-
在嵌套循环中,或者在复杂条件分支中一定要写明注释
-
执行频率,执行条件,甚至维护者,注意点,都要写明注释
-
识别到哪里需要写注释,也是一个对业务的理解能力
4. 小结
-
每一行代码的存在都是有意义的,要对自已的每一行代码负责
-
我们比拼的不是代码行数,能用三行代码实现的就不要用四行代码,优质的代码一定的少即是多的原则
-
IT企业最大的谎言:业务跑得太快了,没时间CodeReview。适时的代码重构才能生成优质的代码,才能保证业务的健康
-
单元测试也是CodeReview的一部分,单测写得少,BUG肯定少。单测三问:是否需要MOCK,是否进行过边界值测试,是否用例覆盖到业务场景
标签:return,userId,代码,高质量,private,日常,ticket,user 来源: https://www.cnblogs.com/yzycode/p/16246023.html