This article is the nuggets community first contract article, not authorized to reprint

Java8’s stream stream, plus lambda expressions, is widely used to make code shorter and prettily available. We also have more options when writing complex code.

Code is meant to be seen first and executed by machines second. Whether the code is written succinctly and clearly, whether it is written beautifully, is of great significance to the subsequent bug repair and function expansion. A lot of the time, writing good code is not about the tool. Code is the embodiment of the ability and culture of engineers, some people, even using stream, lambda, code is still written like shit.

No, let’s take a look at a beautiful piece of code. Boy, there’s a cool logic in filter.

public List<FeedItemVo> getFeeds(Query query,Page page){
    List<String> orgiList = new ArrayList<>();
    
    List<FeedItemVo> collect = page.getRecords().stream()
    .filter(this::addDetail)
    .map(FeedItemVo::convertVo)
    .filter(vo -> this.addOrgNames(query.getIsSlow(),orgiList,vo))
    .collect(Collectors.toList());
    / /... Other logic
    return collect;
}

private boolean addDetail(FeedItem feed){
    vo.setItemCardConf(service.getById(feed.getId()));
    return true;
}

private boolean addOrgNames(boolean isSlow,List<String> orgiList,FeedItemVo vo){
    if(isShow && vo.getOrgIds() ! =null){
        orgiList.add(vo.getOrgiName());
    }
    return true;
}
Copy the code

If you don’t like it, we’ll put a little clip on it.

if(! CollectionUtils.isEmpty(roleNameStrList) && roleNameStrList.contains(REGULATORY_ROLE)) { vos = vos.stream().filter( vo - >! CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList()) && vo.getTaskName() ! =null)
           .collect(Collectors.toList());
} else{ vos = vos.stream().filter(vo -> vo.getIsSelect() && vo.getTaskName() ! =null) .collect(Collectors.toList()); vos = vos.stream().filter( vo -> ! CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList()) && vo.getTaskName() ! =null)
           .collect(Collectors.toList());
}
result.addAll(vos.stream().collect(Collectors.toList()));
Copy the code

Code can run, but it’s redundant. Don’t indent what you should, don’t wrap what you should, it’s not good code to say anything.

How to improve? Apart from technical problems, it is also a problem of consciousness. Always remember that good code is readable before it is functional.

1. Reasonable line breaks

In Java, fewer lines of code for the same function does not necessarily mean your code is better. Due to Java use; As a line of code split, you can even make an entire Java file into a single line if you like, just like JavaScript after it has been obfuscated.

Of course, we knew it was wrong. In lambda writing, there are a few tricks you can use to make your code more tidy.

Stream.of("i"."am"."xjjdog").map(toUpperCase()).map(toBase64()).collect(joining(""));
Copy the code

This code is not recommended. In addition to being difficult to read, it can also be difficult to find problems in the exception stack when something goes wrong with the code, such as an exception being thrown. So, we’re going to wrap it gracefully.

Stream.of("i"."am"."xjjdog")
    .map(toUpperCase())
    .map(toBase64())
    .collect(joining(""));
Copy the code

Don’t take this transformation for granted or take it for granted. In my regular code review, there are so many mixed up pieces of code that you can’t really understand the intent of the person writing the code.

Proper line breaks are a recipe for code longevity.

2. Be willing to split functions

Why do functions get longer and longer? Is it because the technology is high enough to navigate this change? The answer is laziness! Due to the problem of development schedule or consciousness, when there is a new requirement, I directly add ifelse to the old code. Even if THERE is a similar function, I directly choose to copy the original code. Over time, the code will not code.

First, a little bit about performance. In the JVM, the JIT compiler inlines methods for call-heavy, logically simple code to reduce stack frame overhead and allow for more optimization. So, short, snazzy functions are actually JVM-friendly.

In terms of readability, it’s necessary to break down a chunk of code into meaningful functions, which is the essence of refactoring. In lambda expressions, this splitting is even more necessary.

I’ll illustrate with an example of entity transformation that often occurs in code. The following transformation creates an anonymous function order->{}, which is semantically very weak.

public Stream<OrderDto> getOrderByUser(String userId){
    return orderRepo.findOrderByUser().stream()
        .map(order-> {
            OrderDto dto = new OrderDto();
            dto.setOrderId(order.getOrderId());
            dto.setTitle(order.getTitle().split("#") [0]);
            dto.setCreateDate(order.getCreateDate().getTime());
            return dto;
    });
}
Copy the code

In real business code, such assignment copies and conversion logic are often very long, and we can try to separate the dTO creation process. Because the transformation action is not the primary business logic, we usually don’t care what happens.

public Stream<OrderDto> getOrderByUser(String userId){
    return orderRepo.findOrderByUser().stream()
        .map(this::toOrderDto);
}
public OrderDto toOrderDto(Order order){
    OrderDto dto = new OrderDto();
            dto.setOrderId(order.getOrderId());
            dto.setTitle(order.getTitle().split("#") [0]);
            dto.setCreateDate(order.getCreateDate().getTime());
    return dto;
}
Copy the code

This conversion code is still a bit ugly. But if the constructor of OrderDto is Order, public OrderDto(Order Order), then we can remove the conversion logic from the main logic, and the whole code can be very clean.

public Stream<OrderDto> getOrderByUser(String userId){
    return orderRepo.findOrderByUser().stream()
        .map(OrderDto::new);
}
Copy the code

In addition to map and flatMap functions that can be semantically defined, more filters can be used instead of Predicate. Such as:

Predicate<Registar> registarIsCorrect = reg -> reg.getRegulationId() ! =null&& reg.getRegulationId() ! =0 
    && reg.getType() == 0;
Copy the code

RegistarIsCorrect can be used as a filter parameter.

3. Use Optional options

In Java code, NullPointerException is not a mandatory exception, so it can hide in the code and cause many unexpected bugs. So, whenever we get an argument, we validate it to see if it’s null, and the code is full of that.

if(null == obj)
if(null == user.getName() || "".equals(user.getName()))
    
if(order ! =null) {
    Logistics logistics = order.getLogistics();
    if(logistics ! =null){
        Address address = logistics.getAddress();
        if(address ! =null) {
            Country country = address.getCountry();
            if(country ! =null) {
                Isocode isocode = country.getIsocode();
                if(isocode ! =null) {
                    return isocode.getNumber();
                }
            }
        }
    }
}
Copy the code

Java8 introduced the Optional class to address the notorious null pointer problem. In effect, it is a wrapped class that provides several methods to determine its own null-value problem.

The more complex code example above can be replaced with the code below.

 String result = Optional.ofNullable(order)
      .flatMap(order->order.getLogistics())
      .flatMap(logistics -> logistics.getAddress())
      .flatMap(address -> address.getCountry())
      .map(country -> country.getIsocode())
      .orElse(Isocode.CHINA.getNumber());
Copy the code

When you’re not sure if what you’re providing is empty, it’s a good practice not to return null, otherwise the caller’s code will be full of NULL judgments. We have to nip null in the bud.

public Optional<String> getUserName(a) {
    return Optional.ofNullable(userName);
}
Copy the code

Also, try to minimize the use of Optional get methods, which can also make your code ugly. Such as:

Optional<String> userName = "xjjdog";
String defaultEmail = userName.get() == null ? "":userName.get() + "@xjjdog.cn";
Copy the code

Instead, it should be modified like this:

Optional<String> userName = "xjjdog";
String defaultEmail = userName
    .map(e -> e + "@xjjdog.cn")
    .orElse("");
Copy the code

So why is our code still full of all kinds of null values? Even in very professional and popular code? One very important reason is to be consistent with Optional. When there is a break in one of these links, most coders write code in a way that mimics the style of the original code.

Scaffolding designers or reviewers will need to work a little harder if Optional is to be widely used in projects.

4. Return Stream or List?

Many people find themselves in a dilemma when designing interfaces. When I return data, do I return Stream directly or do I return List?

If you return a List, such as an ArrayList, and you modify the List, that will directly affect the values in it, unless you wrap it in an immutable way. Similarly, arrays have the same problem.

For a Stream, however, it is immutable and does not affect the original collection. For this scenario, we recommend returning the Stream directly rather than the collection. Another benefit of this approach is that it strongly hints at the API user’s ability to use stream-related functions in order to unify the code style.

public Stream<User> getAuthUsers(a){...return Stream.of(users);
}
Copy the code

Immutable sets are a strong requirement to prevent external functions from making unpredictable changes to these sets. In Guava, there are a number of Immutable classes that support this type of wrapping. Another example is Java enumerations, whose values() method makes only one copy of the data to prevent external apis from modifying the enumeration.

However, if your API is for the end user and doesn’t need to be modified, then it’s better to just return the List, like in the Controller.

5. Use fewer or no parallel streams

Java’s parallel streams have a lot of problems that people unfamiliar with concurrent programming often stomp on. It’s not that parallel streaming is bad, but if you find that your team is failing at it, you won’t hesitate to reduce the frequency of recommendations.

Parallel flow is a perennial problem, which is thread safety. During iteration, problems can arise if you use classes that are not thread-safe. The following code, for example, runs incorrectly most of the time.

List transform(List source){
 List dst = new ArrayList<>();
 if(CollectionUtils.isEmpty()){
  return dst;
 }
 source.stream.
  .parallel()
  .map(..)
  .filter(..)
  .foreach(dst::add);
 return dst;
}
Copy the code

You might say, let me just change foreach to collect. Note, however, that many developers are not aware of this. Since the API provides such a function, and it makes logical sense, you can’t stop others from using it.

Another abuse problem with parallel flows is that they perform very long IO tasks in iterations. Before using parallel streams, do you have a question? Since it is parallel, how is its thread pool configured?

Unfortunately, all parallel streams share a ForkJoinPool. Its size, which defaults to number of cpus -1, is not sufficient in most cases.

If someone is running time-consuming IO traffic on a parallel stream, you need to queue even a simple math operation. The point is, you can’t stop the other students in the project from using parallel streams, and you can’t tell what they’re doing.

So what? What I’m doing is I’m banning it. It was cruel, but it avoided the problem.

conclusion

The Stream feature added to Java8 is great, so we don’t have to envy other languages anymore, and it’s much easier to write code. It looks awesome, but it’s just a grammar candy, so don’t expect to get superpowers from using it.

As Stream became more popular, our code became more and more like this. But a lot of code now, with Stream and Lambda, gets worse, stinky and long and unreadable. No other reason, abuse!

In general, when using Stream and Lambda, the main process should be simple and clear, with uniform style, reasonable line breaks, skipping functions, using Optional features correctly, and not adding code logic to functions like filter. Consciously follow these tips when writing code. Simplicity is productivity.

If Java doesn’t offer enough features, we have an open source class library, Vavr, that offers even more possibilities, in combination with Stream and Lambda, to enhance the functional programming experience.

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.10.3</version>
</dependency>
Copy the code

However, no matter how powerful apis and programming methods are provided, they cannot withstand the abuse of partners. The code, which makes perfect logical sense, just looks awkward and is hard to maintain.

Writing garbage lambda code is the best way to abuse your colleagues, and the only way to bury them.

Writing code is like talking. Everyone is doing the same job, some people talk nice and good appearance level, everyone likes to chat with him; Some people are not easy to talk, where pain poke where, although he exists but we all hate.

Code, besides what it means to work, is just another way for us to express ourselves in the world. How to write good code is not only a technical problem, but also a problem of consciousness.

This article is the nuggets community first contract article, not authorized to reprint