Re: [討論] 寫程式的追求?
感謝大家熱烈討論,看到不少網友對 interface 的話題有興趣,
另外開一篇,歡迎大家一起來討論
: --
: ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 73.70.25.48 (美國)
: ※ 文章網址: https://www.ptt.cc/bbs/Soft_Job/M.1747295625.A.D41.html
: 推 ikachann: 很多都這樣 一開始能動最重要,真的有閒穩定下來後才是 05/15 22:20
: → ikachann: 重構的部分 05/15 22:20
專案開發節奏跟著商業需求走的,需要速度的時候先能動,但方向確立後,就真的需要
排時間做 refactor,清理成好維護的 code, 不然容易快速 launch,然後就快速墜落
refactor 的時候往往遇到一些 code 無法寫 UT,這時候就是該導入 abstraction layer
的時候了。
: 推 ikachann: interface真的是說到點了,可能補習班教吧 一堆人都照著 05/15 22:22
: → ikachann: 一個service都做一個interface然後就只有自己實作 05/15 22:22
: → neo5277: interface 還是看語言跟框架用啊 05/15 22:42
: 推 stepnight: interface 跟 DDD 就是互相成就 05/15 23:5
: 推 shibin: 推,好奇問,那如果是為了 UT 而弄的 interface 呢? 05/16 14:3
: 推 attacksoil: 有時候寫interface是怕把依賴方向搞爛 有建議嗎 05/17 22:2
: 推 tsaigi: 但c#要mock的話不是一定要interface嗎? 不測試的話的確 05/21 11:0
: → tsaigi: 是不用interface啦
聊到這個,除了 interface 還有 oop 繼承的問題,早年學 OOP 都是狂用繼承
但幫 parent class 改變行為,所有 children 就強迫一起自動獲得這個改變,
如果有部分 children 不需要這個行為,事情就會變得非常棘手。
這就是為什麼會有 SOLID principle 的 O, open-close principle
parent class 要設計成好開放擴充 (open) 但 parent class 本身不能隨便改(close)
但這實務上不好做到,所以現在越來越常見提倡少用繼承,尤其不要為了 reuse 某個
class 的功能而繼承他。如果 class A 需要 reuse class B 的功能,可以在 class A
的 data member 建立一個 B 物件,然後呼叫他就好,不需要繼承整個 B 的全部行為跟
介面,就可以有效避免這問題。 這個就是 compoisition over inheritence,
透過組合多個物件來 reuse 他們的功能,而不是繼承他們的 classes 來取得這些功能。
當這樣做的時候,每個物件就可以只負責一件事情 (single responsibility),
他們就會很單純很好測試,然後組合這堆物件來合作完成一個大功能。
每個物件就可以有很簡單的介面,這就是 SOLID 的 S, single responsibility
這樣對 testability 也有幫助,但這樣衍生的問題,就是物件會拆得很多很散,
把這些東西正確兜再一起很複雜,於是就需要寫 factory 來建造這些物件,
或是借助 dependency injection 工具,使得 dependency 的管理變複雜。
物件之間要正確的互動也複雜,這個的經典案例就是 Windows 輸入法框架 tsf
每個 interface 都只有一個功能,大多只有一個 method,結果就是寫個輸入法
API 文件一打開散成上百個 interfaces,沒有足夠的範例誰知道要從何看起?
這種時候就需要,提供一個簡單易用的 wrapper,來把這堆複雜互動包起來,
提供簡單 API 讓常用場景方便使用,這就是 facade pattern。
解決一個問題,往往會製造另外一個問題,所以最後你逐漸就上了軟體工程全餐
: 推 stepnight: interface 跟 DDD 就是互相成就 05/15 23:56
: 推 shibin: 推,好奇問,那如果是為了 UT 而弄的 interface 呢? 05/16 14:39
如果是為了讓某些 dependency 在測試期間能替換,這是合理的做法。
最常見的場景之一,就是把 clock API 包進 abstract interface 裡面,然後 code
需要時間就 call abstract interface,而不是直接呼叫 system call
這樣在 UT 裡面就可以替換成假的,inject 寫好的固定時間進去,不然每次跑都不一樣
另外一個例子就是你的程式呼叫某個系統 API,但 UT 的環境不允許呼叫,或是每次結果
是隨機的,那我們就會用 interface 把它包起來,讓 UT 時可以替換成不同實作
這確實是很合理而且常見的作法。但除了這些特殊狀況,其他多半不是必要的
測試的時候還是 Prefer real object,使用真實物件如果適合,盡量用真的
使用 fake or stub 每次你改變行為,這些 fake/stub/mock 也要跟著連動,如果沒有
一起改到,UT 就可能會壞掉或是得出錯誤的結果,使用過度會非常難維護
test double 使用的目的,主要是為了觀察內部互動 (mock),提供假的固定數值 (stub)
或是避免外部依賴,例如避免連真實 server,或是加速,例如避免大量 disk I/O,
換成用 in-memory cache 的實作來加速 UT,除了這些狀況,大多都可用 real object
: 推 attacksoil: 有時候寫interface是怕把依賴方向搞爛 有建議嗎 05/17 22:28
我想你在講的是 inversion of control / dependency,也就是 SOLID 的 D
他要解決的問題是物件間高度耦合,所以讓大家都依賴高階抽象 interface,不直接
呼叫低階的實作細節,低階的實作要呼叫其他物件,也透過高階介面,所以稱"反轉"
大家都只依賴介面而非特定實作,就達成低度耦合。
這要解決的問題是耦合,但耦合不等於不好。當兩個物件就只有一種實作,然後他們就
真的只能搭配一起用無法抽換,那耦合其實也沒有問題,沒必要硬要解耦。
等你需要抽換不同實作的時候,再來抽 interface 其實常常也沒關係。
會有問題的是 share library,事後補抽 interface 會變動 ABI,造成 binary 版本
不相容,需要重新編譯。這種情境 interface 一開始訂好不要動就會比較好。
然後 interface 越小越好,每個都只做最少的事情,就不會動一個東西就要到處改
這樣對測試維護都比較好,也就是 SOLID 的 I, interface segregation
: 推 tsaigi: 但c#要mock的話不是一定要interface嗎? 不測試的話的確 05/21 11:00
: → tsaigi: 是不用interface啦 05/21 11:00
上面討論過 UT 了
: 推 jennya: 熱愛嘗試當下流行的新工具新理論的工程師,常常也是團隊主 05/22 01:58
: → jennya: 力,很難找到理由阻止他們嘗試新事物 05/22 01:58
確實,就只能定期 review,定期 refactor 了。
對導入新事物保持開放心態,同時也對萬一發現不合適隨時 rollback 保持開放
勇於嘗試,但也勇於承認錯誤,就會是個比較健康的文化。
講半天架構,耦合,好玩的是,實務上最後決定系統架構的,往往是 Conway's law
你的組織架構長怎樣,系統架構最後就會長成那樣.... XDDDD
雖然現在 AI 當紅,還是很高興有人一起來討論這些日薄西山的傳統軟體工程啦! (淚)
--
Sent from PCMan on PCMan's PC
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 73.70.25.48 (美國)
※ 文章網址: https://www.ptt.cc/bbs/Soft_Job/M.1747904496.A.E67.html
推
05/22 18:30,
3小時前
, 1F
05/22 18:30, 1F
→
05/22 18:31,
3小時前
, 2F
05/22 18:31, 2F
→
05/22 18:35,
3小時前
, 3F
05/22 18:35, 3F
→
05/22 18:35,
3小時前
, 4F
05/22 18:35, 4F
→
05/22 18:35,
3小時前
, 5F
05/22 18:35, 5F
推
05/22 19:05,
2小時前
, 6F
05/22 19:05, 6F
→
05/22 19:06,
2小時前
, 7F
05/22 19:06, 7F
→
05/22 19:07,
2小時前
, 8F
05/22 19:07, 8F
討論串 (同標題文章)
Soft_Job 近期熱門文章
PTT職涯區 即時熱門文章