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界要成為神的人,都必須看過這本,先把祂請來供在書架上。

系統開發之閉門造車

不曉得各位在學習ASP.net時,看的是什麼書,仗著對http protocol的小小了解,我是沒有看任何關於ASP.net的書,知識全由網路上先進的文章而來,但看了前輩的文章,發現這會是有盲點的
(的確是有很多盲點,從自己目前開發系統版本某些特立獨行的地方就可以知道了)

以下是一些引用:
“一直寫 Code 而不看書是不實際的,這樣的寫 Code 過程必須面對許多因為觀念不對而導致寫錯程式的狀況”
“沒有完整的觀念會導致寫程式缺乏效率”
“部分程式設計師都很忙,都在忙著寫 Code,真要讓所有人想清楚再寫是不太可能的,所以大多數的人都一樣先寫了再說,最後如果有時間再重整自己的程式碼,因為程式設計師都是蠻懶的,所以我還蠻懷疑有幾個人會主動重整自己的程式碼,大多應該是沒出 Bug 就得過且過吧!不過會自己主動重整自己程式碼的人大多是有潛力的人才”
(你會重整自己寫寫的程式嗎? Refactoring是個好習慣)

作者推荐了一本入門書《Professional ASP.NET 2.0 Special Edition》,我想可以做為參考。

檔案下載solution

關於使用browser下載檔案,針對http header的設定方式,因為client端的異質性,著實讓我們吃了不少苦。以下是我認為最佳的header設定方式,同時在IE6,IE7,Firefox中work又符合user期望,雖然是用VB,但http header的部份其它language並無不同,其中有幾個地方特別值得我們注意:
1. 使用IE下載檔案,預設是用inline方式,直接開啟在browser中,此點是為了避免IE在下載檔案時的cache defect,至於其它browser,因為可用browser自身的選項決定是否直接開啟,並無設定inline需要。
2. 使用IE下載檔案,若user不想inline開啟,可教導user於下載連結處按右鍵,另存目標即可。
3. 雖然我們設定了response header是用utf-8編碼,但IE仍無法辨識特殊字元的檔名,因此檔名的部份用了url encode,但其它browser並無此問題。
4. 對firefox來說,既然已指定了response header為utf-8,此時若在對檔名進行url encode,反而會得到不符合user期望的檔名。
5. 檔名的部份,最好一定要用引號括起來,以免檔名在包含空白字完時被截斷; IE下載時,因用了url encode,故無此問題,但其它browser因未用url encode故未加引號會導致截斷。

Response.ContentType = dt.Rows(0)("file_type")

Response.AppendHeader("Content-Length", dt.Rows(0)("file_size").ToString())

If IsIe = True Then

Response.AppendHeader("Content-Disposition", "inline; filename=""" & Server.UrlEncode(dt.Rows(0)("file_name")) & """")

Response.AddHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0")

Response.AddHeader("Pragma", "public")

Else

'檔名加入引號, 可避免檔名有空白時,被截斷的問題

'上方的IE,因用UrlEncode雖無此問題,但還是加

Response.AppendHeader("Content-Disposition", "attachment; filename=""" & dt.Rows(0)("file_name") & """")

Response.AddHeader("Pragma", "no-cache")

End If

Response.BinaryWrite(dt.Rows(0)("file_content"))