preface

Today I’m going to talk to you about an interesting topic: how to write code that makes people crazy?

When you see this title, your first impression is that this article might be a hydrology. But I am responsible enough to tell you that it is a technical article with a lot of dry stuff.

Have you ever read someone else’s code and become mad, angry, or angry?

I’m going to talk to you today about the 20 code that drives me crazy.

1. Not paying attention to code format

The code format is very virtual, I use a few cases to demonstrate, do not pay attention to the effect of the code format. As an appetizer to this article.

1.1 the blank space

Sometimes necessary Spaces are not added, for example:

@Service
@Slf4j
public class TestService1{
public void test1(a){
addLog("test1");
 if (condition1){
 if (condition2){
 if (condition3){
 log.info("info:{}",info);
  }
  }
  }
}
}
Copy the code

How do you feel when you look at this code? Does your blood pressure spike?

The code seems to be all screwed up.

So how do you get your blood pressure down?

Answer: Add a space.

Truth:

@Service
@Slf4j
public class TestService1 {
    public void test1(a) {
       addLog("test1");
       if (condition1) {
         if (condition2) {
           if (condition3) {
               log.info("info:{}", info);
            }
          }
        }
    }
}
Copy the code

With just a few extra Spaces and a few tweaks, the hierarchy of this code suddenly becomes very clear.

Okay, I’m calm again.

1.2 a newline

When writing code, if some necessary newlines are not added, you might get code like this:

public void update(User user) {
    if (null! = user.getId()) { User oldUser = userMapper.findUserById(user.getId());if(null == oldUser)throw new RuntimeException("User ID does not exist"); oldUser.setName(user.getName()); oldUser.setAge(user.getAge()); oldUser.setAddress(user.getAddress()); userMapper.updateUser(oldUser); }else{ userMapper.insertUser(user); }}Copy the code

Look at this code, is not a little life without love feeling?

Simply add space to optimize:

public void update(User user) {
    if (null! = user.getId()) { User oldUser = userMapper.findUserById(user.getId());if(null == oldUser) {
            throw new RuntimeException("User ID does not exist");
        }

        oldUser.setName(user.getName());
        oldUser.setAge(user.getAge());
        oldUser.setAddress(user.getAddress());
        userMapper.updateUser(oldUser);
    } else{ userMapper.insertUser(user); }}Copy the code

The code logic suddenly becomes much clearer.

2. Name it casually

Java does not enforce the names of parameters, methods, classes, or packages. But if we don’t develop good naming habits, random naming can lead to a lot of strange code.

2.1 Meaningful parameter names

Sometimes when we write code, it is better to make the parameter names as simple as possible to save a few letters from typing. Suppose colleague A writes the following code:

int a = 1;
int b = 2;
String c = "abc";
boolean b = false;
Copy the code

After A while, colleague A leaves, and colleague B takes over the code.

What does a mean, what does B mean, and C… Then the heart ten thousand grass mud horse.

It is important to give parameters meaningful names to avoid burdening yourself or others.

Truth:

int supplierCount = 1;
int purchaserCount = 2;
String userName = "abc";
boolean hasSuccess = false;
Copy the code

2.2 Meaning is known by name

Meaningful parameter names are not enough, we can’t pursue that. We’d better be able to name our parameters in a way that’s obvious, or else:

String yongHuMing = "Susan"; String User Name ="Susan";
String su3 = "Susan";
String suThree = "Susan";
Copy the code

Don’t these parameter names look a little strange?

Why not make it an internationally accepted English word?

String userName = "Susan";
String susan = "Susan";
Copy the code

The above two parameter names, basically everyone can understand, reduce a lot of communication costs.

Therefore, when defining parameter names, method names, and class names, it is recommended to use internationally common English words to simplify and reduce communication costs. Avoid using Chinese characters, pinyin, or numbers to define names.

2.3 Consistent Style of Parameter Names

Parameter names actually have a variety of styles, such as:

// All lowercase letters
int suppliercount = 1;

// All uppercase letters
int SUPPLIERCOUNT = 1;

// Lowercase letter + underscore
int supplier_count = 1;

// Uppercase letters + underscore
int SUPPLIER_COUNT = 1;

// Hump
int supplierCount = 1;
Copy the code

If you define multiple style parameter names in a class, doesn’t it look a bit cluttered?

It is recommended that member variables, local variables, and method parameters of classes use supplierCount, a hump style: lower case for the first letter and upper case for the first letter of each word. Such as:

int supplierCount = 1;
Copy the code

In addition, it is recommended to use SUPPLIER_COUNT, which is the parameter name separated by uppercase letters and underscores, for static constants for differentiation purposes. Such as:

private static final int SUPPLIER_COUNT = 1;
Copy the code

3. Lots of duplicate code

CTRL + C and CTRL + V are probably the most commonly used keyboard shortcuts by programmers.

Yes, we are nature’s porters. Ha, ha, ha.

In the early days of the project, we were able to use this mode of work to actually increase productivity and write (actually type) a lot less code.

But here’s the problem: there’s a lot of code duplication. Such as:

@Service
@Slf4j
public class TestService1 {

    public void test1(a)  {
        addLog("test1");
    }

    private void addLog(String info) {
        if (log.isInfoEnabled()) {
            log.info("info:{}", info); }}}Copy the code
@Service
@Slf4j
public class TestService2 {

    public void test2(a)  {
        addLog("test2");
    }

    private void addLog(String info) {
        if (log.isInfoEnabled()) {
            log.info("info:{}", info); }}}Copy the code
@Service
@Slf4j
public class TestService3 {

    public void test3(a)  {
        addLog("test3");
    }

    private void addLog(String info) {
        if (log.isInfoEnabled()) {
            log.info("info:{}", info); }}}Copy the code

In TestService1, TestService2, and TestService3 classes, there is an addLog method for adding logs.

It worked fine until one day, there was an online accident: the server disk was full.

The reason is that too many logs are printed and many unnecessary logs are recorded, such as all the returned values of the interface and the specific printing of large objects.

There is no alternative but to change the addLog method to log only debug logs.

Therefore, you need to search the full text, addLog method to change to the following code:

private void addLog(String info) {
    if (log.isDebugEnabled()) {
        log.debug("debug:{}", info); }}Copy the code

There are three classes that need to be changed, but if you have 30 or 300 classes that need to be changed, it can be very painful. Correct wrong, or correct leakage, will be buried hidden trouble, pit oneself.

Why not extract the code for this functionality and put it in a utility class?

@Slf4j
public class LogUtil {

    private LogUtil(a) {
        throw new RuntimeException("Initialization failed");
    }

    public static void addLog(String info) {
        if (log.isDebugEnabled()) {
            log.debug("debug:{}", info); }}}Copy the code

And then, in other places, just call.

@Service
@Slf4j
public class TestService1 {

    public void test1(a)  {
        LogUtil.addLog("test1"); }}Copy the code

If you ever need to change the addLog logic again, just change the addLog method of the LogUtil class. You can modify it with confidence and no more trepidation.

Most of the code we write is maintainable, not disposable. Therefore, it is recommended that in the process of writing code, if there is duplicate code, try to extract it into a public method. Don’t bury hidden dangers to the project because of the initial stage of the project, the maintenance cost may be very high.

4. Never write comments

Sometimes, when time is tight on a project, many people don’t like to write comments when writing code in order to develop features quickly.

In addition, there are technical books that say: Good code, don’t write comments, because code is comments. It’s also a good excuse for people who don’t like to comment code.

But IN my opinion, every programmer in China has different English level, and their way of thinking and coding habits are also very different. You want to predecessors some complex code logic really understand, may need to spend a lot of time.

We see that Spring’s core method, Refresh, is also heavily commented:

public void refresh(a) throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...resetCommonCaches(); }}}Copy the code

If you write code with no comments at all, you may remember the logic for a month, three months, or six months. But a year, two years, or more from now, are you sure you’ll be able to remember that logic without having to spend a lot of time revisiting your code to tease it out?

To be honest, by the end of the project, not writing notes will not only embarrass you, but also your teammates.

Why take this one out?

Because IT happened to me. I took the pot. I got screwed.

5. Methods are too long

When we usually write code, sometimes ideas come to us and the function is developed quickly. But there is also a slight problem, which is that the method is too long.

The pseudocode is as follows:

public void run(a) {
    List<User> userList = userMapper.getAll();
    // Through a series of data filtering
    // 50 lines of code are omitted here
    List<User> updateList = // Finally get the user set
   
    if(CollectionUtils.isEmpty(updateList)) {
      return;
    }
    for(User user: updateList) {
       // After some complicated expiration time calculations
       // Omit 30 lines of code here
    }
    
    // Update user expiration time in pages
    // Omit 20 lines of code
    
    // Send an MQ message to the user
    // Omit 30 lines of code here
}
Copy the code

The above run method contains a variety of business logic, and while it does achieve complete business functionality, it is not good.

Why is that?

A: This method is more than 150 lines long, and the code logic is very messy, with many pieces of code that are not closely related. The responsibility of this method is too diverse, which is very bad for code reuse and later maintenance.

So, how do you optimize?

A: Do method splitting, which is to break up a large method into smaller methods.

Such as:

public void run(a) {
    List<User> userList = userMapper.getAll();
    List<User> updateList = filterUser(userList);
    
    if(CollectionUtils.isEmpty(updateList)) {
      return;
    }
   
    for(User user: updateList) {
        clacExpireDay(user);
    }
    
   updateUser(updateList);
   sendMq(updateList); 
}


private List<User> filterUser(List<User> userList) {
    // Through a series of data filtering
    // 50 lines of code are omitted here
    List<User> updateList = // Finally get the user set
    return updateList;
}

private void clacExpireDay(User user) {
    // After some complicated expiration time calculations
    // Omit 30 lines of code here
}

private void updateUser(List<User> updateList) {
    // Update user expiration time in pages
    // Omit 20 lines of code
}

private void sendMq(List<User> updateList) {
    // Send an MQ message to the user
    // Omit 30 lines of code here
}
Copy the code

As a result of this simple optimization, the code logic of the run method suddenly becomes so much clearer that you can guess what the word methods do just by looking at the names of the child methods it calls.

Each submethod focuses on its own work, leaving the rest to the other methods, making responsibilities more uniform.

In addition, the sendMq method defined above can be invoked directly if there is a new function on the business that also needs to send a message to the user. Isn’t it great?

In other words, splitting a large method into N smaller methods by function modules is more conducive to code reuse.

BTW, Hotspot has a JIT compilation limit for large methods over 8000 bytes of bytecode, beyond which it will not compile.

6. Too many parameters

When we define a method, we may not pay attention to the number of arguments (actually I guess). My recommendation is to have no more than five arguments to a method.

Let’s take a look at the following example:

public void fun(String a, String b, String c, String d, String e, String f) {... }public void client(a) {
   fun("a"."b"."c"."d".null."f");
}
Copy the code

There are six parameters defined in the fun method above, so you need to think about how these parameters should be passed and which parameters can and cannot be null everywhere you call this aspect.

Too many methods will lead to different responsibilities of the method and a higher probability of risks.

So, how to optimize the problem of too many parameters?

A: You can migrate some parameters to the new method.

In this example, arguments D, e, and f can be migrated to otherFun. Such as:

public Result fun(String a, String b, String c) {...return result;
}

public void otherFun(Result result, String d, String e, String f) {... }public void client(a) {
   Result result = fun("a"."b"."c");
   otherFun(result, "d".null."f");
}
Copy the code

After this optimization, the logic of each method is more simple, which is more conducive to the reuse of methods.

If fun still needs to return parameters A, b, and c for the next method, the code can be changed to:

public Result fun(String a, String b, String c) {... Result result =new Result();
   result.setA(a);
   result.setB(b);
   result.setC(c);
   return result;
}
Copy the code

When assigning a value to the Result object, a trick is to use Lombok’s @Builder annotation to make a chain call. Such as:

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class Result {

    private String a;
    private String b;
    private String c;
}
Copy the code

So at the point of the call, we can assign:

Result result = Result.builder()
.a("a").b("b").c("c")
.build();
Copy the code

It’s pretty straightforward.

At this point, one might say, doesn’t ThreadPoolExecutor also provide a method with seven arguments?

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {... }Copy the code

Yes, but it’s a constructor, and we’re talking about normal methods here.

7. Code layers are too deep

I don’t know if you’ve ever seen code like this:

if (a == 1) {
   if(b == 2) {
      if(c == 3) {
         if(d == 4) {
            if(e == 5) {... }... }... }... }... }Copy the code

There are so many layers of if judgment in this code, isn’t it a little confusing?

Those of you who feel the same way, please raise your hand.

If you don’t feel anything, keep reading:

for(int i=0; i<100; i++) {for(int j=0; j<50; j++) {for(int m=0; m<200; m++) {for(int n=0; n<100; n++) {for(int k=0; k<50; k++) {
                ...
             }
         }
      }
   }
}
Copy the code

When you look at this code, you may feel a little nervous. With all these loops, does the code really perform well?

The code in both cases makes the same mistake: it’s too deep.

The problem with having too much code hierarchy is that the code becomes very difficult to maintain, it’s not easy to sort out the logic, and sometimes the performance of the code is worse.

So the key question is, how do you solve the deep code level problem?

If there are many levels of judgment:

if(a! =1) {...return;
}

doConditionB();
Copy the code
private void doConditionB(a) {
   if(b! =2) {...return;
   }
   doConditionC();
}
Copy the code

Return the logic that does not satisfy the condition (a==1). Then extract the logic that satisfies the condition (a==1) separately into a method (doConditionB). DoConditionB ==2; doConditionB ==2 Then extract the logic satisfying the condition (b==2) separately into a method (doConditionC). And so on.

This is a form of defense-oriented programming, in which the code that does not meet the criteria is executed first, and the code that does is executed later. Also, extract the code that meets the criteria into a new method.

Map is generally recommended for optimization solutions with too deep a for loop.

Such as:

for(Order order:orderList) {
   for(OrderDetail detail: detailList) {
      if(order.getId().equals(detail.getOrderId())) { doSamething(); }}}Copy the code

After using map optimization:

Map<Long, List<OrderDetail>> detailMap =  detailList.stream().collect(Collectors.groupingBy(OrderDetail::getOrderId));

for(Order order:orderList) {
   List<OrderDetail> detailList = detailMap.get(order.getId());
   if(CollectionUtils.isNotEmpty) { doSamething(); }}Copy the code

In this example, map is used, eliminating a layer of loops and making the code a little more efficient. However, not all for loops can be replaced with maps, and you should choose according to your situation.

Too much code hierarchy and other scenarios, such as too many returns in methods, can also reduce code readability.

In this case, it is also possible to optimize code through defense-oriented programming.

8. Too many judgements

When we write code, it’s essential to judge conditions. Depending on the judgment conditions, the code logic will usually be different.

Without further ado, let’s look at the following code.

public interface IPay {  
    void pay(a);  
}  

@Service
public class AliaPay implements IPay {  
     @Override
     public void pay(a) {  
        System.out.println("=== Initiate alipay payment ==="); }}@Service
public class WeixinPay implements IPay {  
     @Override
     public void pay(a) {  
         System.out.println("=== Initiate wechat payment ==="); }}@Service
public class JingDongPay implements IPay {  
     @Override
     public void pay(a) {  
        System.out.println("=== Initiate JD Pay ==="); }}@Service
public class PayService {  
     @Autowired
     private AliaPay aliaPay;  
     @Autowired
     private WeixinPay weixinPay;  
     @Autowired
     private JingDongPay jingDongPay;  
    
   
     public void toPay(String code) {  
         if ("alia".equals(code)) {  
             aliaPay.pay();  
         } elseif ("weixin".equals(code)) {  
              weixinPay.pay();  
         } elseif ("jingdong".equals(code)) {  
              jingDongPay.pay();  
         } else {  
              System.out.println("Can't find a way to pay."); }}}Copy the code

The toPay method of the PayService class is mainly used to initiate payments. Depending on the code, it is decided to call the Pay method with a different payment class (for example, aliaPay).

What’s wrong with this code? Maybe that’s what some people do.

Just think, if there are more and more payment methods, such as baidu Pay, Meituan Pay, UnionPay, etc., we need to change the code of toPay method, add a new else… If judgment, more judgment leads to more logic, right?

Clearly, this violates the six principles of design pattern: the open and closed principle and the single responsibility principle.

Open closed principle: Open for extensions, closed for modifications. This means adding new features with minimal changes to existing code.

Single responsibility principle: As the name implies, the logic should be as simple as possible, not too complex, easy to reuse.

So, how to optimize if… Else judgment?

A: Use policy mode + factory mode.

A policy pattern defines a set of algorithms, encapsulates them one by one, and makes them interchangeable. The factory pattern is used to encapsulate and manage the creation of objects and is a creation pattern.

public interface IPay {
    void pay(a);
}

@Service
public class AliaPay implements IPay {

    @PostConstruct
    public void init(a) {
        PayStrategyFactory.register("aliaPay".this);
    }


    @Override
    public void pay(a) {
        System.out.println("=== Initiate alipay payment ==="); }}@Service
public class WeixinPay implements IPay {

    @PostConstruct
    public void init(a) {
        PayStrategyFactory.register("weixinPay".this);
    }

    @Override
    public void pay(a) {
        System.out.println("=== Initiate wechat payment ==="); }}@Service
public class JingDongPay implements IPay {

    @PostConstruct
    public void init(a) {
        PayStrategyFactory.register("jingDongPay".this);
    }

    @Override
    public void pay(a) {
        System.out.println("=== Initiate JD Pay ==="); }}public class PayStrategyFactory {

    private static Map<String, IPay> PAY_REGISTERS = new HashMap<>();


    public static void register(String code, IPay iPay) {
        if (null! = code && !"".equals(code)) { PAY_REGISTERS.put(code, iPay); }}public static IPay get(String code) {
        returnPAY_REGISTERS.get(code); }}@Service
public class PayService3 {

    public void toPay(String code) { PayStrategyFactory.get(code).pay(); }}Copy the code

The key to this code is the PayStrategyFactory class, which is a policy factory that defines a global map that registers the current instance in all IPay implementation classes. The PayStrategyFactory class then gets the payment class instance from map according to code at the call point.

If you add a new payment method, you simply add a new class that implements the IPay interface, defines the init method, and overrides the pay method, leaving the rest of the code largely untouched.

Of course, eliminating the smelly long if… There are many other methods, such as using annotations, dynamic concatenation of class names, template methods, enumerations, and so on. I don’t have enough space to cover it here. For more details, check out my other article, “9 Tips for Eliminating If…else.”

9. Hard coded

I don’t know if you’ve ever encountered one of these needs:

  1. Limit the upload interface for batch orders. Only 200 pieces of data can be uploaded at a time.
  2. Query 100 users on one page in job and calculate user levels.

The 200 pieces of data and 100 users in the above example are easy to hard-code, meaning that the parameters are written out in code.

Let’s take the example of uploading 200 pieces of data:

private static final int MAX_LIMIT = 200;

public void upload(List<Order> orderList) {
   if(CollectionUtils.isEmpty(orderList)) {
     throw new BusinessException("Order cannot be empty.");
   } 
   if(orderList.size() > MAX_LIMIT) {
      throw new BusinessException("Exceeds the number limit for a single request"); }}Copy the code

MAX_LIMIT is defined as a static constant.

Once you’re online, you realize that uploading your history data is too slow, and you need to set the limit a little higher.

Oh, my… This small parameter change requires source code changes, recompilation, repackaging, redeployment…

But if you had made these public parameters configurable, for example:

@Value("${com.susan.maxLimit:200}")
private int maxLimit = 200;

public void upload(List<Order> orderList) {
   if(CollectionUtils.isEmpty(orderList)) {
     throw new BusinessException("Order cannot be empty.");
   } 
   if(orderList.size() > maxLimit) {
      throw new BusinessException("Exceeds the number limit for a single request"); }}Copy the code

This simply requires a configuration change in the configuration center (e.g. Apollo, NOCAS, etc.) without modifying the source code, recompiling, repackaging, and redeploying.

In a word: cool.

In the early stages of development, we prefer to spend a minute thinking about whether this parameter can be changed later, and whether it can be defined as configurable. It takes much less time to change the code, recompile it, repackage it, and bring it back online.

10. Transactions are too large

We like to use the @Transactional annotation to declare transactions when developing projects using the Spring framework. Such as:

@Transactional(rollbackFor = Throwable.class)
public void updateUser(User user) {
    System.out.println("update");
}

Copy the code

Simply declare the @Transactional annotation ona method that needs to use a transaction, and that method automatically takes over Transactional functionality via AOP.

Yes, this approach has brought us great convenience, more efficient development.

But it also brings us a lot of hidden dangers, such as the problem of big things. Let’s take a look at the following code:

@Transactional(rollbackFor = Throwable.class)
public void updateUser(User user) {
    User oldUser = userMapper.getUserById(user.getId());
    if(null! = oldUser) { userMapper.update(user); }else {
       userMapper.insert(user);
    }
    sendMq(user);
}

Copy the code

The getUserById and sendMq methods in this code do not require transactions in this case, only the UPDATE or INSERT methods do.

So the above code is too large for a whole method-level transaction. If the sendMq method is a very time consuming operation, it may cause a transaction timeout for the entire updateUser method, resulting in a large transaction problem.

So, how to solve this problem?

A: You can optimize your code using the programmatic transaction of TransactionTemplate.

@Autowired
privateTransactionTemplate transactionTemplate; .public void updateUser(User user) {
    User oldUser = userMapper.getUserById(user.getId());
    
    transactionTemplate.execute((status) => {
        if(null! = oldUser) { userMapper.update(user); }else {
           userMapper.insert(user);
        }
        return Boolean.TRUE;
     })

    sendMq(user);
}
Copy the code

Only the blocks of code in the execute method really need transactions, and the rest of the methods can be executed non-transactionally to narrow down the scope of the transaction and avoid large transactions.

Of course, using programmatic transactions such as TransactionTemplate to narrow down the scope of transactions is only one way to solve large transaction problems.

If you want to take a closer look at big business problems, check out my other article, “How can Big Business Headaches Be Solved?”

11. Call remotely in a loop

Sometimes we need to remotely call an interface of a third party in an interface.

For example: when registering an enterprise, you need to call the Tianyan interface to check whether the name and unified social credit code of the enterprise are correct.

At this time in the enterprise registration interface, you have to call the tianyan interface check data. If the verification fails, return directly. Registration is allowed only if the verification succeeds.

If only one enterprise is ok, but if a request has 10 enterprises need to register, is it necessary to call 10 times in the enterprise registration interface to determine whether all enterprises are normal?

public void register(List<Corp> corpList) {
  for(Corp corp: corpList) {
      CorpInfo info = tianyanchaService.query(corp);  
      if(null == info) {
         throw new RuntimeException("Incorrect business name or uniform social credit code");
      }
  }
  doRegister(corpList);
}
Copy the code

This can be done, but it will lead to poor performance of the entire enterprise registration interface, which is very prone to interface timeout.

So how do you solve this problem of calling a remote interface in a loop?

11.1 Batch Operations

Remote interfaces support batch operations. For example, Skycheck supports querying data of multiple enterprises at a time, eliminating the need to query the interface in a loop.

However, in actual scenarios, some third parties do not want to provide third-party interfaces.

11.2 Concurrent Operations

After Java8 through the CompleteFuture class, the realization of multiple threads to check the eye of the interface, and the query results unified summary together.

I won’t elaborate on the specific usage, but if you are interested, you can check out another article of mine called “11 Tips for Interface performance optimization”.

12. Catch exceptions frequently

In general, you can choose to catch exceptions manually in order to throw an exception in your program without interrupting the entire program. Such as:

public void run(a) {
    try {
        doSameThing();
    } catch (Exception e) {
        //ignore
    }
    doOtherThing();
}
Copy the code

This code manually catches exceptions, ensuring that the Run method continues to execute even if the doSameThing method fails.

However, in some cases, manual exception catching is overused.

12.1 Abuse Scenario 1

I don’t know if you’ve ever written something like this to print an exception log:

public void run(a) throws Exception {
    try {
        doSameThing();
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw e;
    }
    doOtherThing();
}
Copy the code

With the try/catch keyword, the exception is caught manually for the sole purpose of logging the error and will be thrown later in the code.

Every time an exception is thrown, catch it and print a log.

12.2 Abuse Scenario 2

When writing controller layer interface methods, have you ever written code like this to ensure that the interface returns a uniform value:

@PostMapping("/query")
public List<User> query(@RequestBody List<Long> ids) {
    try {
        List<User> userList = userService.query(ids);
        return Result.ok(userList);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        return Result.fature(500."Server internal error"); }}Copy the code

This exception catching logic is added to each controller layer interface method.

In both scenarios, catching exceptions frequently degrades code performance because catching exceptions costs performance.

In addition, with so much repetitive exception catching code, it can be a pain to watch.

Actually, there are better options. At the gateway layer (such as Zuul or Gateway), there is a unified exception handling code that both prints the exception log and encapsulates the interface return value, which reduces the number of exceptions that can be abused.

13. Incorrect log printing

Printing out logs is one of the things we need to do when we’re writing code.

Because logs can help us quickly locate problems and determine what logic the code is really doing at the time.

It is not necessary to print a log at all times. For example:

@PostMapping("/query")
public List<User> query(@RequestBody List<Long> ids) {
    log.info("request params:{}", ids);
    List<User> userList = userService.query(ids);
    log.info("response:{}", userList);
    return userList;
}
Copy the code

For some query interfaces, request parameters and interface return values are printed in the log.

Everything looks fine at first glance.

But if there are a lot of incoming values in IDS, say 1000. This interface is called so often that it prints so many logs that it doesn’t take long to fill up the disk space.

What if you really want to print these logs?

@PostMapping("/query")
public List<User> query(@RequestBody List<Long> ids) {
    if (log.isDebugEnabled()) {
        log.debug("request params:{}", ids);
    }

    List<User> userList = userService.query(ids);

    if (log.isDebugEnabled()) {
        log.debug("response:{}", userList);
    }
    return userList;
}
Copy the code

Run the isDebugEnabled command to check that logs are generated only when the current log level is DEBUG. In the production environment, the default log level is INFO. In some emergencies, you can change the log level of an interface or method to DEBUG. After printing required logs, you can change the log level back.

It is convenient for us to locate problems without generating a large number of garbage logs, killing two birds with one stone.

14. The input parameter is not verified

Parameter verification is an essential function of interfaces. Generally, strict parameter verification is required for interfaces that are provided for third-party invocation.

We used to check parameters like this:

@PostMapping("/add")
public void add(@RequestBody User user) {
    if(StringUtils.isEmpty(user.getName())) {
        throw new RuntimeException("Name cannot be empty");
    }
    if(null! = user.getAge()) {throw new RuntimeException("Age cannot be empty.");
    }
    if(StringUtils.isEmpty(user.getAddress())) {
        throw new RuntimeException("Address cannot be empty");
    }
    userService.add(user);
}
Copy the code

You need to write verification code manually. If you have a lot of fields in an entity as an input, it takes a lot of time just to write verification code. And the verification code, much of it repetitive, can be disgusting.

The good news is that parameter validation is much easier with Hibernate’s parameter validation framework validate.

Validation framework annotations (@noTEMPty, @notnull, etc.) are used in the validation entity class User to define the validation fields.

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    
    private Long id;
    @NotEmpty
    private String name;
    @NotNull
    private Integer age;
    @NotEmpty
    private String address;
}
Copy the code

Then annotate the Controller class with @Validated, and annotate the interface methods with @Valid.

@Slf4j
@Validated
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/add")
    public void add(@RequestBody @Valid User user) { userService.add(user); }}Copy the code

In this way, the function of parameter verification can be realized automatically.

Now, however, the requirement has changed to add a parameter Role to the User class, which is also required and whose roleName and Tag fields cannot be empty.

But if we accidentally write code like this while checking parameters:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {

    private Long id;
    @NotEmpty
    private String name;
    @NotNull
    private Integer age;
    @NotEmpty
    private String address;
    @NotNull
    private Role role;
}
Copy the code
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {

    @NotEmpty
    private String roleName;
    @NotEmpty
    private String tag;
}
Copy the code

The result was tragic.

The roleName and Tag fields are not checked at all.

If the argument is passed:

{
  "name": "tom"."age":1."address":"123"."role":{}
}
Copy the code

The interface returns success even if the role field is passed an empty object.

So how to solve this problem?

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {

    private Long id;
    @NotEmpty
    private String name;
    @NotNull
    private Integer age;
    @NotEmpty
    private String address;
    @NotNull
    @Valid
    private Role role;
}
Copy the code

The @valid annotation is also required on the Role field.

As a warm reminder, use the validate framework to verify parameters by yourself, because it’s easy to pit them.

15. The return value format is inconsistent

When I interfaced with a third party, they had part of their interface return value structure like this:

{
   "ret":0."message":null."data":[]
}
Copy the code

The return value structure for the other part of the interface looks like this:

{
   "code":0."msg":null."success":true."result":[]
}
Copy the code

I’m a little confused.

Why is there no uniform return value?

I need to write two sets of return value parsing code for their interface, and someone else will look at that code later and wonder, why are there two different return value parsing?

The only explanation is that some interfaces are for the new project and others are for the old project.

However, if both new and old projects have a unified external gateway service, the service authenticates and encapsulates the return value.

{
   "code":0."message":null."data":[]
}
Copy the code

There would be no problem with inconsistent return value structures.

As a warm reminder, business services do not catch exceptions. They throw exceptions directly to the gateway service, which will unify the global catch exception, thus unifying the return value structure of the exception.

16. Code submitted to Git is incomplete

When we write the code, we submit it to GitLab, and there’s some subtlety.

The biggest taboo is that the code is not finished, because of the time (anxious to leave work), I use Git to submit the code. Such as:

public void test(a) {
   String userName="Susan";
   String password=
}
Copy the code

The password variable in this code is not well defined, and the project must report an error when running.

This is the wrong way to submit code, which is usually a beginner’s mistake. However, there is another case where multiple branches merge code, sometimes something goes wrong and the code after the merge does not work properly and is committed.

A good rule of thumb: before committing with Git, always run it locally to make sure your project starts up properly before committing.

Rather than submit code to a remote repository, do not submit incomplete code because of a rush of time, resulting in team members can not start the project.

17. Don’t deal with useless code

Sometimes we don’t do anything with code that doesn’t work in order to be lazy.

Such as:

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void add(User user) {
        System.out.println("add");
    }

    public void update(User user) {
        System.out.println("update");
    }

    public void query(User user) {
        System.out.println("query"); }}Copy the code

The add, update, and query methods of the UserService class are used. Later, some functionality was cut, and now only the Add method is really used.

One day, a new member of the project team needs to add a field to the user table. Does he need to review the add, update, and query methods to assess their impact?

When he found out that only the Add method needed to be changed, he wondered why the previous developers didn’t just delete the useless code, or mark it out.

In Java we can use @deprecated to indicate that the class or method is no longer in use, for example:

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void add(User user) {
        System.out.println("add");
    }

    @Deprecated
    public void update(User user) {
        System.out.println("update");
    }

    @Deprecated
    public void query(User user) {
        System.out.println("query"); }}Copy the code

We can ignore methods marked with the @deprecated annotation as we read the code. Such a seemingly simple effort, can give yourself, or take over the code, save a lot of time to repeatedly check the code.

It is suggested that we delete the useless code first, because there are historical records in GitLab, which can be retrieved. However, in cases that cannot be removed for compatibility with older versions of the caller’s code, it is recommended to annotate the relevant class or interface with @deprecated annotations.

18. Modify the interface name and parameter name

Have you ever written an interface that you thought no one was using, then changed the interface name or parameter name because it didn’t seem right? Such as:

@PostMapping("/query")
public List<User> query(@RequestBody List<Long> ids) {
    return userService.query(ids);
}
Copy the code

Interface name changed:

@PostMapping("/queryUser")
public List<User> queryUser(@RequestBody List<Long> ids) {
    return userService.query(ids);
}
Copy the code

As a result, someone else’s function failed because he was already calling the interface.

Careless…

Therefore, before modifying the interface name, parameter name, parameter type, and parameter number, you must first ask relevant colleagues whether the interface is used to avoid unnecessary trouble in the future.

Do not change the interface name, parameter name, parameter type, parameter number, or request mode of an interface that has been used online, for example, change get to POST. Rather than adding a new interface, try not to interfere with online functionality.

19. Use map to receive parameters

I’ve seen some guys use map to receive parameters in their code. Such as:

@PostMapping("/map")
public void map(@RequestBody Map<String, Object> mapParam){
    System.out.println(mapParam);
}
Copy the code

Using an mapParam object to receive parameters in a Map method is a really handy way to receive data in multiple JSON formats.

Such as:

{
  "id":123."name":"Susan"."age":18."address":"Chengdu"
}
Copy the code

Or:

{
  "id":123."name":"Susan"."age":18."address":"Chengdu"."role": {
    "roleName":"Role"."tag":"t1"}}Copy the code

This code accepts arguments in both formats effortlessly, so cool.

The problem is that you have no control over the data structure of the parameters, and you may know whether the caller is passing the json data in either the first or the second format. But if you don’t write a comment, other people will see this code and be confused. What are the parameters that map receives?

Later in the project, such code becomes very difficult to maintain. There’s a reason why some of you take over code and make fun of it from time to time.

So what about optimizing this code?

We should use objects with explicit meanings to receive arguments, such as:

@PostMapping("/add")
public void add(@RequestBody @Valid User user){
    System.out.println(user);
}
Copy the code

The User object is already defined, so there is no ambiguity.

20. Never write unit tests

Because the project time is really too tight, the system functions are not finished development, let alone unit test?

That’s probably why most people don’t write unit tests.

But I want to tell you that not writing unit tests is not a good habit.

Some of the best programmers I’ve seen are test-driven developers who write unit tests first and then the business logic.

So why do we write unit tests?

  1. Most of the code we write is maintainable and will probably need to be refactored at some point in the future. Imagine if some business logic is so complex that you can easily refactor it. It would be different if you had unit tests, so every time you refactor, you run a unit test, and you know if there’s a problem with your new code.

  2. We write a new external interface, test students can not fully know logic, only the development of their most clear. Unlike page functions, you can operate on a page. When they test the interface, it is very likely that the coverage is not in place, and many bugs are not detected.

It is suggested that due to the tight time of the project, unit tests were not written during the development, but spare time in the late stage of the project is also suggested to make up.

This article combined with their own practical work experience, with the way of ridicule, introduced in the process of writing code, not good places and some optimization skills, to use a need for friends a reference.

One last word (attention, don’t fuck me for nothing)

If this article is of any help or inspiration to you, please scan the QR code and pay attention to it. Your support is the biggest motivation for me to keep writing.

Ask for a key three even: like, forward, look.

Pay attention to the public account: [Su SAN said technology], in the public account reply: interview, code artifact, development manual, time management have excellent fan welfare, in addition reply: add group, can communicate with a lot of BAT big factory seniors and learn.