「聰明」與「自作聰明」的 code

  1. 短碼
  2. 過度抽象語法表現具體的事
    1. 該用擁有的情況,使用繼承
    2. 該用繼承的情況,使用 template
    3. 該用繼承的情況,使用 delegate
    4. 要懂用語法內含語意來得取捨
  3. 非必要時使用 pointer
  4. 說的有點難懂
  5. 參考資料

良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。


Photo by Alec Foege on Unsplash 使用巨石陣圖是對《人月神話》, Ch15 致敬

你是否知道,眼前的這一切是什麼作用?
你再用心、再用力一點,花點心思,是否可以看出一點端倪

沒有任何說明,也沒有任何文件的高科技精品,也就就像現在人觀看古文明一樣,充滿待人解開的謎團。(以後問人家「你在寫什麼糙 code 」可以用「你怎麼做了一個巨石陣(或金字塔)」取代(誤))

很多糙點本身,其實是來自同事的才能,他有用心而不是不用心。但是用心的地方,總是充滿創意,只是難懂!難懂就會讓人覺得糙,這種心思不被懂,雙方都無奈呀

短碼

這是一個來自 1988年 第五屆 國際混碼大賽的 westley 寫的[1]

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

它的它的美好,但是請止於研究與自我滿足。
不要在協作時,展現這份美好

甚至於有日本人出書在介紹[2]

用文學來譬諭短碼,就像是華麗詞藻

  • 「手機」有「噩耗宛如春雷般晴天霹靂地打響了悲哀的黃鐘」
  • 描述「捨不得他人捨不得」有「不是我自居聖人,將自己優雅地安置高處;也不是因為我刻意美化自己,要自己鶴立雞群;更不是因我過度的浪漫,脫離世俗,悠然地嘲笑他人的愚昧」
  • 描述「捨不得捐出藏書」的心情「世界恰似一座巍峨輝煌的宮殿,眾多瑰綺珍寶,鵝黃,鴉綠、紫豔、浮金……樣樣五光十色,耀人耳目,令人款款眷戀」。

別這樣,好嗎?

過度抽象語法表現具體的事

幫狗和貓…(假設有 n 種)類別加上跑步的需求

class Dog {
public:
  int location;
  Dog (const string& name): location(0) {}
  // void run (int distance); <- 想要具備的功能
};

class Cat {
public:
  int location;
  Cat (const string& name): location(0) {}
  // void run (int distance); <- 想要具備的功能
};

該用擁有的情況,使用繼承

繼承,只有兩種使用方式,小心使用,以及禁止使用
繼承會增加程式複雜性
如果繼承符合 Liskov 替代原則 (LSP) 就可以降低因繼承而衍生的複雜性
–《Code Complete 2/e》, Ch6 Working Classes, 6.3 Design and Implementation Issues, Inheritance (“is a” relationships)

之前《Code Complete 2/e》的讀書會的討論中有個小小的精要心得:

  • 要介面 → is a → 繼承
  • 要實作 → has a → 包含 (用 property)

在這個例子,適因為想要的是 run() 所以適合使用繼承

該用繼承的情況,使用 template

用泛型 function
讓你們通通可以用泛型的「跑步魔法」來移動距離

template<typename Animal>
void run(Animal* animal, int distance) {
    animal->location += distance;
}

main()

Dog *lucky = new Dog("lucky");
run(lucky, 20);
cout << lucky->location; //20

但是,這時應該使用父類別來繼承,利用物件導向的語意來實作會更適合。
這種情況,常見於資料表的操作,有些資料表具備某個共同的功能,就寫一個抽象到不行的來套用,完全不管語意的問題,這樣協作的伙伴,怎麼透過語言本身來了解這樣巧妙之處呢?

這就是就是華麗詞藻!!

該用繼承的情況,使用 delegate

就…C# 的用法。

和短碼一樣,這些都是屬於炫技的用法。而不是為了夥伴而寫的 code 。未來會讓你召來負面情緒,也是自己造的…(XD)

要懂用語法內含語意來得取捨

這些要有適合的取捨,就要看開發者是否了解語法中,內含的語意與語言表達極限。

物件導向,不適合用來描述非同步問題,也許這樣的情況之下,才適合使用泛型或 delegate,而 JavaScript 將這兩個用法視為一般用法,反而不強調物件導向,原因也許是 JavaScript 天生就是為了非同步問題而生。

非必要時使用 pointer

這指的是 C++ 的時候,常見的問題
有時,在 class 裡的 member variable,也沒有繼承關係,但是硬生生的就是用了 pointer 。其實,不用 pointer 也可以做到一樣的事情時,盡量別用 pointer ,在 C++ 中沒有垃圾搜集器,所以忘記 delete 就真的一直存在湯瑪森[4]了。

class A
{
  int* m_ptr_i;
public:
  A():m_ptr_i(new int()){
  
  }
  
  ~A(){
    delete m_ptr_i;
  }
};

C++ 的初衷之一也是想要開發者隱藏 pointer 這個複雜度。
一般的宣告方式,變數會隨著類別啟動解構式時,自動消滅,這個特性可以用來隱藏一些「容易忘記」的相對做法。

例如:

  • pointer 的 new/delete
  • critical section 的 lock/unlock[5]

說的有點難懂

寫硬體描述語言時,就有聽過

  • 控制與運算要分開
  • 控制和資料要分開

寫軟體時,就聽說更多的原則

  • 資料和運算要分開
  • 介面和運算分開

這麼說吧!
能用最簡單的語法表達,就用簡單的語法。
能拆分權責就拆分權責。
能寫成程式碼就不要用註解。
能少給權限,就少給權限。

再不行就常常 code review 吧!
高手同事說 ok 就是 ok 滴!!! (好糙的標準!!)

參考資料

[1]: 5th International Obfuscated C Code Contest, 1988 - westley.c
[2]: Short Coding 寫出簡潔好程式-短碼達人的心得技法
[3]: 教育讓作文變得矯情:真正的「語言癌」,在國中會考的答案卷上
[4]: 台北路上觀察學會, 【主題2】印象最深的觀察, 小知識 * 何謂湯馬森?
[5]: API Design for C++