試著從另一個角度來看「單一職責原則(Single Responsibility Principle, SRP)」,我覺得它想討論的其實是一種如何整理程式的思維。
想像一下,一個亂七八糟的書櫃,隨意擺放不同類型的書,數量少的時候,也許我們可以記得每本書的位置,但是當數量變多,可能幾百本,想要快速地從書堆中找到想要的書,幾乎變得不可能。
但如果我們有事先將書籍依照類型整理,比如,小說、科普、散文、漫畫等類型,我會知道我現在要找的書,是哪一種類型,這樣立刻就縮小了搜尋範圍,讓我們可以快速地的找到想要的書籍。
回到程式,當系統功能越來越多,程式越來越肥大,有沒有發現,越來越難找到想要的程式片段、或是越來越搞不懂這一坨code到底想幹嘛了?其實這就代表程式該被整理了。
不整理不行嗎
工作一段時間後,其實會發現寫出程式不難,難的是維護程式。
負責維護程式的是工程師,也就是人類,面對大腦的物理限制,我們必須要承認,閱讀大量且雜亂的程式,大腦在短時間內,是難以充分掌握的,即便掌握了,過了一段時間再回來看同樣的程式片段,還是要再重新來過。
就維護的角度來看,這實際是太沒效率也太不可靠了,花費大量時間閱讀不相干的程式,好不容易才能找到想要的片段,即便找到了,要做修改或擴充,也沒有一個既有的邏輯可以沿用,只能在危樓上繼續加蓋。你永遠沒辦法掌握這些程式到底是怎麼運作的、改了程式後,會不會有一些驚人的副作用。
所以,不整理不行嗎?我覺得不行。
那要怎麼整理程式?
首先要找到一個「整理的邏輯」。
就像雜亂的書櫃,稍微按照「類型」整理一下,之後就可以順著「類型」這個邏輯,去找到想要的書籍了。
而每一段程式就像每一本書,可以被放到自己所屬的類別去。我們只要順著一致的邏輯去分類程式,讓程式條理分明,自然可以減輕大腦的負擔。
舉例
比如說,一個剛開始可能只個簡單的結帳功能,後來隨著業務擴漲,要能夠支援優惠券、然後又有預購的功能,再來還要能夠提供多樣的付款方式。
原本可能一開始只有幾百行的程式,逐漸變成幾千行,長成了一個怪獸類別,不要說要交接給別人,連自己維護起來都相當辛苦。
這個時候就可以按照「結帳流程」這個邏輯,去把程式做分類,將結帳的程式拆解成,扣庫存、成立訂單、付款等三個結帳流程。
這樣初步的分類,就可以讓程式碼爆炸的壓力減輕,分散到三個類別。只是每個類別還是有可能繼續增長⋯⋯。
回到書櫃的例子,如果書籍量不多,初步的分類就足夠了,但如果小說的量多到炸掉,原本的大分類已經不敷使用,那就要繼續往下做子分類,比如說言情小說、推理小說、奇幻小說、驚悚小說⋯⋯等。
所以若是「付款」流程,變得複雜了有越來越多的付款方式,那就可以再把「付款」這個流程內部,再按照「付款方式」這個邏輯去做分類,而得到:信用卡付款、用ATM付款、用行動支付付款⋯⋯等不同的類別,這樣我們又可以將付款的程式,分散到各自所屬的類別去了。
當然每個人分類的邏輯不會是一樣的,就像是書籍,也可以按照「頁數」分,像是100頁以上的放一堆,200頁以上的放一堆,300頁以上的放一堆,或也可以按照「出版年份」分類等等。
只要根據經驗,找到一個合理的「整理的邏輯」,將程式做劃分,我覺得就很足夠了。
程式整理後
前面結帳的例子,當以後想要找到「信用卡付款的片段」,就可以快速地先按照「結帳流程」這個邏輯,找到付款,再按照「付款方式」這個邏輯,找到信用卡付款。
在每個分類之下,你會知道這邊「預計應該」會有什麼,任何不應該出現在這裡的東西,早就在當初被分類出去了。 同時,你也不必為了調整某個類別裡面的東西,而怕去影響到其他類別,也不用費力地去看懂太多其他不相干的程式。
這些不就是「單一職責原則」所倡導的好處嗎?
什麼時候該整理
當系統才剛開始開發的時候,我們可以不用刻意去分類,因為不確定系統是否會繼續長大。
比較好的方式是伴隨著系統的成長,在適當的時機做refactor。
避免過度設計(over engineering)是很重要的。
就像是一開始認為可以按照書籍類型來設計書櫃空間,結果後來才發現自己根本從來不買小說以外的書,那這樣其他的櫃位就浪費掉了。(把多出來的小說擺到科普區?別鬧了、重新分類吧。)
除非系統需求明確,複雜度也在一開始就大到需要被整理否則難以進行的程度,那就可以依照自身經驗,將程式做適當的整理及分類。
總結
「單一職責原則」所說的「改變的理由」過於抽象難以離解,那就換一個角度來看,也許不能精準地表達單一職責的精髓,但我想會有助於理解。
反正,我認為「單一職責原則」某種意義上就是說,要好好的整理程式,好好的把程式歸位,這樣每一區的程式,就可以是單一職責啦。