2008年8月10日 星期日

一字曰心

昨晚的綜藝大哥大『大魔競』單元,場景搬到中國大陸,各參賽者輪番上台,表演完畢後,由台下五位大陸及台灣的評審進行講評,規則為五個評審中少於三位評審按燈,即算過關。其中有一位五十多歲的張姓魔術師(姑且稱呼他為張大哥)的表演,及之後的評審講評,讓我感觸良多。那天張大哥的表演並沒有失常,但他卻沒有過關,其中一位大陸評審認為他的手法熟練,但缺乏新意,評審劉謙則認為他沒有扮演好一位魔術師的角色,『身為一位魔術師,永遠不要忘記兩件事,一.永遠不要忘了娛樂你的觀眾,二.永遠要想辦法提升自己的實力』。張大哥為職業魔術師,平時即以表演魔術為業,接觸魔術超過二十年以上,換句話說,台下許多評審,不論在年紀或是魔術界的資歷,都算是他的後輩。年紀一大把了,還要站在台上被後進小伙子嫌東嫌西,想想真是情何以堪。劉謙的話,移到軟體開發上也同樣適用。『身為一位開發者,永遠不要忘記兩件事,一.永遠不要忘了滿足你的客戶,二.永遠要想辦法提升自己的實力』(客戶除了實際的customer外,也包含主管)。

最近review一位網友的程式,驚訝地發現,一位有兩年以上經驗的職業programmer寫出來的程式,竟然跟一個程式初學者差不多,一些粗淺的觀念都搞不清楚,「這些年來他是怎麼過的?」,內心充滿疑惑,我們彼此平輩論交,沒有長幼問題,但說得太直接怕傷了他的自尊,只是淡淡地點出一些問題。思前想後,會造成這樣結果的原因可能有幾個:
  1. 他從來都沒有嚐試想要提升自己的實力
  2. 他有嚐試想要提升自己的實力,但失敗了
  3. 他有嚐試想要提升自己的實力,他認為他成功了,只是卻不是這麼一回事
1是我所無法想像的,個人自從成為開發者,無時無刻不在思考如何開發出符合客戶需求,又能兼顧效能和維護性、擴充性的系統,因此也總認為所有的programmer皆跟我有相同的想法。第2點也是令我難以理解,難道利用兩年的時間(好吧,可能在這兩年之中,有很多公事要忙),把自己的實力由初學者向上推一階會有困難?讓我無法想像的是,每日浸淫的東西,竟然會差勁如斯,如果是這樣,建議應及早轉行,以免耽誤大好青春。3是我認為最有可能的情況,他非常地努力提升,也很滿意自己提升的成果,但他是在象牙塔中做這件事情,導致無法分辨他所認為的成果是正確還是錯誤、程度是大還是小。或許比起兩年前他已有進步,但以我們對工程師的要求,他的成長小到幾乎看不見;或是比之兩年前,他已有很大的不同,但因為他的努力方向錯誤,導致他愈寫愈像新手。我想張大哥在他二十年的魔術生涯中,一定也想過要提升實力,只是或許他一直以為(或許當年的社會觀感即是如此)只要具備熟練的手法,就是一位好的魔術師。

思考至此讓我心生警惕,會不會二十年後,當我已是公司資深開發人員時,寫出來的程式會讓後生小輩看不下去?自認很有上進心,但我的努力是在正確的方向嗎?一直使用錯誤的練功方法,自然久練不成,但數十年已過去匆匆,再也不會回來。看來廣泛地學習,多觀摩前輩後進的功法,避免坐井觀天,不斷修正自省,是避免落入象牙塔的唯一方法,嗯...要用心。

2008年8月2日 星期六

網拍二三事

迷人的網路拍賣
網路拍賣迷人之處在於,身為買家我們可以用相對便宜的價格,買到理想中的商品,或許在品質上有些妥協,也許是二手的物品本身有些缺陷(如買二手書,書內可能有前一位持有者所留的筆記重點、破損髒污),但對真正有需求的人來說,大部份的缺陷並無損我們對商品價值的期望。譬如,我想買一本書,實際上的情況是,我想要得到書中所闡述的內容,因此對於書面是否有污漬、書內是否有另外一個人的筆記並不在意,只要不影響我正常閱讀即可。若在一般正常的通路購買,可能要花上五百塊,但在拍賣中可能以不到二百五的價格就可以買到。

身為賣家,我們可以把有價值,但用不到的物品,以低廉的成本(拍照、上架所花費的人工)出售。在以往若我們持有這種用不著,但卻明知其價值的物品的處理方式可能為︰
  1. 送給需要的親友
  2. 賣給資源回收商
  3. 敝帚自珍,留給子孫
  4. 在門市出售
1可能是我們以往最常使用的方式,既為親友,又名為『送』,當然無法跟其收取費用,這不失為敦親睦鄰的好方法。但想到古人言:『己所不欲,勿施於人』,這個自己不想要的東西,有時也不怎麼好意思送給別人,也許收到的親友嫌髒舊而未可說。所謂親友,即是在我們周遭,就算不是常見面,也總是遇得到,一個搞不好弄得以後見面尷尬就麻煩了。

2是一般非做生意之民眾最常用的方法,筆者在開始經營網拍之前,就經常將用不到的舊書籍賣給資源回收商。目前廢紙的回收價格約為一公斤六塊錢,一本書能有多重?想到當初花費數百元買的書,如今卻以不到二十元賣出真是情何以堪啊!在這樣的心理下,部份人會採取3的方式,「老子帶到棺材裡總行了吧!」為了賭這一口氣,將無用之物堆滿已經沒啥空間的家中,沒學過經濟學也知道庫存是需要成本的,自己家裡雖然不收租金,但狹小的空間影響生活品質,颱風淹水時還要分神照顧,白蟻蟲咬再來心痛惋惜。

4的方式大概只有平時就是做生意有門市,或是有親友可以寄賣的民眾才能做得到。實體門市的成本高,需要人工去上下貨、門市清潔、收銀、管理、人員...太多事務需要人工去handle,對於有其他正業的人來說,通常難以當做業餘兼顧。

網拍訂價是一門藝術
在網路上賣東西,訂價是一門藝術。在實體商店盛行的時代,資訊較不對稱,貨比三家所付出的代價可能高過價差。今天我在這家鞋店看中意一雙鞋,但要到隔兩條街去對同一雙鞋進行比價,到了兩條街的那家店後發現反而比較貴,這時我應該繼續到第三家店呢,還是回到第一家店去直接買下?網路上比價所付出的代價極小,最多是再開一個視窗google一下,跟您賣同樣物品的商店可能不止一家,若您的訂價比較高,憑什麼客戶要光臨?

東西只要能夠deliver到需要的人手上,其價值是不可限量的
訂價應根據什麼?若是以自身無用做為出發點,那麼不管價格訂得多低,抱著『反正沒用,只要賣得出就賺到』的心態,可能會把價格訂得太低;若是以市價做為出發點,『當初買時花了五百元,現在要賣二百元怎麼賣到下去?』恐怕會把價格訂得太高。訂價太高商品在架上乏人問津內心焦急,訂太低在東西被買走時搥心肝『心痛啊!怎麼不把價格訂高些!』,筆者就曾經在未經調查又急著出門的情況下,匆匆將商品貼標上架,結果瞬間被眼明手快的買家,以迅雷不及掩耳的速度買走,當時可是心痛久久不能平復,做生意誠信最重要,高價物低價賣出只能認了,以當做廣結善緣的想法,希望東西能夠對她(他)有所幫助,並期望自己早日在這樣的複雜心境中,學到訂價策略之奧義。

2008年7月28日 星期一

沒有人可以教(會)你任何事

記得上高中前,在學校中常常有一個困擾-老師教的我都聽不懂。看到這邊可能有人會覺得我是那種功課很差的學生,正好相反,我在班上總是名列前茅,且經常是同學們在課業上的小老師。我發覺那些功課不如我的同學,能夠聽得懂老師的講解,而那些來請教我的同學,在經我解說後,大部份也都能理解。我比別人笨嗎?心中不由得昇起這樣的念頭,是的,我認為我不比其他同學聰明,但這樣的話由一位總是在段考拿第一名的人口中說出來沒有人會相信。

由於上課聽不懂,個性又內向不敢發問,在課後總是要自己再多花時間在書本上,這種情形讓我開始思考學習和教導的本質。在我們開始說明一些東西之前,先定義一些名詞,以避免後文讓人混淆:
  • 老師 任何意識的傳授方我們稱之為老師。
  • 學生 任何意識的接受方我們稱之為學生。
  • 意識 老師意圖讓學生接受並瞭解的所有知識、常識、資訊等。
  • 領悟 學生理解老師所傳授的意識的過程。
教導及學習的過程為:
  1. 老師提供教導
  2. 學生領悟並學會
教導可以分為兩個部份:『教』及『導』。單純指的是老師將意識以任何方式呈現給學生,而指的是老師以各種方式,建立起學生達成領悟的基礎。由上1可知,老師只負責這個過程的教導部份,而學不學得會的關鍵還是在學生身上,如果學生資質不行或基礎太差,恐怕是不成的。「智商太低的學不會」這樣的說法可能會招來許多抗議,我們在成長的過程中,一向被灌輸「勤能補拙」的觀念,這句話在大部份時候還是對的,因為絕大多數的人都有足夠的資質,能夠學會老師教導的東西,但若是駑鈍之資或是朽木不可雕,孔子再世也會束手無策。

某些老師只注重教,而常忽略導,造成的結果是學生經常覺得那位老師的課艱澀難懂,許多學者式的教授都容易落入這樣的巢臼。補習班的名師之所以是名師,是因為他們在教的表達上能夠很清楚,在導的技巧上又熟諳,而上他們課的學生,通常是具有某些基礎的,因此往往在他們的課堂上會覺得如沐春風。在武俠小說中,身懷絕技的高人在教弟子時總是顯得高深莫測,是因為在這樣的領域到達一定程度後,若直接用「教」的方式,學生的資質基礎足夠也就罷了,若學生的基礎不夠,自行曲解,只是一些拳腳刀槍則易走入偏門,若是高級的內功則走火入魔甚至失去性命。

沒有人可以教(會)你任何事,學不學得會關鍵還是在自己身上,學不會的東西有可能是因緣不足、條件不夠,這時放棄是稍嫌過早;也許有些東西一輩都學不成了,或是花了一輩子也不及某些人的一個月,這不打緊,每個人都有他的優勢天份所在,不一定要鑽牛角尖跟著湊熱鬧,找到自己的興趣和長處,好好培養發展,終究能夠有好的成就。

2008年7月15日 星期二

Interface的應用:Dependency Inversion

所謂DIP(Dependency Inversion Principle) 依其定義:
  • 高階模組不應相依於低階模組
  • 高階模組和低階模組皆相依於抽象層
  • 抽象層不應相依於實做細節
  • 實做細節應相依於抽象層
這樣的設計方式,可讓所有物件/模組間的相依關系減至最低,最佳狀況下能夠達成物件鬆藕合的理想。但理想和現實是有差距的,為了達成理想而必須付出難以忍受的代價是不切實際。能夠想像所有的物件定義,皆須有一至多個對應的interface的情況嗎?有誰真能在寫程式時,所有變數皆以抽象層或interface替代?本篇並不是要提倡DIP,甚至在接下來的內容是違反DIP的,若想研究DIP應試著用google key入DIP關鍵字,本篇只在說明如何使用interface達成dependency inversion(相依性反轉)。

所謂Dependency Inversion
假定我們有套件及類別如下:

套件

內含類別

用途

EIP

UpdateUserData

更新使用者資料

EIPLibrary

WriteToDatabase

寫入資料庫


EIP package定位為高階的模組,用於處理使用者相關資料;DbLibrary定位為低階模組,用於協助EIP package將使用者資料寫入資料庫。沒錯,這是典型傳統的結構化程序設計中,高階模組依賴於低階模組的情況,今日我們並沒有打算改變讓DbLibrary相依於EIP,但考慮以下情況:
DbLibrary的部份定義為
class WriteToDatabase {
void WriteNow( UserData obj ) {
WriteName( obj.GetName() );
}
void WriteNow( string userName );
}
由於WriteToDatabase中使用了UserData,所以我們說WriteToDatabase相依於UserData,同時也意味著DbLibrary相依於EIP;但一開始我們已經說明,DbLibrary的目的,是協助EIP,也就是EIP本已相依於DbLibrary,今WriteToDatabase的撰寫方式,豈不是讓WriteToDatabase相依於EIP?循環相依,明顯的是一個設計上的錯誤,同時WriteToDatabase相依於EIP的情況,也讓WriteToDatabase的用途受限(引用WriteToDatabase時,勢必同時要引用EIP package)。有什麼方法可以fix這個窘境?有,答案就是interface,我們可以在package DbLibrary中定義一interface:
interface IUserData {
string GetName();
}

接著讓UserData implement IUserData:
class UserData implements IUserData {
string GetName();
}

再來我們修改WriteToDatabase的定義:
class WriteToDatabase {
void WriteNow( IUserData obj ) {
WriteName( obj.GetName() );
}
void WriteNow( string userName );
}

由於IUserData是定義在DbLibrary中,因此WriteToDatabase並不需要相依於EIP;而UserData因為參考到IUserData,所以反倒是依賴於DbLibrary了,如此即反轉原本程式的相依方向了。

2008年7月13日 星期日

關於Interface

『我不太曉得何時該用Interface』最近一位後進沮喪地這麼跟我說著。當下我只是安慰著他『多用幾次就會了解了』(實際上也是如此),但想起自己當初也曾為了Interface這個奇怪的型別而困惑,因此決定寫一篇文章,期盼能夠對後進同好能有所幫助。

一切要由Lauguage實做的特性說起。一般Language於實做時,對於型別的檢查可分為以下幾種類型:
  • Statically typed language:語言的型別檢查發生於compile time,大部份的statically typed language compiler要求使用者在使用變數前須事先宣告型別。例如:Java、C、C++、C#,好處是多數語法上的錯誤,能夠於早期發現,以及程式執行上更有效率。
  • Dynamically typed language:相對於Statically typed language,語言的型別檢查發生於run time,於第一次指定變數值時,compiler(or interpreter)始決定變數型別。例如:PHP、Perl、Python,好處是使用上具有彈性。
  • Strongly typed language:語言於執行期,其型別不可隨意更改,例如:一個儲存integer的變數,無法用於儲存字串,Java,C,Python等語言皆是。
  • Weakly typed language:相對於Strongly typed language,變數的型別可勿略,例如於PHP中,我們可以將字串'12'和'3'concate在一起,最後再把'123'當作整數123來進行數字運算。
Interface的使用和Lauguage的Statically typed有關,起因於compiler於compile time要求型別的檢查。以下以一個例子說明會較清楚,假定有一要求傳入一個型態為SomeType參數的SomeMethod函式:
void SomeMethod( SomeType inputObject ) {
inputObject.DoSomething();
}
class SomeType {
public void DoSomething() {
//DomSomething implement
}
public void DoOther(){
//DomOther implement
}
}
因為型別檢查,compiler已確定inputObject 確實為型別SomeType,故我們能很放心地於SomeMethod中執行 inputObject.DoSomething() ,而不必擔心inputObject是否已定義了DoSomething()。但SomeMethod這個函式似乎不怎麼好用,因為它要求的參數型別固定,我們無法傳入其它型別的參數,接觸過物件導向Programming的人,應該很快可以想到,我們可以使用繼承的方式,從SomeType衍生出子類別,而此類別的物件,亦能用於呼叫SomeMethod:
class SomeTypeChild extends SomeType {
public void overrides DoSomething();
}
SomeType obj = new SomeTypeChild();
SomeMethod( obj );

類別繼承的好處是,我們可以繼承父類別既有的介面(interface)和實做(implement),若今日我不想繼承SomeType的實做部份,但又想使用SomeMethod呢?又或著我只想繼承SomeType的DoSomething介面,因為SomeMethod中,只需要使用到SomeType的DoSomething?這時Interface的功用就突顯出來了,我們可以改變SomeType的宣告,接受一個有DoSomething介面的物件:
void SomeMethod( InterfaceSomeType inputObject ) {
inputObject.DoSomething();
}
interface InterfaceSomeType {
DoSomething()
}

接下來,只要是繼承了InterfaceSomeType,而有DoSometing介面的型別,皆可用於SomeMethod的參數呼叫,而不用繼承原來SomeType的實做部份,和SomeMethod中不需要用到的DoOther部份。

介面繼承(或介面實做)是類別間的約定,實做該介面的類別『同意』並『保證』要提供介面中所定義到的method和property。由以上的簡單例子,我們可以了解到Interface可以讓我們專注於物件的介面,而不須牽涉到到類別的實做。

下篇,Interface的應用:Dependency Inversion

2008年7月11日 星期五

備份永遠無法全面

Visual studio或是其他的IDE開發工具似乎都有一嚴重缺點,在專案變得龐大時,所耗費的系統資源愈多,電腦回應的時間爆長。這在專案累積至一定規模後簡直是一個惡夢,即使是修改一個小功能,編譯測試也要等半天,有些懷念當時在開發PHP式時的靈便。因此我習慣在現有專案中加入新Features的方式,是新增一個temporary專案,在該tmp專案中撰寫測試無誤後,才將新的程式碼porting至原有專案。這在舊有專案已經肥得不像話時特別好用,小型專案的好處是,不論在開啟、編譯、測試、啟動都是飛快。通常tmp專案是沒有Commit至Subversion的,因為「總有一天」它是要被整合到原有專案的。
今日跟平日一樣,一到坐位上,開啟開發用的PC,正覺得電腦怎麼回應的比平時還要慢上數倍,就聽到硬碟發出哀嚎聲,接著電腦就不理我了,硬碟掛了...心裡的OS這麼說著。正在慶幸好加在平時就有固定把程式上到Subversion,突然心中一痛,想起最近寫好測好,放在tmp專案中還沒整合到原有專案的程式...-_-|||(第二次之後應該會快很多,心裡邊安慰自己邊流淚...)

2008年7月7日 星期一

Code Complete

Code Complete

據說在programming界要成為神的人,都必須看過這本,先把祂請來供在書架上。