9 个让你的 Java 代码变得更好的简单规则
【摘要】 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)