iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 7
3
Software Development

看到 code 寫成這樣我也是醉了,不如試試重構?系列 第 7

SOLID 之 單一職責原則(Single responsibility principle)

  • 分享至 

  • xImage
  •  

雖然軟體量測很方便,也能找到很多可能有問題的程式碼,但最終還是需要人工檢查程式的設計。這時就需要原則(principle),讓檢視過程能有正確的方向。

SOLID 是 Robert C. Martin 提出的物件導向設計五個原則:

  • Single responsibility principle (SRP)
  • Open-Close principle (OCP)
  • Liskov substitution principle (LSP)
  • Interface segregation principle (ISP)
  • Dependency inversion principle (DIP)

剛好字首五個字母合在一起就成為了 SOLID ,這五個原則目的都是為了在面對改變時,能有一套策略來應付。

今天先來講講單一職責原則:


首先必須先考古一下,單一職責原則(Single responsibility principle)的原文定義如下:

A class should have only one reason to change.

大部分朋友看到「單一職責」就會聯想到,這個原則的目的是不是把 class 功能單一化?其實原文是把職責(responsibility)定義成 one reason to change

這好像有點抽象,書中就有舉個例子:我們有個 Modem ,它的介面如下:

// Modem
interface Modem
{
    // 撥號
    public function dial($pno);

    // 掛斷
    public function hangup();

    // 發送資料
    public function send($c);

    // 接收資料
    public function recv();
}

從介面上可以了解,它有一個職責是屬於連線(connection),另一個則是數據溝通(data communication)。 dialhangup 是連線; sendrecv
是數據溝通。

這樣會有什麼潛在風險呢?今天 ADSL 要升級成 100M ,我們會需要修改 Modem 實作,這會導致與它連線無關的 sendrecv 也會跟著重新編譯與佈署,風險範圍也隨之擴增。重構的方法之一,是把這個介面抽離出兩個單一職責的介面:

interface Connection
{
    // 撥號
    public function dial($pno);

    // 掛斷
    public function hangup();
}

interface DataChannel
{
    // 發送資料
    public function send($c);

    // 接收資料
    public function recv();
}

class Modem implements Connection, DataChannel
{
    // ...
}

而原本其他依賴 Modem 介面的 class ,都依職責不同,改依賴對應的介面。

優點

遵守 SRP 的好處如下:

可讀性與可維護性提升

單一類別的複雜度降低,因為要實現的職責都很清晰明確的定義,這將大幅提升可讀性與可維護性。

強健性提升

如果有做好 SRP ,那修改只會對同一個介面或類別有影響,這對擴展性和維護性都有很大的幫助。

潛在問題

SRP 是個充滿爭議的原則。爭議的點是,那個「變化原因」會是什麼?或者說,職責該如何劃分?

因為變化原因和職責都是無法量化的,而且會因為專案需求或環境變化而改變,所以事實上 SRP 很難在專案上完美地實現。如果硬要達成 SRP 的條件,最直接的方法就是一個方法一個介面,結果會變成介面數量劇增,反而帶來更多麻煩。

參考資料


上一篇
軟體量測(Software Metric)
下一篇
SOLID 之 開關原則(Open-Close principle)
系列文
看到 code 寫成這樣我也是醉了,不如試試重構?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Hi
iT邦新手 5 級 ‧ 2020-02-02 09:59:01

您好,我想請問一下您文中所提及

重新編譯與佈署,風險範圍也隨之擴增。

是指什麼意思呢?如果寫了單元測試是否可以避免這個問題?謝謝~

Miles iT邦新手 2 級 ‧ 2020-02-08 17:40:13 檢舉

簡單來說:

程式只要沒有修改,就不會有新的 bug;
程式只要有修改,就有可能有新的 bug。

因此不產生新的 bug 最簡單的方法就是不修改程式碼。如果一個需求調整需要改很多個類別,則這些類別就都是可能產生 bug 的地方。若單一職責有做好的話,理論上修改程式碼範圍會比較小,同時產生新 bug 的範圍也會隨之縮小。


在上面這個情境來看的話,舊的單元測試能保護原有行為的正確性,而修改程式時,新的單元測試可以驗證新的行為是否如預期,但無法保證完全不會有新的 bug。

以上是小弟個人的想法,提供參考。

我要留言

立即登入留言