高质量代码日常总结

代码质量提升日常总结

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,是否进行过边界值测试,是否用例覆盖到业务场景

posted @   水星点灯  阅读(92)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示