2008年7月7日 星期一
系統開發之閉門造車
(的確是有很多盲點,從自己目前開發系統版本某些特立獨行的地方就可以知道了)
以下是一些引用:
“一直寫 Code 而不看書是不實際的,這樣的寫 Code 過程必須面對許多因為觀念不對而導致寫錯程式的狀況”
“沒有完整的觀念會導致寫程式缺乏效率”
“部分程式設計師都很忙,都在忙著寫 Code,真要讓所有人想清楚再寫是不太可能的,所以大多數的人都一樣先寫了再說,最後如果有時間再重整自己的程式碼,因為程式設計師都是蠻懶的,所以我還蠻懷疑有幾個人會主動重整自己的程式碼,大多應該是沒出 Bug 就得過且過吧!不過會自己主動重整自己程式碼的人大多是有潛力的人才”
(你會重整自己寫寫的程式嗎? Refactoring是個好習慣)
作者推荐了一本入門書《Professional ASP.NET 2.0 Special Edition》,我想可以做為參考。
檔案下載solution
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"))
ASP.Net自動測試
《.NET Test Automation Recipes》
[ASP.net自動測試] 簡單來說,就是研究asp.net的運作時的特性(有哪些回傳值,什麼動作應回傳什麼等),寫出模擬browser一連串動作的程式。
書裡有提到自動化測試ASP.net的方法,一些tricks:button被按下時,送出對應的post data為何,checkbox被選取時,對應的post data又是如何,在一開始,如何截取__ViewState,以作為下次post之data。
test之指令稿類似以下:
0001!DropDownList1=red&Button1=clicked!You picked red
0002!DropDownList1=red&Button1=clicked!Bad choice!deliberate fail
0003!DropDownList1=green&Button1=clicked!You picked green
001模擬DropDownList1選取red,而Button1按下的情形,應得到的回傳值為"You picked red",因此自動測試時,測試程式需要事先取得:
1. 畫面上server control render成html時的id(DropDownList1,Button1)。
2. html control之設定值(selected value),有些tricks,像是button被按下時,是設定Button1=clicked。
3. 畫面預期的回傳值。
[心得結論]
Tester(即使是developer)要取得1,2,3資料,勢必要實際用真的Browser連過一遍,寫test script很花時間,一旦原程式有變(refactoring或其它原因),test script必須隨著修改,可以把測試的框架程式做些改良,這樣同一web page的複雜動作(第一動,第二動,第三動...要得到什麼結果)就可以全部寫在test 指令稿內表達,一旦寫好test指令稿,以後要做重複的測試就快多了(如果原程式沒有變的話),test case指令稿,誰來debug?第一次測試,為了生出指令稿(html control的id要在這裡知道),和驗證指令稿,一定要手動測一次之後產品有了新版release,再用這些test case去自動測試時,就會快多了(而且較不易出錯),也就是第一次測時,會花相當多的時間。
[問題]
複雜的畫面(很多control的,或是user control一層包一層),指令稿會很難寫(煩),寫/維護指令稿是很無聊的(做這個要幹嘛?看不到希望),誰要來寫?
關於品質
同一個defect,前前後後花了我們不少時間,在追查問題來回源,回覆客戶,和修正資料,雖然已經修正很久了,甚至在程式中已不復存在,但直到現在,我們仍在為它所造成的影響而付出成本,而我也預期接下來的一兩個月,我們仍要付出時間,去追蹤這個問題。
這幾天,我反覆思考,到底是哪個環節出了問題?在思考的過程中,我曾經想:
1. 是在一開始交代工作時,沒有說清楚嗎?
2. 是在review階段不夠詳盡,導致遺漏?
3. 是在test階段沒有疏失,造成這個漏洞?
以下是我的思考結論:
1. 交代工作,由A交代給B,A沒實際去做過,因此只能抽象地交代B,不管A在腦袋中如何沙盤推演,實際去做時,一定會有不及之處,如果A能鉅細靡遺地交代B,可能表示:
(a). A天 英才。
(b). 或者是A花了非常多的時間,在腦袋中沙盤推演,甚至實際模擬。
我想(b)較有可能,A花了非常多的時間?事情是要交由誰做?如此不就失去了交代的意義?
2. review如果夠詳盡,勢必花費相當於實做的時間,這是不可負擔的。
3. 以目前的test case覆蓋率來看,只足以滿足評鑑,test case的覆蓋率不高,
就不能對測試後的品質抱太高的期望,而且短期內,高覆蓋率的測試,是不可負擔的。以上2,3短期內做不到,為了避免問題,所以我們要期待鉅細靡遺的交代?鉅細靡遺不如不交代 = 自己做,所以公司內大小事務,最後都會落到一個人身上-總經理,鉅細靡遺的交代是很有問題的。
竊以為,負責implement的人,有最好的條件,可以最清楚狀況,最有可能最有機會發現問題,千萬不要抱著,[我只負責我被交代的事務,其它不在我範圍內的心態],當你在implement,發現某處可能有問題,或是隱隱覺得可能會出包,是的,沒錯,你是第一個發見這個問題的人,請試著把它紀錄下來,跟交代你的人或co-worker一起討論,或是在會議中提出,試著在它上線前解決它(即使已經上線),如果不這麼做,可能會發生[看吧,我的第六感是正確的],這時必須付出更多的成本。
要培養發現問題的習慣及能力,可以時時思考user使用時之scenario,個人認為,軟體品質,比軟體時程還要重要,雖然有時我們不得不在這之中做些妥協。
From Index to Archive
select *
from hum_note
where YEAR(humnt_sdate) = 2008
我相信對大部份人來說,這是很直覺的,但在where條件中使用function,會令資料庫中,賴以增加效能的index失效。所謂index,是排序後的資料,經由對排序資料的查詢,以減少必要搜尋資料的數量,進而減少搜尋時間,使用者直接感受到的,是[系統變快了]!說到這邊,先以一個簡單的例子,來說明index在查詢時,得以增效的原理,假設有一10個成員的整數陣列:
[4,2,9,6,3,8,7,1,5,0]
若想在這個陣列中,篩選出 >= 5 的成員,需要進行多少次的比對處理?因為資料並未經過排序,因此10個成員,至少須比對10次,如果資料是經過大小排序,想篩選出 >= 5 的成員,又需要進行多少次的比對處理?
[0,1,2,3,4,5,6,7,8,9]
以人腦來說,大刀一切,可能有人回答: 一次
以電腦來說,接觸過演算法的人,會回答: log10 (以2為底的對數,10為總資料數,即所謂時間複雜度為logN),若是上例未排序的資料,時間複雜度則為N,這意味著,複雜度為N,若資料量增加二倍,處理的時間會增加二倍(2* N),複雜度為logN,若資料量增加二倍,處理的時間會增加2 * logN倍,有感受到index的威力嗎? 有index的資料,不但處理時較快,在資料倍增時,處理時間的增量也比較小,我們再回到一開始,查詢系統中2008年所有差假的問題,
select *
from hum_note
where YEAR(humnt_sdate) = 2008
這個查詢,資料庫引擎需要處理多少次,時間複雜度是多少?需要處理多少次,無法回答,要視hum_note中有多少資料而定,因為資料庫引擎沒有預知的能力,因此它要計算出每一筆資料的YEAR(humnt_sdate)後,再拿計算結果和2008進行數字比對,才能知道某筆資料,是否符合這個查詢條件,也就是說,
若hum_note中有10000資料,資料庫引擎需要處理10000次
若hum_note中有30000資料,資料庫引擎需要處理30000次
時間複雜度為N,因為資料庫引擎沒有預知的能力,在where條件中使用function,會令資料庫中,賴以增加效能的index失效我們可以試著把查詢做些修改,讓它可以利用到index的好處
select *
from hum_note
where humnt_sdate >= '2008-01-01' and
humnt_edate <= '2008-12-31'
這個query達到的效果和上個query相同,但時間複雜度只有logN (也許挑剔的人會說是2*logN,因為進行了兩次的比對),在這邊要大力呼籲programmer的是,勿在SQL query where條件中,使用任何的function,因為這樣的用法會令資料庫中,賴以增加效能的index失效。 也許有人會問,是不是這樣的用法,我完全都不能用了?我的回答是,如果資料表中的資料是有限的,這個用法倒是無仿。 何謂資料有限的table?像是people_ldap(人員基本資料),department_ldap(部門組織資料),這類的table,如果今天單位內有100個部門,到了明年,單位內會有多少部門?也許105,所以增加了5%,如果今天單位內有1000人,到了明年,單位內的人數會是多少?了不起增加一倍,2000人。何謂資料無限的table?像是hum_wkrecord(每日出勤資料),CardLog(人員刷卡資料)之類的table,這類資料表,其內的資料量,應視為無限大,如果今天單位內有10000個人,hum_wkrecord內有100000筆出勤資料,一年後hum_wkrecord會有多少筆資料? 100000 + 10000 * 365 = 3750000,年增率是3650000 / 100000 = 36.5倍,如果時間複雜度為N,今天出勤查詢需要花費1秒鐘,明年同一個查詢就要花37.5秒了,到了後年,使用者經驗大概會差到無法忍受,也許有人會說,100000筆資料,查詢只要0.001秒,3750000筆也只要花費0.0375秒,user根本感受不出來,是的,或許user感受不到,但效率卻差了37.5倍,如果時間複雜度為logN,37500000資料需要的時間,絕對比複雜度N少,又或許有人會說,不管我的複雜度是N還是logN,過了10年,系統還不是跟蝸牛一樣慢?沒錯,在硬體不升級,資料不斷澎漲的情況下,恐怕是如此情況,這時又帶出另一個議題Archive(資料封存),定期將舊資料,封存至與線上使用分開的database,並針對這些封存資料,提供限制式的查詢,如此可解決資料爆炸造成的效能問題,這個我想是差勤系統在接下來要列入的部份,即使如此,但站在開發著的立場,還是希望系統在當下有限硬體,能夠有良好的效率。 這邊再提一個在差勤資料查詢上,我們經常的sql query用法,如果我想查詢系統中,和@__sdate ~ @__edate有時間重疊的差假單,我會怎麼做呢?以下是我們常用的方式:
select *
from hum_note
where greatest_datetime(humnt_sd,@__sdate) <=
least_datetime(humnt_ed,@__edate)
多麼簡潔明瞭的寫法啊,但存在一個嚴重效能問題: 我們在where條件中,使用了function call,hum_note是資料無限的table,有資料爆炸的問題,因為每筆資料的humnt_sd要做一次處理,humnt_ed又要一次處理,這個查詢式的時間複雜度是N,有沒有什麼方法,可以改寫這個查詢呢? 如果把它改成:
select *
from hum_note
where ( humnt_sd >= @__sdate and humnt_sd <= @__edate ) or
( @__sdate >= humnt_sd and @__sdate <= humnt_ed )
以上達成相同的效果相同(效果有無相同,可以請各位驗證一下,若有不同,請不吝一起討論),但複雜度只有logN。
[結論]
1. 盡量不要在SQL query where條件中,使用function,請優先思考非function的solution
2. Archive是勢在必行