3
头图

Author: Xiao Fu Ge
Blog: https://bugstack.cn

I. Introduction

We are not the same, you have no object! Yes, you are process-oriented programming!

What I said, most of the code farmers are suffocated by the demand day and night, no matter how awkward, it is impossible to refactor, only to rewrite. Why? Because the time and cost of rewriting is far more time-saving than refactoring a messy code. But no one can guarantee that the rewritten code will be much better than before, and moreover, you have to bear the risk of code accidents after business value of 16142b34246363 that is hardly reflected!

Although the code is for the machine to run, it is also for people to see, and with each iteration, change, and upgrade of requirements, it is necessary for developers to develop and launch the same code multiple times, then it will be involved here. maintainable, easy to expand, good transfer characteristics.

Those unreasonable hierarchical logic implementation code, do not write code comments, is not according to specifications submitted, not formatting, naming freely even put queryBatch written queryBitch, will not cause problems subsequent code refactoring. Then we will separately introduce how to develop refactorable code!

Two, code optimization

1. Conventions

# 提交:主要 type
feat:     增加新功能
fix:      修复bug

# 提交:特殊 type
docs:     只改动了文档相关的内容
style:    不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
build:    构造工具的或者外部依赖的改动,例如webpack,npm
refactor: 代码重构时使用
revert:   执行git revert打印的message

# 提交:暂不使用type
test:     添加测试或者修改现有测试
perf:     提高性能的改动
ci:       与CI(持续集成服务)有关的改动
chore:    不修改src或者test的其余修改,例如构建过程或辅助工具的变动

# 注释:类注释配置
/**
* @description: 
* @author: ${USER}
* @date: ${DATE}
*/
  • branch : prior to development, agree on the branch specification, such as , 210905_xfg_updateRuleLogic
  • Submit : author, type: desc such as: Xiao Fu, fix: update rule logic problem refer to Commit message specification
  • annotation : Including class annotation, method annotation, attribute annotation, the header information of class annotation can be set in IDEA Editor -> File and Code Templates -> File Header recommended to download and install IDEA P3C plug-in Alibaba Java Coding Guidelines , unified standardized coding method.

2. Interface standards

When writing the RPC interface, the returned result must contain a clear Code and Info description, otherwise it is difficult for the user to know whether the interface is successfully called or abnormal, and what the abnormality is.

define Result

public class Result implements java.io.Serializable {

    private static final long serialVersionUID = 752386055478765987L;

    /** 返回结果码 */
    private String code;

    /** 返回结果信息 */
    private String info;

    public Result() {
    }

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

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

return result package: inherit

public class RuleResult extends Result {

    private String ruleId;
    private String ruleDesc;

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

// 使用
public RuleResult execRule(DecisionMatter request) {
    return new RuleResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo());
}

return result package: 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
}  

// 使用
public ResultData<Rule> execRule(DecisionMatter request) {
    return new ResultData<Rule>(Result.buildSuccessResult(), new Rule());
}
  • The packaging definitions of the returned results of the two interfaces can standardize the returned results. After packaging in this way, the user can use a unified way to determine the Code code and make corresponding processing.

3. Library table design

Three-paradigm : It is the standardized content of the database. The so-called database three-paradigm is generally a set of specifications that should be followed when designing database tables. If you do not comply, the designed database will be irregular and the database field will be redundant. Data query, insertion and other operations.

The database not only has three paradigms (1NF/2NF/3NF), but also BCNF, 4NF, 5NF..., but in the actual database design, it is sufficient to comply with the first three paradigms. Downward will cause too many unnecessary constraints in the designed database.

0NF

  • The zeroth paradigm means that no paradigm is used, a large number of redundant table fields are stored for data, and such a table structure is very difficult to maintain.

1NF

  • The first paradigm is an improvement on the redundant fields of the zeroth paradigm. The repeated fields are extracted and designed into a table structure with less redundant data and easy to store and read.
  • At the same time, it is also pointed out in the first normal form that all fields in the table should be atomic and indivisible. For example, you cannot store the department names and responsibilities of the company employee table in one field. Need to ensure that each column remains atomic

2NF

  • After 1NF is met, the columns in the required table must rely on the primary key, to ensure that each column is connected with the primary key column, and not indirectly, that is, a table can only describe one thing. You need to ensure that each column in the table is related to the primary key.

3NF

  • There can be no dependencies. Student ID, name, department, department and dormitory, you need to ensure that each column is directly related to the primary key column, rather than indirectly related.

Anti-three paradigm

The three paradigms are the rule constraints for designing the database table structure, but partial adaptations are allowed in actual development:

  1. Sometimes, in order to facilitate the query, the snapshot information of the user at the time is redundant in the order table, such as some setting information when the user places an order.
  2. The single-column list data is summarized into a quantity value in the total table, so that the list summary operation can be avoided when querying.
  3. Some fields can be redundant when designing the table to avoid the cumbersome problems of the table due to changes in business development and poor 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 and deduct inventory on database tables, and it is not possible to directly perform a large number of for loop training operations. It is usually necessary to consider how in such a scenario Decentralization and reducing time complexity.

spike: decentralization

  • Background : The initial design of this implementation of a product activity spike is to lock it based on an activity number ID. This ID is locked during the spike, and the user will release it after the purchase. However, when a large number of users , an exception occurred in the business logic processing after the spike distributed 16142b34246858 exclusive lock, and the release of the lock failed. As a result, all users can no longer get the lock, which also causes the problem that there are goods but cannot place orders.
  • Optimization : Optimize the exclusive competition state as segmented static, and use the activity ID + inventory number as the dynamic lock identification. If the current spike user fails to lock, the subsequent users can continue to spike without being affected. And the failed lock will be compensated and restored by the worker, and then oversold and unsold will be avoided in the end.

Algorithm: Negative Textbook

@Test
public void test_idx_hashMap() {
    Map<String, String> map = new HashMap<>(64);
    map.put("alderney", "未实现服务");
    map.put("luminance", "未实现服务");
    map.put("chorology", "未实现服务");
    map.put("carline", "未实现服务");
    map.put("fluorosis", "未实现服务");
    map.put("angora", "未实现服务");
    map.put("insititious", "未实现服务");
    map.put("insincere", "已实现服务");
    
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        map.get("insincere");
    }
    System.out.println("耗时(initialCapacity):" + (System.currentTimeMillis() - startTime));
}
  • Background : HashMap data acquisition time complexity is O(1) -> O(logn) -> O(n), but after special operation, this time complexity can be pulled to O(n)
  • operation : This is a definition HashMap stores the business to realize the key, and the function of calling the service through the key. But here's key , only insincere useful and others are not implement the service. Do you see any problems?

    • At first glance, there is nothing wrong with this code, but if you understand it, there is arsenic in the code! Its purpose is one, to make all the keys into a linked list and put them in the HashMap, and put the useful keys at the end of the linked list, increasing the time-consuming get!
    • First of all, new HashMap<>(64); initialize 64 lengths by default? Because the default length is 8, when inserting elements, when the length of the linked list is 8, the expansion and treeing of the linked list will be judged. At this time, the original key will be hashed, and all the keys cannot be made into a high time complexity. Linked list.
    • Secondly, all key are deliberately selected, because when they HashMap , the subscript value is 0, idx = (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16)) , so that all key can be hashed to the same position for collision. and the word insincere means; insincere, insincere!
    • Finally, the first 7 keys are actually useless key , which has no effect. Only the last key has service. In this way, many such time-consuming collision lists can be built in HashMap. Of course 0.75 must be met, and HashMap should not be expanded.

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

5. Separation of Duties

In order to make the logic implementation of the program more scalable, we usually need to use the design pattern to handle the code implementation structure of each scene. The use of design patterns in code development is mainly reflected in the definition of interfaces, the packaging of abstract classes, and the realization of inherited classes. In this way, the development of various functional areas is isolated, so as to ensure that each time the requirements are expanded, they can be added more flexibly, and the code will not become more chaotic due to the iteration of the requirements.

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. 查询配置
        String ruleConfig = super.queryRuleConfig("10001");

        // 2. 校验信息
        checkRuleConfig(ruleConfig);

        // 3. 执行规则{含业务逻辑,交给业务自己处理}
        this.doLogic(configGroup.get(ruleConfig));
    }

    /**
     * 执行规则{含业务逻辑,交给业务自己处理}
     */
    protected abstract void doLogic(String req);

    private void checkRuleConfig(String ruleConfig) {
        // ... 校验配置
    }

}

public class RuleExec extends AbstractRuleBase {

    @Override
    protected void doLogic(String req) {
        // 封装自身业务逻辑
    }

}

class diagram

  • This is the definition of a template pattern structure, which uses interface implementation and abstract class inheritance. At the same time, it can be seen that in the AbstractRuleBase abstract class, it is responsible for completing the definition of the entire logic call, and this abstract class uses some common configurations and data usage Isolate them separately, and put the common simple methods in their own implementation, and finally the definition and invocation of abstract methods, and the business class RuleExec can implement its own logic functions on demand.

6. Meticulous logic

Has your code experienced an online incident? Why did the accident happen? Is it a question of how many birds are left when ten birds fired one shot on the tree? For example: is the gun silent, is the bird deaf, is there a pregnant woman, is there a bird tied to a tree, is there a bird in the tree next to it, is the bird afraid of gunshots, is there a disabled bird, or a bird? People's eyes are not flowery,...

In fact, your online accidents basically revolve around: database connection and slow query, server load and downtime, abnormal logic, interface idempotence, data anti-repetition, MQ consumption speed, RPC response time, and tool use error and many more.

Here is an example: users pay more points, resulting in a batch of customer complaints.

  • : The background of this product function may be a large part of the research and development involved in the development, in short, it is to meet the needs of users to use the points lottery. The left side of the figure above is the initial design process of R&D. User points are deducted through the RPC interface. After the deduction is successful, a lottery will be carried out. However, due to the unstable RPC service that day, the actual RPC call was successful, but it returned a timeout failure. The uuid that calls the RPC interface is automatically generated every time, and does not have the idempotence of calling. Therefore, the phenomenon of users paying more points is caused.
  • processing : modify the lottery process after the accident, first generate the lottery ticket to be drawn, and call the RPC interface by the lottery ticket ID to ensure the idempotence of the interface. When the RPC interface fails, the lottery is executed by the way of timing task compensation. After the process rectification, it was found that the compensation task occurred 1 to 3 times a week, which proved that the RPC interface did have an availability problem. It also showed that there were process problems a long time ago, but because there were fewer customer complaints, there was no feedback.

7. Domain aggregation

It is not abstract enough, cannot be hard-coded, and not easy to extend. Is it always your code? Every time it is like a hammer sale, it is completely hard-coded and bound, and there is no gap at all for new requirements to expand in.

Why? Because a lot of code written by research and development does not have the characteristics of domain aggregation. Of course, this does not have to be in the structure of DDD. Even in the layering of MVC, it is possible to write a lot of good aggregation logic. , Separate the function realization from the business call.

  • Relying on the design thinking of domain-driven design, establishing domain models through event storms, reasonably dividing domain logic and physical boundaries, establishing domain objects and service matrices and service architecture diagrams, defining code structure models that conform to DDD layered architecture ideas, and ensuring that business models are consistent with Consistency of the code model. Through the above design ideas, methods and processes, guide the team to complete the design and development of microservices in accordance with DDD design ideas.

    • Reject the small monomer of mud ball, reject the pollution function and service, reject the one plus function, and the schedule is one month
    • Architect high-availability application services that are easily compatible with the high-speed iteration of the Internet
    • Materialized, assembled, and orchestrated services to improve human efficiency

8. Service layering

If you want your system engineering code to support the absolute majority of business needs, and can precipitate the functions that can be taken, then basically you need to extract technical components, functional areas and business logic when doing code development and implementation. With such a few layers, do not write frequently changing business logic into each functional area. The functional areas should be more independent and can be connected, orchestrated, and combined by the business layer to achieve different business requirements. In this way, your functional areas can be gradually precipitated, and it is easier to expand every time you need.

  • This is a simplified hierarchical logical structure, with aggregated domains, SDK components, middleware, and code layout, and provides service governance functions that are condensed by general commonalities. Through such stratification and various levels of implementation, it is possible to undertake demands more flexibly.

9. Concurrency optimization

In a distributed scenario development system, it is necessary to use distributed capabilities as much as possible, and avoid some centralized, distributed things, and database locks as much as possible from the program design, because these methods may be used in some In extreme cases, the load of the system will exceed the standard and cause an accident.

  • Therefore, under normal circumstances, it is more necessary to do decentralized processing, use MQ to eliminate peaks, reduce coupling, so that the data can be eventually consistent, and also consider the use under Redis to reduce the large number of locks on the database.
  • Reasonable use of MQ, RPC, distributed tasks, Redis, sub-databases, sub-tables, and distributed transactions. Only in this way can you make your own program code support a larger business volume.

10. Source code capability

Have you ever understood the zipper addressing data structure of HashMap, know the hashing and perturbation functions, know how to dynamically switch data sources in combination with Spring, how to implement and use AOP, how does MyBatis combine with Spring to manage Bean objects? , Wait. They seem to be eight-legged essays for interviews, but in actual development, many problems can be solved.

@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!");

    // 计算路由
    String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
    int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();

    // 扰动函数
    int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));

    // 库表索引
    int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
    int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);   

    // 设置到 ThreadLocal
    DBContextHolder.setDBKey(String.format("%02d", dbIdx));
    DBContextHolder.setTBKey(String.format("%02d", tbIdx));
    logger.info("数据库路由 method:{} dbIdx:{} tbIdx:{}", getMethod(jp).getName(), dbIdx, tbIdx);
   
    // 返回结果
    try {
        return jp.proceed();
    } finally {
        DBContextHolder.clearDBKey();
        DBContextHolder.clearTBKey();
    }
}
  • This is the data structure of the HashMap hash bucket array + linked list + red-black tree. The perturbation function (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16)); solves the serious problem of data collision.
  • But in fact, such hashing algorithms and addressing methods can be applied to the design and implementation of database routing, and the entire array + linked list approach is actually similar to the library + table approach.
  • The core logic implementation code of database routing simplification is as above. First, we extract the number of library table products and use it as the same length as HashMap.
  • After idx calculates an index position on the total length, it needs to convert this position into the library table to see which library and which table the overall length index falls into.
  • Finally, the calculated index information is stored in ThreadLocal, which is used to transfer the index information that can be extracted during method invocation.

Three, summary

  • To be reasonable, it is almost impossible for you to clean up a bunch of bad code through refactoring. In detail, you need to change the code structure layering, attribute object integration, call logic encapsulation, but any step of the operation may have a risky impact on the original interface definition and call, and the existing external call to your interface still needs As you upgrade with your changes, you may want to package one layer, but this layer of packaging still requires a large time cost and almost worthless adaptation.
  • Therefore, in actual development, if these codes can be refactored, it is almost real-time refactoring. Whenever you add new functions, new logic, or repair exceptions, you must consider whether you can use the code structure , Implementation methods, design patterns and other means to change unreasonable functional realization. Every time, a little optimization and change will not be so difficult.
  • When you are accepting requirements, carefully think about what kind of data structure, algorithm logic, design mode, domain aggregation, service orchestration, system architecture, etc. need to be built to undertake such business requirements, in order to build a more reasonable Maintenance and scalable system services. If you don’t feel much about these, you can read design patterns and handwriting Spring , these contents can help you improve a lot of programming logic design.

Fourth, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。