Almost three years after graduation, before and after also stayed in a few companies, encountered a variety of colleagues. Have seen a variety of code, excellent, garbage, unattractive, see want to run, etc., so this article records a good back-end Java development should have what good development habits.
Split up a rational catalog structure
Influenced by the traditional MVC model, the traditional approach is mostly a few fixed folders controller、service、mapper、entity
, and then unlimited additions, in the end you will find a service
folder under dozens of hundreds of Service classes, there is no way to distinguish the business modules. The correct approach is to write service
on the upper level to create a new modules
folder, under the moudles
folder according to the different business to create different packages, under these packages to write a specific service、controller、entity、enums
package or continue to split.
When the future development version iteration, if a package can continue to split the field will continue to split down, you can clearly glance at the project business modules. Subsequent demolition of microservices is also simple.
Encapsulating method formal parameters
When you have too many formal parameters of the method please encapsulate an object out …… Here’s an example of the opposite, who the hell 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 do you need to write it this way? For example, if you use this method for querying, in case you add a query condition in the future do you have to modify the method? Each time you add each time you have to change the method parameter list. Encapsulate an object, no matter how many query conditions are added in the future, you only need to add fields in the object on the line. And the key is to look at the code is also very comfortable ah!
Wrapping Business Logic
If you have seen the “shit mountain” you will have a deep feeling, this fucking method can write thousands of lines of code, but also no rules …… Often the person in charge will say that the business is too complex, there is no way to improve, in fact, this is a lazy excuse. Regardless of the complexity of the business, we are able to use reasonable design, packaging to improve code readability. The following post two senior developers (pretending to be senior developers) to write the code
@Transactional
public ChildOrder submit(Long orderId, OrderSubmitRequest.Shop shop) {
ChildOrder childOrder = this.generateOrder(shop);
childOrder.setOrderId(orderId);
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) {
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);
}
This two code inside the business is actually very complex, internal estimates conservative dry 50,000 things, but different levels of people write out completely different, have to praise the note, the business of splitting and encapsulation of methods. A large business inside a number of small businesses, different businesses call different service methods can be followed up to take over even if there is no flow charts and other related documents can quickly understand the business here, and many junior developers write out the business method is the last line of code is A business, the next line of code is B business, in the following line of code is A business, business calls are also nested between this A bunch of unit logic, seems very confusing, code is also more.
The correct way to determine that a collection type is not empty
Many people like to write code like this to determine the collection
if (list == null || list.size() == 0) {
return null;
}
Of course, there is no problem if you want to write this way …… But don’t you think it’s hard, nowadays any jar package in the framework has a collection tool class, such as org.springframework.util.CollectionUtils
, com.baomidou.mybatisplus.core.toolkit.CollectionUtils
. In the future, please write it like this
if (CollectionUtils.isEmpty(list) || CollectionUtils.isNotEmpty(list)) {
return null;
}
Collection type return value do not return null
When your business method return value is a collection type, please do not return null, the correct operation is to return an empty collection. You see mybatis list query, if you do not query the elements returned is an empty collection, not null. otherwise the caller has to do NULL judgment, most of the scenarios for the object is also the same.
Try not to use basic types for mapping database properties
We all know that the default value of int/long and other basic data types as member variables is 0. Nowadays, it is popular to use ORM frameworks such as mybatisplus, mybatis, etc., and it is very easy to insert or update to the database with the default value. I really want to cut the previous development, refactoring the project inside the entity classes are all basic data types. Cracked on the spot ……
Encapsulation 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 poor. Who knows what’s going on in the if? Can’t we just use object-oriented thinking to encapsulate a method inside the loanApp
object?
public void method(LoanAppEntity loan, long operatorId) {
if (!loan.finished()) {
//...
return;
}
LoanApp This class encapsulates a method that simply means that this logical judgment detail should not appear in a 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
We recommend an IDEA plugin CodeMetrics
that shows the complexity of methods, which is calculated for expressions in methods, boolean expressions, if/else branching, loops etc.
Click to see which code increases the complexity of the method, you can refer to it appropriately, after all, we usually write business code, in order to ensure that the normal work of the premise is the most important thing is to allow others to quickly read and understand. When your method complexity exceeds 10 you should consider whether you can optimize.
Using @ConfigurationProperties instead of @Value
I’m surprised to see an article recommending @Value over @ConfigurationProperties, but I’ll be damned. Let’s list the benefits of @ConfigurationProperties.
Holding ctrl + left mouse click on the configuration properties in the projectapplication.yml
configuration file allows you to quickly navigate to the configuration class. It is also possible to auto-complete and associate comments when writing configurations. An additional dependency onorg.springframework.boot:spring-boot-configuration-processor
needs to be introduced.
@ConfigurationProperties supports NACOS configuration auto-refresh, using @Value requires the @RefreshScope annotation on BEAN.
@ConfigurationProperties can be combined with Validation checksums, @NotNull, @Length and other annotations, if the configuration checksums do not pass the program will not start up, early detection of production lost configuration and other issues.
@ConfigurationProperties can inject multiple properties, @Value can only be written one by one.
@ConfigurationProperties can support complex types that map correctly to objects no matter how many layers they are nested in.
In contrast I don’t understand why so many people are reluctant to accept something new, crack …… You can see that all springboot-starter uses @ConfigurationProperties inside to pick up configuration properties.
Recommended to use lombok
Of course this is a controversial issue, my habit is to use it omitting getter、setter、toString
etc.
Do not call BMapper from AService
We have to follow the rule of calling mappers from AService -> BService -> BMapper
, if every Service can call other Mappers directly, what the hell do we need other Services for? Older projects call mappers from the controller, treating the controller as a service.
Write as few tools as possible
The reason why you need to write fewer utility classes is that most of the utility classes you write are in the jar packages you invariably bring in, String’s, Assert assertions, IO upload files, copy streams, Bigdecimal’s, etc. You can easily write them yourself and have to load extra classes. It’s easy to make mistakes and load extra classes.
Do not wrap the OpenFeign interface return value
Can’t figure out why so many people like to wrap the return value of an interface in a Response …… Add a code、message、success
field, and then every time the caller looks 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
- Throwing exceptions in coupon-api
Blocking exceptions in coupon-api, modifying Response.code
The caller determines the response.code and throws the exception if it is FAIELD ……
You could just throw an exception at the service provider. Moreover, the HTTP request will always be wrapped in 200, so there is no way to retry or monitor. Of course, this problem involves how to design the interface response body, most of the current online three schools of thought
- Interface response status is always 200
- Interface response state follows HTTP real state
- Buddhist development, what the leader says goes
I don’t accept rebuttals, and I recommend using the HTTP standard state. For certain scenarios, including parameter validation failures, you can always use 400 to pop a toast to the front end, and the next post will address the disadvantages of using 200 across the board.
The OpenFeign interface is not recommended to be typed as a jar.
Seen a lot of interfaces that use OpenFeign
are used this way, writing the OpenFeign
interface on the service provider, typed as jar
. For example, if the service A
calls B
, make a separate module
project at B
to write the interface definition, and type out a jar
package for A
to introduce the dependency.
Let’s get a feel for the steps involved in calling a Feign
interface implementation:
WriteController
implementation in B service
DefineOpenFeign
interface definition in B service
Change the jar version +1 in service B, and hit a jar package to the local repository
Change the dependency jar version in Service A. Refresh themaven/gradle
At first glance it doesn’t look troublesome right? But you have to realize that we often lose parameters, missing response attributes, etc. in our development, and once there is any small problem, we have to go through the above process again 。。。。
It is suggested to define the OpenFeign
interface on the consumer side A
, B
just need to provide an interface implementation. The bad thing is that the XxxRequest、XxxResponse
class is redundant, but there is no problem, because the BO class for Feign
does not need to have identical fields for the request and response, its decoder will intelligently parse the response and encapsulate it into your XxxResponse
receiver class.
If you understand it this way, you will understand that this class XxxRequest, XxxResponse, etc., is just a mapping data structure customized locally by your A service in order to map the result of the request, and it has nothing to do with the B service, so to speak. This mapping data structure has nothing to do with service B. So you should put it here in A.
You are torn because you think this thing seems to be reusable, so you are torn between putting A or B, and whether to pull out a public dependency. I was also very confused a long time ago, but after stepping on too many pits my thinking changed, high cohesion and low coupling of the essence of the meaning of the service (components, applications, packages, etc., etc., etc., etc., etc., etc., etc., etc., etc., etc., etc., etc.) related to the code is all wrapped together, do not have to have a stake in the outside world, you have to have a stake in the dependency hell will trigger a change in the modification.
Write meaningful method annotations
This kind of comment you write out is afraid of the back to take over the people blind it …… You write the meaning of paragraph parameters in the back ah ……
/**
*
* @param credentialNum
* @param callback
* @param param
* @return phoneVerifyResult
*/
public void method(String credentialNum,String callback,String param,String phoneVerifyResult){
}
Either don’t write it or put a description after it …… Doesn’t it look bad to get a bunch of warnings from IDEA for writing comments like that?
Naming DTO objects that interact with the front end
What VO, BO, DTO, PO I really do not think there is so much need to be so detailed, at least when we interact with the front-end class name should be appropriate, do not directly use the mapping database class back to the front-end, which will return a lot of unnecessary information, and if there is sensitive information has to be handled in a special way.
The recommended practice is to define the class that accepts front-end requests as XxxRequest
and the response as XxxResponse
. Taking orders as an example: the entity class that accepts save updated order information can be defined as OrderRequest
, the order query response is defined as OrderResponse
, and the query condition request for orders is defined as OrderQueryRequest
.
Don’t cycle through databases across services
When querying across services, if there is a bulk data query scenario, write a bulk Feign
query interface directly, not like the following
list.foreach(id -> {
UserResponse user = userClient.findById(id);
});
This is because every request to OpenFeign
is a Http
request, a database IO operation, and goes through various framework interceptors, decoders, and so on, all of which are wear and tear.
Define a batch query interface directly
@PostMapping("/user/batch-info")
List<UserResponse> batchInfo(@RequestBody List<Long> userIds);
Is this the end? No, if you have a very large number of these userIds
‘s, above 2000
, then you can’t just query in()
in the database on the implementation side. You have to split this useIds
on the implementation side. With indexing in() 1000
is usually not a big problem.
public List<XxxResponse> list(List<Long> userIds) {
List<List<Long>> partition = Lists.partition(userIds, 500);
List<XxxResponse> list = new ArrayList<>();
partition.forEach(item -> list.addAll(xxxMapper.list(item)));
return list;
}
Try not to let IDEA call the police.
I have a strong aversion to seeing a string of warnings in the IDEA code window. It’s very hard to see a string of warnings in the IDEA code window, because a warning means that the code can be optimized, or that there is a problem. A few days ago, I caught a small bug within the team, in fact, it has nothing to do with me, but my colleagues are looking at the outside of the business to determine why the branch is not correct, I scanned the problem.
Because java integer literals are int
type, to the collection becomes Integer
, then stepId
point up to see is long
type, in the collection is Long
, then this contains
properly return false
, are not a type.
You see if you focus on the warnings, mouse over and take a look at the hints it’s clear that there’s one less production bug.
Use new technology components wherever possible
I think this is a programmer should have the quality …… Anyway, I like to use the new technology components, because the new technology components must be to solve the shortcomings of the old technology components, and as a technician we should be with the times ~~ Of course, the premise is to do a good job of preparation, not a brainless upgrade. Take a simple example, Java 17 are out, new projects now still use Date to deal with date and time …… What age are you still using Date?
This post briefly describes my daily development habits, of course only the author’s own opinion. These are the only points that come to mind for now, and I’ll update with others as I find them later.
If this post helped you, remember to like and follow! Your support is what keeps me creating!
I’m participating in the Nuggets Tech Community Creator Signing Program recruitment campaign, click the link to sign up to contribute.