Java: How to deal with a more elegant null values?

Lead

In the years of development experience I often see the situation around the null value judgment exists in the project, these judgments, will make people feel perplexed, and it appears likely the current business logic does not matter. But it makes you a headache.

Sometimes, even more frightening is that these systems because null, null pointer exception is thrown, resulting in business systems problems.

This article, I summarize several on handling null values, we want to help the reader.

Null values ​​in business

Scenes

There is a UserSearchService used to provide user query function:

public interface UserSearchService{
  List listUser();

  User get(Integer id);
}

Site problem

For object-oriented language is concerned, in particular the important level of abstraction. Especially for the abstract interface, which accounts for a large proportion in the design and development, we want as much as possible in the development oriented programming interface.

The method described above for the interface point of view, it may be inferred probably contains the following two meanings:

  • listUser (): Query User List

  • get (Integer id): a single user query

In all development, XP respected TDD mode may well lead us to the definition of the interface, so we will TDD as a development code, "facilitator."

For the above interfaces, when we were using TDD test first, we found a potential problem:

  • listUser () if there is no data, then it is returned empty set or null it?

  • get (Integer id) If no object is to throw an exception or return null it?

In-depth study listUser

We first discuss

listUser()

This interface, I often see implemented as follows:

public List listUser(){
    List userList = userListRepostity.selectByExample(new UserExample());
    if(CollectionUtils.isEmpty(userList)){//spring util工具类
      return null;
    }
    return userList;
}

This code returned is null, from my many years of development experience is concerned, for the collection of this return value, it is best not to return null, because if it returns a null, will give the caller a lot of trouble. You will put this to the caller to call risk control.

If the caller is a cautious man, he will be judged is null condition. If he is not careful, or he is an interface-oriented programming fanatics (of course, interface-oriented programming is the right direction), he will follow his own understanding to call interface, without determining whether the condition is null, and if so , is very dangerous, it is likely to occur null pointer exception!

Be judged according to Murphy's Law: "The problem is likely to arise, it will appear in the future!"

Based on this, we will optimize it:

public List listUser(){
    List userList = userListRepostity.selectByExample(new UserExample());
    if(CollectionUtils.isEmpty(userList)){
      return Lists.newArrayList();//guava类库提供的方式
    }
    return userList;
}

For Interface (List listUser ()), it will return List, even if there is no data, it will still return List (a set of no elements);

Through the above changes, we successfully avoid a null pointer exception may occur, such wording is more secure!

Depth study of the get method

For the interface

User get(Integer id)

You can see the phenomenon, I give id, it will give me return to User. But it really is likely not the case.

I have seen the realization of:

public User get(Integer id){
  return userRepository.selectByPrimaryKey(id);//从数据库中通过id直接获取实体对象
}

I believe many people will write.

By the time the code that its return value is likely to be null! We are but the interface can not be seen in!

This is a very dangerous thing. Especially for the caller!

Advice I give is that obviously need to replenish the document in the interface, such as for unusual instructions, using annotations @exception:

public interface UserSearchService{

  /**
   * 根据用户id获取用户信息
   * @param id 用户id
   * @return 用户实体
   * @exception UserNotFoundException
   */
  User get(Integer id);

}

After we define the interface plus the explanation, the caller will see, if you call this interface is likely to be thrown "UserNotFoundException (user not found)," such an exception.

This way you can see the definition of the interface when the caller calls the interface, however, this approach is "weak caveats"!

If the caller to ignore the comments, it is possible to produce a system of business risk, this risk could lead to a one hundred million!

In addition to the above such "weak prompt" approach, there is a way, the return value is likely to be empty. It is up to how to do it?

I think we need to add an interface used to describe this scene.

The introduction of jdk8 Optional, or use of guava Optional See defined as follows:

public interface UserSearchService{

  /**
   * 根据用户id获取用户信息
   * @param id 用户id
   * @return 用户实体,此实体有可能是缺省值
   */
  Optional getOptional(Integer id);
}

Optional has two meanings: presence or default.

So by reading Interface getOptional (), we can quickly understand the intent of the return value, this fact is we want to see it go ambiguities removed.

Its implementation can be written as:

public Optional getOptional(Integer id){
  return Optional.ofNullable(userRepository.selectByPrimaryKey(id));
}

Deep into the reference

By all the above description of the interface, you can determine the parameters must be the id must pass it? I think the answer is: not sure. Unless an annotation on the document to be explained.

How was it bound to the Senate?

I recommend to you two ways:

  • Mandatory constraint

  • Documentation constraints (weak prompt)

1. obligation, we can be strict constraint declaration by jsr 303:

public interface UserSearchService{
  /**
   * 根据用户id获取用户信息
   * @param id 用户id
   * @return 用户实体
   * @exception UserNotFoundException
   */
  User get(@NotNull Integer id);

  /**
   * 根据用户id获取用户信息
   * @param id 用户id
   * @return 用户实体,此实体有可能是缺省值
   */
  Optional getOptional(@NotNull Integer id);
}

Of course, this write operation with the AOP to be verified, but let spring has provided a good integrated solution, in this I will not go into details.

2. Documentation constraints

In many cases, we will encounter the legacy code, legacy code for the possibility of holistic transformation is unlikely.

We hope to be described interfaces by reading the implementation of the interface.

jsr 305 specification, has given us a way into a description of the interface parameters of (the need to introduce library com.google.code.findbugs: jsr305):

You can use Notes: @Nullable @Nonnull @CheckForNull interface description.

such as:

public interface UserSearchService{
  /**
   * 根据用户id获取用户信息
   * @param id 用户id
   * @return 用户实体
   * @exception UserNotFoundException
   */
  @CheckForNull
  User get(@NonNull Integer id);

  /**
   * 根据用户id获取用户信息
   * @param id 用户id
   * @return 用户实体,此实体有可能是缺省值
   */
  Optional getOptional(@NonNull Integer id);
}

summary

The return value through an empty set, Optional, jsr 303, jsr 305 this in several ways, can make our code more readable, less error rate!

  • Empty set Returns: If you have a collection of this return value, unless there really is reason to convince myself otherwise, we must return empty set, rather than null

  • Optional: If your code is jdk8, on the introduction of it! If not, the Optional Guava, or upgrade jdk version! It is to a large extent can increase the readability of the interface!

  • jsr 303: If the new project is being developed, not anti add this to try! There must be a special feeling cool!

  • jsr 305: If the old items in your hand, you can try adding this document annotation type, you can help reconstruct late, or adds new features for the old interface to understand!

Empty Object Pattern

Scenes

Let's look at a DTO transformation scene, objects:

@Data
static class PersonDTO{
  private String dtoName;
  private String dtoAge;
}

@Data
static class Person{
  private String name;
  private String age;
}

Person needs is to convert objects into PersonDTO, and then return.

Of course, for practical terms, if the Person returned is empty, return null, but can not return null PersonDTO (especially such DTO Rest returned interface).

Here, we focus only on conversion operations, see the following code:

@Test
public void shouldConvertDTO(){

  PersonDTO personDTO = new PersonDTO();

  Person person = new Person();
  if(!Objects.isNull(person)){
    personDTO.setDtoAge(person.getAge());
    personDTO.setDtoName(person.getName());
  }else{
    personDTO.setDtoAge("");
    personDTO.setDtoName("");
  }
}

Optimize modifier

Such data conversion, we know very poor readability, each field is determined, if it is empty to an empty string ( "")

A different way of thinking to think that we are to get data Person of the class, followed by the assignment operator (setXXX), who in fact is not a concrete realization of the relationship between Person of Yes.

Then we can create a subclass of Person:

static class NullPerson extends Person{
  @Override
  public String getAge() {
    return "";
  }

  @Override
  public String getName() {
    return "";
  }
}

Person of it as a special case exists if, when Person is empty, some of the get * default behavior is returned.

So the code can be modified as follows:

@Test
 public void shouldConvertDTO(){

   PersonDTO personDTO = new PersonDTO();

   Person person = getPerson();
   personDTO.setDtoAge(person.getAge());
   personDTO.setDtoName(person.getName());
 }

 private Person getPerson(){
   return new NullPerson();//如果Person是null ,则返回空对象
 }

Wherein the getPerson () method can be used to obtain possible Person object according to the service logic (for example in terms of current, if Person does not exist, the return exceptions NUllPerson Person), so if modified, would readability It becomes very strong.

Optional use can be optimized

Null object model, its drawbacks is the need to create an exception object, but if the special case of the more the case, we need to create multiple exceptions are not objects of it, although we also use object-oriented multi-state characteristics, however, complicated business If sex really let us create multiple objects special case, we still have to think twice about this model, it may bring the complexity of the code.

For the above codes, you may also be used to optimize Optional.

@Test
  public void shouldConvertDTO(){

    PersonDTO personDTO = new PersonDTO();

    Optional.ofNullable(getPerson()).ifPresent(person -> {
      personDTO.setDtoAge(person.getAge());
      personDTO.setDtoName(person.getName());
    });
  }

  private Person getPerson(){
    return null;
  }

Optional use of null values, I think the more appropriate, it only applies to "whether there is a" scene.

If you only judge the existence of control, I recommend using Optional.

Proper use of Optioanl

Optional so strong that it expresses the most primitive computer characteristics (0 or 1), then it is how to use it properly!

Optional not as an argument

If you write a public method, this method provides a number of input parameters, there are some that can pass null, and that this time whether it can use the Optional?

I give advice: Do not be so sure use!

for example:

public interface UserService{
  List listUser(Optional username);
}

Methods listUser this example, may tell us need to query all data sets based on username, if username is empty, all users have to return the collection.

When we see this method of time, you will feel some ambiguity:

"If the username is absent, return empty set? Or return all of the user data set?"

Optioanl is a branch of Judgment, that whether we are still concerned about the Optional

Optional.get () do?

I will give you advice is, if you do not want this ambiguity, do not use it!

If you really want to express two meanings, give it split two interfaces:

public interface UserService{
  List listUser(String username);
  List listUser();
}

I think this semantic stronger, and better meet the software design principles of "single responsibility."

If you feel that you are really necessary to the Senate may pass null, then please use jsr 303 or jsr 305 will be described and verified!

Remember! Optional parameters can not be used as the argument!

Optional as a return value

When a return entities

That Optioanl can be used as the return value it?

In fact, it is very satisfying existence of this semantics.

As you say, you want to get information based on the user id, the user is likely to exist or not exist.

You can use:

public interface UserService{
  Optional get(Integer id);
}

When calling this method, the caller clearly get the data returned by the method, there may not exist, so you can do something more reasonable judgment, the better to prevent the null pointer errors!

Of course, if the business side really need a User id must be based on the query, then it should not be so used, please indicate that you want to throw.

Only when it returns null under consideration is reasonable, the return was carried out Optional

Collection of entities return

Not all return values ​​can be so used! If you return a collection:

public interface UserService{
  Optional<List> listUser();
}

This returns the results, let the caller know what to do, and then if I judge Optional, also with the judge isEmpty it?

Such ambiguity caused by the return value! I think it is not necessary. 

We should be agreed for this collection List the return value, if the collection is really null, return an empty collection (Lists.newArrayList);

Use Optional variable

Optional<User> userOpt = ...

If there is such a variable userOpt, please keep in mind:

  • Must not be used directly get, if so used, it loses meaning Optional itself (such as userOp.get ())

  • Do not use direct getOrThrow, if you have such a demand: Get less than to throw an exception. It would have to consider whether the call is an interface design is reasonable

Using getter in

For a java bean, all attributes are likely to return null, then the need to rewrite all of the getter become Optional type it?

I will give you advice is do not do this abuse Optional.

Even in my java bean getter is in line Optional, but because the java bean too much, it will cause your code has more than 50% of Optinal judge, thus contaminating the code. (I would say, in fact, you should all entities in the field of business meaning, thinking seriously over the value of its existence, not because of abuse exists Optional)    

We should be more focus on business, not just judge a null value.

Please do not abuse the Optional in getter.

summary

Optional use can be summed up in:

  • When using empty values, not due to errors, you can use the Optional!

  • Optional Do not operate for the set!

  • Do not abuse Optional, such as the java bean-getter in!

Published 82 original articles · won praise 7 · views 10000 +

Guess you like

Origin blog.csdn.net/a972669015/article/details/103119562