⚠️ This article is the first contract article in the nuggets community. It is prohibited to reprint without authorization

Blog: bugstack.cn

Precipitation, share, grow, let yourself and others can gain something! 😄

One, foreword

We’re different. You’re the only one without a date! Yes, you are process oriented!

As I said, the vast majority of digital farmers are required to suppress the liver out of the code day and night, no matter how puffed up, there can be no refactoring, only rewrite. Why is that? Because the time cost of rewriting is far less than refactoring a crumpled piece of code. But there is no guarantee that the code you rewrite will be much better than it was before, with the risk of code accidents and little business value.

Although the code is for the machine to run, it is also for people to look at, and with each iteration, change and upgrade of the requirements, the r&d personnel need to develop and go online for many times for the same code, so it will involve the characteristics of maintainable, easy to expand and easy to hand over.

Those that don’t layer the code logic properly, don’t comment the code, don’t commit to the specification, don’t format it, don’t name it arbitrarily, and even write queryBatch as queryBitch can cause problems for subsequent code refactoring. So let’s take a look at what it takes to develop reconfigurable code.

Second, code optimization

1. Convention specifications

Feat. Feat. Type of docs feat. Feat. Type of docs. Git refactor is used to refactor code. To add a test or modify an existing test perf: Ci: Changes to chore related to CI (Continuous Integration Services) : remaining modifications to SRC or test, such as changes to the build process or accessibility tools # Annotation: Class annotation configuration/ * * *@description: 
* @author: ${USER}
* @date: ${DATE}
*/
Copy the code
  • branch: Contract the pull branch specification before development, such asDate _ User _ Usage, 210905 _xfg_updaterulelogic
  • submit:Author, Type: descSuch as:Fix: Update rule logic problem Refer to the Commit Message specification
  • annotation: Includes class comments, method comments, and attribute comments. You can set the header information of class comments in IDEAEditor -> File and Code Templates -> File Header You are advised to download and install the IDEA P3C plug-inAlibaba Java Coding Guidelines, unified standardized coding method.

2. Interface standards

When writing an RPC interface, the return result must contain explicit Code and Info description. Otherwise, it is difficult for the user to know whether the interface was successfully called or an exception, and in what case.

Define the Result

public class Result implements java.io.Serializable {

    private static final long serialVersionUID = 752386055478765987L;

    /** returns the result code */
    private String code;

    /** Returns the result information */
    private String info;

    public Result(a) {}public Result(String code, String info) {
        this.code = code;
        this.info = info;
    }

    public static Result buildSuccessResult(a) {
        Result result = new Result();
        result.setCode(Constants.ResponseCode.SUCCESS.getCode());
        result.setInfo(Constants.ResponseCode.SUCCESS.getInfo());
        return result;
    }
    
    / /... get/set
}
Copy the code

Return result wrapper: inheritance

public class RuleResult extends Result {

    private String ruleId;
    private String ruleDesc;

    public RuleResult(String code, String info) {
        super(code, info);
    }
	
	/ /... get/set
}

/ / use
public RuleResult execRule(DecisionMatter request) {
    return new RuleResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo());
}
Copy the code

Return result wrapper: generic

public class ResultData<T> implements Serializable {

    private Result result;
    private T data;

    public ResultData(Result result, T data) {
        this.result = result;
        this.data = data;
    }   
	
	/ /... get/set
}  

/ / use
public ResultData<Rule> execRule(DecisionMatter request) {
    return new ResultData<Rule>(Result.buildSuccessResult(), new Rule());
}
Copy the code
  • Either wrapper definition of the return result of an interface can standardize the return result. After packaging in this way, the user can use a uniform way to judgeCode sizeAnd deal with it accordingly.

3. Library table design

Three normal form: is the content of the standardization of the database, the so-called database three normal form is popularly said to be the design of the database table should comply with a set of specifications, if not comply with the design of the database will cause the database field redundancy, data query, insert and other problems.

Database not only has three normal forms (1NF/2NF/3NF), but also BCNF, 4NF, 5NF… In actual database design, however, it is sufficient to follow the first three paradigms. Further down you can create too many unnecessary constraints on your designed database.

0NF

  • Zeroth normal is when no normal form is used, the data stores a redundant number of table fields, and such a table structure is very difficult to maintain.

1NF

  • The first normal form is an improvement on the redundant fields in the zeroth normal form. The repeated fields are removed and a table structure with less redundant data is designed to be easy to store and read.
  • It is also stated in the first paradigm that all fields in a table should be atomic and indivisible. For example, you cannot store the department name and responsibility of a company’s employees table in one field. You need to make sure that each column remains atomic

2NF

  • If 1NF is met, all columns in the table must depend on the primary key. Ensure that each column is related to the primary key column and not to the primary key column. That is, a table can only describe one thing. You need to make sure that every column in the table is associated with a primary key.

3NF

  • There should be no dependencies, student id, name, to department, department to dormitory, you need to make sure that each column is directly related to the primary key column, not indirectly.

The three paradigms

The three paradigms are regular constraints for designing database table structures, but allow for local flexibility in actual development:

  1. Sometimes in order to facilitate the query, such as order table redundancy on the snapshot information of the user, such as some Settings when the user placed an order information.
  2. Single column list data is summarized into a total table of a number of values, so that the query can avoid the list summary operation.
  3. Some fields can be redundant in the design of the table to avoid the cumbersome problem of the table due to the changeable business development and careless consideration.

4. Algorithm logic

Usually in our actual business function logic development, in order to meet some high concurrency scenarios, it is impossible to lock the database table to reduce inventory, nor can we directly for a large number of circular training operations, usually need to consider how to decentralize 🤔 in such scenarios and reduce the time complexity.

SEC kill: decentralization

  • background: This is a product activity seconds kill implementation scheme, the initial design is based on an activity ID to lock, seconds kill lock this ID, users purchase after release. But when a large number of users buy, there is a second kill distributedAn exclusive lockAn exception occurs in the subsequent service logic processing, and the lock fails to be released. As a result, all users can no longer get the lock, which causes the problem of having goods but not being able to place orders.
  • Optimization: Optimize the exclusive race to piecewise static, using the active ID+ inventory number as the dynamic lock identifier. If the current user fails to lock, subsequent users can continue to lock without being affected. The failed lock will be compensated and restored by the worker, so overselling and unselling will be avoided eventually.

Algorithms: what not to do

@Test
public void test_idx_hashMap(a) {
    Map<String, String> map = new HashMap<>(64);
    map.put("alderney"."Unrealized service");
    map.put("luminance"."Unrealized service");
    map.put("chorology"."Unrealized service");
    map.put("carline"."Unrealized service");
    map.put("fluorosis"."Unrealized service");
    map.put("angora"."Unrealized service");
    map.put("insititious"."Unrealized service");
    map.put("insincere"."Implemented Services");
    
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        map.get("insincere");
    }
    System.out.println("InitialCapacity:" + (System.currentTimeMillis() - startTime));
}
Copy the code
  • background: HashMap data acquisition time complexity in O(1) -> O(logn) -> O(n), but afterspecialOperation, can pull this time complexity up to order n.
  • operation: This is a definitionHashMapStores the service implementation key and invokes service functions through the key. But here’s thekey, onlyinsincereYes, the rest are unimplemented services. Do you see what the problem is?
    • This code at first glance no problem, see is the code under arsenic! The purpose of the HashMap is to make a list of all the keys in the HashMap, and to put useful keys at the end of the list to increase the get time!
    • First of all,new HashMap<>(64);Why are 64 lengths initialized by default? Because the default length is 8, when inserting elements, when the length of the linked list is 8, the expansion and tree judgment of the linked list will be carried out, and the original keys will be hashed, so all keys cannot form a linked list with high time complexity.
    • Secondly, all of themkeyThey were chosen because they were inHashMapWhen you compute the subscripts, the subscripts are all 0, idx is equal to(size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16)), so that allkeyAll hash to the same place to collide.And the wordinsincere1. to do STH.Not sincere or sincere!
    • Finally, the first seven keys are actually uselesskeyOnly the last key is serviced. So you can build a lot of these time-consuming collision lists in a HashMap, of course0.75Do not let HashMap expand.

In fact, many algorithms, including hashing, inversion and load, can be used in many practical business scenarios, including crowd filtering, lottery logic, data routing and so on. The use of these functions can reduce time complexity, improve system performance, and reduce interface response time.

5. Separate responsibilities

In order to make the logical implementation of the program more extensible, we usually need to use design patterns to deal with the code implementation structure of each scenario. The application of design pattern in code development is mainly embodied in the definition of interface, the packaging of abstract classes and the implementation of inherited classes. By isolating the development of each functional area in this way, the requirements can be added more flexibly each time they are extended, without the code becoming more cluttered by requirements iterations.

case

public interface IRuleExec {

    void doRuleExec(String req);

}

public class RuleConfig {

    protected Map<String, String> configGroup = new ConcurrentHashMap<>();

    static {
        // ...}}public class RuleDataSupport extends RuleConfig{

    protected String queryRuleConfig(String ruleId){
        return "xxx"; }}public abstract class AbstractRuleBase extends RuleDataSupport implements IRuleExec{

    @Override
    public void doRuleExec(String req) {
        // 1. Query configurations
        String ruleConfig = super.queryRuleConfig("10001");

        // 2. Verify information
        checkRuleConfig(ruleConfig);

        // 3. Execute rule {contain business logic, leave business to handle}
        this.doLogic(configGroup.get(ruleConfig));
    }

    /** * Execute rule {contain business logic, leave business to handle} */
    protected abstract void doLogic(String req);

    private void checkRuleConfig(String ruleConfig) {
        / /... Check the configuration}}public class RuleExec extends AbstractRuleBase {

    @Override
    protected void doLogic(String req) {
        // Encapsulates its own business logic}}Copy the code

The class diagram

  • This is a definition of a template pattern structure that uses interface implementation, abstract class inheritance, and can be seen inAbstractRuleBaseAbstract class is responsible for the definition of the entire logical call, and this abstract class isolated some common configuration and data use, and common simple methods in their own implementation, and finally about the definition and call of the abstract method, and business classRuleExecYou can implement your own logic functions on demand.

6. Be logical

Has your code ever had an online accident? Why the accident? Ten birds in a tree and one shot left how many? For example: is the gun silent, is the bird deaf, is there a pregnant bird, is there a bird tied to the tree, is there a bird next to the tree, is the bird afraid of gunshots, is there a disabled bird, is the bird hunter blind,… .

In fact, your online accidents are basically around: database connections and slow queries, server loads and outages, logic exceptions, interface idempotency, data resistance, MQ consumption speed, RPC responsiveness, tool class usage errors, and so on.

Here is an example: users pay more points, resulting in bulk customer complaints.

  • Background: A large part of the research and development of this product may have participated in the development of the background, which is simply to meet the needs of users to use the point lottery. The process designed at the beginning of r&d is shown on the left of the figure. Users’ points are deducted through RPC interface, and lottery is carried out after successful deduction. However, the RPC service was unstable on that day. As a result, the RPC call succeeded, but a timeout failure was returned. However, the UUID of calling RPC interface is automatically generated each time and does not have idempotency of calling. Therefore, users pay more points.
  • Treatment: after the accident, the lottery process is modified and the lottery ticket is to be drawn. RPC interface is called by the lottery ticket ID to ensure the idempotency of the interface. When the RPC interface fails, the lottery is performed by the way of scheduled task compensation. After the process rectification, it is found that the compensation task occurs 1-3 times per week, which proves that there is indeed a problem of availability of RPC interface. At the same time, it also indicates that there has been a process problem for a long time, but there is no feedback because there are few complaints from users and customers.

7. Domain aggregation

Not abstract enough, not writable, not extensible enough, is it always your code, like a one-shot deal every time, completely written and bound, with no gaps for new requirements to expand into?

Why? Because a lot of the code written by r&d does not have the characteristics of domain aggregation, of course, this does not have to be in the DDD structure, even in the MVC layer, can also write a lot of good aggregation logic, function implementation and business invocation separated.

  • Based on the design idea of domain-driven design, the domain model is established by event storm, the logical and physical boundary of domain is divided reasonably, the domain object and service matrix and service architecture diagram are established, and the code structure model conforming to DDD hierarchical architecture idea is defined to ensure the consistency between business model and code model. Through the above design ideas, methods and processes, guide the team to complete the design and development of microservices according to DDD design ideas.
    • Refuse mud ball small monomer, refuse pollution function and service, refuse one plus function schedule for one month
    • Architecture of highly available easy to conform to the High-speed iteration of the Internet application services
    • Materialized, assembled and choreographed services improve human efficiency

8. Service layering

If you want to make your systems engineering code can support of the business needs of the majority, and can settle can take, so you need to do basic development of the implementation of the code, pulled out the technology components, functional areas and business logic several layers, not to write the business logic of the frequent change in various functional areas, Functional areas should be made more independent and can be connected, choreographed and combined by business layers to achieve different business requirements. This way your functional areas can be settled down and it’s easier to expand with each requirement.

  • This is a simplified, hierarchical logical structure with aggregated domains, SDK components, middleware, and code choreography, and provides some common, condensed service governance capabilities. Through this layering and implementation of each level, the requirements can be more flexibly accepted.

9. Concurrency optimization

In the distributed scene development system, to use the distributed ability as far as possible, from the program design as far as possible to avoid some centralized, distributed things, database lock, because the use of these ways may be in some extreme cases, resulting in the overload of the system, resulting in accidents.

  • Therefore, it is often more necessary to do de-centralization, using MQ to eliminate peaks and reduce coupling so that the data can be ultimately consistent, but also more important to consider the use of Redis to reduce the amount of locking on the database.
  • The proper use of MQ, RPC, distributed tasks, Redis, database and table, and distributed transactions is the only way you can make your code support a larger business volume.

10. Source code ability

Have you read about HashMap’s zippered addressing data structure, hash hash and perturbation functions, how to dynamically switch data sources with Spring, how AOP is implemented and used, how MyBatis and Spring manage Bean objects, etc. Seems to be some interview essay, but in the actual development is actually can solve a lot of problems.

@Around("aopPoint() && @annotation(dbRouter)")
public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws Throwable {
    String dbKey = dbRouter.key();
    if (StringUtils.isBlank(dbKey)) throw new RuntimeException(Annotation DBRouter key is null!);

    // Calculate the route
    String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
    int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();

    // Perturbation function
    int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));

    // Library table index
    int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
    int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);   

    // Set to ThreadLocal
    DBContextHolder.setDBKey(String.format("%02d", dbIdx));
    DBContextHolder.setTBKey(String.format("%02d", tbIdx));
    logger.info(Database routing method: {} dbIdx: {} tbIdx: {}", getMethod(jp).getName(), dbIdx, tbIdx);
   
    // Return the result
    try {
        return jp.proceed();
    } finally{ DBContextHolder.clearDBKey(); DBContextHolder.clearTBKey(); }}Copy the code
  • This is a HashMap hash bucket array + linked list + red-black tree data structure, via perturbation function(size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16));Resolve serious data collision problems.
  • But in fact, such hash algorithm, addressing mode can be used in the design and implementation of database routing, as well as the whole array + linked list in fact, the way of library + table is similar.
  • First, we extract the number of library table products and use it as a HashMap.
  • When IDX calculates an index position on the total length, it also needs to convert this position into the library table to see the index of the total length because it falls into which library which table.
  • Finally, the index information of this calculation is stored in a ThreadLocal, which is used to pass the index information that can be extracted during the method call.

Third, summary

  • To be fair, it’s almost impossible to take a bunch of broken code and refactor it to clean it up. In detail, you need to change the code structure layer, attribute object integration, call logic encapsulation, but any step of the operation may have a risk to the original interface definition and call, and the external existing call your interface needs to upgrade as you change, you may want to wrap the layer, But this layer of packaging still requires a large time cost and almost no value of adaptation.
  • So we in the actual development, if can let the code has the possibility of refactoring, almost is to real-time reconstruction, whenever you add a new function, new logical, repair exception, whether can consider through the code structure, implementation, design patterns, the use of such methods as the realization of the function of the change is not reasonable. Every time, a little bit of optimization and change, it won’t be that hard.
  • When you are in the demand, think carefully to undertake such business demands, need to build what kind of data structure, algorithm logic, design pattern, domain aggregation, service arrangement, system architecture, etc., in order to build a more reasonable with easy maintenance, scalable system services. If you don’t feel that way, read Design Patterns and Handwriting Spring, which can help you improve your programming logic.