Talk: What good habits should a good backend have?

foreword

It has been almost three years since graduation, and I have worked in several companies before and after, and met various colleagues. I have seen all kinds of code, excellent, rubbish, unattractive, and want to run away, etc., so this article records what good development habits an excellent back-end Java development should have.

Split reasonable directory structure

Affected by the traditional MVC model, the traditional method is mostly a few fixed folders controller, service, mapper, entity, and then add unlimitedly. In the end, you will find that there are dozens or hundreds of Service classes under a service folder. It is impossible to distinguish the business modules at all. The correct way is to create a new modules folder in the upper layer of writing service, create different packages according to different businesses under the moudles folder, and write specific service, controller, entity, enums packages under these packages or continue to split.

When the development version is iterated in the future, if a package can continue to be disassembled, it will continue to be disassembled, and the project business module can be clearly seen. Subsequent dismantling of microservices is also simple.

encapsulating method parameters

When your method has too many formal parameters, please encapsulate an object... The following is a negative teaching material, who taught you to write code like this!

public void updateCustomerDeviceAndInstallInfo(long customerId, String channelKey,
                   String androidId, String imei, String gaId,
                   String gcmPushToken, String instanceId) {}
复制代码

write an object

public class CustomerDeviceRequest {
    private Long customerId;
    //省略属性......
}
复制代码

Why write like this? For example, your method is used for query. If you add a query condition in the future, do you need to modify the method? The method parameter list must be changed every time it is added. Encapsulate an object, no matter how many query conditions you add in the future, you only need to add fields to the object. And the key is that the code looks very comfortable too!

Encapsulate business logic

If you have seen the "shit mountain", you will have a deep feeling. Such a method can write thousands of lines of code, and there are no rules to speak of... Often the person in charge will say that this business is too It's complicated, there's no way to improve it, and it's actually an excuse for laziness. No matter how complex the business is, we can use reasonable design and packaging to improve code readability. Below are two pieces of code written by senior developers (pretending to be senior developers)

@Transactional
public ChildOrder submit(Long orderId, OrderSubmitRequest.Shop shop) {
    ChildOrder childOrder = this.generateOrder(shop);
    childOrder.setOrderId(orderId);
    //订单来源 APP/微信小程序
    childOrder.setSource(userService.getOrderSource());
    // 校验优惠券
    orderAdjustmentService.validate(shop.getOrderAdjustments());
    // 订单商品
    orderProductService.add(childOrder, shop);
    // 订单附件
    orderAnnexService.add(childOrder.getId(), shop.getOrderAnnexes());
    // 处理订单地址信息
    processAddress(childOrder, shop);
    // 最后插入订单
    childOrderMapper.insert(childOrder);
    this.updateSkuInventory(shop, childOrder);
    // 发送订单创建事件
    applicationEventPublisher.publishEvent(new ChildOrderCreatedEvent(this, shop, childOrder));
    return childOrder;
}
复制代码
@Transactional
public void clearBills(Long customerId) {
    // 获取清算需要的账单、deposit等信息
    ClearContext context = getClearContext(customerId);
    // 校验金额合法
    checkAmount(context);
    // 判断是否可用优惠券,返回可抵扣金额
    CouponDeductibleResponse deductibleResponse = couponDeducted(context);
    // 清算所有账单
    DepositClearResponse response = clearBills(context);
    // 更新 l_pay_deposit
    lPayDepositService.clear(context.getDeposit(), response);
    // 发送还款对账消息
    repaymentService.sendVerifyBillMessage(customerId, context.getDeposit(), EventName.DEPOSIT_SUCCEED_FLOW_REMINDER);
    // 更新账户余额
    accountService.clear(context, response);
    // 处理清算的优惠券,被用掉或者解绑
    couponService.clear(deductibleResponse);
    // 保存券抵扣记录
    clearCouponDeductService.add(context, deductibleResponse);
}
复制代码

In fact, the business in these two codes is very complicated. Internally, it is estimated that 50,000 things have been conservatively done, but the writing by people of different levels is completely different. I have to praise this comment, the splitting of this business and the encapsulation of methods. There are multiple small businesses in a big business. Different businesses can call different service methods. People who take over can quickly understand the business even if they don’t have relevant documents such as flowcharts. Many business methods written by primary developers are The previous line of code is for business A, the next line of code is for business B, and the next line of code is for business A. There is also a bunch of unit logic nested between business calls, which is very confusing and has a lot of code.

Correct way to check that a collection type is not empty

Many people like to write code like this to judge collections

if (list == null || list.size() == 0) {
  return null;
}
复制代码

Of course, there is no problem if you insist on writing like this... But don't you feel uncomfortable, now any jar package in the framework has a collection tool class, such as org.springframework.util.CollectionUtils, com.baomidou.mybatisplus.core .toolkit.CollectionUtils. Please write like this later

if (CollectionUtils.isEmpty(list) || CollectionUtils.isNotEmpty(list)) {
  return null;
}
复制代码

Collection type return value do not return null

When your business method returns a collection type, please do not return null, the correct operation is to return an empty collection. You look at the list query of mybatis. If no element is queried, it will return an empty collection instead of null. Otherwise, the caller has to make a NULL judgment, which is also true for objects in most scenarios.

Try not to use basic types for the attributes of the mapping database

We all know that basic data types such as int/long are 0 by default as member variables. It is now popular to use ORM frameworks such as mybatisplus and mybatis. When inserting or updating, it is easy to insert and update the database with default values. I really want to cut off the previous development. The entity classes in the refactored project are all basic data types. Cracked on the spot...

Packaging Judgment Conditions

public void method(LoanAppEntity loanAppEntity, long operatorId) {
  if (LoanAppEntity.LoanAppStatus.OVERDUE != loanAppEntity.getStatus()
          && LoanAppEntity.LoanAppStatus.CURRENT != loanAppEntity.getStatus()
          && LoanAppEntity.LoanAppStatus.GRACE_PERIOD != loanAppEntity.getStatus()) {
    //...
    return;
  }
复制代码

The readability of this code is very poor, who knows what is in this if? Can we use object-oriented thinking to encapsulate a method in the object of loanApp?

public void method(LoanAppEntity loan, long operatorId) {
  if (!loan.finished()) {
    //...
    return;
  }
复制代码

A method is encapsulated in the LoanApp class. In short, the details of this logic judgment should not appear in the business method.

/**
 * 贷款单是否完成
 */
public boolean finished() {
  return LoanAppEntity.LoanAppStatus.OVERDUE != this.getStatus()
          && LoanAppEntity.LoanAppStatus.CURRENT != this.getStatus()
          && LoanAppEntity.LoanAppStatus.GRACE_PERIOD != this.getStatus();
}
复制代码

Control method complexity

Recommend an IDEA plug-in CodeMetrics, it can show the complexity of the method, it is to calculate the expression in the method, Boolean expression, if/else branch, loop and so on.

Click to see which code increases the complexity of the method, and you can refer to it appropriately. After all, we usually write business code. The most important thing is to let others understand it quickly on the premise of ensuring normal work. When the complexity of your method exceeds 10, it is necessary to consider whether it can be optimized.

Use @ConfigurationProperties instead of @Value

I actually saw an article recommending that @Value is easier to use than @ConfigurationProperties. I spit it out, don't misunderstand me. List the benefits of @ConfigurationProperties.

  • In the project application.yml configuration file, hold down ctrl + left mouse button and click the configuration property to quickly navigate to the configuration class. When writing the configuration, it can also be automatically completed and associated with comments. An additional dependency org.springframework.boot:spring-boot-configuration-processor needs to be introduced.

  • @ConfigurationProperties supports automatic refresh of NACOS configuration, using @Value requires @RefreshScope annotation on BEAN to achieve automatic refresh
  • @ConfigurationProperties can be combined with Validation verification, @NotNull, @Length and other annotations. If the configuration verification fails, the program will not start, and problems such as lost configuration in production will be discovered early.
  • @ConfigurationProperties can inject multiple properties, @Value can only be written one by one
  • @ConfigurationProperties can support complex types, no matter how many levels of nesting, they can be correctly mapped to objects

In contrast, I don't understand why so many people are reluctant to accept new things, cracked... You can see that all springboot-starters use @ConfigurationProperties to receive configuration properties.

lombok is recommended

Of course this is a moot point, and my habit is to use it to omit getters, setters, toString, etc.

Don't call BMapper in AService

We must follow from AService -> BService -> BMapper. If each Service can directly call other Mappers, why do you need other Services? The old project also calls mapper from the controller, and treats the controller as a service. . .

Write as few tools as possible

Why do you want to write less tool classes, because most of the tool classes you write are in the jar package you introduced invisibly, such as String, Assert assertion, IO upload file, copy stream, Bigdecimal and so on. It is easy to write your own mistakes and also load redundant classes.

Do not wrap OpenFeign interface return values

I don't understand why so many people like to wrap the return value of the interface in Response...Add a code, message, and success fields, and then each time the caller becomes like this

CouponCommonResult bindResult = couponApi.useCoupon(request.getCustomerId(), order.getLoanId(), coupon.getCode());
if (Objects.isNull(bindResult) || !bindResult.getResult()) {
  throw new AppException(CouponErrorCode.ERR_REC_COUPON_USED_FAILED);
}
复制代码

This is equivalent to

  1. Throwing exception in coupon-api
  2. Intercept exceptions in coupon-api and modify Response.code
  3. The caller judges the response.code if it is FAIELD and then throws the exception...

Can't you just throw an exception directly at the service provider? . . And such a packaged HTTP request is always 200, and there is no way to retry and monitor. Of course, this question involves how to design the interface response body. At present, there are mostly three genres on the Internet.

  • The interface response status is always 200
  • The interface response state follows the HTTP real state
  • Buddhist development, what the leader says is what to do

Rebuttals are not accepted, I recommend using the HTTP standard status. In certain scenarios, including parameter verification failure, 400 will be used for the toast to the front end. The next article will explain the disadvantages of uniform 200.

Write meaningful method comments

Did you write this kind of comment because you were afraid that the person who took over later would be blind...

/**
* 请求电话验证
*
* @param credentialNum
* @param callback
* @param param
* @return PhoneVerifyResult
*/
复制代码

Either don't write it, or just add a description at the end... It hurts to write such a comment and get a bunch of warnings from IDEA

Name of the DTO object that interacts with the front end

What VO, BO, DTO, PO I really don't think it is necessary to be so detailed. At least when we interact with the front end, the class name should be appropriate. Don't directly use the class that maps the database to return to the front end, which will return a lot of Unnecessary information, if there is sensitive information, special treatment is required.

The recommended practice is that the class that accepts front-end requests is defined as XxxRequest, and the response is defined as XxxResponse. Take order as an example: the entity class that accepts and saves updated order information can be defined as OrderRequest, the order query response is defined as OrderResponse, and the order query condition request is defined as OrderQueryRequest.

Try not to let IDEA call the police

I am very disgusted to see a series of warnings in the IDEA code window, very uncomfortable. Because there is a warning, it means that the code can be optimized, or there is a problem. A few days ago, I caught a small bug within the team. In fact, it had nothing to do with me, but my colleagues were looking at the business outside and judged why the branch was wrong. I swept the problem at a glance.

Because the integer literals in java are of int type, it becomes an Integer in the collection, and then stepId is clicked to see that it is of type long, which is Long in the collection, then the contains properly returns false, which is not a type.

You see, if you pay attention to the warning, you can move the mouse over and take a look at the prompt and it will be clear, one less production bug.

Use new technology components wherever possible

I think this is the quality a programmer should have... Anyway, I like to use new technical components, because the emergence of new technical components must solve the deficiencies of old technical components, and as a technician we should Keeping pace with the times~~ Of course, the premise is to make preparations and not to upgrade without thinking. To give the simplest example, Java 17 is out, and new projects are still using Date to handle date and time... What age are you still using Date?

Epilogue

This article briefly introduces my daily development habits, of course, only the author's own opinions. I can only think of these points for the time being, and I will update the others later.


Author: Twilight Enchanting,
Link: https://juejin.cn/post/7072252275002966030
Source: Rare Earth Nuggets
The copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin blog.csdn.net/wdjnb/article/details/124403326