Refactoring: Improving the design of existing code (version 2)

What is a refactoring

Refactoring is the internal structure of a kind of adjustment to the software, the purpose is not to change the software under the premise of observable behavior, improve the understandability, reduce its can modify the cost of reconstruction is to optimize the code structure, to make it better readability, extensibility and stronger an advanced technology program is written for people first, followed by written for the machineCopy the code

The first example of refactoring in Chapter 1

1. Refactor the function

  • Repetitive code: Don’t have a lot of repetitive code in your programming. The solution is to refine it into a single function
  • Inline temporary variables: If a variable is referenced only once, refactor it once
  • Eliminate temporary variables as much as possible: Many temporary variables can be difficult to maintain, so eliminate them as much as possible
  • Introduce explanatory variables: As opposed to the above, if using functions becomes too complicated, consider using explanatory variables
  • Remove assignment to arguments: use temporary variables to receive copy results. In addition, it is best to assign temporary variables declared in a function only once. If more than once, consider declaring variables again to decompose it

2. The refactoring classes

  • Moving method: Each method should be placed in its best place, not in a random place, so many times you need to consider whether a method is the best fit here.
  • Move fields: For each field, variables should be placed in its own class, not arbitrarily. Fields that do not belong to this class also need to be moved.
  • Extract a new class: Extract fields and methods that do not belong to this class into a new class
  • Content movement: Sometimes each subclass has a field or method declared, but there is no field or method in the parent class. Consider moving the field or method into the parent class, removing the field or method in the subclass
  • Refining interfaces: Interfaces, also known as protocols, are now being promoted as interface oriented programming

3. Reorganize data

  • Self-encapsulating fields: The advantage of encapsulating a field is that if a subclass overwrites the getter function for the field, it can change the result of the field’s fetch inside, which may make it more extensible
  • Object instead of value: As development progresses, sometimes a data item representation is no longer simple, such as the first need to know a person’s name, but later the need to know not only the person’s name but also the person’s phone number, address, etc. This is where you need to consider turning the data into an object
  • Constants instead of numbers: Sometimes it’s not a good idea to use a fixed number. It’s better to create a constant and replace it with an interesting name

4. Simplify conditional expressions

  • Breaking conditional expressions: Sometimes an if else statement looks complicated, so we try to break it up
  • Merge condition expressions: Sometimes we write multiple if statements that can be merged together
  • Merge duplicate conditional fragments: It is sometimes possible to write duplicate statements in an if else statement and to extract duplicate statements
  • Replace nested expressions with a guard statement
  • Replace switch statements with polymorphisms

Chapter 2 Principles of refactoring

  1. Definition of refactoring

    • (noun form) An adjustment to the internal structure of software to improve understandability and reduce the cost of modification without changing the observable behavior of the software.
    • (verb form) uses a series of refactoring techniques to adjust the structure of software without changing its observable behavior.
  2. Two hats for software development

    • When you add new features, you shouldn’t modify existing code, just add new features and pass tests. (This is too hard.)
    • Refactoring does not add new functionality, but improves the structure and passes existing tests.
  3. Why does refactoring

    • Refactoring improves software Design by eliminating duplicate code, so I can make sure that everything and behavior is expressed only once in the code.
    • Refactoring makes the software easier to understand and Maintain for future successors
    • Refactoring helps you find bugs. Most bugs can be solved with a brief walk through your computer
    • Refactoring improves programming Efficiency by adding new features with less thought and clarity
  4. When refactoring

    • Three things, three reconstruction
    • Pre-refactoring: It is easier to add new functionality, making multiple changes to the code one change
    • Understandable refactoring: Make code easier to understand and make it self-explanatory
    • Refactoring: Review code that feels bad and change it if you have time
    • Planned refactoring: There are usually major problems
    • Long-term refactoring: first put the refactoring in place, if someone encounters the refactoring in place to change, because after minor changes the system function remains the same
  5. When not to refactor

    • The existing code is too cluttered and does not work properly and needs to be rewritten rather than refactored.
    • Don’t refactor if you don’t need to change the code.
    • Refactoring should be avoided as projects approach deadlines.
  6. Objectives of refactoring

Why is the program so hard to relate to? Design and refactoring goals
Hard to read, hard to modify Easy to read
Logic repetition of the program, difficult to modify All logic is specified in a single place
Programs that require modification of existing code when adding new behavior are difficult to modify The new changes do not compromise existing behavior
A program with complex conditional logic is difficult to modify Express conditional logic as simply as possible
  1. The code should have a complete test suite and be fast.
  2. Write tunable software first, then tune it to get enough speed.

Chapter 3 code Smells Bad

I think this chapter is important because identifying bad code smells is a prerequisite to starting refactoring properly.

  1. Mysterious named
  • Naming this thing just began to learn programming when it is a problem, ABCD, XXX1234, pinyin what demons and ghosts have, look at other people’s code to see these things really will head big. I started with A1, A2, whatever, and after a few days I didn’t know what I was writing. A good name makes it clear what it does and how it’s used.
  1. Duplicate code
  • If you want to change duplicate code, you have to find all the related copy changes, which is tiring to think about, and you need to try to refine them into functions.
  1. Too long to function
  • The longer the function, the harder it is to understand. Whenever you feel that something in a method needs to be commented out, you can put that part of the code into a separate method and name the method after its purpose, not its implementation.
  • Conditional expressions and loops are also often signals for refinement.
  1. Too long parameter list
  • It is definitely not desirable to select global data without parameters.
  • Several ways to improve:
    • If one parameter can be queried for the value of another parameter, it is used to replace the parameter with a query.
    • If you are extracting many data items from an existing data structure, keep the object intact.
    • If several arguments are always present at the same time, an import argument object is used.
    • If a parameter is used as a marker to distinguish the behavior of a function, you can use the remove marker parameter.
  1. Global data
  • The problem with global data is that it can be modified from any corner of the code base.
  • Wrap a global quantity in a function and control access to it, preferably in a class or module that controls its scope.
  1. Variable data
  • Update data in one place without realizing that another part of the software expects something completely different, and a feature fails.
  • Functional programming – Based on the concept of “data never changes” : If you change a data structure, a new copy of the data is returned, leaving the old data unchanged.
  1. Divergent variation
  • Modify the same module in different directions for different reasons.
  1. Shotgun modification
  • For every change you encounter, you need to make many small changes in multiple classes that are easy to miss. You should put the parts that need to be changed in one place.
  1. Attachment bond
  • Functions communicate more with functions or data in another module than they do within the module itself. It is best to move this function into that module.
  1. Data mud pie
  • You see the same three or four items of data in many places, and if you delete one of them, the rest of the data doesn’t make sense, you should create a new object for them.
  1. Basic type paranoia
  • Create basic types that are useful to your problem domain, rather than simply replacing them with strings and so on.
  1. Repeat the switch
  • Whenever you want to add a selection branch, you must find all the switches and update them one by one. You can solve this by using polymorphism.
  1. Looping statements
  • Using pipes instead of loops helps us see the elements being processed and the action of processing them more quickly.
  1. Redundant elements
  • If a class doesn’t deserve to exist, it should disappear.
  1. Talk about universality
  • If the only users of functions and classes are test cases, delete the tests first and then remove the dead code.
  1. Temporary field
  • A field in a class is set only for special cases.
  1. Too long message chain
  • One object requests another, and then another… The code is tightly coupled to the navigation structure of the lookup process, and any changes in the relationship between the objects force the code to change.
  1. A middleman
  • If half the functions of a class’s interface are delegated to other classes, you should remove the middleman.
  1. Insider trading
  • Data exchange between modules is hard to avoid completely and should be kept out in the open.
  1. Too much class
  • Classes should be designed to follow the single responsibility principle.
  1. Classes of the same kind
  • Class substitution is consistent with the interface.
  1. Pure data classes
  • Move data processing to a pure data class, unless used as a const return value.
  1. A rejected bequest
  • A subclass inherits all functions and data from its parent class, and only selects a few to use. Create a new sibling for the subclass, and push down the unwanted function using a pushdown method and pushdown field.
  • A subclass only reuses the behavior of the parent class, but does not want to support the parent class’s interface. Use delegation instead of inheritance to do this.
  1. annotation
  • Comments are not meant to fix bad code. In fact, if we remove all the bad smells from the code, by the time the bad code is removed, comments are redundant because the code says everything.

The fourth chapter constructs the test system

  • Refactoring correctly requires a solid set of tests to help me find the omissions that are hard to avoid.
  • Write good test procedures, can greatly improve the speed of programming.
  • We start out with some code and we like to put the results on the screen and check them one by one, and we could have let the computer do that, so all we have to do is put the desired output into the test code and do a comparison.
  • Writing test code is all about yourself: What do I need to implement in order to add functionality? It also helps me focus on the interface rather than the implementation.
  • Test-driven development – Write a test that fails, write code to make the test pass, and then refactor to keep the code clean

Refactor the list

Java development, because IDE (Intellij Idea) can well support most cases of reconstruction, there are all kinds of automatic prompts, so feel temporarily do not need to use the reconstruction list.

Reorganize functions

Put a piece of code into a separate function and let the function name explain the function’s purpose. Increased readability, small function granularity is easier to reuse and overwrite.

2. Inline Method Inserts the function body at the function call point and then removes the function. The function’s body and name are equally clear and understandable, but there are too many layers of indirection to understand.

3. Inline Temp replaces all references to a variable with the expression that assigns a value to it.

Extract an expression into a separate function and Replace the reference point of the temporary variable with a call to the function. By extending temporary variables to query functions, you can extend the scope of use to the entire class. Reduce temporary variables to make functions shorter and more maintainable.

Put the results of this complex expression into a temporary Variable whose name explains its purpose.

Create a separate, corresponding Temporary Variable for each assignment. Temporary variables can be assigned multiple times, which can lead to ambiguity. If a variable is assigned multiple times (except for “loop variables” and “result collection variables”), it assumes multiple responsibilities and should be decomposed.

7. Remove Assignments to Parameters with a temporary variable in its place. Assigning parameters tends to reduce code clarity; It is easy to confuse passing by value with passing by reference;

If a large function contains a lot of temporary variables, Extract Method can be difficult to disassemble. Instead, place the function in a newly created class and change the temporary variables into the entity variables of the class. Extract Method to disassemble.

A complex Algorithm will increase the maintenance cost. Replacing it with a simpler Algorithm can obviously improve the readability and maintainability of code.

Move between objects

Deciding where to place responsibility is one of the most important things to do in object-oriented design. The most common worry is that you can’t do things right the first time. In this case, you can use refactoring to change your original design.

1. Move Method to Move the behavior of function classes to a single responsibility, do not overreach the longterm. If a class has too much behavior, or if a class has too much cooperation with another class to be highly coupled, you need to move functions. Observe that the end that calls it, the end that it calls, has inherited any of its redefined functions in the system. According to “this function does not exchange more objects”, determine its move path.

If a Field from one class is used more frequently in another class, consider moving it.

例 句 : A Class should be a clear abstraction, handling some distinct responsibilities.

An Inline Class is the opposite of an Extract Class. If a class no longer assumes sufficient responsibilities and has no reason to exist independently. Move all the features of this class to another class, then remove the original class.

Setting up all the functions required by the client on the service class to Hide the Delegate also comes at a cost: Each time a client wants to use a new feature of the delegated class, a delegate function must be added to the server. As the delegate class becomes more and more featurely-capable, the service class becomes a complete “middle man,” and the client should call the delegate class directly. It’s hard to say what level of hiding is appropriate, but as the system changes, tweak it using Hide Delegate and Remove Middle Man.

例 句 : You need to add a function to the class that provides the service, but you cannot modify the class. Create a function in the client class and pass in an instance of the service class as the first argument.

例 句 : You need to provide some extra functions for the service class, but you cannot modify the class. Create a new class that contains these additional functions. Make this extension a wrapper class that subclasses the source class.