Re: [討論] 寫程式的追求?
感謝大家熱烈討論,看到不少網友對 interface 的話題有興趣,
另外開一篇,歡迎大家一起來討論
: --
: ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 73.70.25.48 (美國)
: ※ 文章網址: https://www.ptt.cc/Soft_Job/E.9MBbVdYXtnZk
: 推 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 or dependency inversion,也就是 SOLID 的 D他要解決的問題是物件間高度耦合,所以讓大家都依賴高階抽象 interface,不直接
呼叫低階的實作細節,低階的實作要呼叫其他物件,也透過高階介面,所以稱"反轉"
大家都只依賴介面而非特定實作,就達成低度耦合。
(註: Inversion of control (dependency injection) 和 dependency inversion 是不同的東西,但實際上幾乎會一起用,因為所有呼叫都是 abstract interface 的話,
那使用的 code 大多不會負責建造這些物件,所以幾乎必然會從外面 inject 進來)
這要解決的問題是耦合,但耦合不等於不好。當兩個物件就只有一種實作,然後他們就
真的只能搭配一起用無法抽換,那耦合其實也沒有問題,沒必要硬要解耦。
等你需要抽換不同實作的時候,再來抽 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
--
C#來說虛擬類別出來然後可以有不同實作又可以再掛介面
目前我是覺得我的場景還算滿夠用
有時候覺得OO搞那麼複雜 不如C語言
至少所見即所得
新人也能trace code
推 為了隔離第三方呼叫而弄的 interface 是可接受的
其餘等不同實作開始浮現再來抽
是說 C 也是會使用 function pointer 解耦
後來發現OO寫到後面太複雜了就往functional靠了
推這篇
推推 不過SOLID的DIP跟IoC的DI應該是不同的東西?
推分享~好專業
在這個AI產UT的時代 更不該隨意抽象
發現有模組能共用再抽象出來就好了
樓上說的沒錯,dependency inversion & injection 不同
但兩者實務上常並用,我寫清楚好了,感謝糾正
搞清楚每個東西是解決什麼問題,就可以知道什麼時候要用
但用了一個往往就製造另一個問題,最後就整個套餐集滿了
最後就分開看都是 best practice,但全部組起來卻看不懂
在大型專案有些複雜度有必要之惡,了解脈落有助於理解
Accidental Complexity,不意外。
Uncle Bob的SOLID都是一看合理但實際行不通的觀念。當作
理想可以,但實務是折磨自己。
推推
組合起來就看不懂,有機會是boundary切太大,有多個職責在
裡面。工程師要直接面對程式碼,視角上難免見樹不見林
常常遇到SA/PM開需求都不加思索,畫面上有甚麼就開甚麼需求
任何複雜的商業邏輯,都被他們開得好像只要CRUD就能完成 XD
謝謝大大分享,我經驗不是很多,主要是寫獨立遊戲,現在
用的很接近你的軟體工程全餐,但是我使用起來感覺很條理
system+factory+facade組一個系統,寫完了丟namespace
對項目整體複雜度下降真的很有幫助。沒用設計模式之前
寫出來的東西對心智負擔很大
用設計模式不算壞事吧?
現在我幾乎不寫單元測試而是直接寫整合測試把自己當使
用者去測完整行為了,本地端直接docker compose啟Post
gres Redis等實在很方便,早期docker還沒那麼成熟時只
能靠h2假裝一下…
說這麼多,其實我只是想提倡徹底理解各種practice的優缺
比起把書上的東西當成教條奉行,更重要的是理解其內涵
了解他們解決的問題和侷限,使用上就會更彈性和得心應手
就能吃軟工自助餐,想要什麼就夾什麼,知道自己在做什麼
不就是為了一定要用某個概念,有沒有多偉大不知道。
但這個概念產後的副作用另一個概念/方法來補救。
到最後c++我還是用free function > class xd
推徹底理解各種practice的優缺
有時候人在江湖 身不由己。 每次上面老闆急著要東西
要你最快速趕出來。什麼clean code,design的東西全
丟在旁邊,老闆看到能跑就爽了
之後想要跟上面要時間去整理一下code, 好以後擴張,
上面就會一副不耐煩的表情說不用動
之後需要擴張時上面的又會嫌說當初怎麼寫的那麼亂很
難擴張
了解了 推推
然後技術債就是大家比偷渡,用三寸不爛之舌把一些有隱
憂的部分想盡辦法偷渡進下一個專案的排程
我個人看不順眼的code重構也是包在自己的時間內
重構也不是整個重寫,評估處理個大概能用
就好
project重構本來就應該包,只是很多人不接受
32
首Po寫程式不知不覺也一年半了 看著公司龐大的老舊程式 前人寫的實在雜亂 造成了維護上有一定難度 最近有心想要嘗試從簡單的地方開始試著重構4
程式人最重要的追求 應該是創造良好就業環境 本來一個人的工作 變成三個人做 三個人的工作要一個部門做![Re: [討論] 寫程式的追求? Re: [討論] 寫程式的追求?](https://i.imgur.com/JAcuIOSb.jpeg)
這幾年AI流行 只要你訂好條件,清楚流程,然後約束修改窗口──你清楚你在做什麼 AI幾乎能產出100%正確的程式碼 並且功能清晰,命名合理,還加上一堆註釋 工作流程幾乎就是讓AI生成程式碼,閱讀程式碼,對一些細節做修改4
: : 這幾年AI流行 : 只要你訂好條件,清楚流程,然後約束修改窗口──你清楚你在做什麼 : AI幾乎能產出100%正確的程式碼 : 並且功能清晰,命名合理,還加上一堆註釋11
OK,你說得很有道理,AI 會產測試。 假設你要實作的邏輯不複雜,不罕見, 因此 AI 可以讓你用少少的口語產出字數更多的程式碼及測試好了, 那我就想問,是什麼樣的測試程式? 姑且不論常常被忽略的非用途面驗證好了,光是用途面的驗證就有很多種。3
你會這樣想只有幾種可能 1.過太爽 2.年輕人認為可以學的到東西 3.單身 基本上重構對公司是沒產值的 不知道你有沒有看過一張圖3
純粹對工作上來說 好抽換,好接手(易閱讀),好維護(包含升級,測試) 工作,這樣就很夠了阿,大家一起輕輕鬆鬆,工作之餘聚餐風花雪月。 回家,陪老婆,陪女兒不好嗎? 工作又不是人生的全部![Re: [討論] 寫程式的追求? Re: [討論] 寫程式的追求?](https://i.imgur.com/KDk3kmpb.jpeg)
4
嗯,這三點很因人、因時、因事、因心情而異。 就像李白好還是杜甫好? 金庸好還是古龍好? Chaos 好? --39
好接手,易閱讀… 我想到一個故事 幾年前有個同事,號稱國中時期就開始接案寫代碼 clean code,DDD滾瓜爛熟,對coding極度潔癖 印象比較深的是入職時說了句:我看到不規範的代碼會非常生氣6
我應該也算軟體工程師吧 在電子廠專門用LabVIEW幫實驗室寫自動測試程式。連續寫了十六年。 體驗所謂人生三階段。 看山是山,看山不是山,看山還是山。 第一階段,只追程式會動,能用就好。就算有問題,硬解。遇到什麼不會,就去學什麼。
20
Re: [分享] 用一個簡單的數學公式來幫忙設計OOP類別先講結論: 我反對原文的結論「OOP易學難精」 就我個人到現在的感受是「難學易精」 為什麼呢? 以下分享個人看法![Re: [分享] 用一個簡單的數學公式來幫忙設計OOP類別 Re: [分享] 用一個簡單的數學公式來幫忙設計OOP類別](https://i.ytimg.com/vi/KIto1OQxwsY/maxresdefault.jpg)
20
Re: [請益] Spring boot的依賴注入降低耦合的例子在這個時代依賴注入最重要的用途,特別是在後端開發是讓Application 在多個不同的 環境下(Development, Production, local, etc) 能夠根據profile 組出能正確執行的Application 多型在這裡當然有他的地位,但是一般來說,大部分不接觸system boundary的service objects 是不太需要多型的,如果是java,那種一個interface 只有一個implementation17
Re: [請益] Spring boot的依賴注入降低耦合的例子先講結論: DI(Dependency Injection) 跟 IoC(Inversion of Control) 的原始目的都不是解耦 接著說明一下 DI 跟 IoC: 首先 IoC 的目的是控制權的轉移,如原 PO 文章裡面原本是在 Employee 裡面 new Address,而採用 IoC 之後這樣的行為就轉移到外部來決定12
Re: [心得]以策略模式重構switch case或if (影片)終於有空來加入討論啦~ 這邊有 markdown 好讀版: 這邊我也來提一下我的看法。為了閱讀方便我把一些 code snippet 複製在這邊: ```java= public double shippingFee(String shipper, double length, double width, double9
Re: [請益] 比物件導向更先進的程式設計思想?依照目前看CodeReview 大部分人寫程式的方式 其實都披著OOP的皮 寫不是OOP的程式 甚至還看過很愛嘴別人的主管10
Re: [請益] Spring boot的依賴注入降低耦合的例子很久沒寫Java了 就個人觀念提供簡單思考線索 基本上根據你的內容覺得你對解耦合還沒有很理解 講直白一點 當你import類別就是耦合了4
Re: [請益] 多型用在哪ㄛ現在ㄉ想法4 沒有多型 只有介面 多型的用例之一 for(auto p_actor : actors) p_actor->act() 對ㄛ來縮 p_actor實際上到底是什麼 並不重要2
Re: [討論] 工作上寫單元測試的比例因為大家的討論都很基於心法 實作上相對很模糊 利用這個機會也跟大家請教實作上的方式 因為最近工作被指派要針對公司產品的程式做整理,其實運作都還好 只是大家功能是一層疊一層,一堆巢狀邏輯,跟依賴中的依賴![Re: [討論] 工作上寫單元測試的比例 Re: [討論] 工作上寫單元測試的比例](https://tdd.best/wp-content/uploads/2021/01/play-for-fun-scaled.jpeg)
Re: [請益] Spring boot的依賴注入降低耦合的例子其實就是在做 instance 的管理 物件導向一個很重要概念就是 多型 所以你可以在 runtime 再決定 instance 到底是什麼 IoC 就是實體是外部送進來 不要自己去 new DI 只是在提示 要送東西進來的人說 你想要的是什麼東西
Re: [請益] Spring boot的依賴注入降低耦合的例子比較早版本的spring 只能利用xml來依賴注入 其實反而比較好理解 spring 如果只有設定好xml的依賴注入 但是沒有設好依賴反轉(也就是宣告成介面) 那就會發現