機械式開關切換時訊號會有 bounce(彈跳)現象。

switch bounce

上圖為理想狀況,下圖為實際上有 bounce 的狀況。bounce 持續時間約為 10~20 ms。

程式在 bounce 期間會讀取到 bounce 的訊號造成判斷錯誤。例如按一下按鈕開關我們期望程式只讀到一次 on 的訊號,bounce 會造成程式讀到很多次 on 的訊號。

解決方式軟體及硬體皆有,硬體的我看不懂所以這裡只說軟體。(欸)

軟體解決方式:不處理 bounce 時間內的訊號。「不處理」的實際做法可以自己硬寫也可以在 add_event_detect() 裡設定。

圖片來源:http://www.bbc.co.uk/schools/gcsebitesize/design/electronics/switchesrev2.shtml

接 button、switch 時會用到。(看到 button 這字想到的是 GUI 上的…)

電路中希望維持一個電位基準值,好判斷某個電壓值是 0 還是 1。pull-up 跟 pull-down 電阻就是用來維持基準電位的,如果電路沒有接個東西,程式會讀到雜訊(亂數值)。

Pull-up resistor

switch 斷開時上半部的電路是通的,logic gate 會讀到較高的電位。switch 接上後,logic gate 的電位會變低(從電流來看是下半部電路會接通,以至於電位會改變)。如果認定 switch 接上是邏輯的 1 則 logic gate 是低電位時表示邏輯的 1,高電位表示邏輯的 0。

Pull-down resistor

pull-down 電阻是反過來,switch 斷開時 logic gate 會讀到低電位,接上後是高電位。所以高電位表示邏輯 1,低電位表示邏輯 0。

接 Raspberry Pi 時,上面說的 logic gate 就是 Raspberry Pi 的 GPIO pin。

murmur:被高低電位搞得頭有點暈,不太確定那個電流的理解對不對?沒搞得很懂 logic gate 在乎的是電位還是電位差……我的電路學只有高中程度啊……Orz

圖片來源及 Ref:

最近的感想。

念書的時候常覺得書上的理論跟實際上的使用或實作有一段距離,我說不上來那是落差還是什麼,但就是有點搭得起來又哪裡怪怪的。後來工作後我有點疑問──在學校念的那些理論,真的有用嗎?如果業界是以實務為主,為什麼我們需要學那些理論?從這個問題就看得出來我當學生的時候很少好好理解過為啥自己要念那些書…

我最近得到的答案是──理論會提供我們背景知識與架構,可以讓我們能較快速的理解系統跟軟體的運作方式,而不至於迷失在茫茫程式碼海中,要從許多的片段中慢慢拼湊才能知道整個系統的樣貌。當然,理論提供的是偏向概念上的了解,它不會有太細部的東西,實務上的細節是會隨著情境不同而調整的。

軟體的結構也一樣。最近工作上,我發現因為對整個軟體的架構比較熟悉了,所以能比較快猜測、反應出可能是哪邊有問題,不像之前一遇到問題就得重新 trace,也似乎比較看得出來改動哪個部分會影響到那些東西。開始稍微能體會軟體有其架構的重要性,當它有個結構、有些規則可循,programmer 比較能知道改動的影響範圍,出問題時也能比較快找到。我們可以不用去記憶一些特例跟「這邊改了那邊還有那那邊也需要改」的事情,這也會減少出錯的可能。對我這種不喜歡記那麼多細節還會搞混的人來說這真是太棒了!

最近在看《深入淺出設計模式》,我想 design pattern 也是一種軟體上的結構,如果知道某個部分是使用某個 pattern,就能馬上了解其大致的運作。不過我不了解 pattern 的時候,看那些 code 只有一個感想──幹嘛把事情搞得那麼複雜?

可惜的是,我現在仍然不覺得「軟體有其架構帶來的效益」這件事能有精確量化的指標。因為沒有對照組,沒有一套沒有架構的同樣軟體去比較有架構與沒有架構的差別,可能只能用間接的方式去量度關於軟體有架構的益處。

封裝會改變的東西,減少改動一個部分會影響到其他部分的狀況。

將 class 中容易改變的部分封裝到另一個 class 有助於保護原本 class 有不必要的改變。

程式改變可能改動到相關功能,如果一個 class 中常修改的地方並非該 class 負責的事情,修改它是不必要的,而且可能引入其他功能受到影響的風險。如果能將常修改的地方封裝,就可以保護原本 class 的其他功能不受影響。

「封裝」不單指將東西包成 class,把一堆 property 放到 map 之類的 container 也是封裝。

A cohensive class does one thing really well and does not try to do or be something else.

來來來翻譯一下:一個 class 只做一件事,不會插手做別人的事。

但是,這「一件事」的大小跟規模是由設計的人定義的,可以很大也可以很小,定義清楚即可。

class 內聚力越高,class 間的耦合度(改動一個 class 就要改動其他 code 的程度)越低,越容易 reuse 及擴展。

讓一個 class 只做一件事,只有那件事情需要修改時才會讓這個 class 改變。這樣每次修改的影響範圍可以縮小,可能產生 bug 的範圍也就縮小啦。另一個好處是 debug 的時候,programmer 可以較迅速的知道可能出問題的範圍,降低時間成本。

檢查內聚力的方式:做一項改變時是否牽動到許多 class?是的話表示內聚力低、耦合度高,程式不易修改跟擴充。

Delegate 中文叫「委派」。

將某些操作(例如是否相等)的細節交給 object 自己處理。

用一點簡單的 code 來說明。首先是沒有 delegate 的寫法(這裡先不管 member 放在 public 不是好習慣的問題):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Foo
{
public:
Foo(int x, int y) : m_x(x), m_y(y) { };

int m_x;
int m_y;
};

int main()
{
Foo foo1(10, 20), foo2(200, 300);

if (foo1.m_x == foo2.m_x && foo1.m_y == foo2.m_y)
{
// do sth.
}

return 0;
}

這種方式,當在任何地方需要比較兩個 Foo object 時,都要像上面第 14 行這樣寫。不覺得這樣都把 Foo 肚子裡的東西挖出來到處亂放嗎?如果之後 Foo 要多加一個 member,所有比較 Foo object 的地方都要改,光是有 20 個地方要改就有得受了,更何況還可能漏掉勒。所以啦,delegate 的觀念就可以在這裡拯救可憐的工程師,以下是有 delegate 觀念的版本:

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
class Foo
{
public:
Foo(int x, int y) : m_x(x), m_y(y) { };

bool IsEqual(const Foo& rFoo) const
{
return (m_x == rFoo.m_x && m_y == rFoo.m_y);
};

int m_x;
int m_y;
};

int main()
{
Foo foo1(10, 20), foo2(200, 300);

if (foo1.IsEqual(foo2))
{
// do sth.
}

return 0;
}

增加 IsEqual() 後,我們就可以把判斷放進 class Foo,由 Foo 自己處理如何判斷相等的這件事。外面只需要 call IsEqual() 就可以比較兩個 Foo object,也就是外界將判斷相等的事情「委派」給 Foo。這時候如果多加一個 member m_z,只需要改 IsEqual() 的 implement 就可以了,外面 20 個比較的地方都不用改!世界變得一片美好。

文言一點來說,delegate 有助於保持 loosely coupled。loosely coupled 表示 object 彼此獨立(也可以看成 object 跟其他 code),對一 object 的修改不會引起一連串其他 object 或 code 的修改。

問題時間

  • 為什麼不用 C++ 的 operator==

    operator== 也可以。這裡想表達的是 delegate 的觀念,如何實作不是重點。反過來說,不同語言也有不同的特性跟用法,而一個觀念可以用很多種方式實作。

  • 為什麼感覺有封裝的味道?

    我也這麼覺得。一些 OO 原則跟觀念彼此根本一家親,運用 A 的同時也運用了 B。我暫時想不出更好的範例了,就先這樣吧!:P

設計軟體事先處理整體系統輪廓,將大系統切成多個較小的問題,再逐一反覆處理每個小問題直到完成整個系統。

Feature Driven Development 及 Use Case Driven Development 是「將系統切成小問題再逐一反覆處理」的方式。

Feature Driven Development

以 feature 為主軸,以功能為切入角度。

挑出特定功能,規劃、分析及開發該功能直到完成。

看系統的角度比較 granular。

適合功能較個別獨立、未密切相連的系統。

Use Case Driven Development

以 use case diagram 為主軸,以 scenario(使用情境)及流程為切入角度。從 use diagram 拿出一個個 use case 做,挑出 use case 的 scenraio,寫 code 支援該 scenario,直到完成 use case 的所有 scenario 以及完成所有 use case。

看系統的角度比較整體。

適合由一堆流程構成的系統,例如差勤請假系統。

碎念時間

實際上開發軟體會混合多種方式,例如從 use case driven 開始,接著在 use case 挑出小功能進行設計(這是 feature driven),最後在實作階段用 test driven 來思考如何 implement。

開發軟體的方式很多,各有其適用之所在,重點不在於哪個方法最好,而是哪些方法的搭配使用能較好的解決所面對的問題。

很久很久以前,在那個網路只有撥接還常常撥不上去、撥上去也不能用太久的年代,我開始學寫網頁。那時候網路上的資源沒有現在豐富,搜尋引擎還在用蕃薯藤跟奇摩。當時覺得對初心者的教學好重要,所以一直想著如果之後能夠寫些跟技術有關的東西,要寫寫教學文章。後來高中在社團當教學,寫過社課的教學講義,不過沒放上網路,至於主題是什麼就別問了。

就這樣,我對寫技術相關的文章或 blog,一直想以寫教學的角度撰寫。但是隨著年歲增長,學的東西越多,越發覺自己會的很少,要寫出好教學沒那麼簡單,小時候的豪氣干雲(?)也漸漸弱了下來。唉,還是小時候比較天不怕地不怕。

開這個 blog 以前,我早就有個放只有自己看得懂的筆記的 wiki,但總覺得只有自己看得懂的東西 po 出來像在丟人現眼,也就一直放著自己看。後來慢慢弄了這個 blog,還是很想寫教學(這到底是什麼制約…),但是越這樣想我寫出來的語法就越奇怪,跟寫論文的狀況有點像(咦?)。就像把書上看不懂在寫什麼鬼的文字,再轉成另一種我自己也還是看不懂的句子,這樣寫筆記的意義到底是……?Orz

到這幾天覺得其實沒必要那麼嚴肅,反正就是把從各種地方學來的知識技術,消化吸收後再用自己的話寫出來就好了。

總之就是一個將知識與技術咀嚼消化後再吐出來(?)的過程。

Architecture is your design structure, and highlights the most important parts of your application, and the relationships between those parts.

架構是系統的組織結構,包含分解開的各個元件、元件間的關係、互動機制,以及系統設計中使用的原則及決策。

這次英文比中文好懂一點。

就我的理解,architecture 是整個軟體的結構,有點像骨架。其他那些 requirement、class diagram、design pattern、use case 等等東西就像軟體的,呃,肉(?),有骨架後就能把這些內容填進去。(寫到這裡開始懷疑我到底是怎麼理解這些概念的…)

設計 architecture 的階段會整理出那些 component 是重要的、建立處理順序,以及減少風險。

如何找出重要的事情?

可以問幾個問題:

  1. 它是系統本質的一部份嗎?
    可以用「如果沒有這個功能,這系統還會是它應該是的東西嗎?」來思考。
  2. 某個功能究竟是什麼意思?
    理解功能真正的意思,不懂就問客戶,或者任何要你做這玩意的人。盡量避免你以為你知道那是什麼但其實你不知道的狀況(繞口令時間)。
  3. 如何實作某個功能?
    挑出最困難的部分。不過一項實作困難程度會因人而異。

從誰開始?

找到最重要的幾件事情後,要決定從哪個部分開始。

這裡的重點是「減少風險」。只要能減少風險,從誰開始都可以。所謂風險,諸如 schedule 來不及、做出不是客戶要的東西等等。

在設計架構的階段要做可以減少風險的事,延後不能減少風險的事情。

如何減少風險?

  • 用 scenario 找出大部分的重要 requirement
    scenario 是穿過 use case 裡的一個路徑,也就是一段系統如何被使用的敘述。
  • 延後進入細節的時間,因為在架構階段進入細節無法減少風險。
    • 不要在架構階段寫詳細的 use case。
    • 延後細節的 coding
      可以先有個框,但不要開始寫邏輯細節。
  • 有好的 design
    • 一開始就把事情做對
    • 利用 commonality analysis(共通性分析)設計具有彈性的軟體
  • 搞不清楚意思或軟體要幹嘛時去問客戶,以降低做出不是客戶要的東西的風險。

寫好軟體的方式是先做好了解需求、規劃、組織、架構以及減少風險,盡可能延後 coding、避免在前期一頭栽進細節。

首先,來點文言文…

A use case describes what your system does to accomplish a particular customer goal.

use case 是捕捉新系統或軟體變更的潛在需求之技術。每個 use case 提供一或多個 scenario,傳達系統如何與 end user 或其他系統互動,完成特定目標。

以上文言文看完我也不知道自己是不是知道它在寫什麼。(喂)

白話文來說,use case 會寫出一堆使用這軟體的情境跟過程,藉由這些情境跟流程來描述軟體要做些什麼好達到客戶的目標。(這樣有白話文一點嗎?)

use case 描述軟體要「做什麼」(what),而非描述「如何做」(how)。

組成 Use case 的三部分

  1. clear value
    軟體要幫客戶做的事,也是客戶的目標。
  2. starting and stoping point
    use case 的開始及結束點,總不會一直繞圈圈沒完沒了吧
  3. external initialtor
    既然是軟體的使用流程,總有個開始「使用軟體」的人或其他系統。

Main & Alternative path

main path 是當世界一片美好、沒有任何事情出錯時,使用者會遵循的使用流程。但通常世界不是那麼美好的,工程師的工作之一就是要找出這些不美好(?),讓軟體也能妥妥善善的處理它。alternative path 即是在 use case 中負責描述及處理這些「出錯狀況」的使用流程。

alternative path 可以是…

  1. 完全替代原本的部分 path。遇到某個選擇時可以選 main path 繼續下去,也可以選 alternative path 做。
    例如可以選吃飯或吃麵完成吃晚餐這件事。
  2. optional 的,用來處理額外、例外以及出錯的狀況。
    例如想吃牛肉麵但沒開的時候該怎麼辦。

關於 Use case

形式上沒有固定的限制,我通常會寫成流程步驟,不管形式如何,重點只有一個──看得懂、能正確表達意思。

寫 use case 的時候會進到幾乎可以將 use case 裡的 logic 變成 code 的細節部分。

use case 也要包含檢驗步驟,例如檢驗輸入是否合法、某個物件是否存在等等。

Textual Analysis

寫好 use case,然後咧?跟程式有什麼關係?這時候就是 textual analysis 上場的時候啦!

分析 use case 裡的名詞及動詞,整理出 class 及 method 即為 texttual analysis。

use case 中的名詞有可能是系統中的 class,動詞通常是 class 的 method。當然不是 use case 裡所有的名詞跟動詞都是 class 跟 method,所以需要經過分析,好決定要為那些名詞及動詞建立 class 跟 method。

做完 textual analysis 決定要有哪些 class 跟 method 後,就可以進入設計物件跟物件間關係的階段了。