忙了半年左右,我回來了(?),來寫點早該看完的《Clean Code》小心得。

整本書講 clean code 的原則,最後以一個大例子運用原則跟示範 refactor。寫那些原則像在抄書,只針對比較有感的原則跟 code smell 寫點心得。

最少驚奇(嚇)原則

讓看你的 code 的人(很可能就是你自己)對那段 code 的預期符合實際結果,儘量符合 common sense 以減少驚奇、驚嚇與驚恐。code 愈能符合預期,愈能減少進去實作看細節的時間。反之,如果常常不符預期,不用多久大家就會戒慎恐懼的每次都進去看實作,不然天知道它會發生什麼事,對吧?

不過我承認,似乎不是所有人的 commen sense 都一樣。

(More...)

原本電腦有用 UEFI 裝的 win7 跟 debian,用 grub2 開機。

多灌 win10

其實從來沒搞懂 UEFI 在幹嘛,只知道比古早時代的 BIOS 新,然後常常造成我灌系統的困擾(喂)。

這次用新硬碟裝 win10,原本用 legacy mode 裝起來,但 update-grub 抓不到,在 /etc/grub.d/40_custom 寫 google 來的各種 menuentry 也不行。改用 UEFI mode 裝 win10,它一下說找不到磁碟分割表(明明看起來就有),一下又說無法設定重開機所以不能裝,但明明我 BIOS 裡的 boot priority 已經選那顆新硬碟了(不過事後懷疑說不定應該要選 win7 的那顆才對)。在 BIOS 裡東調西調,從相容 legacy mode 換成 windows 8/10 又換成 windows 8/10 的另一種模式總算裝起來(裝完還把我 linux 那顆硬碟的 SATA port 關掉不知道是怎樣= =)。

看起來 win10 把自己的開機區裝到原本 win7 的 UEFI 系統,所以不動原本 grub 設定的情況下,從原本開 win7 的 partition 進去,就可以再選 win10 或 win7 開機。只是如果要開 win7,它會再重開一次……= =a…..(後來懷疑說不定是因為 win10 預設不會「真關機」,所以某些情況下開 win7 會需要重開,不過只是猜測,沒試過)

grub2 要加開機選項一般只要 update-grub。如果有自訂需求,到 /etc/grub.d/ 底下手動加再 update-grub

在 Linux mount 成 read-only 的 NTFS

裝 win10 後的某天,我在 linux 裡要存檔案到原本 windows 下放資料的硬碟(理所當然的是 NTFS)。

嗯?為什麼寫不進去?ro?為什麼變 ro 了?之前都可以 rw 啊。

umountmount 了一陣,ro 就是 ro,錯誤訊息看起來是說 windows 休眠中,所以不給 mount rw 免得壞掉。

蛤???windows 休眠中???可是我現在是開機成 linux 啊???哪來的 windows???感覺就是 win10 的錯(喂)。重開機進 win10,再重開進 linux,嗯?又好了?從 win10 關機,開機進 linux,又 ro。

這篇 給了我答案。簡單來說,win10 的 shut down 不是真的 shut down,reboot 才會真的 shut down(講人話)。好吧,把那個快速啟動關掉,我也不差那幾秒。win10 關機,開機進 linux,耶,世界一片美好。

前陣子寫 code 遇到 exception 或程式執行錯誤該丟出 exception 的情況,雖然知道 try catch 跟 throw exception,但「什麼時候」要「如何使用」exception 卻沒個概念然後搞得一團亂。只好來念念《例外處理設計的逆襲》,順便整理下嗑完書的簡略筆記。

區分 Fault、Error、Failure、Exception

首先區分幾個名詞的概念,不然這些東西都很像,不分清楚講到後來都不知道在講些什麼了。

  • (從外部看)一個 component 沒有提供正確服務稱為 service failure,簡稱 failure。
  • error 是種「狀態」,表示 component 內部處於錯誤狀態。這種狀態可能導致 component 執行失敗,造成 failure。
  • fault 則是導致 error 發生的「原因」
  • 程式語言以 exception 表達 error 與 failure 的概念。
    • 從不同角度來看(如 caller 跟 callee),exception 可以代表 error 也可以代表 failure。
    • 遇到某種情況要不要丟出 exception,決定方式之一是看在該 function 的語意及功能下,該狀況是不是屬於「目前可能處在不正確的狀態」或「被 call 的 function 沒做好該做的事」。如果是,當然就丟出 exception。
(More...)

prefix scan 是計算一個數值 vector 所有的部份和,每個結果 element 是原始 vector 對應位置之前數值的和,包含自己的稱為 inclusive prefix scan,不包含稱為 exclusive prefix scan。例如 [3, 5, 4, 10, 8] 的 inclusive prefix scan 是 [3, 8, 12, 22, 30],exclusive 是 [0, 3, 8, 12, 22]

(More...)

Bob 大叔的書之一《The Clean Coder》,算是《Clean Code》的續集吧。

《Clean Code》講怎麼寫 code,《The Clean Coder》講怎麼當 clean coder。記錄些心有戚戚焉的片段跟感想。

測試

設計「易於測試的程式碼」(p.48)

我想設計良好的程式應該也會好測試?從設計還是從測試出發,最後目標是一樣的,偏好哪種我覺得沒什麼差。

到目前為止,我大多從設計出發。先想概略設計、class 的責任、class 之間的關係等等,接著實作會邊寫功能邊寫測試,但不是 TDD。通常是實作一小塊、寫那一小塊的測試,而不是先寫測試才實作。即使從設計出發還是需要測試的,因為可以確保程式是對的,之後也不怕修改。

沒有實際用 TDD 寫 code,只在 dojo 小小玩過,所以說不上從測試出發是不是會導向好設計,能想像的只有因為要測試所以功能切分上應該不會太糟。

(More...)

用 concurrency 就是為了更快,為了比 sequential 程式快,不然搞得那麼複雜幹嘛呢。

如何評估 concurrent 程式的效能提升?

speedup = sequential 程式執行時間 / concurrent 程式執行時間

speedup 可能隨著使用的 core 數改變,標示程式在不同 core 數上的 speedup,可以由不同 core 數的 speedup 變化知道程式的 scalability。speedup 的提升隨著使用 core 數增加而增加甚至比例接近 core 增加的數量,表示有好的 scalability。理想狀況是 core 數加倍 speedup 也加倍。

有時會發生 speedup 的提升超過 core 的增加數量,稱為 superlinear speedup。這通常是有問題,要再確認 concurrent 程式執行結果是否正確、是否使用一般情境下的 dataset 而非只是測試資料。superlinear speedup 常見的原因是資料量太小,sequential 程式因為不斷需要新資料而被清掉的 cache 反而在 concurrent 程式中因為各 core 處理的資料量太小而 cache 住,自然 concurrent 程式在資料讀取上會比較快,造成效能變超好的錯覺。(假的!)

如果已有 sequential 程式、要決定是否 concurrent 化,能事先估算效能提升比較好,畢竟 concurrent 程式複雜也需要投入成本開發,如果效能提升不好就白費工夫了。

接下來兩個定理皆假設一個 core 上只執行一個 thread,如果一個 core 上執行多個 thread,就要把下面公式中的 core 數換成 thread 的數量。

(More...)

設計 concurrent algorithm 後當然要確認正確性。在《Principles of Concurrent and Distributed Programming》中 Ben-Ari 定義了為驗證 concurrent algorithm 各種特性的抽象結構(concurrency abstraction),可分為以下四個部份:

  • Programs are the execution of atomic statements.
  • Concurrent programs are the interleavings of atomic statements from two or more threads.
  • All possible interleavings of atomic statements must be shown to retain whatever property we are hoping to verify within a concurrent algorithm.
  • No thread’s statement may be (unfairly) excluded from any arbitrary interleavings.
(More...)

Concurrency vs Parallelism

concurrency 是如何拆分程式成多個獨立的工作,讓這些工作可以一起「正在進行(in progress)」但不一定要「同時執行」。「正在進行」是多個工作可以輪流到 CPU(或者 core)上執行,雖然不是同時執行但這些工作都是正在進行中的。而 parallelism 則是多個工作「同時執行」,也就是實際上必須要有多個 core 能同時執行工作。

打個比方,有一個廚師要煮一頓飯,他「同時」(實際上是輪流,廚師不會分身術)洗菜、切菜、炒菜跟煮湯,這是 concurrency,把煮一頓飯分成四個工作。現在廚師會分身術喔不是,是有四個廚師,一個洗菜、一個切菜、一個煮湯、一個炒菜,所有人一起動作就是 parallelism(如果同時間只有一個廚師能動作,不算 parallelism)。煮一頓飯也可以拆成炒青菜、煮飯、煮湯三件事,每件事各自從洗到切到煮或炒,這樣也是一個 concurrency solution,而如果有多個廚師一起做,便成了 parallelism。

concurrency 關乎程式結構,parallelism 關乎程式執行。concurrency 是程式或系統怎麼切分成多個工作,而 parallelism 則得要同時「執行」。

關於中文翻譯

concurrency 的中文翻譯是「並行」,parallelism 是「平行」。但「平行」很容易出現在描述裡,常常搞不清楚到底「平行」是指 parallelism 還是只是一個形容?我比較喜歡在需要精準指出是 concurrency 或 parallelism 的時候用原文,單純的形容或者描述用「平行」,除非英文用在中文句子有點怪才會用中文「並行」跟「平行」並且加註英文。

Ref

這篇 paper

問題 & 情境

Reactor pattern 處理一個 application 收到從一個或多個 client 同時(concurrently)送 request 的情況。

分散式環境的 server 必須處理多個送 request 過來的 client。為了處理 request,server 必須 demultiplex 以及 dispatch request 給對應的 service。設計 demultiplexing 及 dispatching 需考量:

  • Availability
    server 在等待某些 request 時(例如 server 與 client 間需要多步驟溝通,server 正在等 client 的某個步驟),server 依然能處理收到的 request,不能因為處理某個 request 而 block 住,不然會卡到對其他 request 的 response。
  • Efficiency
    server 要最小化 latency、最大化 throughput 以及避免不必要的 CPU 使用。
  • Programming simplicity
    顧名思義,程式寫得愈簡單愈好。
  • Adaptability
    容易新增或改進 service,修改最少現有的 code 就能加新功能或改進功能,例如新增 service 不用修改底層的 dispatching 機制。
  • Portability
    容易 porting 到其他 OS 平台等。
(More...)

常常聽到看到 reentrancy 但老是跟 thread-safety 搞得不是太清楚。

Reentrancy

reentrancy 是 function 可以在執行過程中被中斷,在上一次執行還沒完成前可以再次進入這個 function 執行而不會有問題,也就是第二次進入 function 執行結束後,第一次的執行仍能正確完成。中斷可以是 jump 或 call,也可以是系統的 interrupt 或 signal。

reentrancy 是 single thread 環境下就有的概念。像是被系統 interrupt 中斷時 interrupt handler 是不是能夠再 call 剛剛執行到一半的 function?反過來說,interrupt handler 能 call 的只有 reentrant function。另外,recursive function 一般會是 reentrancy,但如果它使用了 global 變數就不一定。

reentrancy function 可以是 thread-safe 但不一定是 thread-safe。thread-safe function 可以是 reentrancy 也可以不是(繞口令時間)。function 是 thread-safe 但不是 reentrancy 的例子:

1
2
3
4
5
6
function foo()
{
mutex_lock();
//blah...
mutex_unlock();
}

在 single thread 的環境下,如果 foo() 執行到一半被 interrupt 打斷,interrupt handler 又 call foo() 會永遠等不到 mutex unlock,整個卡在那。

達成 reentrancy 的基本原則:

  • 不使用 global(或 static)的變數
    但如果執行過程確保 global 變數或 state 能在執行前後保持一致,是可以用的。
  • 不修改自身的 code
  • 不 call non-reentrant function

Thread-safety

thread-safety 是在 multiple thread 的環境下,thread 間的 shared data 可以在任意執行順序下依然保持正確。

達到 thread-safety 的方式分兩大類,一是避免 race condition,二是 synchronization。

  1. 避免 race condition
    • 寫成不使用 global 變數的 reentrancy code
      這種 reentrancy code 的寫法可以達成 thread-safety,但不是所有 reentrancy code 都是 thread-safety。反之,因為有很多達成 thread-safety 的方式,不是所有 thread-safety 都是 reentrancy。
    • 使用 thread-local storage(TLS)
      存在 TLS 裡的 data 是屬於某個 thread 的,不會在 thread 間共享。
    • shared data 使用 immutable object
      data 不會變當然就沒有 race condition 的問題啦!XD
  2. synchronization
    • mutual exclusion 以及其變形們
      要注意 dead-lock 或 resource starvation 等等問題
    • atomic operation
      確保 operation 不會被打斷的,通常有賴 instruction 層級的支援。高階 mutual exclusion 也是需要靠 atomic operation 實作的。

不同的 terminology

不同的 library、系統或語言可能自己定義 thread-safe 跟 reentrant。

例如 Qt 將 reentrant 以及 thread-safe 都定義在 multiple thread 的環境下。針對 class 來說,Qt 的 thread-safe class 是 multiple thread 可以 call 同一個 class instance 的 member function,而 reentrant class 則是 multiple thread 只要使用不同的 class instance 就能同時 call member function。