分類 w3HexSchool 中的文章

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
……

閱讀全文


Pocket —— 稍後閱讀,想看再看

前言:由於我這人特喜歡發完文之後一想到就回去改改,因此現在還在修改上一篇物件導向的內容囧。講好聽是有持續交付的精神,講難聽就是比較後知後覺一點。因此這幾篇仍會像之前我要買便當系列一樣,主軸間穿插個幾篇小工具或心得,以這個節奏前進。所以這邊就介紹一下我幾乎每天都會用到的小工具:Pocket

在兩個多月前,我們介紹過將文章用 RSS 訂閱集中起來的工具 Feedly。但是,有些時候雖然對文章挺有興趣的,但並不適合馬上看(例如在公司或學校的時候,看到社論、科技新聞等等);或是像我個人平常休息時逛逛一些論壇或是文檔,這時候如果遇到一些比較長的、主題式的文章,就會想要把文章存放起來,等晚些時候再看。

雖然 Feedly 也有提供 Read Later 的功能,只要勾個標籤就可以之後再到 Read Later 的頁面去觀看。但懶惰如我,就會想要把所有稍後再看的文章集中在一塊,因此必須尋找一個前述場景都共用的做法,這時我就遇上了 Pocket。

Pocket 是一個簡單直接的「稍後閱讀」服務,操作方便,只需要擴充套件或分享,和一段能靜下來好好閱讀的時間即可服用。在開始介紹之前,有幾件事必須報告:

……

閱讀全文


菜雞與物件導向 (7): 介面

如果說繼承是用來表明物件「屬於什麼」;那麼介面就是用來表明物件「能做什麼」。

如果說封裝是將物件視作一個整體,是隱藏複雜度;那麼介面就是封裝精神的體現。

如果說多型是指藉著繼承後能實作不同的行為的可能性達到擴展的彈性;那麼介面就是在實作多型。

介面就是這麼厲害,這麼瀟灑。介面就是我大哥,今天誰不服介面,對不起!我們不認識。

介面就像是針對類別的實作、物件的行為去做規定的一個契約書,會先定義好要實作這個介面的類別所必須要有的方法,而當我們建立符合這個介面的類別時,就必須實作出所有介面中定義好方法才可以。……這樣說起來實在太繞口,總而言之介面的核心概念只有一條:

我不在乎你是誰,我只在乎你能做什麼。

還是公司誠徵工程師的例子

由於介面基本上就是封裝繼承多型抽象之大雜燴,所以我們把前面多型的小明小華例子稍微修改來用吧。也就是以公司徵人的方式去理解介面。

介面就像是老闆開出來的要求列表,例如說:要會寫 C#、要會寫 SQL、要會 VB…等等,於是老闆就貼出了徵人啟示,要求新來的員工必須要有 IProgrammer 寫的能力:

public interface IProgrammer
{
    void WriteCSharp();
    void WriteSQL();
    void WriteVB();
}

特別注意和前面多型的例子的不同處,介面只需要先定義好該做的事,裡面怎麼做不需要管;所以只需要宣告要求的方法,不需要撰寫方法本體

於是今天小華就又(?)來面試了,但是他其實並不會寫 C#:

public class Hua : IProgrammer
{
    // Error: Hua 未實作 IProgrammer.WriteCSharp()

    public void WriteSQL() { /* Work */ }
    public void WriteVB() { /* Work */ }
}

這時候編譯器就會跳出錯誤了:很抱歉,你不符合我們 IProgrammer 的規定,因為我們只喜歡訓練精英(略),請你實作完之後再來。否則你就不能掛上我們 : IProgrammer 的頭銜。

……

閱讀全文


菜雞與物件導向 (6): 抽象、覆寫

備註:這邊的抽象是指程式語言中的抽象類別,而非抽象化

抽象的概念很直接,請回想一下前面的例子就可以了:

當我們在用卡牌的例子時,雖然怪獸卡跟魔法卡都繼承了 Card 這個類別,但是我們仍然能
new Card() 來建立一張新卡牌,那…怪怪的吧,這張卡牌到底是什麼呀,空白的卡片嗎?

又或是動物的例子,我們的狗跟貓都繼承了哺乳類,那我們能實例化一個哺乳類嗎?我們的狗跟鳥都是動物,那我們能實例化一個動物嗎?

小明跟小華都繼承了工程師,那我們能 new 一個工程師嗎…?

有些類別就是這樣,它們負責定義共通的那些特性,然而它們本身不應該被實體化成一個物件,這種類別我們就應該把它們標記為抽象類別

抽象類別在 C# 裡用 abstract 這個修飾詞來表示,可以加在類別或方法上。例如 abstract class Animal 就代表動物這個類別是個抽象類別,它不能被實例化。

而當加在方法上時,例如 public abstract void Eat() 就是代表這個進食的方法無法被叫用,只能由繼承者去重新定義這個方法。

abstract class Animal
{
    public string color { get; set; }
    public abstract void Eat();
}

那麼繼承者們,也就是衍生類別如何去重新定義父類別的方法呢?

所謂「欲戴王冠,必 Override」,這時候就必須使用覆寫(override

覆寫是指對於像是前述的抽象方法時,在同名的方法前加上 override 關鍵字就可以讓程式知道你要覆寫這個方法(你不覆寫的話,編譯器還會生氣)。

例如前述的 Eat,狗就可以用 public override void Eat() 的方式去覆寫吃東西這個方法:

public class Dog : Animal
{
    public string color { get; set; } = "Black";
    public override void Eat()
    {
        /* 嚼嚼嚼 */
    }
}
……

閱讀全文


菜雞與物件導向 (5): 多型

多型算是比較三特性之中給人感覺比較溫和的了,不如說只要有了繼承,那麼多型的到來就是必然的。多型的定義是:不同的物件能夠做出一樣的行為,但必須由他們自己的程式碼來實作。

白話一點說就是:一樣的事,不同做法

多型相對是比較好理解的,畢竟每個人做同一件事的方法本來就不太一樣。例如一樣是泡奶茶,英國就正常地泡,美國就會用微波爐;一樣是肉粽,有些人就是比較愛吃油飯;到了程式的世界裡也是,即使繼承了同一個物件,實現這個行為的方式也可以不同。

在此可以先推菜鳥工程師肉豬的這篇 Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) 中的說明。其中可以從例子看到儘管狗跟鳥都是繼承自動物這個類別,但對於「移動」這個方法,他們實作的方式並不一樣。這就是多型的範例。

我個人更喜歡用職位的方式去理解多型。

被繼承者就像是該職位的工作,例如說:Programmer 要會寫 C#、要會寫 SQL、要會 VB…等等,於是老闆就貼出了徵人啟示,要求新來的員工必須要有 Programmer 寫的能力:

public class Programmer
{
    public virtual string WriteCSharp()
    {
        /* 努力地寫 C# */
    }
    public virtual string WriteSQL()
    {
        /* 努力地寫 SQL */
    }
    public virtual string WriteVB()
    {
        /* 努力地寫 VB */
    }
}
……

閱讀全文


菜雞與物件導向 (4): 繼承

接著要介紹的是繼承 aka 物件導向三大特性之王 aka 濫用榜 Ko.1 ,繼承的強大幾乎和它的惡名一樣可怕,給一個從聊聊程式的這篇 [心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性 摘過來的例子就可以略知一二了:

什麼也不做,僅僅只是繼承而已,就取得了繼承對象(C# 中稱為基底類別)近乎全部的內容,真是太可怕了。在 C# 中,繼承可以取得基底類別除了 Private 以外所有的內容,例如 Protected 更是表明就是只給繼承使用的。

由此可見,在減少重複程式碼的路上,繼承無疑達到了全新的高度。

那麼繼承代表的是什麼意思呢?大多的網站都能直接說明:繼承是一種「is-a」的關係。當你能說出A是一個B的時候,就代表你認為A可以繼承自B

最直覺的繼承例子就是物種的分類。舉例來說,狗跟貓都是哺乳類,因此他們都可以繼承到一些哺乳類共通的特徵(例如哺乳、用肺呼吸)。藉由繼承,我們可以把這些哺乳類共有的特徵全部放在哺乳類這個物件,再由狗和貓分別去繼承哺乳類,藉此讓他們都能得到哺乳類的特徵,再進一步發展出自己的特徵和行為,甚至重新定義基底類別的方法為自己所用。因此,像大話設計模式就將繼承說明如:繼承者是對於被繼承者的一種特殊化。

如此一來,當我們需要修改哺乳類的定義的時候,只需要修改一個地方,而繼承了哺乳類的這些物件(C# 中稱為衍生類別)全都能夠一起修改到,大大地減少了跑來跑去修改的次數,也讓程式碼的重複大幅度地減少

然而也因為如此,繼承最大的惡名出現了:繼承享受了取用基底類別內容的好處,卻也必須背負牽一髮動全身的風險

……

閱讀全文


菜雞與物件導向 (3): 封裝

封裝包含了兩個重要的觀念:

  • 控制物件和外部進行互動的出入口
  • 隱藏物件內部的細節資訊

強者我同事整理的文章裡的例子就舉得不錯:當你按下鍵盤的A鍵,螢幕隨即出現了A,你不必知道中間發生了什麼事,你只需要知道怎麼操作和最後得到什麼就可以了。

其中鍵盤提供的按鍵,就是我們對電腦進行互動的出入口;而電腦實際上做了什麼事情,也被隱藏了起來,讓我們只需要關注結果就好。

此外我也看到過販賣機的例子,當你去販賣機買飲料,你也不需要知道裡面的構造,只要知道你選了飲料投了錢,飲料就會跑出來就行

從上面的兩個例子,相信大家已經掌握到封裝的概念了:將物件視作一個整體,把內部的實作內容隱藏起來,讓使用者只需要知道怎麼使用這個物件即可。(相似的思路,我們後續的介面會再提到)

如果封裝做得夠好,除了可以將程式碼整理得井井有條以外,也能讓物件內部的修改不會直接影響到使用物件的地方,達成了降耦合的目標

並且也能讓物件的使用者直覺地知道如何使用物件提供的方法,如此使用者就可以專注在更高層次的抽象,而不用被物件內部的細節所干擾。

最後,從上面的敘述中我們可以察覺到要實現封裝,最重要的就是:對外的開放程度(存取範圍)的控制。或是套一句前輩的說法:給程式碼隱私的空間

補充:如果想問「什麼是耦合?」的朋友,建議可以看看這篇:實務上的高內聚與低耦合

或是參照本系列後續的 內聚與耦合

存取範圍與存取子

先讓我們從存取範圍開始說起吧,因為我個人慣用的是 C#,因此就介紹一下 C# 是怎麼控制存取範圍的。

在 C# 之中,類別裡控制可見度是使用修飾子來定義存取範圍,也就是當我們替類別宣告欄位時常看到的 PublicPrivate

  • Public: 這是公開的,所有人都看得到
  • Private: 這是私有的,只有自己看得到

除了最常用的這兩個以外,還有其他的修飾子可以先知道一下:

  • Protected: 這是受到保護的,只有自己和繼承的孩子們看得到
  • internal: 這是內部的,只有身為同一個組件的朋友們看得到
  • Protected internal:組合上面兩個,也就是可以給同個組件的朋友們,或是其他組件繼承的孩子們看見

接下來的部分會以最常見的 PublicPrivate 來繼續說明,對存取範圍的這些修飾子有興趣的朋友,可以參照 存取範圍層級 的說明。

現在我們已經知道了有哪些修飾子可以用來控制存取範圍,但為什麼我們會需要宣告存取範圍的大小呢?其根本是為了將控制權掌握在物件本身

就像大話設計模式比喻的:物件就像間房子,我們不希望被看光光,可以看見的 Public 就像門和窗,而不該看見的 Private 則是用牆壁隱藏起來,而對於這間房子而言,門窗是可以控制的。

……

閱讀全文


菜雞與物件導向 (2): 建構式、多載

我就直接說了,有用前面的程式碼區塊的人,絕對執行不了。因為我們建立哥布林和戰士這兩張卡片的時候,根本就沒有給他們數值呀!

雖然可以先呼叫出來再賦值…

var goblin = new Card();
goblin.Name = "哥布林";
goblin.Attack = 3;
goblin.Health = 2;
/* ...其他賦值 */

這實在相當占空間,也有點奇怪。畢竟如果是阿福(狗),一出生的時候應該就確定了一些特徵才對,例如品種、血型、眼睛顏色這種。並不會出生後過一陣子,才突然決定這些東西,既然如此,我們在產生物件的時候,當然也會希望在建立的同時就先決定好一部份內容

這時候我們就可以藉由建構式的方式,在建立物件時就進行一些我們想要的操作。

建構式

事實上,當我們呼叫 new Card() 的時候(不覺得這個 () 很有呼叫方法的感覺嗎?)我們就是正在調用 Card 的建構式。而當我們沒有特別去定義建構式的時候,就會直接使用內建的建構式去幫我們產生物件。

現在我們替 Card 新增一個建構式:

public class Card
{
    public Card (string name, int attack, int health)
    {
        this.Name = name;
        this.Attack = attack;
        this.Health = health;
    }
    /* ... 其他屬性和方法 */
}

在 C# 的時候,建構式必須和類別同名,且不需要定義回傳類型。當我們有了建構式,剛剛的例子就可以改寫成:

var goblin = new Card(name: "哥布林", attack: 3, health: 2);
var warrior = new Card(name: "戰士" , attack: 4, health: 3);
warrior.Hit(goblin);

建構式也就是建立這個物件時執行的函式,通常會用來進行初始化,也就是做一些建立物件必要的準備。例如傳遞必要屬性或是建構需要的其他物件、或是給予私有屬性初始值等等,例如說我們的卡牌一建立,就會需要知道它的名字和戰鬥力,這樣才有卡牌的感覺,而不該像一些 壞決鬥者 邊打牌邊偷偷印卡。

……

閱讀全文


菜雞與物件導向 (1): 類別、物件

直覺上你當然知道什麼是物件;物件就在你的身邊。

汽車、iPhone、收音機、吐司機、廚房用具等等,你說得出來的都是。

  ——《深入淺出學會編寫程式》

什麼是物件?一切都是物件。

物件導向試圖讓抽象的程式碼,更貼近於我們的實際生活,其認為一切是由各式各樣的人事物互動所組成的,因此有了物件這個共通、最基本的概念。

假設現實世界存在一頭狗,叫做阿福。而我們想要在虛擬世界裡表達「有一隻叫做阿福的狗」這件事

這時候就要在系統裡有一個能代表「阿福(狗)」的東西存在,也就是「阿福」這個物件。

關於「把現實世界的物件,抽象化成程式世界裡的物件」的邏輯,可以參考 一個語言如果不改變你的思考方式,就不值得學?談程式語言的本質 這篇,尤其是選擇保留哪些資訊的部份我認為描述得很好。

現在我們知道,物件就是用來在虛擬世界中代表「某個特定的東西」,例如說叫做阿福的狗就是一個物件,阿福今天晚上要吃的飼料罐也是一個物件。

理解物件的概念是相當直覺且迅速的,畢竟你我身邊有著數不清的東西,它們都是一件一件的物件,但這樣的理解還不夠明確。

就像前文所引的 談程式語言的本質 文中所提到的,在抽象化的同時我們必然要選擇保留哪些資訊。

例如說阿福這隻狗,是一個物件;飼料罐也是一個物件

而這些物件之間還會彼此互動,例如說阿福是一隻狗,而我們觀察到狗都有吃東西這個動作,例如「阿福吃了飼料」

同時物件也會有一些專屬於它的特徵,例如說阿福是黑色的,我們就知道狗有毛色的差別。

那麼我們要怎麼表達「阿福」作為一隻「狗」擁有的那些動作和特徵呢?狗的毛色?狗可以吃飼料?

我們需要選擇怎麼去描述「狗」--也就是阿福這隻狗,被我們抽象化後的樣子,我們需要將它用程式碼的方式定義出來。

這時候我們就會需要類別,來定義出我們觀察到同一類的物件該有哪些特徵和動作,也就是我們替物件「分門別類」後、篩選出特定資訊的抽象化結果

延續前面的例子,假設今天我們從阿福身上觀察到進食跟毛色兩個狗的重要資訊,我們就可以建立類別 Dog:

public class Dog
{
    public string color;
    public void Eat(IFood food) { /* 進食與消化之類的 */ };
}

藉由我們定義的類別,就可以從類別中實例化(=建立)出物件。

也就是說,現在我們終於可以用「狗」這個類別,來表達出我們需要的「阿福」了:

Dog afu = new Dog(); // 阿福是一隻狗
Console.Write(afu.Color); // 阿福是黑色的
afu.Eat(food); // 阿福會吃食物
……

閱讀全文


菜雞與物件導向 (0): 前言

在訂便當告一段落之後,其實就已經和同事約好要來整理公司新訓的筆記。但儘管已經到職快一年了,有些工具已經在專案碰過好幾次。但遇到需要跟朋友討論,或是聽前輩說明觀念的時候,還是不自主地會想「我真的懂嗎?」故一直是挺畏懼的。

但幸虧同事的鼓勵和以身作則,最終還是開啟了這個新系列,決定直接開坑把當初前輩新訓指導過的部分整理下來,也算是讓自己能趁著這機會好好複習一番,把自己的想法跟心得記錄下來。

另外,如果你是真心希望弄懂物件導向的朋友,這邊推薦《大話設計模式》的附錄,內容對物件導向的介紹清晰易懂且循序漸進,非常適合作為了解物件導向的起頭。

本篇的段落將會分成以下幾個部份,由於只是筆記一下,因此會附上一些知識點的參考資料,看見的時候可以先行閱讀;末尾也會附上有關的參考資料及文章,對於這類概念性的東西,一向是推薦多方閱讀以增強理解,就像保哥寫的:「如果你問 100 個人這個問題,可能會得到 200 個答案,所以你一定要有自己獨到、有自信、精闢的見解或描述方式。」如果有寫得不錯的文章想推薦給我,或是有地方需要補充和指證,還請不吝指教。共勉之。

……

閱讀全文