1. Introduction

Recently, the author had the honor to reconstruct autonavi taxi order Push project. I would like to share my work experience related to code reconstruction with you, hoping to inspire you.

Sometimes, when we are working on a functional requirement, it takes a lot of time to find the code associated with the requirement. Or, when we read someone else’s code or take on someone else’s project, we often get “scalp pins and needles.” When you’re faced with unstructured code structures, variable names, method names that don’t make sense, I’m sure you’re not in the mood to read. It’s not your problem, it’s your code that needs to be refactored.

2. What is refactoring

Everyone has their own definition of refactoring, and THE one I’m quoting here is by “Martin Fowler,” who defines refactoring in two dimensions.

Noun: Refactoring is an adjustment to the internal structure of software to improve its comprehensibility and reduce its modification costs without changing its observable behavior.

As a verb: Refactoring is the use of a series of refactoring techniques to adjust the structure of software without changing its observable behavior.

The code refactoring I mentioned in this article is more defined as a verb, and we can divide code refactoring into small refactoring and large refactoring, depending on the size and duration of the refactoring.

Minor refactoring: Refactoring the details of the code, mainly for classes, functions, variables, and other code-level refactoring. For example, common standard naming (for the name of a variable that does not make sense), the elimination of oversized functions, the elimination of duplicate code and so on. In general, this kind of reconstruction and modification are relatively concentrated, relatively simple, with relatively small impact and short time. So the difficulty is relatively low, we can do it in the daily version of development.

Large-scale reconstruction: it is the reconstruction of the top-level code, including the reconstruction of system structure, module structure, code structure and class relations. The general methods are service stratification, business modularization, componentization, code abstraction reuse and so on. This kind of refactoring may require a redefinition of principles, a redefinition of schemas, and even a business redefinition. It involves a lot of code adjustment and modification, so the impact is relatively large, time-consuming, and the risk is relatively large (project stop risk, code Bug risk, business vulnerability risk). This requires experience in refactoring large projects, otherwise it is easy to make mistakes and lose more than you gain.

Most people don’t like refactoring, just as no one wants to “wipe up” other people’s asses, and there are probably several concerns:

  • Do not know how to reconstruct, lack of experience and methodology of reconstruction, easy to make mistakes in reconstruction.
  • It’s hard to see short-term gains, and if those benefits are long-term, why make the effort now? In the long run, by the time the project reaps these benefits, you may no longer be in charge.
  • Refactoring can break existing programs and introduce unexpected bugs, and you don’t want to be exposed to unexpected bugs.
  • Refactoring requires extra work on your part, and you may not have written the code you need to refactor.

3. Why refactor

If I work only for today, I will not be able to work at all tomorrow.

Programs have two-sided values: “what can be done for you today” and “what can be done for you tomorrow.” Most of the time, we just focus on what we want the program to do today. Whether it’s fixing bugs or adding features, it’s all about making the program more powerful and more valuable today. But there are several reasons why I advocate refactoring at the right time:

  • Keep the software architecture well designed. Improve our software design, let the software architecture to the favorable direction of development, can always provide stable external services, calmly face all kinds of unexpected problems.
  • Increased maintainability and reduced maintenance costs create a positive cycle for both teams and individuals, making software easier to understand. Whether later generations read the code written by predecessors, or review their own code, they can quickly understand the whole logic, clear business, easy to maintain the system.
  • Improve the speed of research and development and shorten labor costs. You may have experienced that when a system goes live, it is very fast to add features to the system, but if you do not pay attention to the quality of the code, it may take a week or more to add a small feature to the system. While code refactoring is an effective means to ensure code quality, good design is essential to maintain the speed of software development. Refactoring can help you develop software faster because it stops systems from rotting and can even improve design quality.

4. How to refactor

Small refactoring

Small refactorings are mostly done on a daily basis. The general reference is our development specifications and guidelines, which are designed to address bad smells in code. Let’s take a look at some common bad smells.

Generic erasure

/ / {" param1 ":" v1 ", "param2" : "v2", "param3" : 30,... } Map map = JSON.parseObject(msg); //【1】...... // Pass the map as a parameter to the underlying interface xxxservice.handle (map); Void handle(Map<String, String> Map); / / [3]Copy the code

[2], pass the generic erased map to the underlying generic qualified interface. I believe that in the interface implementation, “String value = map.get(XXX)” is used to obtain the value, so that once there is a non-String value in the map, there will be a type conversion exception. The reader must be as curious as I am as to why this business system does not throw a cast exception because the business system does not cast values to String. As you can imagine, when someone uses the standard way to get a value, there is a lot of thunder.

/ / text text 2 $1 ${param1} 3 ${param3} {param2} text String [] terms = [" text 1 ", "$param1", "text 2", "$param2", "3" text, "$param3"]. StringBuilder builder = new StringBuilder(); for(String term: terms){ if(term.startsWith("$")){ builder.append(map.get(term.substring(1))); }else{ builder.append(term); }}Copy the code

Moaning whinge-bags

Config config = new Config(); // setName and md5 config.setname (item.getname ()); config.setMd5(item.getMd5()); // Set the value config.setTypemap (map); // Prints logger. info(" Update done ({},{}), start replace", getName(), getMd5()); . ExpiredConfig expireConfig = ConfigManager.getExpiredConfig(); If (objects.isnull (expireConfig)) {expireConfig = new ExpiredConfig(); }... Map<String, List<TypeItem>> typeMap =... ; Map<String, Map<String, Map<String, List<Map<String, Object>>>>> jsonMap = new HashMap<>(); ForEach ((k1, v1) -> {jsonmap.foreach ((k1, v1) -> {jsonmap.foreach ((k2, v2) -> {jsonmap.foreach ((k1, v1) -> { V3) -> {// loop the innermost list, hey! V3. forEach(e -> {// generate key String ck = getKey(k1, k2, k3); List<TypeItem> types = typemap. get(ck); if (CollectionUtils.isEmpty(types)) { types = new ArrayList<>(); typeMap.put(ck, types); } // Set type}}}}Copy the code

The code itself is easy to see what it’s doing, and the person who wrote it had to put a comment in there that was completely meaningless.

If – else too much

try { if (StringUtils.isEmpty(id)) { if (StringUtils.isNotEmpty(cacheValue)) { if (StringUtils.isNotEmpty(idPair)) { if (cacheValue.equals(idPair)) { // xxx } else { // xxx } } } else { if (StringUtils.isNotEmpty(idPair)) { // xxx } } if(xxxx(xxxx){ // xxx }else{ if(StringUtils.isNotEmpty(idPair)){ // xxx } // xxx } }else if(! check(id, param)){ // xxx } } catch (Exception e) { log.error("error:", e); }Copy the code

Such code, so that the code reading is greatly reduced, so that many people are discouraged. Developers probably won’t touch code like this unless they have to, because you don’t know that a tiny move you make could bring the entire business down.

Other bad smells

Here will not list the relevant cases, I believe that you often see a lot of unreasonable code writing, people do not adapt to the place, summarize the common bad taste in the code and solutions:

Duplicate code

The worst code smell is probably duplicate code, and if you see the same code structure in more than one place, you can be sure that your program will be better if you try to merge them into one.

One of the most common repetition scenarios is when “two functions of the same class have the same expression.” This form of repeated code can extract common methods in the current class for reuse in both places.

Similar to this scenario, where “two sibling subclasses contain the same expression,” this form can extract the same code into the common parent class, and defer the implementation of the differentiated parts to the subclass using abstract methods. This is a common template method design pattern. If two unrelated classes have duplicate code, consider distilling the duplicate code into a new class and then calling the methods of the new class in both classes.

Function is too long

A good function must satisfy the single responsibility principle, be short and concise, and do only one thing. Long function bodies and multi-tasking methods are bad for reading and for code reuse.

Naming conventions

A good naming needs to be able to do “worthy of the name, see the meaning”, direct, there is no ambiguity.

Unreasonable comments

Comments are a double-edged sword, good comments can give us good guidance, bad comments will only mislead people. For comments, we need to modify the comments as well as integrate the code, otherwise the comments and logic will be inconsistent. In addition, comments are unnecessary if the code clearly expresses its intent.

Dead code

Useless code has two ways, one is no use scenarios, if this kind of code is not tool methods or tool classes, but some useless business code, then need to be deleted in time to clean up. The other is a block of code wrapped in a comment that should be removed as soon as it is commented.

Too much class

A class that does too much, maintains too many features, becomes less readable, and its performance deteriorates. For example, order related functions are in A class A, inventory related functions are in A class A, points related functions are in A class A… Think of all the random code blocks crammed into a class. The code should be split up with different classes for a single responsibility.

These are common code “bad smells”, but there are other “bad smells” in real development, such as messy code, unclear logic, and complex class relationships. When you smell these different “bad smells”, you should try to solve them, rather than ignore them.

Large-scale refactoring

Compared to small refactorings, large refactorings have a lot of things to think about, and they need to be done at a good pace, because in large refactorings, things change.

There are usually three steps to putting an elephant in the fridge: 1) open the fridge door (beforehand); 2) Push the elephant into the room. 3) Close the refrigerator door (afterwards). Every day, everything can be solved in three steps, and refactoring is no exception.

In advance

As the first step of refactoring, preparation in advance is the most important and complicated thing involved. If the preparation is not sufficient, it is likely to lead to the phenomenon that the results generated during the implementation or after the refactoring goes online are inconsistent with the expectation.

This stage can be roughly divided into three steps:

  • Define the content and purpose of the refactoring, as well as the direction and objectives

In this step, the most important thing is to make the direction clear, and this direction can withstand the doubts of everyone, at least three to five years to meet the direction. Another is the goal of this reconstruction. Due to technical limitations, historical burden and other reasons, this goal may not be the final goal, so it is necessary to clarify what the final goal is, and what else needs to be done from the goal of this reconstruction to the final goal, it is best to clarify.

  • Sorting data

In this step, we need to sort out the existing business and architecture involved in reconstruction, clarify which service level and business module the content of reconstruction is in, which dependent and dependent parties are there, which business scenarios are there, and what are the data input and output of each scenario. At this stage, there will be outputs, generally precipitation project deployment, business architecture, technical architecture, service upstream and downstream dependencies, strong and weak dependencies, project internal service layering model, content function dependency model, input and output data flow and other related design drawings and documents.

  • project

Project approval is generally carried out through meetings. All departments or groups involved in the reconstruction work are publicized, and the approximate time schedule (rough rough time) is publicized, and the main responsible persons of each group are identified. It is also important to know what businesses and scenarios are involved in refactoring, how refactoring might be done, what the business implications might be, the difficulties, and where bottlenecks might occur.

things

The things and tasks to perform this step are relatively onerous, and the time to pay is relatively more.

  • Architecture design and review

Architecture design review is mainly to design and review standard business architecture, technical architecture and data architecture. Through the review to find architecture and business issues, the review is generally team review, if after a review, found that architectural design cannot be determined, then need to adjust, until the team is agreed to design architecture, in just can take the next step, the review results need to be done after review by mail known as participants.

Outputs of this stage: reconstituted service deployment, system architecture, business architecture, standard data flow, service layering pattern, UML diagram of function modules, etc.

  • Detailed landing design scheme and review

The implementation of this design scheme is the most important one in the project, which is related to the following research and development coding, self-testing and joint commissioning, the docking of dependent parties, QA testing, offline release and implementation of the plan, online release and implementation of the plan, specific workload, difficulty, work bottleneck, etc. This detailed landing plan needs to go deep into the whole research and development, offline testing, online process, gray scene details, including AB gray program, AB verification program.

The most important part of the scheme design is the AB verification procedure and the AB verification switch, which is the standard basis for evaluating and verifying whether we have completed the reconstruction. The general AB verification procedure is as follows:

At the data entry point, the same data is used to make processing requests to both the old and new processes. After the processing is complete, the processing results are printed to the log. Finally, the results of the new and old processes are compared through the offline program. The principle is that the results of the response should be the same with the same input parameters.

In the AB program, two switches are involved. Grayscale switch (only if it is turned on will the request be sent to the new process for code execution). Execute switches (if write operations are involved in the new process, switches are needed to control whether to write in the new process or in the old process). Before forwarding, grayscale switch and execution switch (generally configured to the configuration center, can be adjusted at any time) should be written into the thread context, so as to avoid the inconsistency of switch results obtained from multiple places when the configuration center switch is modified.

  • Code writing, testing, offline implementation

This step is to carry out coding, single test, joint commissioning, functional test, business test and QA test according to the detailed design scheme. After passing, simulate the on-line process and on-line switch implementation process offline, verify AB program, check whether it meets expectations, and whether the coverage of new process code meets on-line requirements. If offline data samples are too small to cover all scenarios, you need to construct traffic to cover all scenarios to ensure that all scenarios meet expectations. The online operation can only be carried out when the offline coverage reaches the expected level and the AB verification program does not check any abnormality.

After the event

This stage needs to be implemented online in accordance with the implementation process of offline simulation, which is divided into several stages, such as online, volume, repair, offline old logic and recheck. One of the most important and energy – consuming is the volume process.

  • Gray switching process

Step by step to the new flow for observation, you can follow the progress of 1%, 5%, 10%, 20%, 40%, 80%, 100%, so that the new flow gradually covered the code logic, pay attention to this stage does not turn on the switch of the actual write operation. Only when the logical coverage of the new process meets the requirements and the AB verification results meet the expectations can the write operation switch be gradually turned on to perform the actual service operation.

  • Switch flow of service execution

After meeting expectations in the process of gray scale new process, the switch flow of business write operation can be gradually opened, and the volume can still be gradually increased in accordance with a certain proportion. After the write operation is opened, only the new logic will execute the write operation, while the old logic will close the write operation. At this stage, you need to observe online errors, abnormal indicators, user feedback, etc., to ensure that there are no problems with the new process.

After the volume work is done and a certain version is stabilized, the old logic and AB validator can be taken offline and the refactoring work is finished. If possible, a reconstruction review meeting can be held to check whether each participant has reached the standard of reconstruction requirements, the problems encountered during the reconstruction review and what the solutions are. Precipitation methodology can avoid similar problems in subsequent work.

5. To summarize

Code technique

  • Follow some basic principles when writing code, such as the single principle and relying on interfaces/abstractions rather than concrete implementations.
  • Strictly follow the coding specification, special annotation using TODO, FIXME, XXX for annotation.
  • Unit testing, functional testing, interface testing, and integration testing are essential tools for writing code.
  • We are the authors of the code, and future generations are the readers of the code. Write code to always review, do predecessors trees descendants cool, do not do predecessors dug a hole with descendants buried things.
  • Don’t do the broken window effect first person, don’t think that the code is already bad, there is no need to change, just continue to heap code. If so, one day they will be disgusted by other people’s code, “out of mix sooner or later is to return”.

Refactoring technique

  • Modeling and analyzing from top to bottom and from outside to inside to clarify various relationships is the top priority of reconstruction.
  • Refine classes, reuse functions, sink core capabilities, and make module responsibilities clear.
  • Relying on interfaces is better than relying on abstractions, relying on abstractions is better than relying on implementations, and class relationships should not be inherited if they can be composed.
  • Classes, interfaces, and abstract interfaces are designed with scope qualifiers in mind, what can and cannot be overridden, and whether generic qualifiers are accurate.
  • Large reconstruction to do a variety of design and planning, offline simulation of a variety of scenarios, online must need AB verification procedures, can be at any time to switch between the old and new.