l

2017年4月30日 星期日

分類是一門學問

April 30 21:56~23:20

屏幕截图 2017-04-30 23.19.59

 

Teddy的指導教授當年在美國念博士班的博士論文是研究「圖形識別」,而且是判斷一張圖中那些是直線、圓、橢圓這種基礎研究。因為這個關係Teddy在唸書的時候聽了不少學弟報告圖形識別領域的論文,論文的內容從來沒聽懂過,但依稀記得要辨識圖形之前需要先做「分類」(classification)的動作,例如把圖形中的訊號與雜訊分開來,才可以把資料丟給後面的演算法繼續處理。

有一次和指導教授聊天,他說「分類」在遠古時代對於人類就很重要,例如人類老祖先要能夠區分那些貝類可以吃,那些香菇有毒,如果分類錯誤吃到有毒的香菇可能連小命都不保。

以前從來沒想過「分類」這件事原來有那麼大的學問在,後來才意識到原來分類真的不容易,很多問題如果具備了分類的能力,答案也就呼之欲出了。

***

這幾天把去年買的《穀倉效應》讀完,幾個月前這本書經常出現在敏捷社群朋友的Facebook動態欄,因為書中所要傳達的觀點,像是「打破穀倉所造成的隔閡」、「系統性(全面性)的思考模式」、「從使用者的角度來重組協作關係」都直接展現在敏捷方法上面。例如敏捷開發經常提到的「跨職能團隊」、「交付end-to-end的價值」、「區域優化 VS 全域優化」等。

看完這本書之後Teddy覺得重點應該不是單純可以用一句「打破穀倉」就可以交代過去的,因為「打破一個穀倉勢必造成另一個穀倉」。以Scrum為例,把component team這個穀倉打掉組成cross-functional team,後者也是一個穀倉啊,只不過這個穀倉叫做cross-functional team,而敏捷開發的人認為cross-functional team這個穀倉是從交付對使用者價值的角度來重組協作關係,是一種比較好的穀倉。所以說,穀倉不死,只是以另一種形式存在。

Teddy覺得書中比穀倉更有趣的概念是「分類」(穀倉是一種分類的結果,和分類本身還是有點區隔),書中提到一個例子,在金融海嘯之前,西方的監管單位並沒有察覺到危機即將發生。其中有一個很重要的原因就是,很多新興的金融商品不屬於傳統監管單位的「分類」之中。傳統上監管單位注意銀行與對沖基金這種已知的金融體系,但對於「衍生性金融商品」卻不甚了解。

後來問題爆發之後,有人去研究原因,提出了影子銀行這個概念(分類),並分析由影子銀行所造成的金融投資其實高達數十兆美元。這是一個非常恐怖的金額,但在金融海嘯之前卻未被加以妥善監管。自從影子銀行被提出之後,很多人都採用這個分類加以研究,等於幫原本「未定義」的界線給了一個範圍。

***

影子銀行的故事讓Teddy想到另一個「設計模式(design pattern)」的故事。設計模式就是一種分類,許久以前有一位鄉民貼了一篇大陸同胞寫的文章給Teddy參考,作者是中國人,美國知名大學的電腦科學博士,專長好像是程式語言、編譯器之類的。這位大陸同胞提到GoF的23個設計模式根本沒什麼,就只是「取名字而已啊」,就好像「把空氣命名為空氣」,這有什麼了不起的嗎?

從「分類」的角度來看,取名字(分類)真的還是挺了不起的。現在回頭去看,Observer、Command、Visitor、Factory Method、State等設計模式,鄉民們可能會覺得很簡單啊,沒什麼學問,就好像現在回頭看影子銀行這個分類一樣。因為這些分類經過先人的努力,已經從未知變成已知,從隱性變成顯性。

穀倉效應》第七章敘述美國克里夫蘭臨床醫學中心打破穀倉的案例,Teddy認為其中很重要的一點就是如何用新的穀倉取代舊的穀倉。這種設計新穀倉的過程,是分類、取名字的過程!

***

友藏內心獨白:「設計就是決定form和context的界線」,這就是分類。

2017年4月28日 星期五

C.C. Agile 56心得

April 28 15:55~16:49

屏幕截图 2017-04-28 15.23.28

 

昨天是C. C. Agile 第56次聚會,邀請服務於NEXCOM公司的Cobalt Chang分享Software Driven Hardware Development這個題目,談談敏捷開發如何應用於嵌入式系統開發中。

第一次認識Cobalt是在去年(2016)某一次C. C. Agile聚會,當時採用open space(開放空間)的形式討論敏捷開發的問題。Cobalt與他的主管和同事一起參加,Teddy剛好參與他們的討論小組,當下聽到了他們所遭遇到的許多問題,也聽到與會者給他們許多不錯的建議。

本以為活動結束就結束了,沒想到之後每個月Cobalt他們都來參加C. C. Agile聚會,在一次聊天當中Cobalt提到他們落實了幾項當初在open space活動中所聽到的建議之後,解決了不少團隊的問題。當下Teddy覺得很意外,因為說實話大部分的人參加活動聽到建議都只是「聽聽而已」,回去公司之後並不會採取什麼改善行動。沒想到Cobalt他們不但行動,而且還有不錯的改善成效,所以Erica和Teddy便邀請他們來C. C. Agile分享。更難得的是,Cobalt他們的產品是屬於軟硬整合的產業,在與硬體相關的產業落實敏捷開發相較於純軟體產業更加困難

屏幕截图 2017-04-28 16.35.57

***

在Cobalt昨晚的分享中,Teddy聽到幾個很有幫助的做法:

  • 以縮短交期(lead time)為做事原則:Cobalt他們採用許多Scrum實務做法與看板方法(Kanban Method)的精神,因為產品包含硬體設計,所以很難用iteration-based的開發方式在iteration開始的時候計畫這個iteration要完成的功能。因此他們採用看板方法的作法,針對專案的每一個工作項目,首先排列優先順序,在施工的時候運用各種方式儘量縮短每一個工作的lead time。因為觀念轉換,傳統上在硬體部門、韌體部門、軟體部門之間丟來丟去沒人管的工作,就由傳統工作流下游的軟體團隊一肩扛起,往上游追朔找與其他部門的人一起合作完成工作。
  • 說對方聽得懂的語言:Cobalt在演講中舉了一個例子,他們有一個案子因為硬體的限制需要把網路速度控制在100M,但原本的硬體設計並不支援。如果軟體團隊跑去跟硬體設計師說:「請把硬體改成網路速度控制在100M」那麼硬體的人會不知道你在說什麼,無從改起。但如果你跟他說:「把某個接線跳到另一個接線」那麼硬體的人就知道該如何做,也會很樂意幫忙。
  • 說明「為什麼」並請求協助:軟體團隊若只是一味地「指使」其他團隊的人改這個、做那個,對方可能會覺的「我為什麼要聽你的?」如果可以先跟對方說明遇到什麼問題,所以要做一些調整或改變,則比較容易得到對方的配合
  • PM很弱不一定是壞事:不管軟體或硬體專案,許多團隊都遇到「PM(專案經理)」有點弱(擺爛?!)的冏境,團隊成員除了碎念以外,還能怎麼辦?Cobalt提到其實團隊成員大可直接去面對客戶,既然PM不管事那就自己管,到頭來反而順利完成專案(自組織團隊的概念)
  • 把繁瑣的固定知識記錄在Wiki上:軟硬體整合的專案有很多關於設定的細節,如果設定錯誤便會造成系統無法運作。這些知識一定要在解決問題的當下趕緊記錄下來,以減少「重複學習」的浪費(精實開發的作法)。

***

Cobalt在整場演講中並沒有對於Scrum或看板方法作任何說明,而是用四個專案作為例子,告訴我們如何落實敏捷與精實開發精神。許多敏捷開發的初學者經常會糾結於「XXX算不算Scrum?」而忘了原本採用敏捷開發的原意—如何在競爭的環境中保持成功。Cobalt的演講以解決問題為出發點,選用任何可以幫助他們縮短lead time並保持持續改善精神的方法,是一場非常棒的分享。

***

友藏內心獨白:從人鬼殊途到通靈少女。

2017年4月27日 星期四

原力的黑暗面

April 27 05:45~06:48

腳踏實地

▲只擷取酸哥數百篇貼文的其中一篇這樣算是「合理使用」吧XD

 

可能是因為朋友按讚的關係前幾天在Facebook上看到外號「酸哥」的Ken Hsu的一篇貼文:「腳踏實地做事不難,難的是得忍著看見不老實的人賺的比你多。」對照到這兩天鬧得沸沸揚揚的谷阿莫「二次創作」事件,以及各種網路上流傳的詐神傳說,腦袋中突然上演了一篇篇「星際大戰」系列電影中安納金黑武士)與尤達大師還有白卜庭議長(西斯大帝)的對話。

絕地武士與西斯都是原力(force)使用者,不同點在於前者使用原力光明面,後者使用原力黑暗面。在「星際大戰三部曲:西斯大帝的復仇」中,白卜庭為了吸收安納金當自己的徒弟,於是唬爛安納金,表示黑暗原力具有起死回生的能力,他可以教安納金拯救他的「未公開老婆」佩咪,也就是前那卜星女王,免於難產死亡的命運。

安納金曾問尤達大師絕地武士這一派是否也有起死回生的「技能」,但尤達大師告訴他,生死有命,這就是原力的自然現象,請安心服用。尤達大師與其他絕地武士大師也曾多次說過,身為絕地武士,要抗拒黑暗力量的誘惑。反之,西斯大帝則是不斷利用憤怒、恨、忌妒等原力的黑暗面來獲取力量。

尤達大師曾經表示,原力的黑暗面並沒有比較強大,但也承認利用原力的黑暗面可以「速成」,但必須付出代價。

***

絕地武士終其一生都在學習著如何「腳踏實地」,多麼簡單也多麼困難的一件事。以前年輕的時候看星際大戰覺得很奇怪,怎麼「當個好人」有那麼困難嗎?都已經具備成為絕地武士的資質了,怎麼還無法對抗黑暗力量的誘惑 ?出了社會之後才慢慢發現,要抗拒黑暗力量還真的不容易。

你是否能抗拒:

一批便宜的牛肉

輕鬆賺大錢的投資方法

讓任何人都可以飛天找到好工作的訓練課程

把別人的創作收集起來當作自己的作品快速出版

用盜版手法獲取暴利

自我感覺良好的話術

***

古人有云:「不恥下問」,但「不恥」和「無恥」兩者之間還是有區隔的。一個人獲取知識、金錢或名聲的方法如果立基於無恥,只能恭喜他成為現代黑武士候選人。

***

友藏內心獨白:也不是人人都有資格成為黑武士。

2017年4月21日 星期五

例外處理壞味道:將例外當作控制流程

April 21 01:33~14:12

擷取

 

在〈例外處理壞味道(上)〉與〈例外處理壞味道(下)〉Teddy介紹了以下幾種例外處理壞味道(exception handling bad smells):

  • Return Code(回傳碼)
  • Ignored Checked Exception(忽略受檢例外)
  • Ignored Exception(忽略例外)
  • Unprotected Main Program(未被保護的主程式)
  • Dummy Handler(虛設的處理者)
  • Nested Try Statement(巢狀Try敘述)
  • Spare Handler(備胎)
  • Careless Cleanup(粗心的資源釋放)

昨天在北科上課review學生的作業,看到另一個很常見的例外處理壞味道:例外當作控制流程(using exceptions as control flow)

***

▼下圖是學生所寫的程式碼示意圖,getCompanyName函數首先判斷參數obj是否包含錯誤,如果是則丟出一個Exception並將其內容設為「找不到資料」。在第39行的catch clause中捕捉例外,然後傳回例外物件的getMessage字串。

屏幕截图 2017-04-21 01.48.25

 

問題一

有經驗的鄉民一眼就可以看出來上面這段程式出了什麼問題,首先在第29行丟出例外然後在第41行捕捉例外這種寫法就是典型的將例外作為控制流程,和寫go to是類似的效果。

▼程式可以改成當判斷obj物件包含錯誤的時候,直接傳回 “找不到資料”這個字串就可以了。

屏幕截图 2017-04-21 01.50.45

 

問題二

原本的程式寫法直接捕捉exception物件,這個物件在Java例外物件的階層中僅次於Throwable,捕捉Throwable或Exception這種寫法叫做blanket catch(請參考〈Java的try、catch、finally(1):Java SE 7之前〉)。在原本的程式碼中27~39行的try clause除了29行以外其他地方也可能會丟出例外,所以就算原本的程式碼不介意使用例外作為控制流程,41~43行的例外處理程式(exception handler)也可能因為其他程式發生例外而被觸發。也就是說,當呼叫getCompanyName函數的人收到"找不到資料"這個字串的時候,在原本的寫法當中有可能因為兩個原因所造成:

  • 真的找不到資料。
  • 因為發生例外被誤判為找不到資料。

很顯然地原本的程式邏輯是不正確的。

***

在絕大部分的情況下,例外應該單純用在程式異常狀況,而非因為丟出例外可以具有流程控制的特性而將例外使用在流程控制上。對於例外處理設計有興趣的鄉民們可以參考泰迪軟體的「例外處理設計與重構實作班」課程,或是Teddy所寫的《笑談軟體工程:例外處理設計的逆襲》 。

image

***

友藏內心獨白:程式不是看起來可以動就沒事了。

2017年4月10日 星期一

BDD(23)從規格、程式和測試思考BDD要解決什麼問題

March 31 12:30~13:24

屏幕截图 2017-03-31 12.38.07

 

前情提要

▲在〈三個圓圈(3):規格、程式和測試〉文章中Teddy介紹了上面這張圖的意義,並在文末留下一個問題:

有沒有什麼方法可以讓區域1變得越大越好?指派專人撰寫詳盡的規格書有幫助嗎?Design by Contract有幫助嗎?組cross-functional team有幫助嗎?BDD/TDD有幫助嗎?這些都是值得思考的問題。

今天從這張圖來思考BDD如何協助團隊把區域1變大。

***

瀑布式流程

在談論BDD之前先看看傳統瀑布式(Waterfall)開發方法,如下圖所示,在瀑布式流程中一開始是撰寫需求文件,也就是定義規格。雖然在定義規格的同時也應該一併把驗證方式準備好,但實務上很少有團隊可以做到,能把規格書在期限內生出來就已經很偷笑了(第一個圈圈)。

屏幕截图 2017-03-31 13.10.17

 

有了規格書之後,當然要請客戶畫押,發誓不再修改規格。之後再依據這份規格開始寫程式(第二個圈圈)。雖著時間演進程式越寫越多,但此時不知為何規格也跟著改變與膨脹(第三個圈圈)。好不容易等程式寫得差不多了,雖然沒有涵蓋全部的規格,但截止時間快到了,只好硬著頭皮先丟給QA部門測試(第四個圈圈),並且利用測試時間再繼續偷偷開發未完成的功能(第五~六個圈圈)。

以上所說都還算是負責任的情況,有時候趕著上市連最後的測試時間都沒有,直接丟給客戶請客戶幫忙測。在這種情況下,規格、程式和測試三者重疊的區域要大到哪裡去說真的不太容易。

***

測試後行的敏捷開發

敏捷開發團隊不須把所有規格都定義好之後才可以開工,以Scrum為例子product backlog有足夠接下來1~2 sprints的user story就可以了(第一個圈不需要很大)。下圖中的黑色圈表示規格,紅色圈表示程式,綠色圈表示測試。對於沒有導入BDD/TDD的敏捷開發團隊,開發順序先寫production code再寫test code,所以先有紅色圈再有綠色圈。基本上程式的範圍由user story的驗收條件所規範,而測試案例也可以參考這個驗收條件來撰寫。加上user story的粒度相較於傳統規格書的一個功能要小很多,因此比較容易讓規格、程式、測試三者重疊。

屏幕截图 2017-03-31 12.54.04

 

但是,有不少敏捷團隊雖然有事後「補寫」測試案例,但也常常因為sprint時間的限制只把程式寫完而來不及讓綠色圈與紅色圈重疊(測試不完整)。長期累積下來規格、程式、測試三者重疊的區域就大幅減低。

***

測試先行的敏捷開發

測試先行的作法利用測試案例來劃定規格的範圍,接著再撰寫程式。只要程式通過測試案例,既代表滿足規格所需。這樣的順序「理論上」可以大大地增加規格、程式、測試重疊的範圍。這裡有一個很重要的前提,使用測試案例所規範的需求要具備代表性,也就是《Specification By Example》書中所說的「關鍵範例(key example)」,否則一開始測試和規格重疊的部分就很小的話,後續依據測試所寫出來的程式也不可能涵蓋原本的規格。

屏幕截图 2017-03-31 13.03.32

***

結論

許多軟體開發方法都嘗試著增加規格、程式、與測試的重疊範圍。BDD透過兩個關鍵的做法來達到這個目的:

  • 透過協作來探索規格的範例。
  • 透過測試案例作為規格的代表,自動驗證規格與程式是否一致。

***

友藏內心獨白:BDD從流程上避免三者發散。

2017年4月6日 星期四

BDD(22)狀態與行為測試

March 30 18:15~19:23

屏幕截图 2017-03-30 18.25.00

 

測試術語

採用BDD開發軟體,首先你先寫一個失敗的驗收測試規範系統的行為與範圍,接著開發production code讓驗收測試通過。在寫production code的時候如果驗收測試無法清楚表達實作程式的行為,則可以進一步撰寫一個失敗的單元測試,然後再用最簡單、最直接、最無腦的方式來撰寫production code。等待單元測試通過之後,再拿出Refactoring(重構)這一把「小刀」,將剛剛快速撰寫的production code修剪一番,朝向clean code目標邁進;後者就是傳統上大家所理解的TDD。

無論是先寫一個失敗的驗收測試,或是失敗的單元測試,這個「待測物」(System Under Test;SUT)通常需要借助其他物件的幫忙來達成任務,例如資料存取物件(OAD)相依於資料庫,網路應用程式需要Socket物件來建立網路連線。這些相依物件稱為Depended-on component(DOC)Collaborator

一個SUT的所有DOC集合稱為這個SUT的Context。為了提高SUT的重複使用性與可測性,在設計上希望SUT不要自己管理Context,而是由外部將它所需要的Context傳入,這也就是Dependency Injection(DI)的作法。

以上名詞解釋完畢,可以回到BDD身上。開發人員撰寫這個「失敗的測試案例」,可以從兩個角度切入,分別是驗證狀態驗證行為

  • State-based testing:測試程式提供SUT所需的Context,然後呼叫SUT,最後驗證SUT的狀態是否正確。例如,傳入5給判斷質數的函數,傳回結果為true,如果傳入8則結果應為false。又例如push一筆資料到一個stack物件,則stack的容量會加一。以上做法都是藉由驗證SUT的狀態來判斷SUT的實作是否正確,有種黑箱測試的味道。
  • Behavior-based testing:測試程式根本不管SUT的執行結果與狀態是否正確,只關心SUT與DOC的互動是否有如預期。聽起來有點玄,但在某些時候卻必須做這種方式的驗證。例如在Observer設計模式中,當subject狀態改變會自動呼叫observer的update函數。如果想驗證的重點是Observer設計模式是否實作正確,則測試者根本不管也不關心subject的business logic到底是在做什麼東東,只要確定當它狀態改變的時候真的會呼叫一次observer的update函數就可以了。這種測試白箱的味道很重,因為測試者必須知道SUT實作的邏輯才有可能知道要如何驗證SUT如何與DOC互動。

***

測試替身

講了這麼多還是沒提到和BDD有什麼關係。當開發人員要讓失敗的測試案例通過的時候,這時候SUT與DOC很可能都還沒產生(因為production code還沒寫啊),如果一口氣要把SUT與DOC全部寫好讓測試通過,可能需要花很長的時間。因此為了讓SUT可以在比較短的時間通過測試,開發人員可以採用Test Double(測試替身)的技巧「欺騙騙」SUT,先專注於SUT的開發而暫緩DOC的實質內容,又可以讓測試案例通過。

因為測試方式有狀態測試與行為測試這兩種,所以測試替身也可以分成這兩大類。Dummy、Stub、Fake屬於狀態測試,而Spy和Mock則屬於行為測試。

Teddy習慣用Dummy、Stub或Fake來做狀態測試,較少用Spy或Mock,總覺得Mock Object Library,例如mockito這種工具用「設定期望值」的方式來做行為測試,程式碼不是那麼容易閱讀,過一陣子沒看還要想一下才知道在驗證什麼。另一方面因為mock object library具有「純屬虛構」的能力,有可能真正的production code行為已經改變但使用mock object library所撰寫的測試案例還是通過,這也是有點傷腦筋的地方。

▼用mockito測試Command pattern

屏幕截图 2017-03-31 10.22.05

 

但是mock object library也有它的好處,尤其在做BDD/TDD的時候它可以讓你比較快速的採用piece by piece的方式完成SUT而不用先把DOC用「老師傅手工打造」的方式手刻一個假的版本出來。對於行為有一定穩定度的程式,例如最常見的就是Observer設計模式,用mock object library會比手刻DOC簡單很多。

***

無論是使用哪種測試替身,程式碼終究還是寫給人看的。工具本身並無對錯,在合適的時機下使用合適的工具需要由人來判斷,很多時候如何判斷時機才是最難的點。

***

友藏內心獨白:Mock到最後連開發人員都被騙了。

 

延伸閱讀

2017年4月4日 星期二

【敏捷開發懶人包:物件導向技能】五月份平日班

March 31 09:00~09:35

擷取

 

敏捷開發懶人包:物件導向技能」已經開了三梯次,課程設定目標是希望鄉民們在找工作面試的時候,如果面試官問到物件導向技術方面的問題,能夠具備輕鬆回答的能力。上課方式第一次規劃以講解為主,但沒想到幾次下來增加了越來越多的活動、討論、與練習,排定的四小時上課時間不夠用。這次把課程時間調整成一日班,可以安排更完整的練習與討論活動。

課程內容也依據學員的回饋每次都稍加調整,對於下列人士特別有效:

  • 寫了多年的C程式,突然被叫去學Java、C#或是開發App,不知道如何著手用物件導向觀念作設計。
  • 寫了多年物件導向程式,但不確定自己是否用正確的方法使用它。
  • 想知道有沒有比較好的方式可以設計物件介面與分配責任。
  • 軟體設計如何有彈性地應付改變?
  • 物件導向分析與設計到底在講什麼東東?
  • 我沒有時間與耐心慢慢學會以上這幾個問題。

 

▼課程照片

屏幕截图 2017-03-31 09.54.10

 

Teddy挑選以下幾個最常使用的重要主題加以介紹,快速幫學員打底,補充身體所缺乏的「物件導向養分」:

  • 物件導向基礎觀念
    • 封裝、多型繼承
    • 耦合、內聚
    • 介面、委託、聚合
    • 物件導向與程序導向之優缺點比較
  • 依合約設計(Design By Contract)
    • 為什麼防衛式程式設計不好?
    • 前置條件、後置條件、類別不變量
    • 違反合約:例外處理機制
    • 合約與繼承
    • 依合約設計(DBC)與測試驅動開發(TDD)比較
  • 物件導向設計原則這樣聽就懂了
    • S.O.L.I.D.五大原則
  • 物件導向分析與設計
    • 問題敘述
    • 環境圖
    • 分析模型
    • 設計模型
  • 實例討論

上課日5月16日(週二) 09:30-14:30,共六小時。

image

***

友藏內心獨白:可快補充不足的物件導向能量。

2017年4月3日 星期一

說故事的技巧:Yes, And

March 31 08:23~08:54

屏幕截图 2017-03-31 08.51.48

▲畫面節錄自Google搜尋結果

 

前幾天和Erica聊天,他說在一門即興表演的課程中學到一種講故事的技巧:YesAnd

Yes就是把別人說過的話用不同的方式再講一次,例如:「小紅帽被大野狼吃掉了;大野狼吃了小紅帽」,而And則是在原本的情節中加入新的內容,例如:「小紅帽被大野狼吃掉了,大野狼穿了小紅帽的衣服,假裝成小紅帽打算連老奶奶一起吃掉。

後來聊到在臉書上許多朋友分享的資訊,也可以分成Yes、And這兩種。Yes派的擅長不斷地轉述別人講過的話、轉貼網路上的文章,以分享之名行刷存在感之實,如果能意外造成「自己也好棒棒」的形象那就更好了。And派的也會轉述與轉貼,在刷存在感的同時還會上加幾句自己的看法,算是盡到一點點「資訊加工」的責任。

***

Teddy覺得Yes,And這種看法很有趣,也可以應用於學習之上。有些人在上完課之後會不斷重複上課中所聽到的「洗腦口號 名言佳句」,但腦袋中的知識只是上課教材的子集合而已,並沒有舉一反三的能力也無後續的自學行為,很顯然是採用Yes的學習方法。也有人上完課之後引發後續的一連串學習行為,所學範圍已大大超出原本上課的內容,這是And的表現。

不少人為了證照、口號、安全感而學習,Yes + Yes + Yes …,不管多少個Yes,如果沒有後續的And,就好像鸚鵡一樣,只是學人講話的音調,就算再多學幾句話,鸚鵡還是鸚鵡,不可能變成人。

你會想跟鸚鵡說話嗎?一開始覺得有趣,看透了之後就乏味了。

***

友藏內心獨白:要平衡Yes與And。