C compiler 編出來的 object file 可以跟 C++ compiler 編出來的 object file link 在一起。

假設 Linux 環境下有一個 object file foo.o 是 C compiler 編出來的,可以將 foo.o 看作一 C library,而 main.cpp 是由 C++ 實作並且使用 foo.o 裡的 function,因此 main.cpp 會 include 內含 function declaration 的 foo.h。C++ compiler 在一般狀況下會將 foo.h 內的 function 當作 C++ function 以 C++ 的規則處理。

由於 foo.o 是 C compiler 編出來的,其對 function 的處理方式不像 C++ 會額外加修飾,使得 link 時 main.ofoo.o 中 symbol 對不起來而產生 undefined reference 錯誤。在 foo.h 中加上 extern "C" 是告訴 C++ compiler 要把這段 function declaration 當作 C function 處理,也就是不做 name decoration,之後 link symbol 才對得起來。實例如下:

foo.h
1
2
3
4
5
6
7
8
9
10
// foo.h
#ifdef __cplusplus
extern "C" {
#endif

void foo();

#ifdef __cplusplus
}
#endif
foo.c
1
2
3
4
5
6
7
8
// foo.c
#include <stdio.h>
#include "foo.h"

void foo()
{
printf("C foo()!\n");
}
main.cpp
1
2
3
4
5
6
7
8
9
10
// main.cpp
#include <iostream>
#include "foo.h"

int main()
{
std::cout << "C++ main" << endl;
foo();
return 0;
}

foo.o 的 symbol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> gcc -c foo.c
> readelf -s foo.o

Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS foo.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 SECTION LOCAL DEFAULT 7
7: 00000000 0 SECTION LOCAL DEFAULT 8
8: 00000000 0 SECTION LOCAL DEFAULT 6
9: 00000000 20 FUNC GLOBAL DEFAULT 1 foo
10: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts

一般狀況的 main.o symbol(重點在 18):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> g++ -c main.cpp
> readelf -s main.o

Symbol table '.symtab' contains 23 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS main.cpp
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 1 OBJECT LOCAL DEFAULT 4 _ZStL8__ioinit
6: 00000000 0 SECTION LOCAL DEFAULT 5
7: 00000039 64 FUNC LOCAL DEFAULT 1 _Z41__static_initializati
8: 00000079 28 FUNC LOCAL DEFAULT 1 _GLOBAL__sub_I_main
9: 00000000 0 SECTION LOCAL DEFAULT 6
10: 00000000 0 SECTION LOCAL DEFAULT 9
11: 00000000 0 SECTION LOCAL DEFAULT 10
12: 00000000 0 SECTION LOCAL DEFAULT 8
13: 00000000 57 FUNC GLOBAL DEFAULT 1 main
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
15: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
16: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait
17: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E
18: 00000000 0 NOTYPE GLOBAL DEFAULT UND _Z3foov
19: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
20: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
21: 00000000 0 NOTYPE GLOBAL DEFAULT UND __dso_handle
22: 00000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit

foo.hextern "C"main.o symbol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>readelf -s main.o

Symbol table '.symtab' contains 23 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS main.cpp
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 1 OBJECT LOCAL DEFAULT 4 _ZStL8__ioinit
6: 00000000 0 SECTION LOCAL DEFAULT 5
7: 00000039 64 FUNC LOCAL DEFAULT 1 _Z41__static_initializati
8: 00000079 28 FUNC LOCAL DEFAULT 1 _GLOBAL__sub_I_main
9: 00000000 0 SECTION LOCAL DEFAULT 6
10: 00000000 0 SECTION LOCAL DEFAULT 9
11: 00000000 0 SECTION LOCAL DEFAULT 10
12: 00000000 0 SECTION LOCAL DEFAULT 8
13: 00000000 57 FUNC GLOBAL DEFAULT 1 main
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
15: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
16: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait
17: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E
18: 00000000 0 NOTYPE GLOBAL DEFAULT UND foo
19: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
20: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
21: 00000000 0 NOTYPE GLOBAL DEFAULT UND __dso_handle
22: 00000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit

盡量不要讓看過的書像船過水無痕,嘗試用自己的話做小結。

這章的重點在:What are the basic qualities of a good API?

Model the problem domain

API 是用來解決某個問題的,問題可大可小。

API 要能對問題提供抽象化的概念,並且能夠將這抽象概念以 interface 表達出來。以 C++ 來說,我的理解是用 class name 以及 function name 表達抽象概念,也就是使用 API 的人應該要能從 class 及 function 看出抽象概念及使用邏輯。

一個問題沒有絕對正確的抽象化方式,重點是 API 要保有某種一致性及邏輯。

如果用物件導向來做 modeling 就會用 object modeling,也就是會定出 object、每個 object 做什麼、object 之間的關係與互動。一個 class 應該要定義為「做什麼」(what)而不是「怎麼做」(how)。

Hide implementation details

隱藏所有實作細節。如果沒藏好,以後要改實作,使用者可能跟著改到崩潰然後就不想用你的 API 了。(無誤)

interface 訂得好,底下怎麼搞都沒關係,改 interface 比較會影響到使用者。

隱藏實作細節的方法:

  • Declaration & Definition
    盡量在 header 只寫 declaration,implementation 放在 cpp。
  • Encapsulation
    • 請把 member 藏起來,外面需要它們的話使用 getter & setter。
    • 隱藏 class 內部才使用的 implementation method 們,API 使用者不需要知道 API 實際上如何實作出功能的。
    • 隱藏 class 內部才使用的 implementation class 們。
    • 所謂隱藏就是設成 private。

PS:這裡說的使用者是指使用 API 的人,通常是其他 programmer 或自己,不是使用軟體的一般使用者。

Minimally complete

API 功能要完整,可以滿足使用者需要的所有功能,要盡可能小但無法再更小了。

要小心 virtual function 可能會公開過多 function 給使用者。不過我還沒很懂這意思…

Easy to use

看到這裡總覺得在軟體設計上到處都有這句話,只是層次不太一樣。在 application 層級上,easy to use 希望的是一般使用者的好用。而在 API 層級上則是針對 API 使用者──通常是 programmer。

  • Discoverable
    使用者光看 API 就能自己心領神會、找出如何使用。
  • Difficult to misuse
    難以誤用,舉例:傳三個 enum Year、Month、Day 當參數比傳三個 int 不容易誤用。《Effective C++》好像說過同樣的話…
  • Consistent
    API 設計的一致性
  • Orthogonal
    • method 之間沒 side effect,例如 call 改變某屬性的 method 不會動到其他屬性。
    • 修改 API 的部分實作也不會影響到其他部分。
    • 做法:保持一種資訊只有一個地方有,不要到處 copy-paste code。盡量封裝,避免一個變數到處都可以 access 以致改了 A 可能就動到 B 的行為。
  • Resource allocation
    • 用 smart pointer 管理 memory。
    • 其他種類的 resource 也可以用 class 加以管理,object 的 construct 是 allocate resource,destruct 則是 release resource,通常稱為 RAII(Resource Acquisition Is Initialization)。舉例:Qt 的 QMutexLocker。
  • Platform independent
    避免在公開的 header 用針對特定平台的 #if#ifdef,例如 #ifdef _WIN32

鬆散耦合

跟 OO 的原則一樣,class 之間、module 之間不要黏太緊。

簡單看兩個 component A 跟 B 黏得緊不緊的方式是看改了 A 後 B 會不會改很多、A 看得到 B 多少東西(如只看得到 public function 還是也看得到 private member 等等)。

要避免兩個 component 互相依賴變成 dependency cycle,不然想用 A 就一定要有 B,但邏輯上可能根本不需要 B,看起來就很怪。

這邊提兩種鬆散耦合的方法:Manager class 跟事件通知。

Manager class

Manager class 會擁有並管理某些 class,例如與 output 有關的 class。

如果有一個 Manager class 管理 N 個 output class,其他 M 個 需要使用 output class 的 class 可以不需要跟所有 output class 都有關係(這樣會有 N * M 個關聯),大家可以只跟 Manager class 有關係(只剩 N + M 個關聯),從而降低 outpu class 與其他 class 間的耦合。

原來 Manager class 還有這種功能…

事件通知

在「某件事發生時需要通知其他人」的情境下,有以下幾種方式可以降低通知者與被通知者的耦合:

  • callback
    C 寫法,使用 function pointer,也可以有很多 callback。
  • observer
    Observer Pattern
  • notification
    不針對特定事件,比較像整個系統的機制,例如 signal-slot。

這些方法可以讓通知方與被通知方彼此不需要知道對方是誰,否則雙方得知道對方是誰才能通知,而當兩邊各有很多 class 的時候,那根本是場災難……

相關 note

Murmur

老覺得跟 OO 的原則很類似,很多概念都有在其他地方看過。不過現在再看,似乎更能配合實際經驗加以理解。

像 naming、argument 順序、standard pattern 的使用、memory model、exception 的使用、error handling 等等,如果設計上有一致性,使用者較容易使用。保有一致性時,使用者可以輕易透過原本的理解認知推測新功能或其他部分如何使用。

在設計 C++ API 上,如果跟 standard library 保有相同的模式與規則也會讓 API 易於使用,畢竟大家都會用 standard library 啊!

雖然這篇標題是說 API 的設計,但小至 class 跟 function 也是有類似的觀念啊~

舉個 argument 不一致的例子:fscanf()fgets(),它們分別長這樣:

int fscanf(FILE* stream, const char* format, ...);
char* fgets(char* str, int num, FILE* stream);

兩個類似的 function,FILE* stream 參數卻是一個在第一個、一個在最後一個,讓我太久沒用就得回頭瞄一下文件……

API 的功能可以是比較 atomic、比較基本的,讓外部使用這些基本功能組合出它想要的東西。也可以是 API 本身即組合一些基本功能、提供較方便的功能給外部使用。這兩種特性雖然互相衝突,但都是作為 API 希望滿足的。

滿足這兩種特性的方式是將僅提供基本功能的 core API 及較方便的 API 分開。可能是以 class 或檔案區隔,甚至直接拆成不同 library。重點是較方便的 API 是使用 core API 的 public interface,就像在 core API 外再包一層,而不會 access 到 core API 的內部結構。

如此使用者就能有方便使用的 API 也能在想自行組合功能的時候使用進階的 core API。這概念類似於 UI 設計上常有的「進階」按鈕──平常設定頁只會有基本設定,按進階按鈕後才會顯示更細部的設定。

常用功能的 plugin 及快捷鍵:

  • 游標停在字上的時候會自動 highlight 同樣的字
    開啟 vim 後輸入 z/,目前還沒找到怎麼一開 vim 就啟動自動 highlight。
  • 跳到游標所在 variable 或 function 的宣告
  • 在 .cpp 跟 .h 之間切換
    a.vim (:A)
  • 跳到游標所在的 function 的定義
    cscope (ctrl+\ g)
    ctags (g] or ctrl+])
  • 跳到游標所在的 variable local 宣告
    ctags (gd)
  • 跳到游標所在的 variable global 宣告
    ctags (gD)
  • 全 project search
    cscope (ctrl+\ t)
  • 列出 file 中有哪些 function
    taglist (F8)
  • 自動補齊
    於 insert mode ctrl + n
  • 自動補括號
  • hex mode
    :%! xxd
  • 切換 history
    ctrl + i & ctrl + o
  • 多行註解
    ESCctrl + V 選範圍、大寫 I、輸入註解符號、ESC
  • 多行取消註解
    ESCctrl + V 選範圍、delete

My .vimrchttps://github.com/cjwind/dotfiles/blob/master/vimrc

ctags taglist

$ sudo apt-get install ctags

切到 project 資料夾產生 tag 資訊:

$ ctags --extra=+f -R *

加上 --extra=+f 可以在 vim 中使用 :tag <filename> 跳到該檔案,:tag <filename> 後再 ctrl + t 可回原本檔案。

http://www.vim.org/scripts/script.php?script_id=273taglist_45.zip,解開後將 taglist.vim 放到 ~/vim/plugin

~/.vimrc 加入相關設定:

1
2
3
4
5
6
let Tlist_Ctags_Cmd = '/usr/bin/ctags'
let Tlist_Auto_Open = 1 " 讓 Tlist 自動開啟
let Tlist_Show_One_File = 1 " 不同時顯示多個文件的tag,只顯示當前文件的
let Tlist_Exit_OnlyWindow = 1 " 如果taglist窗口是最後一個窗口,則退出vim
let Tlist_Use_Right_Window = 1 " 在右側窗口中顯示taglist窗口
nnoremap <silent> <F8> :TlistToggle<CR> " F8 為開啟/關閉 Tlist 的快速鍵

ctrl + w 再加方向鍵可以切換 window,例如加右鍵就是跳到右邊的 window。

patch

有時候切換 tab 會出現 error Taglist error: Error detected while processing function <SNR>29_Tlist_Refresh_Folds,可用 patch 解決:$ patch -p0 ~/.vim/plugin/taglist.vim taglist.diff

cscope

$ sudo apt-get install cscope

切到 project 的資料夾產生 cscope 資料庫:

$ cscope -RC

之後用 vim 開啟 source code,可用 :cs 指令使用 cscope 的功能。也可以在 ~/.vim/plugin 中放 cscope_map.vim 加快捷鍵:

1
2
3
4
5
6
7
ctrl+\ s "s表Symbol,列出所有參考到游標所在字串的地方,包含定義和呼叫。
ctrl+\ g "Find this definition
ctrl+\ c "c表Call,列出所有以游標所在字串當函數名的地方。
ctrl+\ t "t表Text,列出專案中所有出現游標所在字串的地方。
ctrl+\ f "f表File,以游標所在字串當檔名,開啟之。
ctrl+\ i "i表Include,以游標所在字串當檔名,列出所有include此檔的檔案。
ctrl+\ d "d表calleD,以游標所在字串當函式名,列出所有此函式呼叫的函式。

自動 highlight

.vim/plugin 加入 autohighlight.vim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
" Highlight all instances of word under cursor, when idle.
" Useful when studying strange source code.
" Type z/ to toggle highlighting on/off.
nnoremap z/ :if AutoHighlightToggle()<Bar>set hls<Bar>endif<CR>
function! AutoHighlightToggle()
let @/ = ''
if exists('#auto_highlight')
au! auto_highlight
augroup! auto_highlight
setl updatetime=4000
echo 'Highlight current word: off'
return 0
else
augroup auto_highlight
au!
au CursorHold * let @/ = '\V\<'.escape(expand('<cword>'), '\').'\>'
augroup end
setl updatetime=500
echo 'Highlight current word: ON'
return 1
endif
endfunction

輸入 z/ 可開關自動 highlight 的功能。

reference here

a.vim

在 header 及 source 之間切換。script

切換指令為 :A

自動補括號

Auto Pairs

將 script 放到 ~/.vim/plugin/ 底下。

Ref

大學時因為很任性的理由沒有修軟體工程,所以 agile 是一直到開始工作才聽到。之前是聽過 XP 跟 pair programming,不過沒有很清楚在幹嘛。話說 pair programming 實在要看狀況用,像我這種基本上屬於內向害羞的類型,用 pair programming 大多數狀況下根本直接腦殘化…= =

之前被拐去上 scrum 的課,說不定是因為講師一直強調 agile 本身精神很重要云云,有個想法一直在腦袋徘徊不去(?),只好來 murmur 一下看能不能清爽些。阿不過這篇只講對 agile 精神的想法,scrum 方法我不會(欸)。

我將 agile 精神類比為適應,因為根本上概念是共通的。適應就是因應改變,無論那改變來自外在環境抑或是自身變化。差別只在於生物上各種生物有不同的適應方式,人對於環境以及種種變化所做出的調整也是適應,而 agile 則是將這樣的想法套用於組織性的軟體開發。

需求改變(外在環境)、內部人員的不同(內在改變)等等都是變化,有了基本精神後當然是以各種工具與方法達成。而現在有許多經過不少人血淚斑斑的試驗過、覺得還不錯的方法可以使用,但是最後依然要找出適合組織與情境的方式。

我是從個人的角度出發去看「因應變化」這件事。因應變化常常帶著不確定性,所以有時候不太討喜,但是近年我喜歡這種會因應變化的觀念,因為這代表彈性、可能性以及選擇。我喜歡用「成長」的角度去看待改變,改變不見得是成長,但是沒有改變幾乎難以有所成長。有改變代表有新東西,有新東西代表有新選項,有新選項代表有不同未來的可能性。倘若不滿意現況,如果希冀有所不同,又怎麼能夠期望同樣的方式能帶來不同的結果?

至於結果是更好還是更壞,如果以從個人角度出發來看,我是不太在意。因為覺得不好,再改就是了,這不正是彈性的好處?我傾向相信,終究會慢慢摸索出適合的方式。而那方式,也可能只適用於某一陣子,那只是一個暫時的答案,不是絕對。

定義物件間一對多的關係,當一個物件改變狀態,其他相依者都會收到通知並自動被更新。

適用場合

某個物件擁有某些 state,還有一堆其他物件很在乎這些 state 有沒有改變,這些物件在 state 改變時需要做些事情。這邊的 state 是抽象概念,舉凡一個 button 被 click、load data 的 progress、load data 完成與否、畫面是否被修改等等都可以看作 state。另外,一個 object 可能會有很多種 state。

網路上很喜歡用的說法:observer 是一種訂報紙的概念。簡言之,有間報社(subject or observable),要訂報紙的人(observer)跟報社說他要訂報紙,之後報社有新報紙了(狀態改變)就會通知這些訂戶,訂戶拿到報紙後或許拿來看、或許拿來墊便當(?),想幹嘛就幹嘛。如果一個訂戶不想收到新報紙了,就跟報社說一聲退出訂閱,之後報社就不會再通知他了。

再舉另一個例子:UI 操作上滑鼠點了某個東西,程式其他部分需要因應點選做些事情時也適用 observer pattern。

UML

Observer Pattern

Observable 中會有些 state,state 變化時 call NotifyXXX() 通知 observer 們 update。一種做法是 NotifyXXX() call observer 的 update(),而 obsever 會在各自的 update() 中 implement observable 狀態改變時要做的事。

observer 如何取得 observable 的資料?

observer 拿 observable 的資料有兩種方式:

  • push:由 observable 在通知時一起傳給 observer。
  • pull:observer 用 observable 提供的 getter 取得資料。

我是還感覺不出來兩種方式的好壞啦……

有的沒的

  • observer 在 update 時不可以依賴被 notify 的順序,因為不同的 observable 實作可能導致結果不同。
  • Java 裡有現成的 observer pattern API。
  • Java 的 ActionListener 機制是種 observer pattern 的實作──當某件事發生時會 call ActionListener 相應的 function。

Qt signal-slot 轉成 native 寫法

Qt 的 signal-slot 在用途是特定的狀況下,例如某些 signal-slot 是特別針對某個操作時,可以改成自己實作 observer 並取代之。什麼時候會這麼做?不想用 Qt 做某些 notify 甚至不想用 Qt 的時候…

我覺得 observer 跟 listener 差不太多,這裡說的 listener 是稍微簡化的 observer,不會有如上述的 observable interface,而是 listenable 的 class 各自自行增加 listener 相關 member 及 function。

如何將 Qt 的 signal-slot 改成 native 的 listener 寫法、自己處理 notify 呢?

  1. 看懂原本 notify 的流程,區分誰是 listener 誰是 listenable。一般來說 signal 的 sender 是 listenable,signal 的 receiver 是 listener。確認要改哪些 signal、slot、connect()、disconnect()。
  2. 在 listenable class 增加 listener list、add 跟 remove listener function。
  3. 在 listener interface 增加 update function,也就是當某件事發生時會被 call 的 function。
  4. 修改 listenable class 的 emit signal,改為 call listener 的 update function 或 NotifyXXX(),由 NotifyXXX() 再 call listener 的 update function。如果有多處需要通知 listener,建議寫成 NotifyXXX()。如果只有一個地方需要通知,也可以不用 NotifyXXX()。
  5. listener class 繼承 listener interface 並 implement update function。將原本的 slot 改為一般 function,接著看要由 update function call 還是直接 rename 為 update function。如果原本是 auto 或 queued connection 要注意 thread issue。
  6. 修改 listener class,將 connect() 及 disconnect() 改為 call add 及 remove observer。
  7. 需注意 listener 及 listenable object 的生命週期,如果 listenable 活得比 listener 久,listener 死之前要將自己從 listenable 的 listener list 中移除。

Qt 的 signal connection 有些特殊功能也要一併處理:

  • queued connection 可能隱含換 thread,需釐清原本程式是否意圖使用換 thread 機制。
    如果有換 thread 就不是單純 observer pattern 可以處理的,可能要改用 thread + event loop,或 listener 的部分可以使用 Qt 則在 listener 以 signal-slot 換 thread。
  • direct connection 幾乎等同直接 call function,不需要額外處理。
  • unique connection 代換作法之一是在 register observer 時做 filter,已經是 observer 的 object 就不再加入 observer list。

Ref

今天看到這篇文章,加上近日對於整理筆記之厭倦,在想到底寫筆記是寫來幹嘛的?

那篇文章表示不用寫「資訊」,但以學習知識來說,我還是需要一點「重點整理」方便日後查找,尤其是一些從書上看來、稍微消化過但也還沒熟到整個記下來的部分。資訊的確幾乎都可以找 google 大神,但是重新 google 到的資訊可能跟我的印象有出入,還有一些眉眉角角不見得記得,有個地方作為資訊的集中地對我還是有用的。

只不過,有時候似乎會趨向小時候在課堂上抄寫筆記的狀況,像在抄書。這感覺就怪怪的,有時候分不清楚自己是在抄書還是那真是重點(好吧,我猜如果覺得很厭倦的話大概就是在抄書或者已經不需要寫了)。想著想著發現原來今年初寫過筆記這回事

以上是跟專業、學習知識有關的「筆記」。至於看其他書啊、聽演講啊之類的,我比較會寫心得感想,像那篇文章所說的第二種筆記。在專業有關的學習上,這種感想會在書裡變眉批,這裡一句、那裡一段的,有時候是疑問,有時候是吐槽(欸?),但這種亂七八糟的心得就不太會去整理了。

可能會慢慢增加對於專業領域的想法吧,我對時事或一些事情會有某些想法,但很奇怪,對於自身專業卻不太有自己的想法。

C++ 不像 Java 有 interface,這邊說的 interface 是所有 function 皆為 pure virtual function 的 abstract class。

interface 依然要有 virtual destructor,否則用 interface pointer 去砍 object 的時候會變成 undefined behavior。如果希望 interface 中的 virtual destructor 仍是 pure virtual,可以這樣寫:

1
2
3
4
5
6
7
class Interface
{
public:
virtual ~Interface() = 0;
};

inline Interface::~Interface() { }

由於只要有 class 繼承 Interface,Interface 的 destructor 就會被 derived class call 到,所以必須要有 definition 才不會 link error。

virtual destructor 另一個使用場合──希望訂一個 interface 但不想訂定其中要有什麼 function──不想訂 function 但又需要該 class 是 abstract class 時就可用 virtual destructor 達到目的。聽起來有點怪,我遇到這種狀況是希望有個 interface 可以代表某種類的 object,但是在 interface 的階段卻又無法確定 derived class 會有那些 function,最後因為覺得結構上不需要而沒有真的使用,不曉得有沒有其他更好的方法?

2018-12:現在看看覺得「希望訂一個 interface 但不想訂定其中要有什麼 function」的情況有點怪……又想不起來之前遇到什麼了……要訂 interface 至少要有基本 function,否則是否先不訂 interface、先做 class,等漸漸看到有共通的 function 時再訂定 interface?

Ref

看完《深入淺出物件導向分析與設計》的筆記,這篇概括整理用 OOAD 開發軟體的開發週期,沒有太多細節。

1. 搞清楚客戶想要什麼

跟客戶聊天,從模模糊糊中搞清楚他想要什麼。或者問可以決定要做什麼的人,不管那個人是 PM、PL 還是其他各種頭銜…

利用 Commonality 及 Variability 了解客戶想要什麼、軟體要做什麼。

老實講,我覺得這真是門藝術。

2. 建立 feature list 及 use case diagram

跟客戶聊完天、了解系統要做什麼後建立 feature list,表示整個系統的功能概觀。畫張 use case diagram 呈現使用者或其他系統使用這個系統的藍圖,既然是藍圖,當然不會有太細節的東西啦!feature list 是以功能面的角度看整個系統,use case diagram 則是以「使用」的角度來看整個系統。feature list 裡的功能不管直接或間接,得要能跟 use case diagram 的 use case 互相對應。

Use case diagram example:

到這裡,對整個軟體系統,還只有比較概略性的藍圖,還沒進到 detail,我們要盡可能延後 detail!

Domain Analysis

辨識、收集、組織及表示領域相關資訊的流程,根據既有系統與其開發歷程的研究、領域專家的知識、潛在理論、領域中的新興技術。

是段看不懂在寫什麼的文言文…好吧這種時候我承認原文比較好懂…

在我的理解裡,這部分包含 survey 相關技術,例如 framework 如何使用,包括分析、了解原有架構及既有程式碼、找出相關的物件、物件間關係與互動等等,也包含了解此系統相關的背景知識、相關流程等等,例如要做一個請假系統,得要了解請假流程。

用客戶能理解的語言跟方式描述問題以及系統,不要跟他講什麼 class、variable 就對了。簡單來說──「講人話」。

3. 設計架構

根據 feature list、use case diagram 跟現有程式碼等等資訊將大問題分解成多個各自負責不同功能的 module。組織這些 module 並決定從哪個 module 開始動工。這個階段的重點是建立做事情的順序以及減少風險,更多細節

如果有需要,套用 design pattern。design pattern 是解決特定問題的方式,能結構化程式,讓程式較易被理解、維護而且更有彈性。

4. 一個個處理小功能

將大問題分成許多小問題之後,準備各個擊破啦!

不同的開發方式:Feature driven & Use case driven development

4-1. 想小功能的 requirement,建立 requirement list

依照描述的需求建立需求清單。類似 feature list 的概念,只是小一點,著重在要處理的小功能有何需求,會比最開始的 feature 更進入細節事項。

需求不只包含客戶想要的,也包含當事情不按正常狀況來時系統依然要能正常運作。畢竟客戶通常希望當事情不如預期時,系統仍然能正常運作。可以從兩方面著手:

  1. 這個功能要拿來做什麼、該做什麼?
  2. 在出錯的情況中,系統要做些什麼事?

4-2. 寫 Use case

requirement 及 use case 要能互相對應,跟 feature 與 use case diagram 一樣。

因為系統是跑在真實世界,不是只跑在預期狀況中,要考慮出錯的狀況。但是呢,有時候系統遇到出錯狀況時「如何反應才是正常」不見得是工程師能決定的,所以,請騷擾請教可以決定這件事的人。

更多跟 Use case 有關的細節

4-3. 設計物件細節及物件間的關係

4-4. 實作

運用 OO 原則:

5. 測試

測試所有能想到的可能使用狀況跟不按規矩來的使用狀況。

unit test 已經有抽象上的「功能」觀念。unit test 一次只測一個小功能,但測一個小功能不等於測一個 function,也可能是測很多個 function 組合而成的「功能」。

在測試中要模擬 code 真正被使用的狀況跟情境,而不是測試簡單 call function 但實際上並不會這麼使用的狀況。

Murmur

這只是大通則,裡面很多細節是要依照 project 各自狀況不同有所改變的。雖然說是 OOAD 軟體開發週期,但以概念上來說我覺得前面的需求分析等等跟 OO 沒多大關係。

現在理解到程式設計到處都是 divide and conquer,小時候(?)不懂以為 divide and conquer 只存在 algorithm 裡……

最開始我很容易犯的毛病是一下子就想動手寫 code,然後搞得很崩潰,幾次之後就不會想這麼幹了。還有會一下子就掉進細節而且還出不來,難以維持以 big picture 的角度去看,就整個攪再一起,這點現在到底治好了沒我也不太確定…(欸)

雖然目前我只用到裡面的部分方法,但是似乎有個開發週期的框架後變得比較知道自己在幹嘛,腦子比較不會像果醬糊成一團。