單元測試分享(一) --- Why ? How ? What ?

前情提要

目前參加的讀書會有點進入了倦怠期,
參加的人數出席不穩定,會議過程有點感覺像是在照本宣科。
所以我們安排每隔幾周,由不同分享一下個人工作上不同的經驗。
讓負責分享章節的人可以有更多時間準備,
同時也可以讓聽者換換口味同時喘口氣。

對於我來說,可以重新整理一下過往的經驗。
試著能不能更有組織的分享單元測試這個工具與知識。
同時,因為是線上分享會的形式,當我無法觀察觀眾的表情時,
遠端會議應該怎麼進行才能更有效呢?

遠端協作的 Conversation Cost 仍然相當高

進行方式

  • 先問為什麼?
  • 輪流發表一下想法(TimeBox:60s)
  • Live Coding (如果是現場分享,我會希望多一點實作)

思考問題

  1. (單元)測試是什麼呢 ?
  2. 為什麼要寫(單元)測試 ?
  3. 什麼樣的情境需要(單元)測試?

這裡的設計是 What、Why、When,我覺得改成 What、When→How、Why 會更好。
主要的目的是引導聽眾思考 Why,這裡我的受眾應該為開發人員,
實務上還有可能會是 QA(測試人員)、產品經理(PM/PO*)、維運人員、
營運部門甚至是老闆或客戶。

舉例情境如下:

問:單元測試或是測試是什麼呢?
答:是一種自動化的對程式的方法進行驗証 blab blab ….

捕捉到關鍵字 自動化 方法…可以延伸提問

  • 測試不一定要自動化,但是單元測試建議要作到一鍵測試(半自動)
  • 單元不一定是方法,但我們可以以方法當作最小粒度來討論,
    後續的分享會對粒度再作討論,但重要的是團隊的認知要一致

進一步提問**什麼樣的情境需要(單元)測試?**,
如果現場乾掉了,可以改成以下的提問:

  • 上 Prod 前要不要測試?
    • 那上 QA/Stage 要不要測試?
  • 開發新功能要不要測試 ?
    • 那 HotFix 要不要測試 ?
  • 拿到一份全新沒看過的 Source Code 要不要測試 ?

你會發現測試無所不在,那我們為什麼(Why)要測試啊?
我的回答,(單元)測試是品質保証的一種手段,

如果上線前,(單元)測試全過,
我就對被測試保護的方法(情境)有信心不會壞。
同樣的,在部署到其它環境時測試通過,我對品質信也就會跟著提昇。
換句話說,測試是品質的可量化指標。

可能的問題:測試全過,上線還是有可能會壞掉啊
回答: 壞掉是什麼樣的情境 ? 是不是一種沒有被測試保護到的情境 ?
是的話只要加上情境即可,一般來說我們應該可以作到 96 % 以上的常態情境
剩下考慮發生機率與重要性,應該加上測試就加測試保護,
這裡不限定單元測試也包含整合/端到端/手動等其它測試。

另一個情境是開發新功能,
任何功能都是方法與流程的組合
當你在開發新方法或新功能時,如果能同時寫好測試*,
那你就會有一份具備可量化品質的代碼
同時還可以帶來的額外好處是,當有一個新人拿到你的代碼時,
他會有一份測試可以當作規格書來閱讀。

注意:這裡可以代入很多其它相關的概念
比如說:TDD、Code Review、可讀性、Pair Programming

理解方法與流程是最耗時的一件事,
如果有測試可以節省相當多的時間,
但要注意測試也是人寫的,如果為了寫而寫,很容易寫出垃圾測試,
試著在開發流程上引入 Pair Programming、Code Review、Pull Request 等…機制
另外當代碼成長到一定程度時,
如果需要重構,測試將提供一定的保護網(看覆蓋率多少)。
但要小心實務上不應該過度追求覆蓋率。

這裡提供一個觀念,不要為了 Design Pattern 而 Design Pattern
但是即有的代碼需要重構之時,Design Pattern 可以提供像是燈塔般的指引作用
只要巧妙的設計測試案例,走向目標。

此外,當代碼有了單元測試的保護,
在開發日常的除錯作業將會有很大的幫助,
測試可以幫你快速的定位錯誤。
即使測試並未攔截到錯誤的話,那也表示你發現了一個前所未有的情境,
而只要加上這個測試情境,再修改代碼,未來這個情境將不會再有錯誤。

經典書籍對單元測試的定義

1
2
3
4
5
6
7
一個單元測試是一段自動化的程式碼,這段程式會呼叫被測試的工作單元,
之後對這個單元的單一最終結果的某些假設或期望進行驗証。
單元測試幾乎都是可以使用單元測試框架進行撰寫的。
撰寫單元測試很容易,執行起來快速。單元測試可靠,易讀,並且很容易維護。
只要要產品不發生改化,單元測試執行結果是穩定一致的。

--- <<單元測試的藝術2nd>>

F.I.R.S.T Principles

1
2
3
4
5
6
7
- Fast : 快速→不夠快就不會被頻繁執行
- Independent :獨立→互相依賴的測試,會讓除錯變得困難
- Repeatable : 可重複→在任何環境下執行都有相同的結果(EX:時間/網路)
- Self-Validating : 自我驗証→測試是否通過,不需額外的判斷與操作
- Timely : 即時→產品代碼前不久先寫測試

---<<Clean Code>>

小結

上面提到很多工程面的實踐,
也不僅限於單元測試,更多的是測試與品質的關係。
一再強調的觀念是:

測試是品質的可量化指標。
測試是品質的可量化指標。
測試是品質的可量化指標。

而回到工程面上來說,作為第一線的品質把關,
開發人員本來就應該對自已的代碼提供單元測試,
而 TDD、Test As Document、Refactoring、Design Pattern 等…是環環相扣的工程實踐,
Pair Programming、Code Review、Pull Request 等…是工程流程的實踐
單元測試正是那個將各種實踐結合在一起的一種工具。

Unit Test is basic tool

在心法上,要將單元測試視作工具,而非聖盃,
TDD 或是覆蓋率也是同樣的道理,御物而不御於物。

1
2
可用的軟體 重於 詳盡的文件
<<敏捷軟體開發宣言>>

引用一下敏捷宣言:
可用的軟體 重於 100%覆蓋率的代碼
可用的軟體 重於 測試全過的代碼
右側項目雖然有其價值,但我們更重視左側項目。

而這裡可用的最低標準,我認為是符合品質期待

First Test

第一個單元測試,加法計算器

Case:Add(+) : 1 + 1 = 2

1
2
3
4
5
6
[Fact]
public void Add_1_1_is_2()
{
var target = new Calculator();
Assert.Equal(2, target.Add(1, 1));
}

Production 代碼

1
2
3
4
5
6
7
public class Calculator
{
public int Add(int first, int second)
{
return 2;
}
}

Next Case

用 Test Case 逼出邏輯,用最簡單的方法實踐

1
2
3
4
5
6
[Fact]
public void Add_2_1_is_3()
{
var target = new Calculator();
Assert.Equal(3, target.Add(2, 1));
}
1
2
3
4
5
6
7
public class Calculator
{
public int Add(int first, int second)
{
return first + second;
}
}

3A 原則

  • Arrange(準備、初始化)
    如果 Arrange 過長會是一個壞味道,
    表示這方法相依太多參數、服務或模組
    範例:

    • target = new Calculator();
    • first number is 2
    • second number is 1
  • Act(執行/呼叫受測行為)

    • target.Add(2, 1)
  • Assert(驗証)

    • `Assert.Equal(3, acted result);

不要過度追求可讀性,而將測試程式變得難以理解,
可以使用測試驗証框架(ex:Fluent Assertions),
或是抽出方法來增加可讀性,但比起可讀,更重要是可理解。

彩蛋

後續的 Live Coding 會讓參加者完成後續的方法測試,
加法、減法、乘法、除法…
特別計算到除法的除零邏輯時,應該拋出錯誤。
趁這個機會可以介紹如何驗証 Exception。

進一步可以介紹如何使用 fluentassertions

1
2
3
4
5
6
[Fact]
public void Divide_7_0_is_Exception()
{
Func<int> act = () => _target.Divide(7, 0);
act.Should().Throw<DivideByZeroException>();
}

(fin)

[生活筆記] TUTORING APP 多特英 第四屆體驗大使 --- 心得篇

前情提要

直接連結前篇 吧。
APP 下載連結我也不囉唆,直接給。

使用記錄

我在使用前就知道接下來兩個禮拜並不輕鬆,
除了一些平日的課程之外,還有排舞與游泳的練習與它牌的英語課。
所以我計劃第一周上 4 次課(80分鐘),
第二周上 5 次課(100分鐘)。

我原本的想法是這個 app 應該可以用一些空閒時間來填補,
但是實際上真的沒有那麼簡單。

心態面

畢竟這是一個跟真人對話的英文口說 APP ,同時要使用你並不擅常的語言。
以我來說並不是那麼容易的東西,通常老師也會跟你作一小段的開場白,
問問你今天過得好嗎,或是簡單的自我介紹,身為內向者其實要保留一點心力來回答。

環境面

一開始我認為我可以中午午休在咖啡廳/樓梯間/公園之類的地方進行,
實際上沒那麼容易,一方面是心態上面的問題,
如同上面所說,開始的時候需要一點心理建議,需要一點儀式感,
在公眾場合會讓困難度倍增,有陌生人在旁邊,即使有使用耳機,
但恥度不夠仍然無開始課程。
另一方面,在公眾場合有較高的機率發生未逾期的狀況,所以可能會導致課程被中斷。

我自已經驗是,有一次我跟一個老師通話,才開始 5 分鐘,
家裡就有人回來,然後敲我的門,被迫中斷了課程,
雖然我有跟老師說我馬上回來,但是不知何種原故,我回來時老師已經中斷課程,
並且離線了。

師資面

這次體驗老師都是國際老師,我的經驗是大多是菲律賓老師,
只有一位應該是韓國老師,希望有機會在平台上看到台灣老師唷
然後有部份的老師我上課起來仍然是有一些環境音的,
影不影響,我覺得就看個人,是可以跟老師溝通一下。

未來有機會的話,我可能還是會試試母語老師,不過沒有上過也很難比較就是了。
這次的 9 堂課就老師我來說,覺得都是很 OK 的唷。

課程面

對我來說理想的上課時間可能至少 40 分鐘一次比較能有完整的對話。
但是 20 分鐘以目前的課程來說,其實也是夠了。
它內部推薦給我的課程等級是 Int. Low 大概還在講解基本的時態,
這個評分我相信是準確的,但是課程仍是有些無聊,比起課程的理論,
也許我需要更多的實際應用,能在對話上實際演練。

APP 面

主要的操作是蠻簡單的,也算好理解,
不懂的地方也可以問老師,但是我不確定是不是安卓與 iOS 的差異,
我之前問的一些按鈕的位置,老師的說明跟我實際上看到並不一樣,
所幸我很快就有找到,不然蠻浪費時間在這裡的。

說明一下課程中,你會看到教材頁,老師在滑動時,你的教材也會連動,
另外還有對話頁,可以在這些透過打字跟老師溝通。
我是使用 iPhone 7 ,在課程中老師有時候會傳訊息給你,
當我切到對話頁,想再切換回教材頁時,常常會反應不良,
我不確定是延遲或是觸發區太小我按不到,但是的確是一個困擾。

還有的時候教材連動會斷掉,不確定機制為何。
但是對我來說不影響,只要問一下在第幾頁就好了。

心得與題外話

我在上面說到,我本來打算第一周上 4 堂課,第二周上 5 堂課的,
但是我真的是在第二周忙到爆炸了,所以我在最後的一天的晚上 10 點鐘,
一口氣上完了 5 堂課。

唯一的問題大概就是太晚了找不太到老師(因為大多為菲律賓老師,而時區與台灣相同),
另外中間還有發生跟老師有誤會導致課程中斷的事。
所以整體而言,跟我原先的預期大概只完成了 87 % 左右。

不過三更半夜的在 2 個小時內找到 4 個老師上課,這就是現實生活中難以實現的事吧。
多虧了網路的無遠弗界,再加上 TUTORING APP 這樣的線上英文學習媒合才辦得到吧,
當然希望未來有更多時區的老師可以選擇囉。

參考

(fin)

[翻譯] AHA Programming 💡

The dangers of DRY, the web of WET, the awesomeness of AHA.

前情提要

為什麼我要翻譯 ? 練習英文,同時學習程式開發的原則 。
程式開發原則其實超級多,稍稍整理一下。

物件導向程式設計的 SOLID 原則

這五個知名的原則,可以一言以蔽之 — 高內聚、低耦合。
而我自已更喜歡 Teddy 老師的五則皆變:「面對原始碼改變的五種不同策略」

SRP(Single Responsibility Principle) 單一職責原則

A class should have only one reason to change
以一個類別來說,應該只有一個引起它變化的原因。

舉個實務上在改動代碼的例子:
線上回報了一個購物車的問題,
你去追查發現購物車保含禮品與折價券的情境下,才會發生問題,
同時主管要求你,這段代碼發生異常時加上 Log 並發 Email 通知。

你會怎麼改呢 ? 加個 if 判斷購物車/禮品/折價券之間的對應關係 ?
使用 Try Catch 補捉異常,記錄 Log 並發送 Email ?
那麼這些邏輯應該被加到哪些類別呢 ?
請思考一下
在這個原則底下,應該考慮 if 的邏輯屬於購物車/禮品/折價券中的哪一類 ?
而三個商務邏輯如何互動,當前的設計也是要注意的方向。
Log / Email 也是同樣的,是否也應該隸屬獨立類別呢。
請思考一下,請思考一下。

這遵循這個原則的程式設計下,可以有效減少單一類別被改變的機會。
以上面情境為例,Log / Email / if 的邏輯都是不同的職責,
如果設計得當,一個職責需要異動時只會修改到一個類別。

OCP(Open Closed Principle) 開閉原則

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
軟體實體(類別,模組,函式等等) 應該是可以擴展的,但不能被修改

同上的情境,遵循 OCP 在面對新增需求的時候,可以單純透過新增類別或方法來達成需求。

LSP(Liskov Substitution Principle) 里氏替換原則

Subtypes must be substitutable for their base types.
基底類別應該要能夠被他的衍生類別給替代而不影響原本的功能

這裡提到的原則,其實是對繼承的限制,在強型別語言都已經有這方面的實作了。
以上述的例子來說,Log/Email 的實作至少應該有一層的抽象隔離,
並以繼承抽象的實作來,
也就是說實際上如何 Log 可以是 files、DB、Cloud 或任何其它的東西。

這裡也跟下面的 ISP 相呼應。

ISP(Interface Segregation Principles) 介面隔離原則

No client should be forced to depend on methods it does not use.
不應該讓使用者去依賴他根本不會使用到的東西

介面是方法的抽象,所以我認為當一個介面有超過一個方法時,
就應該思考一下有沒有違背這個原則。
實務上會有許多的同類型的介面應該被歸屬到同一類之中,
簡單可行的方法是當你實作一個介面時,會不會要跟著實作許多用不到的方法呢?
如果有的話,試著將它們拆分成不同的介面吧。

DIP(Dependency Inversion Principle) 依賴反轉原則

High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
高層模組不應該依賴於低層模組。兩者皆應該依賴抽象。

Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
抽象不應該依賴細節。細節應該依賴抽象
DRY(an acronym for “”)

DRY

直接透過 wiki 的圖片來說明上面的例子。
想像 Object A 是購物車 Object B 是 Logger,
Figure 1 顯示的就是直接相依的情境,而如果我們可以透過一個 ILogger 的介面就可以反轉相依,
使得購物車與 Logger 都相依與介面,
現行主流的程式也都有提供對應的 DI Framework 或 Library,
只要好好使用,這個原則並不難實踐了。

小記

說得有點太多了,以上的部份未來另外抽成相關的文章吧。

主文

DRY

DRY(Don’t Repeat Yourself 的縮寫,不要重複你自已),一個古老的軟體原則,
在維基百科的定義如下:

Every piece of knowledge must have a single,
unambiguous, authoritative representation within a system
每一項知識都必須在系統中具有單一,明確,權威的表示形式

儘管實務上比定義更為寬鬆,我仍然認為這個一個好習慣。
使用 Copy/Paste 最大的問題是,當某處發現了一個錯誤,
我會意識到其它複製貼上的地方也有相同的錯誤,並且需要被修正。

我曾經接手了一份代碼,有著大量重複代碼,為了修正一個 Bug 我需要修改 8 個地方!
抽像化這部份的代碼成為一個隨處可呼叫 function 將有很大的幫助。

WET

另一個概念是WET(Write Everything Twice)

You can ask yourself “Haven’t I written this before?”
two times, but never three

你可以問自己 “我以前沒有寫過這個嗎?” 兩次,但從來沒有三次

在上面的例子中,有時候過度抽像比重複更有害。
那是一份 AngularJS 代碼與一些 AngularJS controller,
這份代碼傳遞 this 給一個猴子補丁(Monkey Patch)方法(註:一種在runtime時動態修改代碼的技巧),
這個方法會用某種方式提供 controller 額外的能力。
但是這超級讓人困惑,而讓人害怕修改。

這個代碼在三個以上的地方重複使用,但是抽象性很差,我寧可代碼重複。

AHA

Avoid Hasty Abstractions
避免草率抽象
prefer duplication over the wrong abstraction
寧可重複也不要錯誤抽像
Optimize for change first
為改變而優化

關鍵是我們不會知道代碼的未來。
我們無法確認,我們能確認的只有改變可能會發生,
如果沒有發生,為什麼要修改代碼 ?

注意以下事實:

  • 不要介意重複,直到你對重複代碼的用例有信心為止
  • 代碼有差異的部份,是否可以抽出參數提供給你的方法
  • 逐步執行的過程,可以抽象共用的部份將會躍來而上
  • 不用擔心何時該對重複代碼進行抽象,直到你接觸到他

結論

不用擔心何時該對重複代碼進行抽象,
這不是一個硬性規定,也是為什麼我更喜歡 AHA 勝於 DRY 或 WET.

相關文章

(fin)

[生活筆記] L 的轉職閒聊與問題

前情提要

朋友想轉職,隨便尬聊了一下,有談到自已的職涯,
還有我對軟體產業的一些看法。

自我解析

資策會對我的意義,系統的整理學習程式的概念,
在 2008 年找不到工作時,透過它成為就業的門檻。
薪資的成長 N → O 我大概花了 10 年。

  • N,N+5,N+7,N+10…
  • M,M+1,M+2…
  • O…

軟體

  • 軟體工程師機會仍然很多,但薪資在掉,整體趨勢有偏向 M 型化。
  • 第一份工作用來洗經歷
    • 挑一下薪水(起薪不要太低)
    • 找能加分與學到東西的公司/產業/新創 => 可以透過社群的聯結去尋找,facebook 也找得到
    • 前期要不恥下問,閒聊去建立聯結,有機會可以找到好公司(有大神的公司)
    • 這個技能很百搭,其實會走各自的路
  • 1~2 年跳回到 6 萬 => 一直跳槽就有機會,不要騎驢找馬但是要建立連結。
  • 為何不換公司而要換產業 ?
  • 程式 & 英文 是百搭的必修技能
  • 無人車/電動車/人臉辨識
  • 寫程式也有很高壓的環境,也會作假,也會有不合理的要求
  • 新創公司大多要年輕人,30 歲以上會嫌老,但頂薪有限
  • 中大型公司大多要穩定,結婚生子會加分,
  • 這個產業要賺錢的話跳槽可能最快
  • 理想加薪路徑
    • 4x up 第一份工作,每半年 Promo 自已一次
    • 一年後至少上 50k 繼續每半年 Promo 自已一次(包含社群分享之類的…)
    • 靠跳槽與 Promo 自已,兩年後上 60 k …
    • 時間是理想值(實際上,3~6 我大約花了 5 年,有很多人是比我快的)
    • 6~10 應該都還有上升空間,應該可以到 120 ~ 150 / year
    • 再往上應該要跨管理
  • 薪水不會比時間重要
  • 我們的好處是可以接案(但是也要花時間)

資策會的課程設計(AIoT)

資料庫

  • MySQL
    • PostgreSql
    • MsSQL

用來有系統有結構性的儲存資料。
主要先掌握關聯性資料庫的概念就好,
如果要專精這塊未來主要會效能調校(需要理解資料庫的底層設計才能作到又快又好,不同牌的資料庫多少會有不同)

  • NoSQL
    • MongoDB
    • 其它

後端

主要的商業邏輯的程式部份,學好 Java 才是課程主體。
可以考慮學習其它的語言。

  • Java to DB
  • Java
  • Java to Web

前端

HTML、CSS、JavaScript 是前端工程師的必須項目,
門檻也不高但是 JavaScript 會有很多特異的行為要注意。
學好後再學主流的 Framework(Angular、Vue 或 React)。

  • Html & Css
  • JavaScript
  • JQuery(不要拿來跟 Angular、Vue 或 React 比較,它的存在有歷史因素,現在學的 CP 值比較低,直接學好 JavaScript 為主)

主流程式開發(Web & App)之路

Before

F2E

F2E

Back-end
Back-end

DevOps
DevOps

參考

(fin)

[生活筆記] Young Marvel 1.0 小結 --- 需改善消化系統

有些事情一時間我沒辦法說得清楚,
總要一些沉澱…

這個課程也算是跌跌撞撞的完成了。
中間卡了個中共肺炎,又遇上炎熱的夏天,
練習室的冷氣顯示好像是我殘餘的血量而不是希望的溫度。

消失了兩個教室,但是人都還在,沒事、沒事。
肺炎的關係室外的 Social 沒有了,
淹水的關係地下室也沒有了,
上課 Follower 總是少幾個,但也不是大問題;
資深的 Leader 沒有走到最後,其實對我也還好。
沒事、沒事…吧。

喜歡兩個人的 Solo 再結合的橋段,
喜歡聽著音樂即興發揮,雖然我常常是即興發呆,
開始會注意到 Routine 之中的 And 拍。
試著 6+2 或是 2+6 甚至是 2+4+2 的方式打散固定的東西。

但是老實說,我還是覺得沒有進步。
總歸一句,吸收與消化能力太差,
從生物的例子來說,我缺乏分解肉的酶。
花再多時間啃頂級的牛排也得不到養份,
小藍莓雖然好消化,但是沒有蛋白質。

下個問題是…如何產生酶 ?
太大哉問了,好難回答。

其它零碎的想法:
風城的熱情大過天龍,是因為取之不易,所以珍惜 ?
如果斷掉了,黏了幾次還是好不了就放棄了吧 。
停損很重要、退場很重要,理財如此、工作如此、生活如此。
點頭之交仍然是最好的狀態,再熟一點就會開始莫名奇妙的要求。

(fin)

[生活筆記] TUTORING APP 多特英 第四屆體驗大使

前情提要

學英文一直以來都是我的硬傷,
而且不論是補習、聽歌、看美劇各種方法我都試過,
也不能說沒有效,就是無法持續且無法應用在生活上,
所以有什機會能夠讓自已更進步我都會想試試看。

這篇文章可能有點微工商,
總之前幾天我滑到這個廣告,

看到這個廣告

我只是一個小小工程師,
不是網紅應該沒什麼流量(應該是我在蹭 TUTORING APP 的流量 XD),
竟然也能錄取,真的覺得很開心呢,
這也是這篇文章誕生的原因啦。

同時,這個兩禮拜我剛好特別忙(應該會忙到 10 月初),
工作之外還有表演的排練、教學課程的準備以及一場讀書會的發表。
我本來應該拒絕,但是我還是想挑戰看看,
首先是 9 堂課其實只有 180 分鐘(9x20)而已,應該不是難到無法克服程度。
再來是 TUTORING APP 其實很方便,我想試試是否能隨時找到安靜的角落,
就能夠完成一次英文對話(比起上課我更想閒聊啦)。
最後是我原本就有打算要寫一篇 Blog 來記錄這件事,
那就順水推舟一下,讓這篇文章誕生啦。

參加 TUTORING 兩週學習挑戰的動機/原因

在今年初,肺炎還沒有在世界上造成大流行,可以出國趴趴造的一月,
就曾經試用過這個 APP,當時我在上面臨時找了一個菲律賓老師,
選擇了一個簡單的課程,體驗的時間很短,應該不到一小時,
但是給我留下很好的印象,時間超彈性,找到老師到上課大概花不到 1 分鐘。
這對我來說很方便。

APP 的操作也很直覺順手,當時其實有想要再體驗幾次,
可惜那時候工作比較忙錄的情況下,
一不小心就過期了(還是次數用完 ? 我忘了)

其實我比較希望由母語教師來教,但是上了那堂課後的確讓我改觀,
TUTORING APP 其實也是有母語教師的,但是好的老師就是好的老師,是不分國籍與母語的。
所以可以看一下老師的簡介與評分,也可以聽聽發音,
可能我聽多了印度英文(線上程式課),我反而覺得英國腔有點不習慣XD,
但那次上課的菲律賓老師,咬字與發音真的都很好,完全不比母語老師差,人也很有耐心。

網路上有很多的英文教材,台灣也有很多英語學習的管道與廣告,
為什麼 TUTORING APP 會吸引我呢 ?
不是因為他的圖示很可愛,而是不用留電話這一點,
蠻多英文教學的課程廣告都會要求留電話,
為了你留電話,會打折會送測試送體驗,但是千萬不要留電話
除非你想要有接不完的銷售電話…
一直約課程、約實體見面,甚至催你買課…。
而我追求的是時間彈性,空間彈性的上課方式,
明明有類似 TUTORING APP 的服務(真人線上英文學習一對一家教),
我就不懂為什麼要留言電話,電訪行銷不是 1980 年代的東西了嗎 ?
另外一方面,我會參加一些語言交換的聚會,
不過目前程度還是太差,所以聊得很乾(其實我用母語聊天一樣乾…),
有一個教材讓我跟著學習反而是比較輕鬆的方式練習英文口說,當然我也是會繼續參加線下活動。
多認識一下外國的朋友:)。

參考

(fin)

[翻譯] 每個 Sprint 都需要 Retrospective Meeting 嗎 ?

擷錄翻譯

常見的問題

  • 我們的團隊很棒,我們沒有需要改進的項目…
  • 回顧會議很無聊,所以…
  • 我們實際的工作太忙了(或回顧會議太花時間了)…
  • 我就不喜歡回顧…

你永遠不夠好到太好

作者舉自已經驗 10 年的團隊,每 2 周的回顧仍有許多改善的地方。

實際上你永遠不會好到太好,即便你已經是市場上最好的團隊。
在 VUCA 的世界一切都在變,就像一句老話說的「逆水行舟,不進則退」
看看世上最好的職業球隊,三連冠很棒,但是你試過四連冠、五連冠嗎?
再談談軟體的熵,為了滿足各種需求,你開始切前後端、開始用微服務
容器化技術、DevOps 到 SRE …
只會變得更細緻、更專業、更多的溝通…

所以真正的問題或許是,為什麼你覺得你不能更好了呢 ?

回顧會議很無聊

作者提供了幾個方法讓回顧會議回覆活力

  1. 由別的團隊的 Scrum Master 來帶領你的團隊的回顧會議
  2. 改變場地
  3. 使用不同風格進行
    推薦書單 Improving Agile Retrospectives

尋求社群的建議,我個人再推薦一個網站 FunRetrospectives
回顧會議很無聊對 Scrum Master 應該是個警鐘,
但是醫生也會生病,請勇敢的尋求協助,而不是僵化的照本宣科,

No one said a retrospective should be as exciting as the latest Hollywood blockbuster.But there are things you can to do to liven them up.

Too Busy To Improve

too busy to improve

作者僅說明這樣是一個短視的團隊,
同時引用《高效率人士的七個習慣》書中的故事
短視近利的樵夫永遠不會打磨他的斧頭。

請注意「短視」的文化根歸何處 ?
這是一個複雜的問題,需要細心的觀察。
是團隊嗎 ? 或是 PO ? 也有可能是老闆或是客戶。
需要用系統思考的方式找到癥結點。
然後 — Change Your Company !

我就是不喜歡回顧

這個可能回顧會議變得無聊的變形
但作者將兩者分別出來的原因是,有的人就只是單純不喜歡而已
這些人只想作他們想作的部份…

作者例子基本上是團隊的害蟲了,
實務上我認為更多是人的價值觀不同,
在一般的開發人員或是 Team Member 身上或許還好解決,
透過溝通,排除他的困難。
個人經驗上比較麻煩的反而是中階主管,
比如說 : Team Leader 身兼 Team Member,
但是每次都翹掉回顧會議,說「我有其它會議、我很忙、我很特別…」

我的想法是將他的角色提昇到 StackHolder,如果他的權限會影響開發,
鼓勵他下放,讓 Team Members 具備獨力開發的能力。
讓他去作特別的事,反之如果他回頭想參與開發,
那應該與其它 Team Members 一樣全程參與會議。

黑暗兵法:在回顧會議取回他會影響開發的權限。喔,你當然可以選擇一場他不在的場次 :)

最後作者仍然補上可以減少回顧的可能性,

當你的團隊真得很棒,
或是為了讓回顧不無聊而付出巨大的努力,
或是你的團隊其實不太忙,甚至不太需要什麼改善計劃(可能要改善需求端)
能理解工作與喜歡作的項目的價值
團隊在一個極短的 sprint之中
可以減少團隊的回顧會議

If your team:

Is really good.
Has made significant efforts to make sure retrospectives aren’t boring.
Is not too busy to invest in improvements that don’t pay them back immediately.
And understands the value of doing other than just the most pleasant work.

… and if they work in short sprints, I’ll say it’s OK for the team do retrospectives less frequently.

但是並不建議,僅僅為了這個理由改變 Sprint 的長度,
而 Scrum Master 應該悍衛每個 Sprint 應執行的回顧會議,
但也可以適當調整為每兩個 Sprint 一次。

參考

(fin)

[N社筆記] 沒有後端不行嗎?

前情提要

我在今年3月已經離開 N 社了,
不過這幾天在社群與朋友蠻多人在討論一個來自靠北工程師的話題,
我看了一下後,有些自已的想法,稍微記錄一下。

另外延伸討論另一個最近炎上的話題,
前端不用寫程式 !? 出道(?)為全端網站工程師的我,談談我的看法。

事件 靠北工程師-把序號寫在 JS 裡面

靠北文章聯結
簡單的說,這是一個抽獎的功能,可以抽最高 550 元的折價券,
然後所有的實現都在前端,facebook 留言的回覆蠻有趣,
已經有人寫相關文章,有興趣可以看看始末。

回覆的留言大多分為幾種,

  1. (只?)有註解很棒
  2. 序號、次數等不應該寫在前端
  3. 資料筆數很多,應該怎麼處理等等…
    另外有一些來自 N 社的員工(?)留言這是行銷部門替 T 廠找的外包,
    所以不代表 N 社的工程單位會寫出這樣的代碼。

20200902 補充:
可靠消息指出「那包 code 不只是外包而已,是給一包範本
叫廠商自己改完,然後放到某位置去做活動」

以上需求為前提,陣列的處理也可以理解了,
整份代碼也是相同適合廠商修改,雖然不知道改得人會是工讀生還是總經理。
總之這樣的需求下,這份代碼我覺得是合格的。
反思 : If I Had Only One Hour To Save The World I Would Spend 55 Minutes Defining The Problem And 5 Minutes Finding The Solution.

反思 靠北工程師

這邊說說我的看法,
現在的我已經離開前端開發一陣子了,
但是我覺得這份代碼(只論抽獎 js 的部份)是簡單好懂的,
反而釣出有人作免費的 Code Review ,很多建議也算是不錯。
但這是公司文化層面上的一個問題,
有沒有遇過「前輩」堅持某種(好壞不論)寫法呢 ?

撇除 Style 與 Code smell,這段代碼我覺得已經用最簡單的方式達到目的了。
抽獎與增券,稍微操作一樣可以理解了,抽獎完後,你可以拿到一個折價券號。
然後…這些資訊都在 js 代碼之中。
稍微有一點概念的話,你可以直接拿到 550 元的折價券的序號,

然後呢 ?

如果你去消費的話,T 廠與 N 社行銷的目的就達成了… 。
而合理推測, 550 元已經在原本的計劃成本之中,而越多人買好像反而是個多贏的結局。

  1. 行銷目的達成
  2. 廠商銷售成功
  3. 節少內部開發效能

那另一方面,這樣的代碼出去會有什麼壞處 ?

  1. 讓人覺得 N 社技術不專業 ?
  2. 轉移行銷焦點,從折扣碼到程式碼 XD

有辦法改善嗎 ? 首先,我認為行銷找外包技術單位實作,是一個合理且正確的行為。
技術人員在各個公司仍是稀缺資源,所以流程上通常會被加上層層保護,
而行銷兵貴神速,用找傭兵的方式, 迅速達成客戶的需求將是必然的選擇。

再談談外包,雖然不知道收的價金多少,最主要的功能他們是實現了,
那有沒有辦法再好一點呢 ? 對我來說,這樣是有點傷害到 N 社開發部門的形象,
比如先簡單的 uglify 與 minify,
再進一步用一些 serverless 的作法將折扣碼隱藏起來 ?
或是真的起一個小小 api 服務來作這層的運算 ?
以我來說,頂多就多作 uglify 與 minify 就足夠了吧。
這也是取捨,倒是沒有什麼對錯。

如何信賴專業

再來我想討論行銷人員因為缺乏專業,所以尋求外包。但是沒有專業的他們,如何進行驗收?
本想舉年初館長 300 萬網站的話題,但是館長的事另有後續,故不在本篇詳述了。
舉蓋房子為例,雖然房子蓋好了,也能住人。但是風一吹就倒,或是海砂屋,你能接受嗎?
目前業界主要幾種解法:

  • 合約法:

透過合約的法律強制力來要求甲乙方,但是在文字表述上與理解上都有太多的模糊空間,
最後合約會變成一本厚厚的法律全書,好像什麼都有又好像什麼都沒有。
專案的運作,最後仍然回歸於人;當專案有所爭議之時,怎麼解讀合約反而會取代產品本身成為焦點。

  • 靠關係法:

找信任的人作為代理人來處理,但是這樣的方式只是轉價問題,代理人缺乏專業也不少見,
層層溝通反而更沒效率,更常見到上線即掛點的慘案

  • 第三方認證:

這個方法,是透過機構的認証來証明這個人具備某種能力。
對單一的技能是很好的門檻設定,畢業証書或是駕照都是類似的套路。
但是軟體開發真的只是「單一技能」嗎 ? 而你對第三方機構的信任,是不是會犯了「靠關係法」同樣的錯呢 ?
証照的產業,現在也有另一個困境,
比如說「學店」或是「雞腿換駕照」的情況不勝玫舉,這裡我不再深入討論了…
舉個例子來說,n 小時學會 xxx 與 1x 天學會 ooo 真的好嗎 ?

最後,這件事其實對一間軟體公司的專業形象是有損的,
我就用 N 社的回應 作為小結。
雖然我不知道為什麼不同步回覆到原始文章的底下,可能是公關想冷處理(之前 PTT 或其它事件也都無聲無息)
整個事件有很多反思的點,也有所取捨,這就是人生吧(茶)。

事件二、前端不用寫程式

另外這個事件也引起許多討論,因為我不想幫別人賣課,所以就不貼上啦。
可以 google 看看「 前端不用寫程式」加「15 天」,應該會有意外的驚喜。
批判的話我就不說了,不過參考上面的事件,是不是再次說明了第三方任証機構的問題 ?
這種廉價的認証或是「學園」 ,他們的公信力真的可以相信嗎 ?

這裡我想談的是,marketing funnel(行銷漏斗)
當行銷在推廣一件事情的時候,會盡可能的把餅作大,
但是有時候會將一些事情去脈絡化或簡化,這樣的好處是可以減少門檻讓新手玩家快速進入,
缺點是很有可能會誤導學員,有些觀念會被錯置。
而學生如果再深入一點,肯定會遇到問題。
對我來說,這反而是一個過濾器(filter),進一步將專業分類,
學生必須學會分辨,這是成為專業的第一步。
有的人只是想要體驗,有的人想學以致用,有人想深入學習…。

以程式來說,先入門程式大概是什麼概念,而目前主流市場大概會用到什麼技術 ?
整個軟體開發的系統為何? 前端、後端、DBA、DevOps…
你感興趣的是哪一段 ? 你擅長的又是哪一段 ?
往廣度延伸或是往深度延伸,或是擴及整個產業,
而我的看法是當這個產業,越多人參與且越多人討論的時候,才會越健全。

後續補充

CSScoke 的 Amos 的直播直接拉事主出來討論謝罪了,
有這樣的討論是很好的,也可以看出來,軟體的教學方式與系統還有很多嘗試與調整的空間,
看看網頁 15 天 Taker(道歉與回應)
寫程式這行仍在茁壯擴展之中,並且深入百業之中,但人學習的速度確漸漸的跟不上了。
舊有的學校系統跟不太上了,所以才產生了補教業的市場,而其中當然有良莠不齊的現象,
當然市場成熟後,市場機制本身就可以作一些汰除,
也或許這些講師也正在學習當中,我們可以繼續 Diss 督促,讓整個產業逐漸更加成熟。

順便評論一下兩方的公關處理方式,冷處理或直面道歉似乎效果都不錯,
不過我比較欣賞後者就是了。

(fin)

[閱讀筆記] 大話設計模式 --- 橋接模式(Bridge Pattern)

前情提要

因緣繼會下參加了一個線上讀書會,
讀的是一本老書,「大話設計模式」
簡體書應該是 2007 年出版,但是國內有繁體書,
我是在 2010 年左右入手這本書的,號稱簡單易懂,
當時翻了幾遍,但是對 Design Pattern 與 OOP 並沒有很深刻的認知。

如果對 OOP 沒有什概念的人,這本書的附錄也有簡單的介紹。
算是適合當作入門的書。

在實務上,卻很少看到同事在用 Design Pattern 在解決問題,
更多是前人怎麼作,我就怎麼作。
一直到我學習了 TDD 與重構,
我才漸漸了解 Design Pattern 是怎麼一回事,
書中小菜有幸遇到大鳥而我沒有,只能更多努力了。

本書小得

這篇 blog 主要是寫書中的第 22 章–橋接模式,
但是我想提一下心得,也許之後會寫別的章節,也許不會,
但心得就先收錄在這裡了。

首先是這本書面向的讀者應該是小菜,
同時是書中的主角,對物件導向與設計模式不熟悉的人,
所以書中用了很多現實生活中的例子來舉例,
當然我們可以將現實投影到程式當中,但我實際上的感受還是有差異的。
比起生活實例,我現在可能更希望是代碼實例吧。

第二點,書中的背景是 2007 年,所以時空背景已經不太相同了,
以本章(22 章,橋接模式)為例,書中提到的手機遊戲跨平台問題,
在 2020 年已經不存在了,現在的智慧型手機與書中的「掌上電腦」功能描述差不多了。

第三點,網路上有簡體版的書在流通,但是用語與正體中文有所差異。

想讀這本書的人參考一下上述幾點,
或許結合一些其它的書籍或是網路資源,
對你來說可以對設計模式有更好的理解。

橋接模式

我猜想命名的原因是來自完成後的類別圖看起來的樣子,
在本書中舉了手機品牌(Brand)與手機軟體(Soft)的關係作為例子,
你可以以 Brand 作為分類,也可以用 Soft 作為分類,
但最後都會長出三層繼承的類別圖,
然後其中都會包含奇怪的職責; Soft 包含 Brand 的資訊(或是 Brand 包含 Soft 的資訊)。

而真正的問題是,難以擴充,每當我們需要一個新的 Soft 或是 Brand
我就需要為所有的 BrandSoft 新增一整組的類別。
而這問題背後的原因就是過度繼承。

書中的例子,有一處我覺得不佳的地方,
在使用橋接模式前,主邏輯如下

1
Console.WriteLine("Run N Brand Game");

使用橋接模式後的代碼如下,

1
Console.WriteLine("Run Game");

作者可能想強調其橋接的觀念,而不討論橋接的兩端實際有資訊互通的需求,
但是我認為這是不正確的,我們不應該套用了某種 Design Pattern 而改變其原始行為。

接下來我會以下面這張圖,Demo 一下怎麼重構到橋接模式

手機品牌

首先,先寫測試,由於我視作這個結構是遺留代碼的產物,
所以我不認為他會有完整的單元測試,當然如果有的話就要考慮這些測試是否仍然適用。

1
2
3
4
5
6
7
8
var handsetNokiaAddressBook = new HandsetNokiaAddressBook();
handsetNokiaAddressBook.Run();
var handsetNokiaGame = new HandsetNokiaGame();
handsetNokiaGame.Run();
var handsetMotorolaAddressBook = new HandsetMotorolaAddressBook();
handsetMotorolaAddressBook.Run();
var handsetMotorolaGame = new HandsetMotorolaGame();
handsetMotorolaGame.Run();

我寫了一些 End To End 測試列舉出所有的情境。

延申問題

  1. 目前只是 2 x 2,所以要寫 E2E 測試似乎不難,如果是 3 x 3 或更多呢 ? 你會怎麼作 ?
  2. 這裡隱含著一件事,當你看到你的繼承鏈與商業邏輯的交互,
    已經開始出現 2 x 2 的現象時,是一個明示你應該重構它了。

下一步,我可以很明顯的發現中間層的類別,其實一點意義也沒有

1
2
public override void Run()
{}

所以我們要將繼承鏈中最葉端(leaf node)的類別繼承關係移除,

1
public class HandsetNBrandGame : HandsetNBrand

改成

1
public class HandsetNBrandGame

當然我不認為實務上有這麼簡單能移除一個繼承關係,
所以要達到這一步之前,我們也許要先創造無意義的中間層。
因為這步是對葉端類別的處理,所以我喜歡稱它為「修剪枝葉節點」。

下一步,當我把所有葉端的類別剪除後,我會先作分類,
實務上我會更傾向在腦海中作好分類再剪除,然後一個分類一個分類重構。
比如說 Game 類別:

1
2
3
4
5
6
7
8
9

public class Game
{
public void Run(string brand)
{
Console.WriteLine($"Run {brand} Game");
}
}

可以看到我已經將 brand 抽出來作為方法變數,
作為過渡時期多載(overloading)或許是個手段
此外,可以看到我透過參數傳遞來解除相依,
這個手段甚至可以套用在 delegate 或是複雜型別。

同樣的步驟再作一次,

1
2
3
4
5
6
7
8
9

public class AddressBook
{
public void Run(string brand)
{
Console.WriteLine($"Run {brand} Address Book");
}
}

我們可以明顯發現重複的項目可以抽出介面,
實務不需要特別介意是 interfaceabstract class
依最小改動為原則,選擇適當的手段進行即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public interface Application
{
void Run(string brand);
}

public class AddressBook : Application
{
public override void Run(string brand)
{
Console.WriteLine($"Run {brand} Address Book");
}
}

public class Game : Application
{
public override void Run(string brand)
{
Console.WriteLine($"Run {brand} Game");
}
}

這時候我們必須將 brand 傳入,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class HandsetNBrand
{
protected HandsetSoft Soft;
protected string Brand;
public HandsetNBrand(HandsetSoft soft)
{
Soft = soft;
Brand = "NBrand";
}

public void Run()
{
this.Soft.Run(Brand);
}
}

而 Client 端就可以組合起來使用。

1
2
var game = new HandsetNokia(new HandsetGame());
game.Run();

同樣的手法作在 HandsetMBrand 之中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class HandsetMBrand
{
protected HandsetSoft Soft;
protected string Brand;
public HandsetMBrand(HandsetSoft soft)
{
Soft = soft;
Brand = "MBrand";
}

public void Run()
{
this.Soft.Run(Brand);
}
}

這時候可以發現重複,重構後如下。
除了各自的品牌資訊,大多可以共用的方法與欄位,
我們就抽到父類別。

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
29
30
31
32

public class HandsetBrand
{
private readonly Application _app;
protected string Brand;

protected HandsetBrand(Application app)
{
_app = app;
}
public void Run()
{
this._app.Run(Brand);
}
}

public sealed class HandsetNBrand : HandsetBrand
{
public HandsetNBrand(Application app):base(app)
{
Brand = "NBrand";
}
}

public sealed class HandsetMBrand : HandsetBrand
{
public HandsetMBrand(Application app):base(app)
{
Brand = "MBrand";
}
}

完成

Recap

  1. 修剪枝葉,
  2. 製造重複,重構並產出 Implementor
  3. 如果橋接的兩端有需要傳遞的資訊,考慮使用方法參數
  4. Abstraction 抽象呼叫 Implementor 來建立橋接
    Bridge

參考

(fin)

[閱讀筆記] 物件導向的心得與隨筆

  • Object 泛指所有的物件,Instance 是明確指透過 Class 創建的 Object

  • Class 是 Object 的藍圖/設計書 ( Class 是 Object 的抽象 )

    • Object 是 Class 的具像化
    • 通常透過 new Class
    • 一種常見的應用是 Object 裡面只有欄位,用來作為資料的載體
    • 另一種常見的應用是 Object 包含可執行的 Function
    • Static 另作討論
  • Abstract Class 是 Class 的藍圖/設計書 ( Abstract Class 是 Class 的抽象 )

    • Class 是 Abstract Class 具像化
      • 通常透過繼承方式
    • Abstract Class 會定義共用的欄位與方法給它的子類別
      • 當某一個方法,實作細節必須由子類別處理時,會宣告成抽象方法 abstract method
      • 子類別繼承後必須實作 abstract method
      • Abstract 無法直接具像成 Object
  • Interface 是 Method 的藍圖/設計 ( Interface 是 Method 的抽象 )

    • Method 是 Interface 的具像化
      • 但是 Method 必須生存在 instance 上
      • 所以不會是 Static Object
      • 所以 Interface 必須與 Class 共存,才能在 instance 上實現

(fin)