×
Community Blog Coding Specifications | Elegant Java Functions (Part One)

Coding Specifications | Elegant Java Functions (Part One)

This article is the first part of the 'Java Coding Specifications' series and aims to impart useful coding advice to Java programmers for enabling them to produce more efficient code.

By Amap_tech team.

Introduction

As software project code accumulates over time, system maintenance costs rise. This problem is common across all software teams. Continuously optimizing and improving code quality is a great way to improve system vitality. Also, as the popular saying among developers goes: Think more, codeless. Therefore, we need to think more when programming and strive to improve our coding skills. This way, we can produce more elegant, higher quality, and more efficient code.

This article will describe a set of coding rules relevant to Java functions. We hope to give some coding advice to Java programmers, which will help them produce code with the aforementioned highlights. The Amap data collection department has put these rules into practice to great effect.

Using General Tool Functions

Case 1

The following snippet shows the symptom description.

Incomplete code

thisName != null && thisName.equals(name);

More complete code

(thisName == name) || (thisName != null && thisName.equals(name));

Refer to the following recommended solution.

Objects.equals(name, thisName);

Case 2

The following snippet shows the symptom description.

!(list == null || list.isEmpty());

Refer to the following recommended solution.

import org.apache.commons.collections4.CollectionUtils;
CollectionUtils.isNotEmpty(list);

Benefits

  • Functional programming reduces the amount of business code and intuitively displays the logic.
  • General tool functions allow us to deliberate on the logic and reduce the probability of errors.

Splitting Large Functions

When the code of a function exceeds 80 lines, it is a large function and therefore needs to be split up.

Case 1) Each code block must be encapsulated as a function

Each code block must contain a comment to explain its functionality. If a comment line is present in front of the code block, it allows replacing the code with a function and names the function based on the comment. If a function has a descriptive name, it is easy to update directly without having to check how the internal code is implemented.

The following snippet shows the symptom description.

// 每日生活函数
public void liveDaily() {
    // 吃饭
    // 吃饭相关代码几十行

    // 编码
    // 编码相关代码几十行

    // 睡觉
    // 睡觉相关代码几十行
}

Refer to the following recommended solution.

// 每日生活函数
public void liveDaily() {
    // 吃饭
    eat();

    // 编码
    code();

    // 睡觉
    sleep();
}

// 吃饭函数
private void eat() {
    // 吃饭相关代码
}

// 编码函数
private void code() {
    // 编码相关代码
}

// 睡觉函数
private void sleep() {
    // 睡觉相关代码
}

Case 2) Each loop body must be encapsulated as a function

The following snippet shows the symptom description.

// 生活函数
public void live() {
    while (isAlive) {
        // 吃饭
        eat();

        // 编码
        code();

        // 睡觉
        sleep();
    }
}

Refer to the following recommended solution.

// 生活函数
public void live() {
    while (isAlive) {
        // 每日生活
        liveDaily();
    }
}

// 每日生活函数
private void liveDaily() {
    // 吃饭
    eat();

    // 编码
    code();

    // 睡觉
    sleep();
}

Case 3) Each condition body must be encapsulated as a function

The following snippet shows the symptom description.

// 外出函数
public void goOut() {
    // 判断是否周末
    // 判断是否周末: 是周末则游玩
    if (isWeekday()) {
        // 游玩代码几十行
    }
    // 判断是否周末: 非周末则工作
    else {
        // 工作代码几十行
    }
}

Refer to the following recommended solution.

// 外出函数
public void goOut() {
    // 判断是否周末
    // 判断是否周末: 是周末则游玩
    if (isWeekday()) {
        play();
    }
    // 判断是否周末: 非周末则工作
    else {
        work();
    }
}

// 游玩函数
private void play() {
    // 游玩代码几十行
}

// 工作函数
private void work() {
    // 工作代码几十行
}

Benefits

  • The shorter a function is, the simpler its functionality and the longer its lifecycle.
  • The longer a function is, the harder it is to understand and maintain. In this case, the maintenance personnel will try not to modify it whenever possible.
  • Long functions often contain repeated code that is difficult to locate.

Keep Code Block Levels Consistent Within the Same Function

Case 1

The following snippet shows the symptom description.

// 每日生活函数
public void liveDaily() {
    // 吃饭
    eat();

    // 编码
    code();

    // 睡觉
    // 睡觉相关代码几十行
}

As you see, the sleep code block is not at the same level as eat and code, which is rather strange. If you compare writing code with writing an essay, eat and code are then the main ideas of a paragraph, while sleep is a detailed paragraph. For the LiveDaily function, you only need to write the main process, that is, the idea of the paragraph.

Refer to the following recommended solution.

public void liveDaily() {
    // 吃饭
    eat();

    // 编码
    code();

    // 睡觉
    sleep();
}

// 睡觉
private void sleep() {
    // 睡觉相关代码
}

Benefits

  • Function calls indicate their purpose, whereas functions implement the expression logic. The logic is clearly arranged in levels for ease of understanding.
  • Code blocks do not have to be hierarchical in a function as this makes functions seem top-heavy.

Encapsulate Identical Functional Code as a Function

Case 1) Encapsulate identical code as a function

The following snippet shows the symptom description.

// 禁用用户函数
public void disableUser() {
    // 禁用黑名单用户
    List<Long> userIdList = queryBlackUser();
    for (Long userId : userIdList) {
        User userUpdate = new User();
        userUpdate.setId(userId);
        userUpdate.setEnable(Boolean.FALSE);
        userDAO.update(userUpdate);
    }

    // 禁用过期用户
    userIdList = queryExpiredUser();
    for (Long userId : userIdList) {
        User userUpdate = new User();
        userUpdate.setId(userId);
        userUpdate.setEnable(Boolean.FALSE);
        userDAO.update(userUpdate);
    }
}

Refer to the following recommended solution.

// 禁用用户函数
public void disableUser() {
    // 禁用黑名单用户
    List<Long> userIdList = queryBlackUser();
    for (Long userId : userIdList) {
        disableUser(userId);
    }

    // 禁用过期用户
    userIdList = queryExpiredUser();
    for (Long userId : userIdList) {
        disableUser(userId);
    }
}

// 禁用用户函数
private void disableUser(Long userId) {
    User userUpdate = new User();
    userUpdate.setId(userId);
    userUpdate.setEnable(Boolean.FALSE);
    userDAO.update(userUpdate);
}

Case 2) Encapsulate similar code as a function

Encapsulate similar code as a function, with the differences controlled by function parameters.

The following snippet shows the symptom description.

// 通过工单函数
public void adoptOrder(Long orderId) {
    Order orderUpdate = new Order();
    orderUpdate.setId(orderId);
    orderUpdate.setStatus(OrderStatus.ADOPTED);
    orderUpdate.setAuditTime(new Date());
    orderDAO.update(orderUpdate);
}

// 驳回工单函数
public void rejectOrder(Long orderId) {
    Order orderUpdate = new Order();
    orderUpdate.setId(orderId);
    orderUpdate.setStatus(OrderStatus.REJECTED);
    orderUpdate.setAuditTime(new Date());
    orderDAO.update(orderUpdate);
}

Refer to the following recommended solution.

// 通过工单函数
public void adoptOrder(Long orderId) {
    auditOrder(orderId, OrderStatus.ADOPTED);
}

// 驳回工单函数
public void rejectOrder(Long orderId) {
    auditOrder(orderId, OrderStatus.REJECTED);
}

// 审核工单函数
private void auditOrder(Long orderId, OrderStatus orderStatus) {
    Order orderUpdate = new Order();
    orderUpdate.setId(orderId);
    orderUpdate.setStatus(orderStatus);
    orderUpdate.setAuditTime(new Date());
    orderDAO.update(orderUpdate);
}

Benefits

  • Encapsulate common functions to reduce the number of code lines and improve code quality.
  • Encapsulate common functions to refine the business code, making it easier to understand and maintain.

Encapsulate Functions That Fetch Parameter Values

Case 1

The following snippet shows the symptom description.

// 是否通过函数
public boolean isPassed(Long userId) {
    // 获取通过阈值
    double thisPassThreshold = PASS_THRESHOLD;
    if (Objects.nonNull(passThreshold)) {
        thisPassThreshold = passThreshold;
    }

    // 获取通过率
    double passRate = getPassRate(userId);

    // 判读是否通过
    return passRate >= thisPassThreshold;
}

Refer to the following recommended solution.

// 是否通过函数
public boolean isPassed(Long userId) {
    // 获取通过阈值
    double thisPassThreshold = getPassThreshold();

    // 获取通过率
    double passRate = getPassRate(userId);

    // 判读是否通过
    return passRate >= thisPassThreshold;
}

// 获取通过阈值函数
private double getPassThreshold() {
    if (Objects.nonNull(passThreshold)) {
        return passThreshold;
    }
    return PASS_THRESHOLD;
}

Benefits

  • Parameter values are independently obtained from business functions, clarifying the business logic.
  • An encapsulated GET parameter value function is an independent function that might be reused in the code.

Encapsulate Identical Logic Through Parameter-based APIs

Case 1

The following snippet shows the symptom description.

// 发送审核员结算数据函数
public void sendAuditorSettleData() {
    List<WorkerSettleData> settleDataList = auditTaskDAO.statAuditorSettleData();
    for (WorkerSettleData settleData : settleDataList) {
        WorkerPushData pushData = new WorkerPushData();
        pushData.setId(settleData.getWorkerId());
        pushData.setType(WorkerPushDataType.AUDITOR);
        pushData.setData(settleData);
        pushService.push(pushData);
    }
}

// 发送验收员结算数据函数
public void sendCheckerSettleData() {
    List<WorkerSettleData> settleDataList = auditTaskDAO.statCheckerSettleData();
    for (WorkerSettleData settleData : settleDataList) {
        WorkerPushData pushData = new WorkerPushData();
        pushData.setId(settleData.getWorkerId());
        pushData.setType(WorkerPushDataType.CHECKER);
        pushData.setData(settleData);
        pushService.push(pushData);
    }

Refer to the following recommended solution.

// 发送审核员结算数据函数
public void sendAuditorSettleData() {
    sendWorkerSettleData(WorkerPushDataType.AUDITOR, () -> auditTaskDAO.statAuditorSettleData());
}

// 发送验收员结算数据函数
public void sendCheckerSettleData() {
    sendWorkerSettleData(WorkerPushDataType.CHECKER, () -> auditTaskDAO.statCheckerSettleData());
}

// 发送作业员结算数据函数
public void sendWorkerSettleData(WorkerPushDataType dataType, WorkerSettleDataProvider dataProvider) {
    List<WorkerSettleData> settleDataList = dataProvider.statWorkerSettleData();
    for (WorkerSettleData settleData : settleDataList) {
        WorkerPushData pushData = new WorkerPushData();
        pushData.setId(settleData.getWorkerId());
        pushData.setType(dataType);
        pushData.setData(settleData);
        pushService.push(pushData);
    }
}

// 作业员结算数据提供者接口
private interface WorkerSettleDataProvider {
    // 统计作业员结算数据
    public List<WorkerSettleData> statWorkerSettleData();
}

Benefits

  • Extract the core logic from various business functions to clarify the business code and make it easier to maintain.
  • Avoid repetitively writing the same code. The more complex the function, the greater the benefit.

Reduce the Number of Function Code Levels

To achieve elegant functions, we recommend keeping the number of code levels between 1 and 4. Excessive indentation makes functions hard to read.

Case 1) Use return to retrieve a function in advance

The following snippet shows the symptom description.

// 获取用户余额函数
public Double getUserBalance(Long userId) {
    User user = getUser(userId);
    if (Objects.nonNull(user)) {
        UserAccount account = user.getAccount();
        if (Objects.nonNull(account)) {
            return account.getBalance();
        }
    }
    return null;
}

Refer to the following recommended solution.

// 获取用户余额函数
public Double getUserBalance(Long userId) {
    // 获取用户信息
    User user = getUser(userId);
    if (Objects.isNull(user)) {
        return null;
    }

    // 获取用户账户
    UserAccount account = user.getAccount();
    if (Objects.isNull(account)) {
        return null;
    }

    // 返回账户余额
    return account.getBalance();
}

Case 2) Use continue to terminate a loop in advance

The following snippet shows the symptom description.

// 获取合计余额函数
public double getTotalBalance(List<User> userList) {
    // 初始合计余额
    double totalBalance = 0.0D;

    // 依次累加余额
    for (User user : userList) {
        // 获取用户账户
        UserAccount account = user.getAccount();
        if (Objects.nonNull(account)) {
            // 累加用户余额
            Double balance = account.getBalance();
            if (Objects.nonNull(balance)) {
                totalBalance += balance;
            }
        }
    }

    // 返回合计余额
    return totalBalance;
}

Refer to the following recommended solution.

// 获取合计余额函数
public double getTotalBalance(List<User> userList) {
    // 初始合计余额
    double totalBalance = 0.0D;

    // 依次累加余额
    for (User user : userList) {
        // 获取用户账户
        UserAccount account = user.getAccount();
        if (Objects.isNull(account)) {
            continue;
        }

        // 累加用户余额
        Double balance = account.getBalance();
        if (Objects.nonNull(balance)) {
            totalBalance += balance;
        }
    }

    // 返回合计余额
    return totalBalance;
}

Notes

Other Method: In the loop body, call the getUserBalance function in case 1 to obtain the user balance and then calculate the cumulative balance.

In the loop body, we recommend using continue only once. If you want to use continue multiple times, we recommend that you encapsulate the loop body as a function.

Case 3) Use a conditional expression function to reduce the number of levels

For more information, see "Case 2: Encapsulate a complex conditional expression as a function" in the next section.

Benefits

  • Reduce the number of code levels and the amount of code indentation.
  • Modules are clearly divided for easy reading and maintenance.

Encapsulate Conditional Expression Functions

Case 1) Encapsulate a simple conditional expression as a function

The following snippet shows the symptom description.

// 获取门票价格函数
public double getTicketPrice(Date currDate) {
    if (Objects.nonNull(currDate) && currDate.after(DISCOUNT_BEGIN_DATE)
        && currDate.before(DISCOUNT_END_DATE)) {
        return TICKET_PRICE * DISCOUNT_RATE;
    }
    return TICKET_PRICE;
}

Refer to the following recommended solution.

// 获取门票价格函数
public double getTicketPrice(Date currDate) {
    if (isDiscountDate(currDate)) {
        return TICKET_PRICE * DISCOUNT_RATE;
    }
    return TICKET_PRICE;
}

// 是否折扣日期函数
private static boolean isDiscountDate(Date currDate) {
    return Objects.nonNull(currDate) && 
currDate.after(DISCOUNT_BEGIN_DATE)
        && currDate.before(DISCOUNT_END_DATE);
}

Case 2) Encapsulate a complex conditional expression as a function

The following snippet shows the symptom description.

// 获取土豪用户列表
public List<User> getRichUserList(List<User> userList) {
    // 初始土豪用户列表
    List<User> richUserList = new ArrayList<>();

    // 依次查找土豪用户
    for (User user : userList) {
        // 获取用户账户
        UserAccount account = user.getAccount();
        if (Objects.nonNull(account)) {
            // 判断用户余额
            Double balance = account.getBalance();
            if (Objects.nonNull(balance) && balance.compareTo(RICH_THRESHOLD) >= 0) {
                // 添加土豪用户
                richUserList.add(user);
            }
        }
    }

    // 返回土豪用户列表
    return richUserList;
}

Refer to the following recommended solution.

// 获取土豪用户列表
public List<User> getRichUserList(List<User> userList) {
    // 初始土豪用户列表
    List<User> richUserList = new ArrayList<>();

    // 依次查找土豪用户
    for (User user : userList) {
        // 判断土豪用户
        if (isRichUser(user)) {
            // 添加土豪用户
            richUserList.add(user);
        }
    }

    // 返回土豪用户列表
    return richUserList;
}

// 是否土豪用户
private boolean isRichUser(User user) {
    // 获取用户账户
    UserAccount account = user.getAccount();
    if (Objects.isNull(account)) {
        return false;
    }

    // 获取用户余额
    Double balance = account.getBalance();
    if (Objects.isNull(balance)) {
        return false;
    }

    // 比较用户余额
    return balance.compareTo(RICH_THRESHOLD) >= 0;
}

The preceding code may also be filtered by using streaming programming.

Benefits

  • Conditional expressions are independently obtained from business functions, clarifying business logic.
  • An encapsulated conditional expression is an independent function that might be reused in the code.

Avoid Unnecessary Null Pointer Judgments

This section applies only to internal project code that you are familiar with. It demonstrates how to avoid unnecessary null pointer judgments. For third-party middleware and system APIs, null pointer judgments are necessary to ensure code robustness.

Case 1) Call a function to ensure that the parameters are not null in order to avoid unnecessary null pointer judgments

The following snippet shows the symptom description.

// 创建用户信息
User user = new User();
... // 赋值用户相关信息
createUser(user);

// 创建用户函数
private void createUser(User user){
    // 判断用户为空
    if(Objects.isNull(user)) {
        return;
    }

    // 创建用户信息
    userDAO.insert(user);
    userRedis.save(user);
}

Refer to the following recommended solution.

// 创建用户信息
User user = new User();
... // 赋值用户相关信息
createUser(user);

// 创建用户函数
private void createUser(User user){
    // 创建用户信息
    userDAO.insert(user);
    userRedis.save(user);
}

Case 2) The called function ensures that the response is not null in order to avoid unnecessary null pointer judgments

The following snippet shows the symptom description.

// 保存用户函数
public void saveUser(Long id, String name) {
    // 构建用户信息
    User user = buildUser(id, name);
    if (Objects.isNull(user)) {
        throw new BizRuntimeException("构建用户信息为空");
    }

    // 保存用户信息
    userDAO.insert(user);
    userRedis.save(user);
}

// 构建用户函数
private User buildUser(Long id, String name) {
    User user = new User();
    user.setId(id);
    user.setName(name);
    return user;
}

Refer to the following recommended solution.

// 保存用户函数
public void saveUser(Long id, String name) {
    // 构建用户信息
    User user = buildUser(id, name);

    // 保存用户信息
    userDAO.insert(user);
    userRedis.save(user);
}

// 构建用户函数
private User buildUser(Long id, String name) {
    User user = new User();
    user.setId(id);
    user.setName(name);
    return user;
}

Case 3) The value assignment logic ensures that the data in the list is not null in order to avoid unnecessary null pointer judgments

The following snippet shows the symptom description.

// 查询用户列表
List<UserDO> userList = userDAO.queryAll();
if (CollectionUtils.isEmpty(userList)) {
    return;
}

// 转化用户列表
List<UserVO> userVoList = new ArrayList<>(userList.size());
for (UserDO user : userList) {
    UserVO userVo = new UserVO();
    userVo.setId(user.getId());
    userVo.setName(user.getName());
    userVoList.add(userVo);
}

// 依次处理用户
for (UserVO userVo : userVoList) {
    // 判断用户为空
    if (Objects.isNull(userVo)) {
        continue;
    }

    // 处理相关逻辑
    ...
}

Refer to the following recommended solution.

// 查询用户列表
List<UserDO> userList = userDAO.queryAll();
if (CollectionUtils.isEmpty(userList)) {
    return;
}

// 转化用户列表
List<UserVO> userVoList = new ArrayList<>(userList.size());
for (UserDO user : userList) {
    UserVO userVo = new UserVO();
    userVo.setId(user.getId());
    userVo.setName(user.getName());
    userVoList.add(userVo);
}

// 依次处理用户
for (UserVO userVo : userVoList) {
    // 处理相关逻辑
    ...
}

Case 4) The returned list and data items of a MyBatis query function are not null, so null pointer judgment is unnecessary

MyBatis is an excellent persistent-layer framework and a database middleware component widely used in projects. By analyzing MyBatis source code, you may ensure that the list and data items returned by the query function are not null and null pointer judgments are unnecessary in the code.

The following snippet shows the symptom description.

This development idea is fine but too conservative.

// 查询用户函数
public List<UserVO> queryUser(Long id, String name) {
    // 查询用户列表
    List<UserDO> userList = userDAO.query(id, name);
    if (Objects.isNull(userList)) {
        return Collections.emptyList();
    }

    // 转化用户列表
    List<UserVO> voList = new ArrayList<>(userList.size());
    for (UserDO user : userList) {
        // 判断对象为空
        if (Objects.isNull(user)) {
            continue;
        }

        // 添加用户信息
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        voList.add(vo);
    }

    // 返回用户列表
    return voList;
}

Refer to the following recommended solution.

// 查询用户函数
public List<UserVO> queryUser(Long id, String name) {
    // 查询用户列表
    List<UserDO> userList = userDAO.query(id, name);

    // 转化用户列表
    List<UserVO> voList = new ArrayList<>(userList.size());
    for (UserDO user : userList) {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        voList.add(vo);
    }

    // 返回用户列表
    return voList;
}

Benefits

  • Avoid unnecessary null pointer judgments to streamline the business code processing logic and improve its running efficiency.
  • These unnecessary null pointer judgments are basically dead code that will never be executed. Therefore, deleting them will facilitate code maintenance.
0 0 0
Share on

Alibaba Clouder

2,605 posts | 747 followers

You may also like

Comments