iT邦幫忙

2023 iThome 鐵人賽

DAY 3
0

(English follows Chinese)

前言

根據「Refactoring to Patterns」一書的作者Joshua Kerievsky所提供的附件表格「Smells to Refactorings Quick Reference Guide」(以下簡稱對照表),一共列出了共27種氣味對應重構技巧的關係。當時看到這張表格的時候,很快聯想到「哇!這也太適合當作鐵人賽主題了吧?一天一種氣味還不穩一波輸出!」計算開賽宣言、結語、氣味的五種分類加上27種氣味,一共34篇文章主題跑不掉!只會嫌棄三十天不夠,完全不需要擔心題材不足的問題。

某種意義上,本篇才是真正系列開始的第一篇,討論氣味與重構之間的對應關係,然後我立即遇上一個麻煩。

正如同我們昨日所介紹的Bloaters所揭示,文章與類別相似,一樣也有合適的長度,不宜過長否則將不利於閱讀理解。我很快地發現首個氣味Long Method對應的重構方法多得要命,如果要全部打包進同一篇文章,顯然會超標超量,同時也對我每日固定輸出的一種負擔。

但若是將氣味與重構技巧拆開為兩個部分,也就是每種重構技巧單獨存在一篇,而介紹氣味的文章只提供索引連結的呈現方式,那將會是超級長篇的展開,同時每種技巧單獨存在的份量可能又太過輕巧。另一個問題是,不同的氣味可能會對應到相同的重構技巧,如果每篇介紹氣味的文章內都包含完整重構指引加上範例,那麼某些重構方法將會不斷的重複再重複。而重複一樣是工程師所厭惡的邪惡行為,違反「DRY原則」(Don't repeat yourself)。

我必須坦誠以告,行文至此時,我內心中依舊沒有完美答案,究竟哪一種編排方式才是最佳最合適的介紹方式?這樣的問題網路上也不太可能找到解答,只能說這是一段作者與讀者共同的探索。或許當我完賽後,手上已經有了完整的素材,就能夠重新編排彙整成為更合乎邏輯與閱讀體驗的順序。但至少參賽的第三日,當大部分的文章依然未知只存在標題的此刻,我還無法知道答案。

感謝讀者與我一同經歷這段未知的旅程。

(備註:本篇前言為鐵人賽中文版專屬,並未見於英文版本。This foreword only has Chinese version.)

氣味的徵兆

過長的方法(Long Method)常見的特徵是在一個方法內同時想要完成太多任務。更具體的說,當一個方法的程式碼超過十行時,就很可能太長了。

每一個方法在首次簽入git版本控制時,看上去通常相當袖珍可愛,就像是哺乳類動物的幼仔通常會特別討人憐愛一樣,目的是吸引照顧者的餵食與保護。但隨著時間過去,這些嬌小可愛的貓咪有一日也可能成為駭人的巨獸,反噬這些粗心大意的開發者。每當新需求到來時,開發者總是傾向相信只要新增一兩行程式碼,加上一點額外邏輯來滿足需求與行為的改變,就可以讓這些方法能夠做到更多不同的商業邏輯。這在每次版本發布前的程式改動回顧會議時看上去都人畜無害,畢竟只是一兩行為了新需求的改動,誰能夠拒絕呢?但是隨著時間迭代,每次一行兩行的新增程式碼逐漸會讓方法成為失控且搖搖欲墜的疊疊樂,在不知不覺間讓方法擔負了過重也不洽當的職責。

氣味的原因

氣味之所以是氣味,必然有其足夠解釋的原因:

  • 難以閱讀與理解: 二十行程式碼比起十行所需閱讀理解的時間更長,而五行甚至三行又比起十行來說更好懂。簡單來說,長方法更容易產生Bug與消耗不必要的閱讀時間,容易讓開發者產生誤解。
  • 違反單一職責原則 Single Responsibility Principle (SRP): 當方法過長時,很可能是因為這個方法企圖完成不只一件個任務。另一個判斷方式是當方法內的任務很難用方法名稱來總結概括意圖時,最好拆出更多小的方法來取代。
  • 可能存在副作用: 長方法可能存在很多子任務,而多項子任務可能導致開發者在使用時產生非預期的結果。常見的範例如同混用查詢與修改資料狀態,例如當我們有一個Book.get(id)方法,預期行為是從資料庫中回傳得到一個書本類別的實體物件,但若是方法內包括了計數器去累計書本被閱讀或點擊的次數實作後,很可能在不同情境下會錯誤增加了許多非預期的紀錄。
  • 可能存在重複的片段: 當我們比較幾個結構相似的長方法時,我們很可能會發現其中存在重複的片段,值得抽出來共用。
  • 執行時間更長:No code is faster than no code”. 一個常見的觀察是,最快的程式碼往往是做最少事情的程式碼,這也是鼓勵我們盡量保持程式碼簡潔的原因之一。
  • 增加測試成本: 當方法越長越複雜,我們自然需要更多不同邏輯與路徑去維護單元測試。同時長方法也可能讓測試所需時間增加,而不僅僅是維護測試本身所消耗的成本。

總體而言,我們必須定期檢視方法中的程式碼片段是否長度超乎必要。當我們把長方法透過各種手段技巧拆解為更小的單元並分散管理,便可以加快閱讀理解、有利於測試,也更好維護。

氣味對應的重構技巧

在Sandi的分享裡,她提到對照表上的重構方法可以分別對應到兩本重構領域的經典之作,分別是"Improving the Design of Existing Code",作者是 Martin Fowler;以及 "Refactoring to Patterns",作者是Joshua Kerievsky。在對照表中分別以「F」與「K」代表兩書,並附上書籍頁碼。可惜的是,或許因為版本差異因素,我個人並沒有辦法依照頁碼對應到指定的重構方法,反而是依靠名稱從書籍目錄中找到對應的區塊。

以下是對照表所列出的Long Method長方法可以對應的重構技巧,因為無法對應所以移除頁碼索引部分:

  1. Extract Method
  2. Compose Method
  3. Introduce Parameter Object
  4. Move Accumulation to Collecting Parameter
  5. Move Accumulation to Visitor
  6. Decompose Conditional
  7. Preserve Whole Object
  8. Replace Conditional Dispatcher with Command
  9. Replace Conditional Logic with Strategy
  10. Replace Method with Method Object
  11. Replace Temp with Query

原本天真的我打算參考對照表,針對重構技巧逐一進行介紹。但當我開始著手調查研究時,很快發現一個問題:除了對照表的意見外,網路上實際存在各家門派不同的看法。最巨大的差異是,如同氣味可以分為五大類,重構技巧當然也可以分門別類,而對照表中所列的「Compose Method」在大部分的參考資料中,實際上是作為重構技巧的分類存在,Compose Method之下又包含了七種重構技巧,包括了對照表中的第一項重構「Extract Method」。

考慮到重構技巧分類存在的合理性,不得不打破原本對照表的設計,根據參考資料重新彙整而成為全新的二階版本:

  1. Composing Method
    1. Extract Method
    2. Inline Method
    3. Inline Temp
    4. Extract Variable (Introduce Explaining Variable)
    5. Replace Temp with Query
    6. Replace Method with Method Object
    7. Substitute Algorithm
  2. Simplifying Method Calls
    1. Introduce Parameter Object
    2. Preserve Whole Object
  3. Simplifying Conditional Expressions
    1. Decompose Conditional
    2. Replace Conditional with Polymorphism
    3. Replace Conditional Dispatcher with Command
  4. Move Accumulation
    1. Move Accumulation to Collecting Parameter
    2. Move Accumulation to Visitor

往好地方想,這次的鐵人賽我肯定能產生出自己的創建,而不只是單純的知識搬運工,從英文世界搬運知識到華文市場;但令人不安的是,我其實沒有自信與把握,自己的詮釋與見解能夠通過驗證與挑戰,這些只是我面對文章挑戰時個人的想法。

行筆至此,要完全包括重構技巧在同一篇文章內或許強人所難,所以請容我將Long Method的對應重構技巧完全轉移到另外一篇文章來做介紹。

缺乏未知的旅程會讓人索然無味。雖然跨出已知疆界的邊緣會讓人感到不安,但同時也會感到興奮,每一點所得可能是全新的創建,還能有比這更令人興奮的收穫嗎?


Sign of Smell

Long methods may be doing too many things within a single method. If you come across a method with more than ten lines, be suspicious.

Every method begins as very small pieces when first committed into Git. When new features arrive, developers tend to believe that adding a few lines of code to an existing method is easier. It may seem harmless to add one more feature to a method with only a couple of lines of code. However, by repeating this process every sprint, the method becomes longer and longer.

Reason of Smell

Long methods are problematic for the following reasons:

  • It's hard to read and understand. When a method grows longer, it becomes increasingly difficult to understand and maintain. This can lead to mistakes or bugs easily.
  • It might violate the Single Responsibility Principle (SRP), which is part of the SOLID design principles. This principle states that a method or function should have only one responsibility, and it should be completely encapsulated by that responsibility.
  • It might have side effects. Long methods often involve multiple subtasks, which can result in unexpected behavior easily. These methods can be responsible for more tasks than necessary and are likely to have multiple reasons for requiring changes.
  • It might have duplicated code. When we compare two long methods, they may contain duplicated code. By breaking them down into smaller methods, you can often identify ways for the two to share logic.
  • It’s slower to run. ”No code is faster than no code*”.* A simplified version of this principle is: "The fastest code is usually the code that does the least." Generally speaking, if you want your code to run as quickly as possible, you should try to achieve the same results while doing less work.
  • It’s hard to test. If a method executes a lot of code, it may require a large number of test cases to adequately cover all possible paths. This can make it more time-consuming to test the code and can lead to defects.

Overall, it's important to be aware of the signs of long methods and to refactor them when necessary. By breaking up long methods into smaller, more manageable pieces, developers can improve code readability and make it easier to maintain and test.

Refactoring Recipe

In Sandi's talk, she provides a table outlining how to convert from code smell to refactoring treatment. Each refactoring skill can be referenced in two books: "Improving the Design of Existing Code" by Martin Fowler and "Refactoring to Patterns" by Joshua Kerievsky.

  1. Extract Method
  2. Compose Method
  3. Introduce Parameter Object
  4. Move Accumulation to Collecting Parameter
  5. Move Accumulation to Visitor
  6. Decompose Conditional
  7. Preserve Whole Object
  8. Replace Conditional Dispatcher with Command
  9. Replace Conditional Logic with Strategy
  10. Replace Method with Method Object
  11. Replace Temp with Query

But on the Refactoring Guru website and in Martin's book, they state that Composing Method is a catalog of refactoring techniques that also includes the Extract Method and other techniques, which is also a good match for long methods. So, we can reorganize the smell-refactoring pairs into two levels as shown below:

  1. Composing Method
    1. Extract Method
    2. Inline Method
    3. Inline Temp
    4. Extract Variable (Introduce Explaining Variable)
    5. Replace Temp with Query
    6. Replace Method with Method Object
    7. Substitute Algorithm
  2. Simplifying Method Calls
    1. Introduce Parameter Object
    2. Preserve Whole Object
  3. Simplifying Conditional Expressions
    1. Decompose Conditional
    2. Replace Conditional with Polymorphism
    3. Replace Conditional Dispatcher with Command
  4. Move Accumulation
    1. Move Accumulation to Collecting Parameter
    2. Move Accumulation to Visitor

If the Long Method is the most common code smell, then Compose Method would be the most common refactoring technique for addressing it as a smell-refactoring pair. But I removed some of the skills that I think are not 100% targeted to the Long Method smell, like "Split Temporary Variable" and "Remove Assignments to Parameters". These techniques can definitely make the method clearer, but they do not make it shorter.

Reference

https://www.jobsity.com/blog/how-to-identify-code-smells
https://refactoring.guru/smells/long-method
https://medium.com/@joshsaintjacque/reacting-to-code-smells-bloaters-3e452d0c01b
https://www.industriallogic.com/img/blog/2005/09/smellstorefactorings.pdf


上一篇
Code Smells > Bloaters 臃腫怪
下一篇
Long Method > Refactoring 如何重構Long Method
系列文
程式碼氣味到重構之路 Code Smells to Refactorings37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Hell Kiki
iT邦新手 4 級 ‧ 2023-09-04 10:29:17

講的很好

Bater iT邦新手 4 級 ‧ 2023-09-04 12:29:15 檢舉

謝謝

我要留言

立即登入留言