Refactoring Ruby

Refactoring, a First Example

Make the code clean first and then use a profiler to deal with performance issues.

The rhythm of refactoring: test, small change, test, small change, test, small change. It is that rhythm that allows refactoring to move quickly and safely.

  1. Write tests
    • Refactoring changes the programs in a small steps. Then run test.
  2. Refactor
    • Extract Methods
      • Nhìn vào local variables trong scope của method:
        • Biến không thay đổi value: Pass vào method mới as args
        • Đối với biến có thay đổi value: Cần chú ý hơn khi modify, và trả về trong kết quả của hàm.
    • Move Methods
      • In most cases a method should be on the object whose data it uses
    • Replace Temp with Query
      • Chú ý tới các temporary variables, thay thế nó bằng Query (private function) để tính logic riêng.
    • Replace Conditional logic with Polymorphism
      • Check đoạn code có case - when, cân nhắc refactor sử dụng Đa hình.
        • Bad idea: Case when mà lại dựa trên attribute của object khác. Nếu bắt buộc phải sử dụng case - when, nó phải là trên attributes của chính class đó.
      • Inheritance
        • Nếu sử dụng Kế thừa, ta có thể tận dụng tính Đa hình. Nhược điểm của cách này là object không thay đổi được class name khi run time.
      • Replace Type Code with State/Strategy
        • We can remove the case statement with the state pattern (Gang of Four).
          • Ví dụ: Thay vì tạo subclasses cho Movie class, ta sẽ tạo PriceStrategy object cho 3 loại: NewReleasePrice, StillHotPrice, RegularPrice, sau đó khởi tạo video với 3 chiến lược tính tiền này.
      • Extract Module

Principles in Refactoring

Define Refactoring

Refactoring (noun): A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.

Refactor (verb): To restructure software by applying a series of refactoring without changing its observable behavior.

  • Refactoring

    • Làm code trở nên dễ hiểu
    • Tiết kiệm cost khi phải thay đổi
    • Không thay đổi behavior của observable.
  • The Two Hats

    • Adding function:
      • Không thay đổi code cũ, chỉ add thêm function mới + add tests
    • Refactor
      • Restruct the code, không add thêm tests mới.

Trong thực tế, mình thường xuyên phải đổi qua lại giữa 2 cái mũ này. Add new functions Thấy không ổn Refactor OK Add new functions Refactor.

Why?

  • Refactoring Improves the Design of Software
  • Refactoring Makes Software Easier to Understand
  • Refactoring Helps You Find Bugs
  • Refactoring Helps You Program Faster

When?

  • The Rule of Three: 1st do something, just do it. 2nd you do something similar, we duplicate thing anyway. 3rd time you do something similar, you refactor.
  • When You Add Function
  • Refactor when you need to fix a bug
  • Refactor as you do a code review
  • Refactor for Greater Understanding

Problems with Refactoring

  • Changing Interfaces
  • Databases

Refactoring and Performance

Bad Smells in Code

  • Duplicated Code: Same code structure in more than one place

    • Same expression trong 2 methods của cùng 1 class Use Extract Method
    • Same expression trong 2 class cùng cha Pull Up Method
    • Code is similar but not the same From Template Method để tách phần chung ra.
    • Hai methods làm cùng 1 việc nhưng với giải thuật khác nhau Substitute Algorithm
    • Nếu phần duplicate nằm ở giữa một method Extract Surrounding Method
    • Duplicate code trong 2 class không liên quan tới nhau Extract Class hoặc Extract Module
  • Long Method

    • Một method dài, nhiều parameters, nhiều temporary variables, nhiều Conditional & loops Cần refactor
      • Extract Method
      • Replace Tempt with Query
      • Replace Temp with Chain
    • Nếu tách method rồi mà vẫn thấy còn nhiều temps + parameters, thử Replace Method with Method Object
    • Conditional Decompose Conditional
    • Loops Collection Closure Methods
  • Large Class

    • Dấu hiệu: Có nhiều instance variables.
    • Bundle a number of the variable bằng Extract Class/ Extract SubClass/ Extract Module
      • Những variable dạng deposit_ammount, deposit_currency Thuộc về other Component là deposit (dấu hiệu là có chung prefix/ suffix)
  • Long Parameter List

    • Sử dụng Replace Parameter with Method khi mình có thể sử dụng object (inject object)
    • Preserve Whole Object
    • Nếu data items with no logical object Introduce Parameter Object / Introduce Named Parameter
  • Divergent Change

    • Những đoạn code mà sẽ bắt buộc phải thay đổi nếu có thêm 1 loại/ kind nào đó.
    • Sử dụng Extract Class Cô lập nó
  • Shotgun surgery

    • Khi có thay đổi, ta phải thay đổi nhiều đoạn nhỏ, ở nhiều class khác nhau dễ miss
    • Move Method + Move Field để đặt tất cả thay đổi vào 1 class. Có thể sử dụng Inline Class
  • Feature Envy

    • Hàm đặt không đúng chỗ Extract Method / Move Method
    • “The fundamental rule of thumb is to put things together that change together. Data and the behavior that references that data usually change together, but there are exceptions. When the exceptions occur, we move the behavior to keep changes in one place.”
  • Data Clumps

    • Một vài data (ví dụ như arguments), được sử dụng/ truyền đi truyền lại giữa các methods, hoặc nó xuất hiện rất nhiều lần
    • Extract Class + Introduce Parameter Object + Preserve Whole Object
    • Introduce Parameter Object là phương pháp mà sẽ wrap các params này lại thành 1 object, sau đó inject object này vào hàm
  • Case Statements

    • Sử dụng Extract Method để tách class Sử dụng Move Method để đưa tới đúng nơi case statement này có thể convert về dạng đa hình Sử dụng Replace Type Code with Polymorphism, Replace Type Code with Module Extension, or Replace Type Code with State/Strategy
    • Nếu không expect phải sửa hết thì có thể cân nhắc Replace Parameter with Explicit Methods
    • Khi conditional case chứa null, có thể dùng Null Object
  • Parallel Inheritance Hierarchies

    • Khi add thêm subclass cho 1 class nào đó, bạn sẽ phải tạo thêm subclass cho 1 class khác. (Có thể nhận biết bằng Prefix của class này giống nhau)
    • Move Method + Move Field
  • Primitive Obsession

  • Lazy Class

    • Mỗi khi tạo thêm class mới, ta đều phải tốn costs để maintain + understand. Nếu class nào không mang lại giá trị đủ trả cho chỗ costs trên, thì tìm cách refactor lại. Sử dụng:
    • Collapse Hierarchy/ Inline Class or Inline Module
  • Speculative Generality

  • Temporary Field

    • Extract Class/ Introduce Null Object/ Method Object
  • Message Chains

    • Message chains: when client asks one object for another object …
    • Hide Delegate / Extract Method / Move Method
  • Middle Man

    • Remove Middle Man/ Inline Method/ Replace Delegation with Hierarchy
  • Inappropiate Intimacy

  • Alternative Classes with Different Interfaces

  • Incomplete Library Class

  • Data Class

  • Refused Bequest

  • Comments

  • Metaprogramming Madness

  • Disjointed API

  • Repetitive Boilerplate

Each refactoring has five parts, as follows:

• I begin with a name. The name is important to building a vocabulary of refactorings. This is the name I use elsewhere in the book.

• I follow the name with a short summary of the situation in which you need the refactoring and a summary of what the refactoring does. This helps you find a refactoring more quickly.

• The motivation describes why the refactoring should be done and describes circumstances in which it shouldn’t be done.

• The mechanics are a concise, step-by-step description of how to carry out the refactoring.

• The examples show a simple use of the refactoring to illustrate how it works.

Composing Methods

  • Extract Methods
  • Replace Temp with Query: get rid of any temporary variables
  • Split Temporary Variable: make the temp easier to replace
  • Replace Method with Method Object
  • Remove Assignments to Parameters: Handle Parameters
  • Introduce Named Parameter/ Remove Named Parameter/ Unused Default Parameter.

Extract Method ⭐⭐⭐

Turn the fragment into a method whose name explains the purpose of the method.

  • Motivation

    • Method that is too long
    • Code that need to comment to understand its purpose
    • Should into its own method.
    • Không nên quá quan tâm là 1 method dài bao nhiêu thì có thể tách được. Nếu việc tách làm code rõ ràng hơn, hãy tách nó.
  • Mechanics

    1. Create new method, give it a name
    2. Copy extracted code from source method into the new target method
    3. Scan extracted code for references to any variables that local in scope to the source method. local variables + parameters
    4. Check temporary variables: Những biến được khai báo và dùng tạm trong phần code vừa được tách thì sẽ trở thành temporary variables trong code mới.
    5. Nếu code thay đổi nhiều biến temporary variables, ta phải mà phải sử dụng Split Temporary Variable để tách chúng ra trước (hoặc sử dụng Replace Temp with Query, sau đó mới tiến hành Extract Method.
    6. Trong function chính sẽ call lại function mình vừa extract.
    7. Xóa các temporary variables
    8. Test
  • Example: Extract Method

Replace Temp with Query ⭐⭐⭐

Extract the expression into a method. Replace all references to the temp with the expression. The new method can then be used in other methods.

  • Motivation
    • Biến này là temporary và local Chỉ sử dụng trong context của method hiện tại, nếu giữ ở đó thì sẽ làm method trông dài hơn Tách nó ra.
    • Replace Temp with Query thường được dùng kèm với Extract Method. Local variable khiến việc tách method trở nên khó hơn Tách được bao nhiêu local variables thì cứ tách =))
    • Đối với các biến temp phức tạp, ta cần Split Temporary Variable or Separate Query from Modifier trước.
  • Mechanics
    • Extract Test Inline Temp

Replace Method with Method Object ⭐

Thường dùng trong trường hợp long method sử dụng local variables theo cách mà không thể sử dụng Extract Method

Turn the method into its own object so that all the local variables become instance variables on that object. You can then decompose the method into other methods on the same object.

  • Motivation
    • Sử dụng Replace Temp with Query sẽ giúp giảm đi nhiều độ phức tạp cho method. Tuy vậy, trong 1 số case, việc break down a method that needs breaking. Method Object
    • Đưa hết local variable sang 1 method object, sau đó sử dụng Extract Method trong object mới đó.
  • Mechanics
    • Create a new class, name it after the method
    • Give a new class an attribute for the object that hosted the original method, and an attribute for each temporary variable
    • Give the new class a constructor that takes the source object and each parameter.
    • Give the new class a method named “compute”
    • Copy the body of the original method into compute. Use the source object instance variable for any invocations of methods on the original object.
    • Test.
    • Replace the old method with one that creates the new object and calls compute.

Extract Surrounding Method ⭐⭐

Extract the duplication into a method that accepts a block and yields back to the caller to execute the unique code.

Nếu có 2 methods mà chứa code gần giống nhau Tách ra thành method mới, có thể sử dụng yield để pass phần khác biệt vào

  • Motivation

    • Main code nằm ở giữa long method. Unique code nằm ở giữa method. Đoạn đầu và cuối dễ lặp lại.
    • Có thể sử dụng Form Template Method
    • Cách này sử dụng tới Ruby’s block, tách ra thành surrounding.
  • Mechanics

    • Sử dụng Extract Method tách 1 phần nhỏ bị duplicate. Named nó.
    • Test
    • Modify calling method to pass a block to to surrounding method
    • Replace the unique logic in the extracted method with yield keyword
    • Identify any variables in the surrounding method that are needed by the unique logic and pass them as parameters in the call to yield.
    • Test
    • Modify any other methods that can use the new surrounding method.
  • Example: https://github.com/manelromero/refactoring/blob/master/composing_methods/extract_surrounding_method.md

Inline Method

Put the method’s body into the body of its callers and remove the method.

Đưa method body vào trong body của method gọi nó.

  • Motivation
    • Đôi khi có các method mà body của nó đã clear như tên method rồi không cần tách riêng method cho nó nữa.
    • Nên làm điều này trước khi áp dụng Replace Method with Method Object
    • Thường sử dụng khi code cũ đang sử dụng quá nhiều indirection, mà mỗi method lại rất đơn giản/ chỉ là delegation.
  • Mechanic
    1. Check xem method đó có phải là polymorphic không? Không được inline nếu subclasses đang override method này.
    2. Tìm các chỗ đang call method này
    3. Replace each call with the method body
    4. Test
    5. Remove the method definition
  • Example: https://github.com/manelromero/refactoring/blob/master/composing_methods/inline_method.md

Inline Temp

Replace all references to that temp with the expression.

Tương tự inline method, inline temp sẽ thay các biến tạm bằng source code tại vị trí gọi nó luôn.

  • Motivation
    • Thường thì Inline Temp sẽ được dùng kèm với Replace Temp with Query
    • The only time Inline Temp is used on its own is when you find a temp that is assigned the value of a method call.
  • Mechanics
    • Tìm tất cả những chỗ đang sử dụng Replace Test Remove declaration Test.

Replace Temp with Chain

Change the methods to support chaining, thus removing the need for a temp.

Nếu 1 method mà support chaining, thì không cần dùng biến tạm để lưu thông tin lại.

  • Motivation
    • Make sense remove temp variables
  • Mechanic
    • Return self from methods that you want to allow chaining from Test Remove local variable Test

Introduce Explaining Variable

Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose.

Tạo thêm 1 biến temp mới, đặt code vào đó, kèm theo 1 cái tên có ý nghĩa cho biến. Ví dụ: if (platform.upcase.index("MAC") && browser.upcase.index("IE") && initialized? && resize > 0)

  • Motivation
    • Expression có thể trở nên phức tạp, khó để đọc. Sử dụng biến temp này để “introduce” thì sẽ dễ hơn cho việc đọc hiểu code.
    • Khi chia nhỏ, ta cũng dễ nhìn thấy điều kiện tổng hơn well-named Dễ hơn trong việc tách điều kiện thành method nếu cần
  • Mechanics
    • Tạo biến temp Replace Test Repeat for other parts of the expression.

Split Temporary Variable

Make a separate temporary variable for each assignment.

Tạo các biến temp khác nhau, với tên khác nhau, để lưu trữ các assignment khác nhau.

  • Motivation
    • Nếu dùng cùng 1 biến temp lưu giá trị, sau đó lại dùng biến này để lưu 1 giá trị assignment khác, sẽ gây confuse Không nên.
  • Mechanic
    • Change name Change references Test Repeat

Remove Assignments to Parameters

Use a temporary variable instead.

Không modify args trong hàm, tạo 1 temporary variable cho nó và modify.

  • Motivation
    • Tránh việc confuse giữa giá trị truyền vào và giá trị đã modify trong hàm
  • Mechanics
    • Create temporary variable Replace references Test

Substitue Algorithm

Replace the body of the method with the new algorithm.

Thay đổi thuật toán được sử dụng trong hàm =)) (Code xấu quá thì dùng cách ngon hơn :v)

  • Motivation
    • If you find a clearer way to do something, you should replace the complicated way with the clearer way.
    • Replace cái phức tạp thành cái đơn giản hơn.
  • Mechanics
    • Prepare your alternative algorithm Run new again your test Debug

Replace Loop with Collection Closure method

Replace the loop with a collection closure method.

  • Motivation
    • Trong Ruby, Enumerable module, bao gồm Array + Hash có rất nhiều các hàm collection closure, hãy sử dụng nó thay cho vòng lặp nếu có thể.
    • select, collect, find, map, inject, reduce, sum, ...
  • Mechanics
    • Xác định loop Replace loop với collection closure methods Test

Introduce Class Annotation

Note: This no need to do. New version Ruby support it!

Introduce Named Parameter

Convert the parameter list into a Hash, and use the keys of the Hash as names for the parameters.

  • Motivation:
    • Dễ đọc =))

Remove Named Parameter

Convert the named parameter Hash to a standard parameter list.

Đối với các class đã được đặt tên tốt, thì có thể k cần dùng tới hash paramenter

eg: IsbnSearch.new(:isbn => "0201485672") IsbnSearch.new("0201485672")

Remove Unused Default Parameter

Remove the default value

  • Motivation
    • Thêm default value vào để đảm bảo code có thể gọi mượt / không lỗi. Theo thời gian, hầu như không chỗ nào không truyền value vào Default value có thể k cần tới nữa. Nếu k cần nữa thì có thể remove
  • Mechanics
    • Remove default parameter Test Remove any code within the method that checks for the default test

Dynamic Method Definition

Define the methods dynamically.

  • Motivation
    • Một số trường hợp có thể sử dụng Dynamic Method Definition
  • Mechanics
    • Dynamically define one of the similar methods Test Convert the additional similar methods to use the dynamic definition Test

Replace Dynamic Receptor with Dynamic Method Definition

Use dynamic method definition to define the necessary methods.

  • Motivation
    • Implement method_missing để chặn việc stack level to deep
  • Mechanics
    • Dynamically define the necessary methods
    • Test
    • Remove method_missing
    • Test

Isolate Dynamic Receptor

Introduce a new class and move the method_missing logic to that class.

Move Eval from Runtime to Parse Time

Move the use of eval from within the method definition to defining the method itself.

Moving Features Between Objects

  • Để đưa method về đúng class của nó Move MethodMove Field
  • Class có nhiều responsibility Extract Class or Extract Module
  • Nếu class trở nên rời rạc Inline Class để merge nó với class khác.
  • Hide Delegate , Remove Middle Man

Move Method

Nếu 1 method mà được sử dụng ở nhiều features hoặc ở các class khác Move method

Create a new method with a similar body in the class it uses most. Either turn the old method into a simple delegation, or remove it altogether.

  • Motivation
    • Move Method làm cho class được gọn gàng hơn, đơn giản hơn
    • Nhìn vào các methods trong 1 class, tìm thử 1 method mà có vẻ đang reference tới 1 object khác hơn là object hiện tại.
  • Mechanics
    • Xem tất cả các features được sử dụng trong source method hiện tại. Cân nhắc xem chúng có thể chuyển đi được không? - Thường nó sẽ là method/source toàn gọi tới object khác.
    • Check the sub- and superclasses of the source class for other definitions of the method.
    • Define the method in the target class.
    • Copy the code from the source method to the target. Adjust the method to make it work in its new home.
    • Determine how to reference the correct target object from the source.
    • Turn the source method into a delegating method.
    • Test
    • Decide whether to remove the source method or retain it as a delegating method.
    • If you remove the source method, replace all the references with references to the target method.
    • Test

Move Field

Move field mà được sử dụng ở các class khác nhiều hơn là ở class/object hiện tại.

Create a new attribute reader (and if necessary, a writer) in the target class, and change all its users.

  • Motivation