What?
In refactoring, Martin Fowler defines it as "changing the internal structure of the software without changing the external behavior of the software to make it easier to understand and modify ".
Why?
- Myth: a well-managed software project should first develop demand in a systematic manner and define a rigorous list to describe the functions of the program. The design fully complies with the requirements and is done very carefully, so that programmers can write code from start to end in line. It indicates that most of the Code is perfect after being written for the first time, and the test can be left behind. The only time when the code is modified occurs after the new version is delivered for use.
- Reality: in the initial development stage, code will be substantially evolved. In the initial code writing process, there will be many drastic changes. Even a well-managed project changes about 1/4 of requirements every month, which inevitably leads to code changes-sometimes substantial code changes
Reason for reconstruction
How? Data-level reconstruction/data-level refactorings
Statement-level refactoring/Statement-level refactorings
- Break down a Boolean expression: introduce an accurate named intermediate variable to help you understand a Boolean expression.
- Converts a complex Boolean expression to a Boolean function that is clearly accurate: Improves the readability of complex expressions and facilitates reuse.
- Duplicate code snippets in different parts of the merge Condition Statement: if the same code exists in the IF/else block, move it from the block to the outside.
- Use break or return instead of loop control variables: Do not judge the end of a loop by using variables such as stop.
- Replace conditional statements with polymorphism (especially duplicate case statements): the logic in the case statement can be placed in the inheritance relationship and implemented by using polymorphism call functions.
- In the nested if-then-else statement, if you know the answer, return immediately: instead of setting a return value, after a series of judgments, return
- Create and use a null object instead of checking the null value: extract the function for processing the null value from the customer code and put it into the corresponding class.
Subroutine-level reconstruction/routine-level refactorings
- Extract subroutines or methods: Avoid duplication
- Concatenate the subprogram code: opposite to the previous one. If the subprogram itself is simple and self-explanatory, use the Code directly.
- Converts lengthy subprograms into classes to improve code readability
- Replacing complex algorithms with simple algorithms: writing code that only you can understand is not brilliant.
- Add/delete Parameters
- The query operation is independent from the modification operation: One method only does one thing.
- Merge similar subprograms and differentiate their functions through parameters: if the two subprograms use different constants, you may wish to pass constants as parameters.
- Split the behavior dependent on the subprogram of the parameter: Do not use the identification space in the parameter to control the subprogram Behavior
- Passing the entire object rather than a specific member: if multiple specific members of an object are found to be taken out as parameters of a method, why not directly use this object?
- Passing a specific member rather than the entire object: If you find that an object is created to pass in a method as a parameter, why not let the method directly obtain a specific member of the object as a parameter?
- Wrap down Transformation Operation: When a subroutine returns an object, it should return the most accurate known object, especially for iterator, Collections
Class implementation refactoring/class implementation refactorings
- Convert a value object to a reference object: If you find that you are maintaining an extremely large and complex object, you may want to reference it wherever you use it.
- Converts a referenced object to a value object. If a small simple object is referenced multiple times, you can directly use a value object.
- Use Data initialization instead of virtual functions: instead of overwriting member functions in multiple Derived classes, it is better for the derived classes to set appropriate constant values during initialization, and then use common code in the base class to process these values.
- Change the position of a member function or member variable: Move the subroutine/member/constructor to the base class/down to the derived class
- Extract special code as a derived class: if some code in a class is only useful to some of its instances, put this part of code into the derived class.
- Combine similar code into the base class: opposite to the previous one
Class interface reconstruction/class interface refactorings
- Move a member function to another class: Create a new member function in the target class, move the function body to the target class in the original class, and finally call the function of the target class in the original class.
- Change a class to two: If a class has two completely different functions at the same time
- Delete class: When he is idle
- Remove delegation relationships: prevents excessive calls
- Remove man-in-the-middle: Before a-> B-> C, after a-> C
- Replacing "is-a" with "has-a": you do not need to expose all member functions of the base class.
- Replace "is-a" with "has-a": All member functions of the base class must be exposed.
- Encapsulate exposed member variables: change data members to private, and provide accessors
- For members that cannot be modified, delete the Set () function.
- Hide member functions that are not used outside the class
- Merge the base classes and derived classes with similar implementations
System-level reconstruction/system-level refactorings
- Create a clear index source for uncontrollable data: organize data into one
- Change one-way class contact to two-way class contact: two classes need to use each other's functions
- Change two-way class contact to one-way class contact: in fact, only one class needs to access another class.
- Use the fatory mode instead of a simple constructor: When you need to create an object based on the type code, or want to use a reference object instead of a value object, you need to use the factory mode.
- Use the exception mechanism to replace the error code, or replace it with the opposite: Starting from the actual needs, depending on the error handling policy
Rebuilding with Security drops
The knife is sharp.
No one who cut the head of such a knife will be slow.
Whether it's others' headers or their own headers.
- Save initial code: with mature VCs
- The pace of reconstruction should be smaller: small steps
- Only one rebuild at a time: Same as the previous one
- List all the tasks to be done one by one: maintaining a refactoring list will make you know what has been done and what needs to be done urgently
- Use multiple checkpoints: when you accidentally mess up a task, you can quickly restore it to the previous working version.
- Retest: keep a set of excellent test cases
- Add/delete/modify test cases in real time during refactoring
- Code Review
- Adjust the reconstruction method based on the risk level of reconstruction: for example, "refactoring those magic numbers" obviously does not require too much test effort, and does not require code review; when classes, interfaces, and database architecture changes are involved, you must be careful.
Reconstruction strategy
- Refactor when adding a subroutine
- Refactoring when adding classes
- Refactoring when fixing Defects
- Focus on error-prone modules
- Focus on complex modules
- Clearly define the boundary between clean code and poor code, and then let the code cross this boundary