1. Foreword
Recently, the author has the honor to refactor the Push project of Autonavi taxi order, and share with you the work experience related to code refactoring, hoping to inspire you.
Sometimes, when we are doing a certain functional requirement, we need to spend a lot of time to find the code related to the requirement. Or, when we read the code written by others and take over other people’s projects, we always have "scalp tingling". When you are faced with a chaotic, unstructured code structure, and unsatisfactory variable names and method names, I I believe you are not in the mood to read on. This is not your problem, but the code in your hands needs to be refactored.
2. What is reconstruction
Everyone has their own definition of refactoring. What I am quoting here is "Martin Fowler", who defines refactoring from two dimensions.
As a noun: Refactoring is an adjustment to the internal structure of software. The purpose is to improve the comprehensibility and reduce the cost of modification without changing the observable behavior of the software.
As a verb: Refactoring is the use of a series of refactoring techniques to adjust the structure of the software without changing the observable behavior of the software.
The code refactoring I mentioned in this article, I prefer it as the definition of verb , and according to the scale of the refactoring and the length of time, we can divide the code refactoring into small refactoring and large refactoring .
small refactoring : Refactoring the details of the code, mainly for the refactoring of the code level such as classes, functions, and variables. For example, common canonical naming (rename for variables that are inconsistent with words), eliminate super-large functions, eliminate duplicate codes, and so on. Generally, this type of reconstruction and modification are concentrated, relatively simple, with relatively small impact and short time. Therefore, the difficulty is relatively low, and we can do it in the daily development with the version.
large-scale reconstruction : It is the reconstruction of the top level of the code, including the reconstruction of the system structure, module structure, code structure, and class relationship. The generally adopted methods are service layering, business modularization, componentization, and code abstraction reuse. This type of refactoring may require redefinition of principles, redefinition of models or even business redefinition. There are many code adjustments and modifications involved, so the impact is relatively large, time-consuming, and the risks are relatively large (project suspension risk, code bug risk, business vulnerability risk). This requires us to have experience in large-scale project refactoring, otherwise it is easy to make mistakes and ultimately outweigh the gains.
In fact, most people don't like refactoring work, just like no one is willing to "wipe their ass" to others, there may be the following concerns:
- I don’t know how to refactor, lack the experience and methodology of refactoring, and it is easy to make mistakes in refactoring.
- It is difficult to see short-term benefits. If these benefits are long-term, why bother to make these efforts now? In the long run, when the project reaps these benefits, you may no longer be responsible for this work.
- Refactoring will destroy the existing program and bring unexpected bugs. I don't want to bear these unexpected bugs.
- Refactoring requires you to pay extra work, not to mention the code that may need to be refactored is not written by you.
3. Why refactor
If I work purely for today, I won’t be able to work at all tomorrow.
The program has two values: "what can be done for you today" and "what can be done for you tomorrow". Most of the time, we only 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 stronger and making it more valuable today. But why do I still advocate that everyone should refactor the code at the right time? The main reasons are as follows:
- allows the software architecture to always maintain a good design. improve our software design, let the software architecture develop in a favorable direction, can always provide stable services to the outside world, and calmly face various unexpected problems.
- increases maintainability and reduces maintenance costs. It is a positive virtuous circle for the team and individuals, making the software easier to understand. Whether later generations read the code written by the predecessors or review their own code afterwards, they can quickly understand the entire logic, clarify the business, and easily maintain the system.
- improves the speed of research and development and reduces labor costs. , the completion speed is very fast, but if you do not pay attention to the code quality, adding a small function to the system in the later period may take a week or more Long time. And code refactoring is an effective means to ensure the quality of the code, and good design is the foundation of maintaining the speed of software development. Refactoring can help you develop software more quickly, because it prevents system decay and deterioration, and can even improve design quality.
4. How to refactor
Small refactoring
Most of the small refactorings are carried out in daily development. The general reference standard is our development specifications and guidelines. The purpose is to solve the bad smell in the code. Let's take a look at what are the common bad smells?
Generic erasure
//{"param1":"v1", "param2":"v2", "param3":30, ……}
Map map = JSON.parseObject(msg); //【1】
……
// 将map作为参数传递给底层接口
xxxService.handle(map); //【2】
//看一下底层接口定义
void handle(Map<String, String> map); //【3】
[2] The map that has been generically erased is passed to the underlying interface that has been generically qualified. I believe that in the implementation of the interface, the value is obtained by using "String value = map.get(XXX)", so Once there is a non-String type value in the map, a type conversion exception will occur here. Readers must be as curious as I am as to why the type conversion exception is not thrown in the business system because the way the business system takes values is not converted to the String type. It is conceivable that once someone uses standard methods to obtain values, they will step on the thunder.
// 文本1${param1}文本2${param2}文本3${param3}
String[] terms = ["文本1","$param1", "文本2", "$param2", "文本3", "$param3"];
StringBuilder builder = new StringBuilder();
for(String term: terms){
if(term.startsWith("$")){
builder.append(map.get(term.substring(1)));
}else{
builder.append(term);
}
}
moaning
Config config = new Config();
// 设置name和md5
config.setName(item.getName());
config.setMd5(item.getMd5());
// 设置值
config.setTypeMap(map);
// 打印日志
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<>();
// 循环一级map
jsonMap.forEach((k1, v1) -> {
// 循环里面的二级map
v1.forEach((k2, v2) -> {
// 循环里面的三级map
v2.forEach((k3, v3) -> {
// 循环最里面的list,哎!
v3.forEach(e -> {
// 生成key
String ck = getKey(k1, k2, k3);
// 为空处理
List<TypeItem> types = typeMap.get(ck);
if (CollectionUtils.isEmpty(types)) {
types = new ArrayList<>();
typeMap.put(ck, types);
}
// 设置类型
}
}
}
}
The code itself can be seen at a glance to understand what it is doing. The person who writes the code has to add a non-trivial comment to this place. This comment is completely slobber and worthless.
too many if-else
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);
}
Such code greatly reduces the readability of the code, which discourages many people. Unless forced to do so, it is estimated that developers will not move such code, because you don't know that a small move may paralyze the entire business system.
Other bad taste
I won’t list relevant cases here anymore. I believe you often see a lot of unreasonable code writing and unsuitable places in your daily life. Summarize the common bad smells and solutions in the code:
Repeat code
The most bad smell of code is probably the duplication of code. If you see the same code structure in more than one place, then you can be sure: if you try to merge them into one, the program will become better.
The most common repetitive scenario is " two functions of the same class contain the same expression ". This form of repetitive code can extract common methods in the current class for reuse in two places.
There is another kind of scene similar to this kind of scene, that is, " two subclasses that are brothers contain the same expression ". This form can extract the same code into the common parent class, and target the differentiated parts. , Use abstract methods to defer to subclass implementation, this is a common template method design pattern. If there are duplicate codes in two unrelated classes, you should consider distilling the duplicate codes into a new class at this time, and then call the methods of this new class in these two classes.
function is too long
A good function must satisfy the single responsibility principle, be short and concise, and only do one thing. The long function body and the method of multiple roles are not conducive to reading, and it is not conducive to code reuse.
naming convention
A good naming needs to be able to "be worthy of the name, see the name and know the meaning", be straightforward, and there is no ambiguity.
Unreasonable comment
Comments are a double-edged sword. Good comments can give us good guidance, but bad comments will only mislead people. Regarding comments, we need to modify the comments when integrating the code, otherwise there will be inconsistencies in the comments and logic. In addition, if the code has clearly expressed its intentions, then the comments are redundant.
useless code
There are two ways to useless code. One is that there is no usage scenario. If this type of code is not a tool method or tool class, but some useless business code, then it needs to be deleted and cleaned up in time. The other is a code block wrapped with comment characters. These codes should be deleted when they are marked with comment characters.
class
One class does too many things, maintains too many functions, the readability becomes poor, and the performance decreases. For example, you put order-related functions in a class A, product inventory-related functions are also put in class A, and points-related functions are also put in class A... Imagine that the messy code blocks are all in one class. Stop talking about readability. Should be based on a single responsibility, using different classes to separate the code.
These are relatively common code "bad smells", of course there will be other "bad smells" in actual development, such as messy code, unclear logic, and intricate class relationships. When you smell these different "bad smells" , We should try to solve it, rather than indulge in disregard.
large refactoring
Compared with small-scale reconstructions, large-scale reconstructions need to consider more things. It is necessary to set a good rhythm and implement step by step, because in large-scale reconstructions, the situation is changeable.
The steps of putting the elephant in the refrigerator can generally be divided into three steps: 1) open the refrigerator door (before the event); 2) push the elephant in (during the event); 3) close the refrigerator door (after the event). All daily things can be solved in a three-step method, and refactoring is no exception.
beforehand
Pre-preparation is the first step of refactoring. The things involved in this part are more complicated and the most important. If the previous preparation is not sufficient, it is likely that the results produced during the implementation or after the refactoring is online may be inconsistent with expectations. phenomenon.
At this stage, it can be roughly divided into three steps:
- Clarify the content, purpose, direction, and goal of the
In this step, the most important thing is to clarify the direction, and this direction can withstand everyone's doubts and can at least meet the direction of the next three to five years. The other is the goal of this reconstruction. Due to technical limitations, historical baggage and other reasons, this goal may not be the final goal. Then it is necessary to clarify what the final goal is, from this goal of this reconstruction to the final goal. It is best to be clear about what to do.
- organize data
In this step, it is necessary to sort out the existing business and architecture involved in the reconstruction part, and clarify which service level of the system, which business module belongs to the content of the reconstruction, what are the relying party and the relying party, what business scenarios are there, and each What is the data input and output of the scene? There will be outputs at this stage. Generally, project deployment, business architecture, technical architecture, service upstream and downstream dependencies, strong and weak dependencies, project internal service layered models, content function dependency models, input and output data streams, etc. will generally be precipitated. Design drawings and documents.
- project
Project establishment is generally carried out through meetings. All the departments or groups involved in the restructuring are preached about the restructuring work. The approximate time schedule (rough approximate time) is known, and the main responsible persons of each group are clarified. In addition, you also need to know the business and scenarios involved in the refactoring, the approximate refactoring method, the possible business impacts, the difficulties and the possible bottlenecks in which steps.
incident
The tasks and tasks involved in performing this step during the event are relatively onerous, and the time spent is relatively large.
- Architecture Design and Review
Architecture design review is mainly to design and review standard business architecture, technical architecture, and data architecture. The architecture and business problems are found through the review. This review is usually an internal review. If after a review, it is found that the architecture design cannot be determined, then adjustments are needed until the team agrees on the architecture design. Before proceeding to the next step, the review results need to be emailed to the participants after the review is passed.
Outputs of this stage: refactored service deployment, system architecture, business architecture, standard data flow, service layering model, functional module UML diagrams, etc.
- detailed landing design plan and review
This landing design plan is the most important plan for implementation in the event, which is related to the subsequent R&D coding, self-test and joint debugging, relying party docking, QA testing, offline release and implementation plan, online release and implementation plan, and specific Workload, difficulty, work bottleneck, etc. This detailed landing plan needs to go deep into the entire R&D, offline testing, online process, and grayscale scene details including AB grayscale procedures and AB verification procedures.
The most important part of the program design is the AB verification program and the AB verification switch, which are the standard basis for evaluating and verifying whether we have completed the reconstruction. The general AB verification procedure is roughly as follows:
At the data entry point, the same data is used to initiate processing requests to both the new and old processes. After the processing is over, the processing results are printed to the log respectively. Finally, the offline program is used to compare whether the results of the new and old processes are consistent. The principle to follow is that under the same input parameters, the response results should also be consistent.
In the AB program, two switches are involved. Gray switch (Only when it is turned on, the request will be sent to the new process for code execution). executes the switch (if a write operation is involved in the new process, here you need to use a switch to control whether to write in the new process or write in the old process). Before forwarding, it is necessary to write the gray switch and the execution switch (generally configured to the configuration center and can be adjusted at any time) into the thread context, so as to avoid inconsistent results obtained in multiple places when the configuration center switch is modified.
- code writing, testing, offline implementation
This step is to perform coding, single testing, joint debugging, functional testing, business testing, and QA testing in accordance with the detailed design plan. After passing, simulate the online process and online switch implementation process offline, verify the AB program, check whether it meets expectations, and whether the code coverage of the new process meets the online requirements. If the offline data samples are relatively small and cannot cover all the scenes, it is necessary to cover all the scenes by constructing the traffic to ensure that all the scenes can meet expectations. When the offline coverage reaches the expected level and the AB verification program does not verify any abnormalities, the online operation can be performed.
afterwards
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, heavy volume, repair, offline old logic, and reopening. One of the most important and energy consuming is the heavy-duty process.
- Gray switch process
Gradually increase the volume into the new process for observation. You can increase the volume according to the progress of 1%, 5%, 10%, 20%, 40%, 80%, 100%, and let the new process gradually cover the code logic. Pay attention to this stage Will not open the switch to actually perform the write operation. When the logic coverage of the new process meets the requirements and the results of the AB verification are in line with expectations, the write operation switch can be gradually turned on to perform real business execution operations.
- Business execution switching process
After the grayscale new process meets expectations, the business can be gradually opened to execute the write operation switch process, and the volume can still be gradually increased according to a certain proportion. After the write operation is turned on, only the new logic will perform the write operation, and the old logic will close the write operation. . At this stage, you need to observe online errors, indicator abnormalities, user feedback and other issues to ensure that there are no problems with the new process.
After the heavy workload is over, after a certain version is stabilized, the old logic and AB verification program can be taken offline, and the reconstruction work is over. If possible, you can hold a reconstruction review meeting to check whether each participant meets the required reconstruction standards, the problems encountered during the reconstruction reconstruction, and what the solutions are, and the methodology is precipitated to avoid subsequent follow-ups. Similar problems occurred at work.
5. Summary
code skills
- Follow some basic principles when writing code, such as a single principle, relying on interfaces/abstractions instead of specific implementations.
- Strictly follow the coding standards, and use TODO, FIXME, and XXX for special notes.
- 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 readers of the code. When writing code, you should always review it, do the things that the predecessors planted trees and the descendants enjoy the shade, and don’t do the things that the predecessors dig pits and bury them.
- Don’t be the first person to not be the window-breaking effect. Don’t think that the code is bad now, and there is no need to change it. Just continue to stack the code. If this is the case, one day I will be disgusted by other people's code, "You will have to pay it back sooner or later when you come out.
Refactoring techniques
- From top to bottom, from the outside to the inside, modeling and analysis, clarifying various relationships, is the top priority of reconstruction.
- Refine classes, reuse functions, sink core capabilities, and make module responsibilities clear.
- Relying on the interface is better than relying on abstraction, and relying on abstraction is better than relying on implementation. Class relationships can be combined without inheritance.
- When designing classes, interfaces, and abstract interfaces, consider the scope qualifiers, which can be overridden and which cannot be overridden, and whether the generic qualification is accurate.
- Large-scale reconstructions do a variety of designs and plans, and simulate various scenarios offline. The AB verification program must be required to go online, and the new and old can be switched at any time.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。