9 个让你的 Java 代码变得更好的简单规则

举报
千锋教育 发表于 2023/06/27 14:40:06 2023/06/27
【摘要】 1. 使用java.util.Optional代替null通过使用,java.util.Optional您将迫使客户检查该值的存在。考虑getBeer(...)下面的方法,该方法的调用者期望接收一个Beer对象,但从方法 API 中并不清楚该对象Beer可以是null。顺便说一下,调用者可能会忘记添加检查null,并且可能会收到NullPointerException程序员错误。前publi...

1. 使用java.util.Optional代替null

通过使用,java.util.Optional您将迫使客户检查该值的存在。

考虑getBeer(...)下面的方法,该方法的调用者期望接收一个Beer对象,但从方法 API 中并不清楚该对象Beer可以是null。顺便说一下,调用者可能会忘记添加检查null,并且可能会收到NullPointerException程序员错误。

public Beer getBeer(Customer customer) {
  return customer.age < 18
    ? null
    : this.beerCatalogue.get(0);
}

public Optional<Beer> getBeer(Customer customer) {
  return customer.age < 18
    ? empty()
    : Optional.of(this.beerCatalogue.get(0));
}


2.返回空集合/数组而不是null

动机与上面相同——我们不应该依赖null

public List<Beer> getBeerCatalogue(Customer customer) {
  return customer.age < 18
    ? null
    : this.beerCatalogue;
}

public List<Beer> getBeerCatalogue(Customer customer) {
  return customer.age < 18
    ? emptyList()
    : this.beerCatalogue;
}


3. 使用var

类型推断减少了样板代码的数量并降低了认知复杂性,从而提高了可读性。

static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor,
                               SchemaDefinition schemaDefinition) {
    ValueGetter valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
    return (dynamicMessageBuilder, row) -> {
        Iterable<DynamicMessage> iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
        for (Object entry : iterable) {
            dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
        }
    };
}

static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor,
                               SchemaDefinition schemaDefinition) {
    var valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
    return (dynamicMessageBuilder, row) -> {
        var iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
        for (var entry : iterable) {
            dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
        }
    };
}


4. 制作局部变量final

创建局部变量final会向程序员提示该变量无法重新分配,这通常会提高代码质量并有助于避免错误。

static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor,
                               SchemaDefinition schemaDefinition) {
    var valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
    return (dynamicMessageBuilder, row) -> {
        var iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
        for (var entry : iterable) {
            dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
        }
    };
}

static FieldMapper fieldMapper(FieldDescriptor fieldDescriptor,
                               SchemaDefinition schemaDefinition) {
    final var valueGetter = valueGetter(schemaDefinition, fieldDescriptor);
    return (dynamicMessageBuilder, row) -> {
        final var iterable = (Iterable<DynamicMessage>) valueGetter.get(row);
        for (final var entry : iterable) {
            dynamicMessageBuilder.addRepeatedField(fieldDescriptor, entry);
        }
    };
}


5.使用静态导入

静态导入使代码更简洁,因此更具可读性。

请注意,此规则有一个边缘情况 - Java 中有一堆静态方法(List.of()、等)静态导入Set.of()Map.of()这会损害代码质量,使其变得模糊。因此,使用此规则时,请始终问自己 -此静态导入是否使代码更具可读性?

public static List<FieldGetter> fieldGetters(SchemaDefinition schemaDefinition,
                                             FieldName fieldName,
                                             FieldDescriptor fieldDescriptor) {
    final var schemaField = SchemaUtils.findFieldByName(schemaDefinition, fieldName)
        .orElseThrow(SchemaFieldNotFoundException::new);
    if (fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
        return schemaField.getFields().stream()
            .flatMap(it -> fieldGetters(schemaDefinition, it.getName(), it.getDescriptor()))
            .collect(Collectors.toList());
    }
    return Collections.emptyList();
}

public static List<FieldGetter> fieldGetters(SchemaDefinition schemaDefinition,
                                             FieldName fieldName,
                                             FieldDescriptor fieldDescriptor) {
    final var schemaField = findFieldByName(schemaDefinition, fieldName)
        .orElseThrow(SchemaFieldNotFoundException::new);
    if (fieldDescriptor.getJavaType() == MESSAGE) {
        return schemaField.getFields().stream()
            .flatMap(it -> fieldGetters(schemaDefinition, it.getName(), it.getDescriptor()))
            .collect(toList());
    }
    return emptyList();
}


6. 优先选择完全合格的进口产品

与上面相同,它使代码更具可读性。

public SchemaDefinition.GraphView makeSchemaDefinitionGraph() {
    final var rootNode = new SchemaDefinition.RootNode();

    final var messageNode1 = new SchemaDefinition.MessageNode.MessageNodeBuilder()
        .withHeader("message-header-1")
        .withBody("message-body-1")
        .build();
    final var messageNode2 = new SchemaDefinition.MessageNode.MessageNodeBuilder()
        .withHeader("message-header-2")
        .withBody("message-body-2")
        .build();

    rootNode.addNode(messageNode1);
    rootNode.addNode(messageNode2);

    return rootNode.asGraph();
}

public GraphView makeSchemaDefinitionGraph() {
    final var rootNode = new RootNode();

    final var messageNode1 = new MessageNodeBuilder()
        .withHeader("message-header-1")
        .withBody("message-body-1")
        .build();

    final var messageNode2 = new MessageNodeBuilder()
        .withHeader("message-header-2")
        .withBody("message-body-2")
        .build();

    rootNode.addNode(messageNode1);
    rootNode.addNode(messageNode2);

    return rootNode.asGraph();
}


7. 在长方法/构造函数声明中将每个参数放在新行上

拥有特定的代码风格并在代码库中使用它可以降低认知复杂性,这意味着代码更容易阅读和理解。

public void processUserData(String name, int age, String address, double salary, boolean isEmployed, String occupation) {
    //...
}

// or

public void processUserData(
    String name, int age, String address, double salary, boolean isEmployed, String occupation
) {
    //...
}

// or

public void processUserData(String name, int age,
  String address, double salary, boolean isEmployed, String occupation
) {
    //...
}

// or

public void processUserData(String name, int age, String address, double salary, 
                            boolean isEmployed, String occupation
) {
    //...
}

public void processUserData(String name,
                            int age,
                            String address,
                            double salary,
                            boolean isEmployed,
                            String occupation) {
    //...
}

// or

public void processUserData(
    String name,
    int age,
    String address,
    double salary,
    boolean isEmployed,
    String occupation) {
    //...
}


8. 创建不可变的 POJO 或使用record

不可变类比可变类更容易设计、实现和使用。它们不太容易出错并且更安全。对于不可变对象,您不必担心同步或对象状态(对象是否已初始化?)。

使类不可变:

  • 使类中的所有字段final
  • 创建一个constructor/builder来初始化所有字段
  • 如果该值是可选的(可以是null)使用java.util.Optional
  • 使用不可变集合或在 getter 中返回不可变视图(Collections.unmodifiableList(...)等)
  • 不要公开修改对象状态的方法

public class User {
    private String name;
    private int age;
    private String address;
    private List<Claim> claims;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Claim> getClaims() {
        return claims;
    }

    public void setClaims(List<Claim> claims) {
        this.claims = claims;
    }

// ...
}

public class User {
    private final String name;
    private final int age;
    private final String address;
    private final List<Claim> claims;

    public User(String name, int age, String address, List<Claim> claims) {
        this.name = requireNonNull(name);
        this.age = requirePositive(age);
        this.address = requireNonNull(address);
        this.claims = List.copyOf(requireNonNull(claims));
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }

    public List<Claim> getClaims() {
        return claims;
    }

// ...
}

record或者如果您使用 java 14+ 则使用

public record User(String name, int age, String address, List<Claim> claims) {
    public User {
        requireNonNull(name);
        requirePositive(age);
        requireNonNull(address);
        claims = List.copyOf(requireNonNull(claims));
    }
}


9. 对具有许多参数/可选参数的类使用构建器模式

Builder 模式模拟 Python/Scala/Kotlin 中可用的命名参数。它使客户端代码易于阅读和编写,并且使您能够更流畅地使用具有默认值的选项/参数。

public class User {
    private final UUID id;
    private final Instant createdAt;
    private final Instant updatedAt;
    private final String firstName;
    private final String lastName;
    private final Email email;
    private final Optional<Integer> age;
    private final Optional<String> middleName;
    private final Optional<Address> address;

    public User(UUID id,
                Instant createdAt,
                Instant updatedAt,
                String firstName,
                String lastName,
                Email email,
                Optional<Integer> age,
                Optional<String> middleName,
                Optional<Address> address) {
        this.id = requireNonNull(id);
        this.createdAt = requireNonNull(createdAt);
        this.updatedAt = requireNonNull(updatedAt);
        this.firstName = requireNonNull(firstName);
        this.lastName = requireNonNull(lastName);
        this.email = requireNonNull(email);
        this.age = requireNonNull(age);
        this.middleName = requireNonNull(middleName);
        this.address = requireNonNull(address);

        if (firstName.isBlank()) {
            // throw exception
        }

        // ... validation
    }

    // ...
}

// And then you would write:
public User createUser() {
    final var user = new User(
        randomUUID(),
        now(),
        now().minus(1L, DAYS),
        // firstName, lastName and email are String, what if you
        // mix up parameters order in constructor?
        "first_name",
        "last_name",
        "user@test.com",
        empty(),
        Optional.of("middle_name"),
        empty()
    );

    // ...
    return user;
}

public class User {
    private final UUID id;
    private final Instant createdAt;
    private final Instant updatedAt;
    private final String firstName;
    private final String lastName;
    private final String email;
    private final Optional<Integer> age;
    private final Optional<String> middleName;
    private final Optional<Address> address;

    // private constructor
    private User(Builder builder) {
        this.id = requireNonNull(builder.id);
        this.createdAt = requireNonNull(builder.createdAt);
        this.updatedAt = requireNonNull(builder.updatedAt);
        this.firstName = requireNonNull(builder.firstName);
        this.lastName = requireNonNull(builder.lastName);
        this.email = requireNonNull(builder.email);
        this.age = requireNonNull(builder.age);
        this.middleName = requireNonNull(builder.middleName);
        this.address = requireNonNull(builder.address);

        if (firstName.isBlank()) {
            // throw exception
        }

        // ... validation
    }

    // ...

    public static class Builder {
        private UUID id;
        private Instant createdAt;
        private Instant updatedAt;
        private String firstName;
        private String lastName;
        private String email;
        // Optionals are empty by default
        private Optional<Integer> age = empty();
        private Optional<String> middleName = empty();
        private Optional<Address> address = empty();

        private Builder() {
        }

        public static Builder newUser() {
            // You can easily add (lazy) default parameters
            return new Builder()
                .id(randomUUID())
                .createdAt(now())
                .updatedAt(now());
        }

        public Builder id(UUID id) {
            this.id = id;
            return this;
        }

        public Builder createdAt(Instant createdAt) {
            this.createdAt = createdAt;
            return this;
        }

        // ...

        public Builder address(Address address) {
            this.address = Optional.ofNullable(address);
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// And then:
public User createUser() {
    // You end up writing more code in User class but the
    // class API becomes more concise
    final var user = newUser()
        .updatedAt(now().minus(1L, DAYS))
        .firstName("first_name")
        .lastName("last_name")
        .email("user@test.com")
        .middleName("middle_name")
        .build();

    // ...
    return user;
}

文章如果没看够可以,B站搜索千锋教育


【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。