菜雞與物件導向 (10): 單一職責原則

我們在前面的 內聚和耦合 有提到過,內聚並不是無腦把相關的程式碼都封在一起就好了,也有分成健康的和不健康的。但我們要怎麼知道這個類別是否足夠健康呢?單一職責原則就是很好的檢驗方式,這篇就讓我們來紀錄一下。

單一職責原則 (Single Responsibility Principle)

「單一職責」原則顧名思義,就是一個類別應該只負責一個職責

但是這樣太過籠統了,「職責」相當容易產生誤會,容易變成各說各話。畢竟咱們工程師最愛戰定義了嘛。

「你這類別不優,它有兩個職責!登入跟登出!」

『沒有啦,我這個類別就是負責帳戶管理的啊』

OSSO。乾脆你全部放一起,然後說是負責網站管理算了,呵」

『……你存心來找碴的是不是?』

為了避免像這樣產生職場糾紛,我們需要先定義一下什麼是「職責」。經過前輩們的努力(解釋)之後,單一職責的定義就成了:

就一個類別而言,應該只有一個引起它變化的原因

另外,我也看過「一個類別應該只對一個角色負責」的說法,這兩者的核心概念是一樣的。

……

閱讀全文



C#: 元組 (Tuple)

因為隔壁介紹原則的部分有點卡住了,所以這週來紀錄一下挺常用到的方便東西:Tuple

這篇的 Tuple 指的是 C# 7.0 後提供的 ValueTuple 和相關語法,舊版得用 Tuple.Create 建立,成員的名稱也只能使用 Item1, Item2…,實用性並不是很高。但新 Tuple 出現後,方便程度大大提升,這邊就稍作紀錄一下。

註:此處使用的 Dump 是 Linqpad 提供的輸出方法,把它當成 Print 就行了。

var student = (1, "王小明");
student.Item1.Dump(); // 1
student.Item2.Dump(); // 王小明

student.Dump();

可以看到 Tuple 的建立相當簡單,只需要用小括號 () 括選起來即可。建立後的內容就會像這樣:

但這樣使用就和之前一樣,取出來時只能拿 Item1, Item2,放個幾天根本就不記得 Item1 裡面是啥東西了。這時我們就可以替成員們取名字

(int ID, string Name) student = (1, "王小明");
student.ID.Dump(); // 1
student.Name.Dump(); // 王小明

如此使用的時候就和一般操作物件的習慣沒有差別,也增加了可讀性。

……

閱讀全文



菜雞與物件導向 (9): SOLID

終於進入了原則篇,接下來的幾篇我們會介紹幾個物件導向的原則(基本上就是指 SOLID 原則)。因此這篇就讓我 水一下 當成後半段的目錄,方便之後可以把相關的部分整理進來。

為什麼我們需要這些原則?

我們在前面的章節已經說明了一些物件導向的特性,例如繼承和多型等等。然而我們並沒有討論到怎麼運用、或是怎樣設計才能算是更好的、更優雅的、更符合物件導向精神的;我們並沒有提到一個評估的標準,或是指引一個更好的方向。

然而,混亂的使用物件導向對整個專案的毀滅性甚至比乾脆不使用物件導向還高。

這些特性使用起來很簡單,大多數語言只需要一個符號或標示就能完成繼承,把一堆東西全部塞在一起就可以說我在封裝。但怎麼使用得好,又該什麼時候使用呢?這就是難的地方吧。

例如說濫用繼承,或是封裝時完全不隱藏複雜度一路 Puuuuublic 到底,又或者是類別之間過於相互依賴,全部耦合成一團等等。如果隨便地使用物件導向的各項特性,就會讓整個架構變得僵化、脆弱、危險、充滿臭味。

更可怕的是,這個發臭的過程是每一次設計、每一次修改都會有所影響,所謂「持續發生,腐敗成真」,隨著物件導向的亂用、誤用、無腦用,軟體就會逐漸腐化。一組腐化的軟體可能會有以下特徵:大量的依賴使得修改變得困難、修改後看似不相干的各個地方發生問題、或是修改時沒辦法依循原本的設計、到處出現不必要的複雜性和不必要的重複,模組也變得難以理解等等。

阻止程式碼的腐化、追求更好的架構和設計、寫出更好的代碼,當然是我輩所追求的目標。儘管面對的可能是不同的問題和不同的環境,那些優質、穩固、具有反脆弱特質的程式碼也必然會有些共通之處。例如說:需要具有面對改變的能力、具有方便管理的能力、具有隱藏複雜性的能力。

因此,大前輩們整理並提出了一些可以致力的方向,也就是所謂的「原則」。如同心法、教義一般,只要實作的同時將其牢記在心,就能讓我們作為一些行動的準則和依據。

……

閱讀全文



菜雞與物件導向 (8): 內聚、耦合

做為前後段落的分水嶺,這篇文章我將紀錄一下 「內聚」(Cohesion)「耦合」(Coupling),這兩者是評估一個類別或元件的重要概念。

在實務上,為了提升擴展性,降低維護成本等因素,我們對於單個類別或元件,會有著 「低耦合」「高內聚」 的期待。例如我們在 菜雞與物件導向 (3): 封裝 中,我們就有提到封裝的好壞相當重要,其中也包含了「提高類別內的內聚性,降低對外的耦合性」。那麼,到底什麼是內聚,什麼又是耦合呢?

內聚

「把需要的程式和資料都包裝在同一個模組內,使得該模組能夠做為一個單獨的個體執行」

白話一點說,就是就是把用到的東西都打包到一處,該有的自己都有了,所以即使單獨一個人也能完成工作的能力、可以自己 Carry 整場不用看豬隊友臉色的能力。越能自己單幹,越不需要依賴其他類別的時候,內聚力也就越高。

也就是說:如果你的類別什麼都要依賴其他類別,像小嬰兒一樣需要呵護照顧,那內聚力就很低。反之,如果像野外求生大師,啥都靠自己,那內聚力就超高。

內聚代表的是該模組的獨立性,當這個模組可以獨力完成工作,就代表我們能夠重複使用它,且不需要擔心影響到其他模組。

並且也基於這點,我們不用擔心變動這個模組時需要先處理其他的模組,因為這個工作所需的都包含在模組內了,這樣就可以單獨修改該模組,減少維護成本。

例如你的筆已經包含了所有寫字工具的條件,具有墨水跟筆芯等等,可以只使用筆就完成寫字這個工作。那麼我們就可以隨身帶著,在任何需要的時候重複使用它,而不用擔心我們會不會漏了什麼必要零件沒有帶出門。同時,如果我們需要換筆芯或墨水,我們也知道要更換的部份就在筆裡面,不需要去找鉛筆盒中別的地方。

……

閱讀全文



讀《先問為什麼》

如果你樂於接納新事物,希望成功能持久,也相信自己的成功需要別人的幫助

我向你提出一個挑戰 ——

從今天起,做任何事情之前,請先問自己「為什麼」。

這本書的中文副標很好地點出了本書的重點:顛覆慣性思考的黃金圈理論,啟動你的感召領導力。大多篇幅用在舉例以及逐步說明何謂感召,以及黃金圈。

本書的想法和一些例子,尤其是書中最重要的黃金圈理論,在作者上 TED 的影片「偉大的領袖如何鼓動行為」都有說明,有興趣的朋友可以直接看演講影片,足夠掌握到黃金圈理論的核心。

(偉大的領袖如何鼓動行為 - TED)

……

閱讀全文



C#: 時區轉換、民國西元、國曆農曆、中文月份週期

聊到將時間從 UTC 轉到台灣時間,居然還是聽到朋友表示使用 +8 小時的做法,驚為天人。這種做法可能會造成後續的問題,例如時區並不會跟著變動,或是遇到日光節約等特殊狀況就容易出事。和西元民國轉換直接 -1911 一樣不穩定。

這篇就用來記錄一下之前看過比較優雅的時區轉換方式,順便將先前存著的時間處理相關資料整理一下,方便之後需要時可以馬上回來查詢。

TimeZoneInfo: 時區資訊

轉換方式主要參考自 [食譜好菜] DateTime 具有文化特性的格式化及時區的轉換在各時區間轉換時間,感謝前人的指引。

關於文化特性,也可以參考本站的 菜雞抓蟲: DateTime.ToString() 之我們不一樣 & CultureInfo 文化特性小筆記 呦。

// 假設現在是要從標準時區 +00:00 轉換到台灣時區,故這邊使用 UtcNow 先取標準世界協調時間
var nowDateTime = DateTime.UtcNow;

nowDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 15:56:05 +00:00

// ==================================================

// 傳統的 直接對時間做計算的方式…
var addedDateTime = nowDateTime.AddHours(8);

addedDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 23:56:05 +00:00
// 可以看到儘管時間變動了,時區仍然還在 +00:00

// ==================================================

// 使用 TimeZoneInfo 先取得台北時區
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");

// 再使用 TimeZoneInfo 來變更時間
var convertedDateTime = TimeZoneInfo.ConvertTime(nowDateTime, timeZone);

convertedDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 23:56:05 +08:00
// 可以看到除了時間變更以外,時區也切換到 +08:00 了!

上面取得台北時區的步驟,可以參照 Time Zone IDs 來查詢想要的時區。這樣的時區切換方式,不僅副作用少,不會因為時區沒轉雷到後續接手的人,也省卻了擔心日光節約等等問題,這種事就交給微軟去煩惱吧!

……

閱讀全文



Powershell 美化作戰 —— 字型、執行原則和 oh-my-posh

最近在兩天內經歷了記憶體死去、系統毀損、機殼碎裂等等,終於電腦重灌。一堆設定都要重弄,正好也是個機會,這篇順手記一下常用好幫手 Powershell 的美化步驟。

先放一張施工後的圖鎮樓:

可以在開始圖示上用右鍵打開選單,之後點選 Powershell;或是 Win + X 打開選單,然後按 I 或 A (後者會以系統管理員身分開啟)就能開啟了。

如果選單打開還是 CMD 而不是 Powershell 的,可以先去切換成 Powershell,真的是比較好用啦(Windows Terminal 笑而不語)

剛打開的畫面是這樣的:

抱歉,光細明體我就不太行了。所以接下來就從字型這些內建設定開始!

……

閱讀全文



C#: 字串插值 (String interpolation) 的格式化

自從 C# 有了 字串插值 這東西之後,我就一直是愛用者。畢竟比起 string.format 這東西可是看起來優雅多了。例如:

var message = $"哈囉,{userName} 您的點數將於 {cutoffTime} 到期。";

簡潔又明瞭,一眼就能理解字串內容。實在是挺方便,後來發現這東西還有一些延伸用法,這邊就稍加紀錄一下:

字串插值中能夠做簡易計算,例如:

var message = $"您輸入的數值為:{a}、{b}。他們相加為:{a + b}";

同時,在字串插值時可以針對內容作格式化,只需要用 : 來區隔,妥善運用可以省下一堆 ToString() 的空間。

例如當我們要將時間格式化的時候,就可以:

var date = new DateTime(2020, 8, 9);
var message = $"您的商品已於 {date:yyyy/MM/dd} 抵達。";
// 您的商品已於 2020/08/09 抵達。

另外,數值當然也可以格式化,不過數值的應用比較複雜,主要是用來定下小數點、百分比等符號的位置。 可以參見 自訂數值格式字串 - Microsoft Docs

var cost = 2100;
var message1 = $"您的商品一共是 {cost:#,###} 元";
// 用 # 可以替數字預留位置
// 您的商品一共是 2,100 元

var message2 = $"您的商品一共是 {cost:#,###.00} 元";
// 也可以用 0 來預留位置,若該數字有值就會顯示該數字,沒有就會自動補 0
// 您的商品一共是 2,100.00 元
……

閱讀全文



C#: 使用 System.Environment 取得環境資訊、特殊資料夾路徑

有時候我們會需要取得一些系統資訊,例如說取得設備和當前使用者等資料來寫 Log,或是取得特殊資料夾路徑、讀取環境變數等等。這些時候就可以使用 System.Environment ,這邊就稍微紀錄一下用法。

先列出幾個常用的環境資訊,詳細可查詢的內容可以到 Environment Class 查詢:

資料夾路徑則需要用 Environment.GetFolderPath 搭配 Environment.SpecialFolder 列舉使用,該列舉包含資料夾可以到 Environment.SpecialFolder Enum 查詢。

……

閱讀全文



C#: 程式碼風格備忘

前言:本篇是整理公司規範和網路文章後,方便我自己在各個場所也能回來查閱使用的,故仍會不定時修改(畢竟我這人挺三心二意的)。另外本篇有重新調整過行距,發現有點跑版的朋友可以先 Ctrl+F5 一下,感謝閱讀。

如果你想知道的是如何寫出更優雅、更乾淨、品質更高的程式碼,那並不是該看這篇我個人的備忘錄,我會建議可以閱讀《無暇的程式碼》。或是可以參考這幾篇,我覺得都寫得很好:可不可以不要寫糙 codeClean Code 無瑕的程式碼閱讀筆記易讀程式之美學,共勉之。

如果你是正巧路過並且也寫 C#,希望這篇能讓你做為參考。但請記得,程式碼風格沒有絕對,最終還是回歸到團隊能否接受和將來的可維護性去考慮,畢竟教條是死的,人是活的。了解這樣做背後的原因,以及為自己寫的程式碼負責,這些都比對著隻字片語斤斤計較更加重要。

Nothing is true, everything is permitted. —— Assassin’s creed.

命名原則

想像下一個接手你程式碼的人是個暴力傾向的重度精神病患者

而且他知道你住在哪。  —— 《無瑕的程式碼》

  • 使用有意義的命名,請重視描述性。除了迴圈計數器例外
  • 盡量不要超過五個單字
  • 業界和慣例中有對應縮寫時可以使用縮寫
  • 承上,縮寫兩個字母時全大寫,三個字母以上時只第一字大寫

命名空間

  • 基礎類別庫:{組織} . {大類/應用範圍} . {小類/專案名稱}
    • MyStudio.Libs.Basic
    • MyStudio.Libs.Web.BaseTools
  • 專案類別庫:{專案名稱} . {子專案/類別/用途}
    • MyProject.Permiss.Proxy
    • MyProject.Permiss.Repository
……

閱讀全文