Автор Команда Amap_tech.

Фон

В этой статье описывается набор правил кодирования, относящихся к функциям Java, и предлагается дать некоторые советы программистам по кодированию, которые помогут им создавать более элегантный, качественный и эффективный код.

Перейдите по этой ссылке, чтобы просмотреть первую часть этой статьи.

Используйте базовый тип для параметров внутренней функции

Случай 1) По возможности используйте базовый тип для внутренних параметров функции

В следующем фрагменте показано описание симптома.

// 调用代码
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) По возможности используйте базовый тип для возвращаемых значений внутренних функций.

В следующем фрагменте показано описание симптома.

// 获取订单总额函数
public double getOrderAmount(List<Product> productList) {
    double amount = 0.0D;
    for (Product product : productList) {
        if (Objects.isNull(product) || Objects.isNull(product.getPrice())
            || Objects.isNull(product.getNumber())) {
            continue;
        }
        amount += calculate(product.getPrice(), product.getNumber());
    }
    return amount;
}
// 计算金额函数
private Double calculate(double price, double number) {
    return price * number;
}

См. Следующее рекомендуемое решение.

// 获取订单总额函数
public double getOrderAmount(List<Product> productList) {
    double amount = 0.0D;
    for (Product product : productList) {
        if (Objects.isNull(product) || Objects.isNull(product.getPrice())
            || Objects.isNull(product.getNumber())) {
            continue;
        }
        amount += calculate(product.getPrice(), product.getNumber());
    }
    return amount;
}
// 计算金额函数
private double calculate(double price, double number) {
    return price * number;
}

Этот пример предназначен только для иллюстрации. В этом случае лучше использовать потоковое программирование.



Преимущества

  • По возможности используйте базовый тип для внутренних функций, чтобы избежать упаковки и распаковки неявных типов инкапсуляции.
  • Когда базовый тип используется для внутренних параметров функции, это синтаксически позволяет избежать оценки нулевого указателя для этих параметров.
  • Когда базовый тип используется для возвращаемых значений внутренних функций, это синтаксически позволяет избежать суждений о нулевом указателе для возвращаемых значений при вызове функции.

Избегайте возврата нулевых массивов и списков

Случай 1) По возможности избегайте возврата нулевых массивов, чтобы избежать ненужных суждений о нулевом указателе

В следующем фрагменте показано описание симптома.

// 调用代码
UserVO[] users = queryUser();
if (Objects.nonNull(users)) {
    for (UserVO user : users) {
        // 处理用户信息
    }
}
// 查询用户函数
private UserVO[] queryUser() {
    // 查询用户列表
    List<UserDO> userList = userDAO.queryAll();
    if (CollectionUtils.isEmpty(userList)) {
        return null;
    }
    // 转化用户数组
    UserVO[] users = new UserVO[userList.size()];
    for (int i = 0; i < userList.size(); i++) {
        UserDO user = userList.get(i);
        users[i] = new UserVO();
        users[i].setId(user.getId());
        users[i].setName(user.getName());
    }
    // 返回用户数组
    return users;
}

См. Следующее рекомендуемое решение.

// 调用代码
UserVO[] users = queryUser();
for (UserVO user : users) {
    // 处理用户信息
}
// 查询用户函数
private UserVO[] queryUser() {
    // 查询用户列表
    List<UserDO> userList = userDAO.queryAll();
    if (CollectionUtils.isEmpty(userList)) {
        return new UserVO[0];
    }
    // 转化用户数组
    UserVO[] users = new UserVO[userList.size()];
    for (int i = 0; i < userList.size(); i++) {
        UserDO user = userList.get(i);
        users[i] = new UserVO();
        users[i].setId(user.getId());
        users[i].setName(user.getName());
    }
    // 返回用户数组
    return users;
}

Случай 2) По возможности избегайте возврата нулевых списков, чтобы избежать ненужных суждений о нулевом указателе

В следующем фрагменте показано описание симптома.

// 调用代码
List<UserVO> userList = queryUser();
if (Objects.nonNull(userList)) {
    for (UserVO user : userList) {
        // 处理用户信息
    }
}
// 查询用户函数
private List<UserVO> queryUser(){
    // 查询用户列表
    List<UserDO> userList = userDAO.queryAll();
    if(CollectionUtils.isEmpty(userList)) {
        return null;
    }
    // 转化用户列表
    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);
    }
    // 返回用户列表
    return userVoList;
}

См. Следующее рекомендуемое решение.

// 调用代码
List<UserVO> userList = queryUser();
for (UserVO user : userList) {
   // 处理用户信息
 }
// 查询用户函数
private List<UserVO> queryUser(){
    // 查询用户列表
    List<UserDO> userList = userDAO.queryAll();
    if(CollectionUtils.isEmpty(userList)) {
        return Collections.emptyList();
    }
    // 转化用户列表
    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);
    }
    // 返回用户列表
    return userVoList;
}

Преимущества

  • Убедитесь, что возвращаемые массивы и списки не равны нулю, чтобы избежать суждений о нулевом указателе.

Инкапсулировать входные параметры функции

Случай 1) Когда вводится слишком много параметров, они должны быть инкапсулированы как классы параметров

Спецификации Java запрещают слишком много параметров функции, что затрудняет поддержку и расширение функций.

В следующем фрагменте показано описание симптома.

// 修改用户函数
public void modifyUser(Long id, String name, String phone, Integer age, 
    Integer sex, String address, String description) {
    // 具体实现逻辑
}

См. Следующее рекомендуемое решение.

// 修改用户函数
public void modifyUser(User user) {
    // 具体实现内容
}
// 用户类
@Getter
@Setter
@ToString
private class User{
    private Long id;
    private String name;
    private String phone;
    private Integer age;
    private Integer sex;
    private String address;
    private String description;
}

Инкапсулировать параметры группы как класс параметров

Поскольку параметры появляются в группах, необходимо инкапсулировать класс для описания этой ситуации.

В следующем фрагменте показано описание симптома.

// 获取距离函数
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;
}

Преимущества

  • Инкапсулируйте несколько параметров функции в виде классов, чтобы упростить расширение и обслуживание функций.
  • Инкапсулируйте параметры групповой функции в виде классов, чтобы прояснить бизнес.

По возможности используйте функции для реализации анонимных внутренних классов

Преимущества и недостатки анонимных внутренних классов в Java: В анонимном внутреннем классе (включая лямбда-выражения) вы можете напрямую обращаться к членам внешних классов, включая переменные-члены классов и внутренние переменные функций. В этом случае, поскольку вы обращаетесь к внешним переменным по желанию, границы кода неясны.

Мы рекомендуем использовать лямбда-выражения для упрощения анонимных внутренних классов, а затем использовать функции для реализации сложных лямбда-выражений.

Случай 1) По возможности используйте функции для реализации анонимных внутренних классов (включая лямбда-выражения).

В следующем фрагменте показано описание симптома.

// 发送结算数据
sendWorkerSettleData(WorkerPushDataType.CHECKER, () -> {
    Date beginDate = DateUtils.addDays(currDate, -aheadDays);
    Date endDate = DateUtils.addDays(currDate, 1);
    return auditTaskDAO.statCheckerSettleData(beginDate, endDate);
});

См. Следующее рекомендуемое решение.

// 发送结算数据
sendWorkerSettleData(WorkerPushDataType.CHECKER, () -> statCheckerSettleData(currDate, aheadDays));
// 统计验收员结算数据函数
private List<WorkerSettleData> statCheckerSettleData(Date currDate, int aheadDays) {
    Date beginDate = DateUtils.addDays(currDate, -aheadDays);
    Date endDate = DateUtils.addDays(currDate, 1);
    return auditTaskDAO.statCheckerSettleData(beginDate, endDate);
}

На самом деле есть способ попроще. Чтобы вычислить дату начала и дату окончания перед вызовом функции sendWorkerSettleData (для отправки данных оператору для расчета), используйте функцию auditTaskDAO.statCheckerSettleData (beginDate, endDate) вместо анонимного внутреннего класса.

Случай 2) Разделение сложных анонимных API реализации внутреннего класса на несколько API классов функций

Если функции API, реализованные анонимным внутренним классом, менее релевантны друг другу, разделите API на несколько функциональных API, чтобы облегчить использование лямбда-выражений.

В следующем фрагменте показано описание симптома.

// 清除过期数据
cleanExpiredData("用户日志表", new CleanExpiredDataOperator() {
    @Override
    public List<Date> queryExpiredDate(Integer remainDays) {
        return userDAO.queryExpiredDate(remainDays);
    }
    @Override
    public void cleanExpiredData(Date expiredDate) {
        userDAO.cleanExpiredData(expiredDate);
    }
});
// 清除过期数据函数
private void cleanExpiredData(String tableName, CleanExpiredDataOperator ,
cleanExpiredDataOperator) {
    // 功能实现代码
}
// 清除过期操作接口
interface CleanExpiredDataOperator {
    // 查询过期日期
    public List<Date> queryExpiredDate(Integer remainDays);
    // 清除过期数据
    public void cleanExpiredData(Date expiredDate);
}

См. Следующее рекомендуемое решение.

// 清除过期数据
cleanExpiredData("用户日志表", userDAO::queryExpiredDate,userDAO::cleanExpiredData);
// 清除过期数据函数
private void cleanExpiredData(String tableName, QueryExpiredDateOperator queryExpiredDateOperator, CleanExpiredDataOperator cleanExpiredDataOperator) {
    // 功能实现代码
}
// 查询过期日期接口
interface QueryExpiredDateOperator {
    // 查询过期日期
    public List<Date> queryExpiredDate(Integer remainDays);
}
// 清除过期操作接口
interface CleanExpiredDataOperator {
    // 清除过期数据
    public void cleanExpiredData(Date expiredDate);
}

Преимущества

  • Определите функции и укажите параметры, чтобы прояснить границы кода анонимных внутренних классов.
  • Используйте лямбда-выражения, чтобы упростить реализацию анонимных внутренних классов и упростить код.

Используйте возврат, чтобы удалить ненужный код

Случай 1. Удалите ненужные «если»

В следующем фрагменте показано описание симптома.

// 是否通过函数
public boolean isPassed(Double passRate) {
    if (Objects.nonNull(passRate) && passRate.compareTo(PASS_THRESHOLD) >= 0) {
        return true;
    }
    return false;
}

См. Следующее рекомендуемое решение.

// 是否通过函数
public boolean isPassed(Double passRate) {
    return Objects.nonNull(passRate) && passRate.compareTo(PASS_THRESHOLD) >= 0;
}

Случай 2) Удалите ненужные «else’s»

В следующем фрагменте показано описание симптома.

// 结算工资函数
public double settleSalary(Long workId, int workDays) {
    // 根据是否合格处理
    if (isQualified(workId)) {
        return settleQualifiedSalary(workDays);
    } else {
        return settleUnqualifiedSalary(workDays);
    }
}

См. Следующее рекомендуемое решение.

// 结算工资函数
public double settleSalary(Long workId, int workDays) {
    // 根据是否合格处理
    if (isQualified(workId)) {
        return settleQualifiedSalary(workDays);
    }
    return settleUnqualifiedSalary(workDays);
}

Случай 3) Удалить ненужные переменные

В следующем фрагменте показано описание симптома.

// 查询用户函数
public List<UserDO> queryUser(Long id, String name) {
    UserQuery userQuery = new UserQuery();
    userQuery.setId(id);
    userQuery.setName(name);
    List<UserDO> userList = userDAO.query(userQuery);
    return userList;
}

См. Следующее рекомендуемое решение.

// 查询用户函数
public List<UserDO> queryUser(Long id, String name) {
    UserQuery userQuery = new UserQuery();
    userQuery.setId(id);
    userQuery.setName(name);
    return userDAO.query(userQuery);
}

Преимущества

  • Удалите ненужные строки кода, чтобы получить более простой и удобный код.

Используйте временные переменные для оптимизации кода

В некоторых сегментах кода вы часто видите синтаксис a.getB().getC()...getN(). Это называется каскадным вызовом функции и приводит к плохой устойчивости и удобочитаемости кода. Мы рекомендуем не выполнять каскадные вызовы функций. Вместо этого используйте временные переменные для разделения вызовов и выполнения проверок нулевого указателя на объектах.

Случай 1) Используйте временные переменные, чтобы прояснить логику

В следующем фрагменте показано описание симптома.

// 是否土豪用户函数
private boolean isRichUser(User user) {
    return Objects.nonNull(user.getAccount())
        && Objects.nonNull(user.getAccount().getBalance())
        && user.getAccount().getBalance().compareTo(RICH_THRESHOLD) >= 0;
}

Это простой метод для упрощенного управления кодом, но он приводит к плохой читаемости.

См. Следующее рекомендуемое решение.

// 是否土豪用户函数
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;
}

Это решение увеличивает количество строк кода, но проясняет логику. Когда вы обнаружите, что сложно сбалансировать простоту и удобочитаемость, мы рекомендуем больше взвесить удобочитаемость.

Случай 2) Используйте временные переменные для упрощения кода

В следующем фрагменте показано описание симптома.

// 构建用户函数
public UserVO buildUser(UserDO user) {
    UserVO vo = new UserVO();
    vo.setId(user.getId());
    vo.setName(user.getName());
    if (Objects.nonNull(user.getAccount())) {
        vo.setBalance(user.getAccount().getBalance());
        vo.setDebt(user.getAccount().getDebt());
    }
    return vo;
}

Код написан таким образом, чтобы уменьшить количество временных переменных.

См. Следующее рекомендуемое решение.

// 构建用户函数
public UserVO buildUser1(UserDO user) {
    UserVO vo = new UserVO();
    vo.setId(user.getId());
    vo.setName(user.getName());
    UserAccount account = user.getAccount();
    if (Objects.nonNull(account)) {
        vo.setBalance(account.getBalance());
        vo.setDebt(account.getDebt());
    }
    return vo;
}

Преимущества

  • Используйте временные переменные, чтобы прояснить бизнес-логику.
  • Используйте временные переменные, чтобы упростить код. Имя переменной указывает на ее цель, что позволяет избежать множества бесполезного кода.
  • Если получение функций является сложным и требует много времени, используйте временные переменные для повышения эффективности работы.
  • Используйте временные переменные, чтобы избежать каскадных вызовов функций и предотвратить исключения нулевого указателя.

Сохранять только параметры, необходимые для функции

В некоторых сегментах кода вы часто видите синтаксис a.getB().getC()...getN(). Это называется каскадным вызовом функции и приводит к плохой устойчивости и удобочитаемости кода. Мы рекомендуем не выполнять каскадные вызовы функций. Вместо этого используйте временные переменные для разделения вызовов и выполнения проверок нулевого указателя на объектах.

Случай 1) Удалить избыточные параметры

В следующем фрагменте показано описание симптома.

// 修改用户状态函数
private void modifyUserStatus(Long userId, Integer status, String unused) {
    userCache.modifyStatus(userId, status);
    userDAO.modifyStatus(userId, status);
}
其中,unused参数是无用参数。
建议方案:
// 修改用户状态函数
private void modifyUserStatus(Long userId, Integer status) {
    userCache.modifyStatus(userId, status);
    userDAO.modifyStatus(userId, status);
}

Случай 2) Заменить объекты атрибутами

В следующем фрагменте показано описание симптома.

// 删除用户函数
private void deleteUser(User user) {
    userCache.delete(user.getId());
    userDAO.delete(user.getId());
}

См. Следующее рекомендуемое решение.

// 删除用户函数
private void deleteUser(Long userId) {
    userCache.delete(userId);
    userDAO.delete(userId);
}

При вызове функции нет необходимости создавать специальный объект параметра. Если функция использует более трех атрибутов, вам не нужно применять это правило.

Преимущества

Сохраните только параметры, необходимые для функции, чтобы уточнить параметры, которым необходимо присвоить значения во время вызова, и избежать создания бесполезных параметров во время вызова.

Постскриптум

Если у вас есть лучшие предложения или лучшие варианты кода, мы приветствуем ваш вклад. Мы надеемся, что эта статья послужит справочником и внесет свой вклад в формирование полного набора спецификаций кодирования Java.

Первоисточник:



Получите доступ к экспертному обзору - Подпишитесь на DDI Intel