今年,專業上:

  • 開了這個 blog
  • 看完《Effective C++》(跳過 template 就是了,那塊太不熟根本看不懂)
  • 《深入淺出物件導向分析與設計》一到九章(剩一章,耶!)
  • 正在看《程式設計師的自我修養──連結、載入、程式庫》
  • 邊做邊學 Ruby on Rails
  • 對 C++ 有再熟一點,吧?
  • 設計架構崩崩中
  • 有比較會切細工作,吧?
  • 多會一點 Git

2015 年目標:

  • 吃掉《深入淺出物件導向分析與設計》
  • 啃掉《程式設計師的自我修養──連結、載入、程式庫》
  • 看《深入淺出設計模式》
  • 看《Clean Code》(寫到這裡我在想是不是在找死?)
  • 寫個 Ruby Gem
  • 練習 OOAD
  • 練習「想清楚再做」跟「避免分析癱瘓」(有時候會一直在那裡想然後做不了決定 X_X)
  • 找些 open source project 來 follow(好像有點抽象)
  • 填補理論跟實務之間的空虛地帶(具體上還沒決定,先列些主題)
    • OS
    • Compiler
    • Multi-Thread Programming

我也不知道寫這些目標會不會照著做,很具體的應該會,寫得越抽象的越容易歪掉就是了。明年底再來看哪些有做到、哪些歪掉、又有什麼是吃飽太閒在亂玩。:P

一個檔案 include 另一個檔案時,這兩個檔案便形成 compilation dependency。一個被 include 的檔案一旦修改,跟它有 compilation dependency 的檔案(無論直接或間接 include)都需要重新 compile。假設 A.h include B.h,當 B.h 改變或 B.h include 的檔案改變時,所有 include A.h 的檔案都需要重新 compile。

應該盡量降低 compile dependency,以減少修改一個 header 而需要重新 compile 的檔案數量,免得改個檔案全世界都要重新 compile。(工程師無數青春在哭泣)

原則上盡量以 declaration 的 dependency 取代 definition 的 dependency

  • 可以用 object pointer 或 object reference,就不要用 object。

    可以在 class 裡宣告 pointer 或 reference 作為 member,就不要定義 object 當 member,因為定義 object 就需要該 class 的 definition。

  • 分開 class 的宣告與實作

    讓 include(提供 definition)可以從 header file 移到真正使用這些 class、function 的 cpp file。

    • Handle class

      使用兩個 class,一個代表宣告,一個是真正的實作。前者通常稱為 Handle class,只宣告 function 並有個 pointer 指向後者。外界使用 Handle class,而 Handle class 會轉 call 真正 implement 的 class 達到功能。這個方式用到第一點的概念──避免使用 object,改用 pointer。

      因為兩個 class 會有相同的 function,會造成同時 maintain 兩份類似 class 的問題。

    • Interface class(abstract base class)

      interface class 沒有 member data、沒有 constructor,只有一 virtual destructor 跟一堆 pure virtual function。實作的 class 繼承 interface class 進行功能實作,以 virtual function 的機制讓外界用 interface class 的 pointer 使用。

    這兩種方法的 trade off 是速度會慢一點、多用點 memory,不過普遍上來說好處比 trade off 重要。

Ref

  • 《Effective C++》item 31

program by constract(按契約程式設計)就是寫 code 跟用那份 code 的人說好,這些 function 要做什麼、負責什麼、使用的人要怎麼用,大家照說好的去做。

defensive programming(防禦性程式設計)則是大家互相不信任,會做許多檢查以確保自己不會當掉、使用的人用錯也不會當。defensive programming 會產生許多用於檢查的 code,讓 code 變得冗長、效率也可能較差,所以一般建議在 input 要進入系統時做檢查,系統內部互相 call 時就不再做檢查。當檢查發現問題、出現不該出現的錯誤時,較建議盡快讓程式 fail 好找出錯誤,而非忽略錯誤、繼續讓系統執行下去。

Ref

Linux 本身沒有 thread 的概念,它把 process 跟 thread 都視為 Task

fork() 用來生 child process,生出來的是概念上的 process。

clone() 可以生出跟 parent process 共用 memory space、file descriptor 的 table、signal handler 的 table 等東西的 child process,也就是概念上的 thread。Linux 用 clone() 實作 thread 概念。

clone() 有 glibc 包過的:

1
2
3
4
5
#include <sched.h>

int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

也有純 system call:

1
2
3
long clone(unsigned long flags, void *child_stack,
void *ptid, void *ctid,
struct pt_regs *regs);

UML class diagram 只是個概觀,不會說明所有細節(例如如何實作),可以為需要建立的 class 進行 modeling。

用一張圖解釋:

繼承

1
2
3
4
class Parent
class Child

Child -up-|> Parent

實作(implement)

1
2
3
4
interface Interface
class Concrete

Concrete .up.|> Interface

class 間的關係

抽象上來看 class 之間的關係,程式語言裡不見得有對應的語法,但是釐清抽象上的關係有助寫 code。只要對系統來說是合理的,class 間的關係可能因為系統的演進而改變。

關聯強度:composition > aggregation > association

Association

class 間有關係但關聯性較弱。

1
2
3
class Foo
class Bar
Foo -right-> Bar

class Foo 知道 class Bar

Aggregation

class 間是 has a 的關係。class A 包含 class B,但 B 不是 A 的一部分。

main object 不負責 composed object 的 create 及 destroy,composed object 可以獨立於 main object 之外。例如車子跟輪胎間的關係(Car has a Tire),輪胎可以獨立於車子之外(可以從車子上拆下來)。

UML 以空心菱形表示:

1
Car o-right- Tire

Composition

class 間是 own 的關係。(碎念:own 跟 has 根本沒辦法用中文區分…)

main object own composed object.

composed object 是 main object 的一部分(a part of),composed object 不會獨立存在於 main object 之外。main object 負責 composed object 的 create 及 destroy,main object destroy 時 composed object 也會 destroy。例如生物跟細胞間的關係(Animal owns Cell)。

UML 以實心菱形表示:

1
2
3
4
class Animal
class Cell

Animal *-right- Cell

composition 跟 aggregation 最大的不同在於 composed object 的生命週期是否與 main object 有關。

Dependency

class 間是依賴關係時,UML 以虛線箭頭表示:

1
2
3
4
5
6
class Animal
class Water
class Air

Animal .right.> Water
Animal .left.> Air

這個原則跟何時該使用繼承有關:derived class 必須能替代 base class。

使用繼承時必須確保 base class 的所有特性及 method 對 derived class 仍成立,反過來說,derived class 必須有 base class 所有特性。一個 derived class 的 object 是一個(is-a)base class 的 object,原本對 base class 的 object 所做的所有操作可以直接換成對 derived class 的 object 操作。

C++ 的 public 繼承在意義上即是 LSP 所說的繼承觀念。

繼承的替代方案

使用繼承一般是希望擴充原有 class,但如果套用繼承時發現會違反 LSP,有什麼替代方案呢?

class 應該只負責一件事、只有一個改變的理由,class 聚焦於實作該責任。避免一個 class 有多重責任,或者一個責任分散在多個 class 中。SRP 的主要目的是增加 class 的 cohesion(內聚力)。

OO 中會以 class 為單位,但可以提升到 process level。舉例來說,將多個 process 啟動與結束的控制分散在各個 process 以及 watch dog,就讓「控制 process 的啟動與結束」這個責任分散在多個地方。這會導致較難理解控制流程、不知道某個時間點是誰在控制,修改時也容易出 bug。

一個 class 可以負責一個「大」責任。運用 SRP 應該會讓系統中的 class 比較少但比較大,因為會將功能性集中在負責該功能的 class。在思考一個 class 應該負責什麼時,用較抽象的方式去想會比較好,想這 class 要做什麼?它的名稱顯示它該負責什麼?某件事究竟關不關它的事?

我覺得區分 class 的責任蠻困難的,取決於設計者怎麼想、怎麼看待一個 class 以及其想法的合理性。

SRP 分析

分析一個 class 是否符合 SRP。把 class 跟 method 名稱套到以下句子,用人類語言看看通不通順。

The [class] [method] itself.
The [class] [method] [arg] itself.
該 [class] [method] 它自己
該 [class] 自己 [method]

想起來順表示該 method 屬於這個 class、符合 SRP,反之則表示這 method 應該移到其他地方。

分析時要注意參數可能在 construct 已先傳入,不一定是 method 的 argument。

名稱就說明了它的意思──不要重複。

這也是概念上的原則。不僅限於不重複相同的程式碼,也包括不要重複相同的程式碼、相同的功能(例如多個軟體間不重複相同的功能)等等,確保一件事情、一項資訊只存在一個地方。

遵守這個原則的好處是當一件事情需要修改的時候,只需要修改一個地方,不會因為需要改很多地方而漏掉、造成問題。

DRY 也跟如何好好分解系統有關──如何將每項資訊、功能、程式碼放在單一且合理的地方。

Software entities such as class, modules, functions should be open for extension, but closed for modification.

直接英翻中就是「對擴展開放,對修改關閉。」

換句話說,允許擴展但不允許修改原有的 code。在現有程式運作得好好、通過測試的狀況下,應該以擴展的方式新增系統功能,而非修改原有的 code。OCP 著重於軟體彈性,遵守這原則能在增加新功能時確保原本行為不會被更動,減少在原本運作良好的部分產生 bug 的風險。不過遵循 OCP 可能會引入新的抽象層、增加 code 的複雜度,所以並不是整個系統所有地方都該遵守,而是找出最有可能改變的地方才使用 OCP,沒必要的地方使用也是種浪費。

OCP 是設計層次上的原則。

舉個例子:較 high level 的 class 應該以 interface 使用較 low level 的 class,而非直接使用 low level 的 class。如此需要支援其他種 low level 實作時,便能直接實作 interface 並修改產生 instance 的地方,即可做到「不修改原本 high level 的 code,而能擴充 low level 實作」。

另外像瀏覽器的 plugin、Linux kernel 的 module(即使 C 語言不是所謂的物件導向語言,依然能以物件導向的概念撰寫)同樣都運用了 OCP。

原本想說既然在 Windows 上開發,用 Visual Studio 好了,但打開網站看到價格,呃,我還是繼續用 Eclipse 跟 Qt Creator 吧!謎版?找謎版的時間我都可以處理好了…

Install Windows SDK

安裝 Windows 7 的。

Microsoft Windows SDK for Windows 7 and .NET Framework 4

Troubleshooting

裝 Windows SDK 失敗,像這篇的狀況。移掉 Microsoft Visual C++ 2010 redistributable packages 後再裝 Windows SDK 就可以了。

compile 時遇到 error C2059: syntax error : 'constant'patch

Eclipse

Install CDT Visual C++ Support

在 Eclipse 裝 CDT Visual C++ Support,才有 Visual C++ tool chain。我的 Eclipse 是 Juno。

CDT site:http://download.eclipse.org/tools/cdt/releases/juno

Configuration

Project property

C/C++ Build -> Tool Chain Editor 的 toolchain 設為 Microsoft Visual C++。

C/C++ Build -> Environment 增加 PATH C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin,這樣才找得到 Visual C++ compiler。

C/C++ General -> Paths and Symbols -> Includes -> GNU C++

Eclipse Configuration

Qt Creator

因為想到要有 GUI 介面,所以需要 Qt + Windows SDK,但現在 Eclipse 跟 Qt 的整合不太好,改用 Qt Creator + Windows SDK。

直接裝 Qt 5.3.1 for Windows 32-bit (VS 2010, OpenGL, 537 MB)

Kits

如果已經先裝其他版本的 Qt,應該可以在選項裡加 Kit:

Qt Creator Windows SDK Kits

pro setting

INCLUDEPATH 要加 Windows SDK 的路徑,如 Eclipse Configuration 圖中的路徑。

Troubleshooting

compile 時遇到 LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt 的錯誤。

Solution:http://qt-project.org/wiki/Category:Tools::msvc