??xml version="1.0" encoding="utf-8" standalone="yes"?>狠狠综合久久av一区二区老牛 ,91成人精品,狠狠狠综合7777久夜色撩人http://www.aygfsteel.com/emu/articles/63112.htmlemuemuFri, 11 Aug 2006 19:18:00 GMThttp://www.aygfsteel.com/emu/articles/63112.htmlhttp://www.aygfsteel.com/emu/comments/63112.htmlhttp://www.aygfsteel.com/emu/articles/63112.html#Feedback0http://www.aygfsteel.com/emu/comments/commentRss/63112.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/63112.html

讓錯的程式看得出?/a>


作? 周思博 (Joel Spolsky)
譯: Paul May 梅普?
2005.5.11

 時間回到1983q九? 我第一個真正的工作是在以色列的Oranim. 這家大型麵包工廠每晚都用六個貨般大的巨型爐子烤出為數十萬的麵?

我第一ơ走進那安包廠時覺得裡頭實在髒得離? 爐壁發黃器生鏽而且到處都是?

"這裡一直都這麼髒嗎?"我問?

"什? 你講這什D?"E理回答?"我們才剛打掃過. 這已E是qN׃來最乾淨的時候了."

說得真好!

我花了好qր月每天早上打掃才真正瞭解他們的意? 麵包工廠來? 乾淨是指器裡沒有生늳在烤, 垃圾堆裡沒有發酵的麵p? 而且地板上也沒有堆生늳.

乾淨並不是指爐子漆得雪白亮麗. 爐子大概十年才會漆一? 並不會每天都來一? 乾淨也不是說把a擦得乾乾淨淨. 事實上很多機器都得定期上? 一層薄淨的沚w常暗示器剛做過清潔保?

This is what a dough rounder looks like. 麵包工廠裡這整套乾淨的概念都得E由學習而來. 圈外Z可能走進去p說出哪裡乾淨哪裡? 圈外人絕不會惛_要看늳滑֜?把方늳滾成球Ş的機? 見右邊附?內壁有沒有刮乾淨. 圈外人會爐子外壁鑲板掉色是有問題?因為鑲板?em>?/em>很顯? 不過麵包師傅Ҏ不在意爐子的塗漆開始發黃. 因為麵包的味道還是一樣棒.

在麵包工廠待兩個月, 你學會如?看出"乾淨.

E式g是一樣的.

當你剛開始寫E式或嘗試讀用新語言寫的E式? 所有程式碼看v來都一樣神U不可解. 而在瞭解該種E式語言? 你連明的語法錯誤都看不出?

在學的W一階段, 你會開始發現一E我們通常Eq"R程風格"的東? 於是你開始注意那些不遵@^排標準的程式碼和有用多個大寫字母的變數.

也就是這個階D你會說:"該死的؜? 我們這裡一?/em>要定Z些一致的R程風格!" 然後W二天寫Z份你們團隊用的編E風? 接下來用六天來討論One True Brace Style(譯著:是K&R style), 然後再花三星期把舊程式碼改寫成符合One True Brace Style, 一直做到經理發現並責怪你把時間浪d不能賺錢的事為止. 你想惛_實不需要一ơ全部改? 看到哪裡改到哪裡也沒什麼關? 於是有一半的E式已E改成True Brace Style, 而沒多久你就忘記這g事了. 接下來你開始滿腦子惌其他與賺錢無關的? 比如把某個字串類別換成另一個字串類別等{?

當你某特定環境下的E式愈來愈精通時, 會開始學著看到其他p. 那些p可能完全合法並符合編E風? d會讓你擔心不?

舉例來說在C語言?

char* dest, src;

這是合語法的E式? 這可能符合你的編E規? 甚至可能是故意這樣寫的, 不過如果你寫C的經驗夠, 會注意這種寫法?tt>dest宣告?tt>字元指標Lsrc宣告?tt>字元而已, ?em>可能是你的意? 不過也可能不? 反正這段E式看v來有點不勁.

來看更細微的例子:

if (i != 0)
    foo(i);

這段E式是百分之百正的;它符合大多數的編E規也完全沒有錯誤, 不過你可能會質疑if敘述所接的單敘qC體並未用大括號包起來, 因為你腦子裡惛_有h可能會插入另一行程式碼

if (i != 0)
    bar(i);
    foo(i);

...又忘a加上大括號, i果?tt>foo(i)變成气R會執? 所以當你看到沒有用大括弧包起來的程式碼區D|, 可能會感覺Ci讓你不舒服的氣味.

好啦, 到目前為止我已經提到三種E式師的成就層級:

1. 你不知道乾淨和髒有什麼分?

2. 你對乾淨有粗淺的認知, 主要以是否符合編E規為?

3. 你開始能嗅出藏在表面下不勁的蛛i馬? 你會察覺這是問題並且扑և來修?

不過其實還有更高的層? 而這也是我真正要說的:

4. 你有a劃地架構程式碼, 藉助能察覺問的靈眼讓程式碼更正?

這是真正的藝? 仔細?em>a計讓錯誤顯而易見的R程規範, 藉此製作出穩固的E式.

所以現在我要帶你看一個小例子然後再展CZ個通用的規? 你可以利用這個通則a計出創造增加程式穩固的R程規範. 最後我會把主題引到為某種匈牙利命名法(可能不是讓h們暈到的那種)進行辯護, 並且批判某些環境(也可能不是你最常用的那E環?下的例外處理.

不過如果你深信匈牙利命名法不是好p, 認為例外處理是從自y克力奶昔以來最的發明, 而且完全不想聽聽其他意見, 沒問? 你可以改ȝ力那裡看?a >好看的O?/a>; 反正你在這裡也沒什麼好看的; 事實上在一分鐘內我會拿出實際的程式碼例, 這些例很可能會讓你在不爽前暈睡過M. 沒錯. 我想我的a畫是把你哄到沈沈入? 你睡著無法抉|時把"匈牙利命名法=? 例外處理=?的想法偷偷塞進你腦子裡面.

一個例?/strong>

Somewhere in Umbria

好了. 提到這個例? 讓我們假裝你正在寫某Eweb應用E式, 因為這陣子小朋友g都流行寫這玩?

珑֜有一E叫跨站x漏洞(Cross Site Scripting Vulnearability)的安全漏z? ^寫?a >XSS. 我在這裡不談細節: 你只需要知道在寫web應用E式? 一定要心i不能把使用者填入表單的M字串直接傛_?

舉例來說, 如果你有一個網頁會讓用者在R輯框入姓? 傳送後會跛_另一個寫?你好? 張三!"(假設使用者的名字是張?的網? 很好, 這就是個安全漏z? 因為使用者可能不輸入"張三"而入某E奇怪的HTML及JavaScript, 這些奇怪的JavaScript可能會做些低級事情, 比如讀Z寫的cookie內容轉送到壞h的壞E站? 而這些低級事現在看起來是你搞的鬼.

讓我們把E式用虛擬碼的方法寫Z. 惛_以下的程?/p>

s = Request("name")

會由HTML表格讀取用者?一個POST的參?. 如果你曾E寫Z面的E式?

Write "你好, " & Request("name")

那你的網站已E有讓XSSL的漏z了. 光這樣夠?

你必須在複製回HTML之前先編才能避免這個漏z? 所編就是把"換成", ?tt>>換成>, 如此推. 所?/p>

Write "你好, " & Encode(Request("name"))

是絕安全的.

所有來自用者的字串都是不安全的. M不安全的字串都得先編後才能輸出.

讓我們嘗試設a一i編E規? Z當你犯這種錯時E式?em>看v?/em>是錯的. 如果E式有?臛_看v?/em>?, 很有機會被修改或審視這段E式的h抓到.

可能Ҏ一

Ҏ一是將所有字串立即編? ׃用者取得後馬上進行:

s = Encode(Request("name"))

所以我們的規範會寫? 如果你看到沒有被Encode包住?tt>Request, E式一定是錯的.

你開始訓R自q眼睛扑ְ落單?tt>Request, 因為它們違反規?

這是有用? 因為只要你遵循規就不會有XSS問題. 不過這並不是最好的架構. 比方說你可能惌把這些使用者字串存到資料n? 這時候儲存以HTMLR碼過的字串並不合理, 因為字串有可能會用在HTMLE頁以外的場? 假如是信用卡處理E式要用時編過的資料就會產生問? 大部份web應用E式開發都會依@一個原? 所有字串在內部都是?/em>R碼? 要等到送至HTMLE頁?em>前一瞬間才會處理, 因此這可能並不是正確的架?

我們真的要能讓字串E持在不安全格式一D|?

好吧. 我再試看?

可能Ҏ?/font>

如果建立一E編E規? 要求?em>寫出M字串時必須加以編? 是否可以滿要求?

s = Request("name")

// 很後?
Write Encode(s)

珑֜當你看到一個落單沒?tt>Encode跟著?tt>Write時就知道有有問題?

? 這也不太?..有時候你的程式裡會有一段的HTML? 這種情況下是不能?/em>R碼?

If mode = "linebreak" Then prefix = "<br>"

// 很後?
Write prefix

這照我們的規範來看是錯? 我們必須要在出時加以R碼:

Write Encode(prefix)

不過珑֜應該要新增一行的"<br>"卻被R碼?tt>&lt;br&gt;, i果變成使用者可以看到的字元< b r >. 這樣的解法也不對.

所以說有時候你不能在讀入字串時R碼, 有時候你也不能在輸出時編? 這兩E提案都不能? 可是沒有適當的編D? 我們還是有Z列問的風險:

s = Request("name")

...好幾頁之?..
name = s

...好幾頁之?..
recordset("name") = name // 把名字存在資料n中的姓名?/tt>

...好幾天後...
theName = recordset("name")

...好幾頁甚臛_qր月之後...
Write theName

我們還會記得要字串編嗎? 你在M單一的地斚w看不到問? 連可以嗅的地斚w沒有. 如果這種E式有一大? 要一大票偉|才能q蹤出所有字串的來源並確認是否已R碼..

正解

所以讓我提CE能用的R程規範. 我們只有一個規?

所有來自用者的字串都必須存在以"us"(表示Unsafe String,不安全字?為字首的變數(或資料nƄ位)? 所有經HTMLR碼或來自確認安全來源的字串都必須存在以"s"(表示Safe String,安全字串)為字首的變數?

讓我們重寫程? 只是依規重新命名變? 其他完全不動.

us = Request("name")

...好幾頁之?..
usName = us

...好幾頁之?..
recordset("usName") = usName

...好幾天後...
sName = Encode(recordset("usName"))

...好幾頁甚臛_qր月之後...
Write sName

新規中值得注意的是, 只要遵@R碼規範, 不安全字串相關的錯誤一定可以由單一行的E式看Z:

s = Request("name")

是之前的錯誤, 因為你可以看?tt>Request的結果被指派i以s開頭的變? 這違反了規則. Request的結果一定是不安全的, 所以必須指z?us"開頭的變?

us = Request("name")

一定沒問題.

usName = us

一定沒問題.

sName = us

一定是錯的.

sName = Encode(us)

一定是的.

Write usName

一定是錯的.

Write sName

沒問? 下面也一樣沒問題

Write Encode(usName)

每一行程式光是看E式?/em>本np以檢? 而且如果每一行程式都? i合起來整個程式也是對?

i於好了, 利用這套R碼規範, 你的眼睛學著看到Write usXXXq道是錯的, 而且你也立即知道要如何修? 我知道一開始要看到錯誤的E式是有一點難, 不過進行三個星期後你的眼睛會慣, 像麵包廠的工h看到大麵包工廠就會馬上說:"搞什麼鬼, 這裡都沒人在掃哦! 這算啥麵包廠."

事實上我們可以再把規則g怸? ?tt>Request?tt>Encode函數改名(或封??tt>UsRequest?tt>SEncode...換句p, 傛_不安全字串以及安全字串的函數要和變數一? 分別要用Us?tt>S作為字首. 珑֜看看E式?

us = UsRequest("name")
usName = us
recordset("usName") = usName
sName = SEncode(recordset("usName"))
Write sName

看到我們的成果? 珑֜你可以看看等號兩邊的字首是否相同p扑ֈ錯誤.

us = UsRequest("name") // 沒問? 兩邊都以US開頭
s = UsRequest("name") // ?br />usName = us// ?br />sName = us// 一定錯.
sName = SEncode(us) // 一定對.

我還能再進一步把Write改名?tt>WriteS並把SEncode改名?strong>SFromUs:

us = UsRequest("name")
usName = us
recordset("usName") = usName
sName = SFromUs(recordset("usName"))
WriteSsName

這得錯?em>更加而易? 你的眼睛會學?看出"可疑的程式碼, 另外這也能協助你E由一般撰寫或pE式的動作扑ֈp的安全漏z?

讓錯的程式看得出錯是很棒沒錯, 不過M是所有安全問的最佌{? 它無法找到所有可能的問題或錯? 因為你可能沒法子看過每一行程式碼. 不過i對比什麼都不做要好, 而我很希望有套編D能讓錯誤的E式D看起來是錯? 你馬上就能獲得好? 每當E式師的眼睛掃過一行程? p檢查並防止某些特定的錯誤.

一個通則

這種讓錯誤程式看起來錯的作法有個前? 是要讓的p在螢q上R靠在一? 當我看到某個字串時並要決定 E式正? 我必須知道字串出珄所有位|以及字串是安全的還是不安全? 我不希望這些資料出現在另一個檔案或是要捲動畫面才能看到的另一? 我必須能當場看到, 而這說的就是一套變數命名規?

有很多其他的例子可以說明, 只要把某些東西搬在一起就可以改善E式? 大多數的R程規範都有如下的規?

  • 保持函數名稱短.
  • 變數宣告的地斚w使用的位|愈q愈?
  • 不要用巨集徏立你個h屬的程式語a.
  • 不要使用goto.
  • 不要讓右括弧離左括弧過一個畫?

這些規則有一個共同點, 是儘量讓一行程式碼實際作用的相關資a在畫面上愈q愈? 這樣能提高眼球找出程式實質運作內容的會.

大體上我得承認我有點x會藏東西的E式語言功能. 當你看到E式?/p>

i = j * 5;

... C來說你至會知道j會乘?而結果會存到i.

不過如果你在C++裡看到相同的片段, 你什麼都不知? 在C++中唯一能知道真正發生什g的方法就是找?tt>i?tt>j所屬的型別, 而這個型別可能會在完全不一樣的地方宣告. 因為j?tt>運算?可能有過? 在你要做乘法時會做些很機靈的? ?tt>i?tt>運算?可能也是過荷? 而兩者型別可能是不相容的, 於是又呼叫到某個自動型別D制轉換的函數. 光是檢查變數的型別還不以確? 還得檢查實作該型別的E式才? 萬一實作時又有繼承其他型別就更麻煩了, 因為你得回溯別J承的祖宗八代才能找到真正的E式? 不y又有用到別處的多型就真的有大ȝ? 因為光是知道i?tt>j宣告的型別並不夠, 還得知道它?em>此刻的型? 這不知道要看多少的程式碼, 而且依照a算理論的停問? 你永遠都不能真的癑ֈ之百定自己已經看完所有地方了(啊啊啊啊?!!).

當你看到C++?tt>i=j*5時你只能自求多福? 兄弟. 這對我來說就降低了光看程式碼扑և在問的能力.

當然? 理論上這應該沒什麼關? 當你做些過荷運算?之類聰明事時, 只要Z要提供一個優而安全的抽象|了. 天啊, 其實j是個萬國碼字串型別, 一個萬國碼字串乘以一個整?em>然是把正體中文轉成體中文的良好抽象作? 嗎?

問題當然出在沒有i對安全的抽象方? 我已E在抽象出錯定律裡討論很多了, 所以不會在這裡重複.

Scott MeyersC範了各E抽象出?臛_是C++)的型式以及所造成的傷? 他靠這個主就創出一番事業了. (順便一? Scott的書Effective C++W三版剛剛上? 整本曔R重寫? 今天去買一本吧!)

好吧.

有點q? 我最好回一下到目前為止的內?

扑և能讓錯誤E式看v來錯的編E規? 讓正的資訊集中在程式碼中相同的地方, 方便你看出某些問並立即修正.

我是匈牙?/strong>

Lugnano, Umbria, Italy 我們現在回到惡名昭彰的匈牙利命名法.

匈牙利命名法是微軟程式設a師Charles Simonyi發明? Simonyi在微軟做的主要計劃是Word; 事實上他還主持了世界上第一個所見即所得的文書處理?在Xerox Parc名為Bravoa劃).

在所見即所得的文書處理中會用到可捲動的視窗, 所以標值有兩種意義:相對DH或相對D理頁. 兩種座標的差異很? 所以好好安排是非常重要?

我猜這正是Simonyi開始採用某些之後被稱作匈牙利命名法的原因之一. 它看起來像匈牙利? 而Simonyi是從匈牙利來, 所以以匈牙利為? 在Simonyi版本的匈牙利命名法中, 每個變敔R會加一個小寫的字首, 表示變數內容的種?

打個比? 如果變數名為rwCol, rw是字首.

我是故意?em>E類(kind)這個詞, 因為Simonyi在他的文章中誤用?em>型別(type), i果好幾世代的程式師都誤解了他的意?

如果你仔細讀Simonyi的文? 會發現他所的和我之前例所用的命名規範是一樣的, 在我的範例中?tt>us?tt>s分別定義Z安全字串和安全字? 這兩者的型別都是字串. 如果你把某種字串指派另一E? R譯器並不會iQ何警? Intellisense也不會說些什? 可是他們的語意是不同的; 他們解讀和處理的方式都不? 要把兩種字串互相指派時還要某些轉換函數做轉換, 否則會?em>埯時期的問? ?/em>你好?

微軟內部ESimonyi匈牙利命名法的原始概念為應用匈牙利命名? 因為它用於應用程式部門, 也就是Word及Excel. 在Excel的原始程式碼裡有大量?tt>rw?tt>col, 你看到這些字首q道它們指的是?row)和列(column). 沒錯, 它們都是整? 可是兩者間的轉換完全沒有意? 有h告訴我說Word的程式碼裡有大量?tt>xl?tt>xw, xl代表相對於排版頁面的水^座標, ?xw則代表相視H的水^座標. 兩者都是整怽L不能互轉? 兩個程式裡都有很多cb, 意思是位元i的個數. 沒錯, 這也是整數型? 不過光看變數名就可以得到更多資訊: 這是位元i的個數, 也就是m衝區的大? 另外如果你看?tt>xl = cb可以拉警報? 這顯然是錯的E式, 雖然xl?tt>cb都是整數, 可是把以像素為單位的水^位移a成位元i個數i對是瘋?

在應用匈牙利命名法中字首可以用於函數和變? 因此雖然我真的沒看過Word的原始碼, 我還是敢打賭Word裡一定有個叫YlFromYw的函? 可以把垂直方向的視窗座標轉成垂直方向的排版頁座標. 應用匈牙利命名法?tt>TypeFromType取代傳箔的?tt>TypeToType, 這樣每個函數名會以傳回的型別開頭, 這正與我E早在範例中?strong>Encode改名?tt>SFromUs的作法相? 事實上在正規的應用匈牙利命名法中Encode函數一?/em>要改名為SFromUs. 應用匈牙利命名法在該函數命名上並沒有提供其他選擇. 這其實是件好? 因為你少一件事要背, 另外也不必擔?tt>EncodeI竟是用什麼型? E式也變得精多?

應用匈牙利命名法非常有用, 特別是當初C語言盛行, 而編譯器未提供很有用的型別pȝ?

不過接下來卻Z一些問?

黑暗世界占用了匈牙利命名?

g沒有人知道為什麼或是如何發生的, 不過g是視H團隊中寫文件的Z心創造出後來名為pȝ匈牙利命名法的東?

某處有h讀了Simonyi的文章看到裡面用?型別"這個字? 因此認為作者指的就是型? 意思就像是別或是型別pȝ? 或是R譯器所做的型別檢查. 其實不然. 作者很心並精的解釋他用"型別"這個字的意? 不過沒有? 傷害已經造成?

應用匈牙利命名法的字首很有用而且有意? "ix"表示陣列索引, "c"表示個數, "d"表示兩個數字間的差(比如"dx"表示"寬度"), 如此推.

pȝ匈牙利命名法的字首作用就差多? "l"表示h? "ul"表示正長整數?dw"代表雙字i?? 事實上就是正h?. 在系i匈牙利命名法中, 字首只能告訴你變數真正的資料型別.

這誤解了Simonyi的意圖和實作, 差異雖細微實質上L完全不同. 這g事唯一的教a是讓你知道, 如果你寫Z沒h能懂的艱深難解學術文? 你的x可能會一再被誤解, i果變得非常荒謬, 完全違背你的原意. 所以在pȝ匈牙利命名法中會出現大量?tt>dwFoo表示"雙字i的某某", 可惡的是某個變數是雙字i這g事對你幾乎是完全沒用? 難怪大安很討厭系i匈牙利命名?

pȝ匈牙利命名法的流x深又? 它是整個視H程式設a文件的標準; Charles Petzold的視H程式設a?/a>(學習視窗E式a計的聖E?{書c更為它廣為宣揚, 很快的它也成為匈牙利命名法的主要勢力, 即在微軟內部也一? 在微軟內也只有少怸在Word和Excel團隊的程式師瞭解他們搞Z麼樣的錯.

接下來就是大反抗? 有群E式師們從一開始沒搞懂過匈牙利命名? 他們發現自q的竟是煩人又q近無用的分? 於是pv來反? 不過pȝ匈牙利命名法裡還是有些好p可以q你看出問題. 如果用系i匈牙利命名? 臛_會在使用時知道變數型? 不過沒應用匈牙利命名法那麼有價值就是了.

大反抗在.NET.W一版發行時到達巔峰, 那時微軟i於告訴大家"不徏C用匈牙利命名?. 這還真是歡聲雷動? 我根本不認為微軟會花心思解釋原? 他們只是掃瞄文件中命名指引的章然後加上"不要使用匈牙利命名法"的字? 當時匈牙利命名法非常不受歡迎所以沒有h會真的抱? 而除Excel及Word以外的h都因Z必再用這麼ȝ的命名規而鬆了一口氣, 他們認為在有D型別檢查及Intellisense的時代也不需要這種規範.

不過應用匈牙利命名法還是很有價值的, 它加強了E式的連結讓程式碼更易p, 撰寫, 除錯及維? 最重要的是它讓錯誤的程式看得出?

在繼U之前還有一件事我說過要? 是再罵一ơ例外處? 我上ơ這樣做惹來很多麻? 我在周思博談軟體首頁上一即興的評論?a href="file:///C:/Documents%20and%20Settings/spolsky/Local%20Settings/Temporary%20Internet%20Files/items/2003/10/13.html">?/a>說我不喜歡例外處? 因為它實際上是p的goto, 我認為這比看得到的goto更糟p? 當然有q萬h跑出來痛||. 全世界唯一跛_來替我辯L當然也就是Raymond Chen. 順帶一? 他既然是世界上最好的E式? 當然得出來講話, 嗎?

這篇文章到例外處理的重點了. 你的眼睛學著看到錯誤的程式碼, 這樣p防止問題發生. Z讓程式能變得真正I固, 進行E式檢視時得有一套能集中資訊的命名規? 換而言? 你眼前有關程式運作的資訊愈多, 找錯誤的結果愈? 當你看到以下的程式碼?/p>

dosomething();
cleanup();

...你的眼睛會說沒什麼問啊. 我們總是要做清除的動作! 不過dosomething有可能會引發一個例? 所以有可能不會呼叫cleanup. ?tt>finally{很單p修正這個問? 不過這並不是我的重點: 問題在於要知?tt>cleanup一定會被呼叫到的唯一Ҏ, 是調查整?tt>dosomething呼叫? 看看是否有Q何場合會產生例外. 這也還好, 可控制式例外處理(checked exception)可以讓你不用那麼辛苦, 不過重點是例外處理把資訊分散開來? 你得ȝ其他地方才能知道E式能正執? 所以無法運用你眼睛天c的功能去學習看出錯的E式? 因為Ҏ沒東西可?

如果我寫個小xE式, 只是每天一ơ到處收集資料然後印Z, 這時候例外處理好用得不得? 我只惛_略所有可能出錯的地方, 直接把整個程式用一個大try/catch包v? 如果有出什麼問就用catch把錯誤電自己. 例外處理簡單隨便寫的程式很有用, 腳本程式或是不是非帔R要或無關生死的程式也不錯. 不過如果你在寫一套作業系i或栔R廠程? 或是用於開心手術的高速電? 例外處理可是危險的很.

我知道大家會認為我是個無法正理解例外處理的W程式師, 完全不知道只有當我衷心接納例外處理後它才能改善我的生z? 這種x真是太糟p了. 惌寫出真正可信賴的E式? 應該要嘗試用考慮Ch有弱點的單工具, 而不是靠那些提供有問的抽象並把副作用隱藏v? 還認為程式師i不出錯的複雜工?

補充讀?/strong>

如果你還是衷心於例外處理, 讀讀Raymond Chen的文?a >更乾淨更優雅, 不過更難讀. "例外處理用得正確與否, 很難q式碼看得Z... 例外處理太難? 我實在不夠聰明無法掌?"

Raymond致命巨集的文章A rant against flow control macrosa論了另一個讓資訊分散致E式無法E護的例? "當看C用[巨集]的程式碼? 你必須看遍各個標頭檔才能瞭解它們的作用."

惌瞭解匈牙利命名法的歷史背? 可以由Simonyi的原?a >匈牙利命名法開始. Doug Klunder在另一比較清楚的文章?a >把它引進Excel團體 . 想知道更多匈牙利命名法的故事以及如何被文件撰寫h破壞的始? 可以ȝLarry Osterman站上的貼? 特別?a >Scott Ludwig的評?/a>, 或是Rick Schaut貼的文章.



文g來源: Making Wrong Code Look Wrong (英文) 

emu 2006-08-12 03:18 发表评论
]]>
StrutsTestCase 试用手记 http://www.aygfsteel.com/emu/articles/4775.htmlemuemuWed, 18 May 2005 08:19:00 GMThttp://www.aygfsteel.com/emu/articles/4775.htmlhttp://www.aygfsteel.com/emu/comments/4775.htmlhttp://www.aygfsteel.com/emu/articles/4775.html#Feedback2http://www.aygfsteel.com/emu/comments/commentRss/4775.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4775.html

 
junit不能struts的actionQhttpunit也只能测servlet。用struts作项目的时候无法方便的对action层做单元试一直是我的心头大恨。现在好了,我们有了StrutsTestCase。按照网上的介绍QStrutsTestCase用v来应该是非常单的Q只要下了jar包回来引用到工程里面可以了。实际上可能也差不多Q-如果你运气不象我q么臭的话?/DIV>


在sourceforge上随便挑其中一个镜象的下蝲地址Q?BR>http://aleron.dl.sourceforge.net/sourceforge/strutstestcase/strutstest213-1.2_2.4.zip

flashget回来Q放到jbuilder的userhome里面Q找一个struts action创徏test caseQ创建的时候吧test case的基cL?MockStrutsTestCaseQ测试的Ҏ一个都不用选(因ؓ我们是要针对action的具体每一个逻辑分支试而不是具体的某一个方法)。创建成功后d一个测试:

  public void testSuccessfulRefresh()
  {
    setRequestPathInfo("/RefreshSystemData");
    actionPerform();
    verifyForward("success");
  }

嘿嘿Q我_ֿ挑了一个没有参数的action来实验?/P>

一切看h很顺利。run testQ噩梦开始了Q?/P>

java.lang.NullPointerException
 at servletunit.struts.MockStrutsTestCase.getActionServlet(MockStrutsTestCase.java:331)
 at servletunit.struts.MockStrutsTestCase.tearDown(MockStrutsTestCase.java:130)
 at hospital.tongren.oa.system.action.TestRefreshSystemDataAction.tearDown(TestRefreshSystemDataAction.java:34)
...(Click for full stack trace)...

q好我没有开音箱Q不然又是一大炮轰出来?/P>

看来要调试了Q先d一个地方下了StrutsTestCase原码回来
 http://aleron.dl.sourceforge.net/sourceforge/strutstestcase/strutstest-213-src.zip

加进userhome里面的source。debugq去Q跟到org.apache.struts.action.ActionServlet里面Q出错的地方是:

        InputStream input =
            getServletContext().getResourceAsStream("/WEB-INF/web.xml");

        try {
            digester.parse(input);

        } catch (IOException e) {
....

input 为空指针。不知道Z么ServletContextSimulator在模拟ServletContext的时候没能够正确的找到webmodule的位|。上|搜了好一会儿文档Q在 http://strutstestcase.sourceforge.net/api/servletunit/struts/MockStrutsTestCase.html 中发Cq样一D:

NOTE: By default, the Struts ActionServlet will look for the file WEB-INF/struts-config.xml, so you must place the directory that contains WEB-INF in your CLASSPATH. ...

先照它说的试试把webmodule路径放进classpath中,没有用?BR>往下看Q发Cq个好东东:setContextDirectory。在startup中加一句:

this.setContextDirectory(new File("E:\\projectPath\\webModulePath\\"));

l于把那个空指针l过了。但是报一个新的异常:

junit.framework.AssertionFailedError: received error 400 : Invalid path /RefreshSystemData was requested

 at servletunit.HttpServletResponseSimulator.sendError(HttpServletResponseSimulator.java:463)

 at org.apache.struts.action.RequestProcessor.processMapping(RequestProcessor.java:684)

 at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:242)

 at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482)

 at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525)

 at servletunit.struts.MockStrutsTestCase.actionPerform(MockStrutsTestCase.java:394)

 at hospital.tongren.oa.system.action.TestRefreshSystemDataAction.testSuccessfulRefresh(TestRefreshSystemDataAction.java:51)

...(Click for full stack trace)...


找不到strutsconfig里面配置的path。strutsconfig是在web.xml里面配置的,应该q是web.xml没找到造成的,那么指定strutsconfig文g的位|咯Q?BR>    setConfigFile("E:\\......\\struts-config.xml");

l于可以q行h了?/P>

随后发现Q如?nbsp; setServletConfigFile("E:\\....\\WEB-INF\\web.xml");的话MockStrutsTestCase也能够根据web.xml中的配置扑ֈstrutsconfig文g?/P>

最后把上面用到的绝对地址E:\\...全部改ؓ相对地址Q?BR>    setContextDirectory(new File("modulePath\\"));
    setServletConfigFile("modulePath\\WEB-INF\\web.xml");
    //   this.setConfigFile("modulePath\\WEB-INF\\config\\system\\struts-config.xml");

血吐完了,l箋郁闷Qؓ什么别人都不用配置的这么麻烦呢Q到底我做错了什么,q是jbuilder的错Q?/P>

CactusStrutsTestCase也没配vQ好像要加个什么包吧,再看看先?/P>
[点击此处收藏本文]

发表?2005q?1?2?4:04 PM

emu 发表?SPAN>2005-01-22 8:00 PM  
http://jakarta.apache.org/cactus/getting_started.html 中有详细的说明。感觉确实复杂了一炏V现在进展是Q?

org.apache.cactus.util.ChainedRuntimeException: Failed to get the test results at [http://localhost:8083/TongRenOA/ServletRedirector]

at org.apache.cactus.internal.client.connector.http.DefaultHttpClient.doTest_aroundBody0(DefaultHttpClient.java:92)

at org.apache.cactus.internal.client.connector.http.DefaultHttpClient.doTest_aroundBody1$advice(DefaultHttpClient.java:206)

at org.apache.cactus.internal.client.connector.http.DefaultHttpClient.doTest(DefaultHttpClient.java)

at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runWebTest(HttpProtocolHandler.java:159)

at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runTest_aroundBody0(HttpProtocolHandler.java:80)

at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runTest_aroundBody1$advice(HttpProtocolHandler.java:206)

at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runTest(HttpProtocolHandler.java)

at org.apache.cactus.internal.client.ClientTestCaseCaller.runTest(ClientTestCaseCaller.java:144)

at org.apache.cactus.internal.AbstractCactusTestCase.runBareClient(AbstractCactusTestCase.java:215)

at org.apache.cactus.internal.AbstractCactusTestCase.runBare(AbstractCactusTestCase.java:133)

...(Click for full stack trace)...

下班先?/DIV>



emu 2005-05-18 16:19 发表评论
]]>
java版的sha-1 ?MD5http://www.aygfsteel.com/emu/articles/4774.htmlemuemuWed, 18 May 2005 08:16:00 GMThttp://www.aygfsteel.com/emu/articles/4774.htmlhttp://www.aygfsteel.com/emu/comments/4774.htmlhttp://www.aygfsteel.com/emu/articles/4774.html#Feedback0http://www.aygfsteel.com/emu/comments/commentRss/4774.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4774.html java版的sha-1

/*
 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
 * in FIPS PUB 180-1
 * Copyright (C) Paul Johnston 2000 - 2002.
 * See http://pajhome.org.uk/site/legal.html for details.
 */

/*
 * Convert a 32-bit number to a hex string with ms-byte first
 */
var hex_chr = "0123456789abcdef";
function hex(num)
{
  var str = "";
  for(var j = 7; j >= 0; j--)
    str += hex_chr.charAt((num >> (j * 4)) & 0x0F);
  return str;
}

/*
 * Convert a string to a sequence of 16-word blocks, stored as an array.
 * Append padding bits and the length, as described in the SHA1 standard.
 */
function str2blks_SHA1(str)
{
  var nblk = ((str.length + 8) >> 6) + 1;
  var blks = new Array(nblk * 16);
  for(var i = 0; i < nblk * 16; i++) blks[i] = 0;
  for(var i = 0; i < str.length; i++)
    blks[i >> 2] |= str.charCodeAt(i) << (24 - (i % 4) * 8);
  blks[i >> 2] |= 0x80 << (24 - (i % 4) * 8);
  blks[nblk * 16 - 1] = str.length * 8;
  return blks;
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left
 */
function rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Perform the appropriate triplet combination function for the current
 * iteration
 */
function ft(t, b, c, d)
{
  if(t < 20) return (b & c) | ((~b) & d);
  if(t < 40) return b ^ c ^ d;
  if(t < 60) return (b & c) | (b & d) | (c & d);
  return b ^ c ^ d;
}

/*
 * Determine the appropriate additive constant for the current iteration
 */
function kt(t)
{
  return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
         (t < 60) ? -1894007588 : -899497514;
}

/*
 * Take a string and return the hex representation of its SHA-1.
 */
function calcSHA1(str)
{
  var x = str2blks_SHA1(str);
  var w = new Array(80);

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;
  var e = -1009589776;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;
    var olde = e;

    for(var j = 0; j < 80; j++)
    {
      if(j < 16) w[j] = x[i + j];
      else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
      var t = safe_add(safe_add(rol(a, 5), ft(j, b, c, d)), safe_add(safe_add(e, w[j]), kt(j)));
      e = d;
      d = c;
      c = rol(b, 30);
      b = a;
      a = t;
    }

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
    e = safe_add(e, olde);
  }
  return hex(a) + hex(b) + hex(c) + hex(d) + hex(e);
}




java版的MD5










public class MD5
{
  /*
   * A Java implementation of the RSA Data Security, Inc. MD5 Message
   * Digest Algorithm, as defined in RFC 1321.
   * Based on the JavaScript implementation of Paul Johnston
   *      Copyright (C) Paul Johnston 1999 - 2000.
   *      See http://pajhome.org.uk/site/legal.html for details.
   * Java Version by Thomas Weber (Orange Interactive GmbH)
   */
 
  /*
   * Convert a 32-bit number to a hex string with ls-byte first
   */
  String hex_chr = "0123456789abcdef";
  private String rhex(int num)
  {
    String str = "";
    for(int j = 0; j <= 3; j++)
      str = str + hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F);
    return str;
  }
 
  /*
   * Convert a string to a sequence of 16-word blocks, stored as an array.
   * Append padding bits and the length, as described in the MD5 standard.
   */
  private int[] str2blks_MD5(String str)
  {
    int nblk = ((str.length() + 8) >> 6) + 1;
    int[] blks = new int[nblk * 16];
    int i = 0;
    for(i = 0; i < nblk * 16; i++) {
      blks[i] = 0;
    }
    for(i = 0; i < str.length(); i++) {
      blks[i >> 2] |= str.charAt(i) << ((i % 4) * 8);
    }
    blks[i >> 2] |= 0x80 << ((i % 4) * 8);
    blks[nblk * 16 - 2] = str.length()*8;
     
    return blks;
  }
 
  /*
   * Add integers, wrapping at 2^32
   */
  private int add(int x, int y)
  {
    return ((x&0x7FFFFFFF) + (y&0x7FFFFFFF)) ^ (x&0x80000000) ^ (y&0x80000000);
  }
 
  /*
   * Bitwise rotate a 32-bit number to the left
   */
  private int rol(int num, int cnt)
  {
    return (num << cnt) | (num >>> (32 - cnt));
  }
 
  /*
   * These functions implement the basic operation for each round of the
   * algorithm.
   */
  private int cmn(int q, int a, int b, int x, int s, int t)
  {
    return add(rol(add(add(a, q), add(x, t)), s), b);
  }
  private int ff(int a, int b, int c, int d, int x, int s, int t)
  {
    return cmn((b & c) | ((~b) & d), a, b, x, s, t);
  }
  private int gg(int a, int b, int c, int d, int x, int s, int t)
  {
    return cmn((b & d) | (c & (~d)), a, b, x, s, t);
  }
  private int hh(int a, int b, int c, int d, int x, int s, int t)
  {
    return cmn(b ^ c ^ d, a, b, x, s, t);
  }
  private int ii(int a, int b, int c, int d, int x, int s, int t)
  {
    return cmn(c ^ (b | (~d)), a, b, x, s, t);
  }
 
  /*
   * Take a string and return the hex representation of its MD5.
   */
  public String calcMD5(String str)
  {
    int[] x = str2blks_MD5(str);
    int a = 0x67452301;
    int b = 0xEFCDAB89;
    int c = 0x98BADCFE;
    int d = 0x10325476;
 
    for(int i = 0; i < x.length; i += 16)
    {
      int olda = a;
      int oldb = b;
      int oldc = c;
      int oldd = d;
 
      a = ff(a, b, c, d, x[i+ 0], 7 , 0xD76AA478);
      d = ff(d, a, b, c, x[i+ 1], 12, 0xE8C7B756);
      c = ff(c, d, a, b, x[i+ 2], 17, 0x242070DB);
      b = ff(b, c, d, a, x[i+ 3], 22, 0xC1BDCEEE);
      a = ff(a, b, c, d, x[i+ 4], 7 , 0xF57C0FAF);
      d = ff(d, a, b, c, x[i+ 5], 12, 0x4787C62A);
      c = ff(c, d, a, b, x[i+ 6], 17, 0xA8304613);
      b = ff(b, c, d, a, x[i+ 7], 22, 0xFD469501);
      a = ff(a, b, c, d, x[i+ 8], 7 , 0x698098D8);
      d = ff(d, a, b, c, x[i+ 9], 12, 0x8B44F7AF);
      c = ff(c, d, a, b, x[i+10], 17, 0xFFFF5BB1);
      b = ff(b, c, d, a, x[i+11], 22, 0x895CD7BE);
      a = ff(a, b, c, d, x[i+12], 7 , 0x6B901122);
      d = ff(d, a, b, c, x[i+13], 12, 0xFD987193);
      c = ff(c, d, a, b, x[i+14], 17, 0xA679438E);
      b = ff(b, c, d, a, x[i+15], 22, 0x49B40821);
 
      a = gg(a, b, c, d, x[i+ 1], 5 , 0xF61E2562);
      d = gg(d, a, b, c, x[i+ 6], 9 , 0xC040B340);
      c = gg(c, d, a, b, x[i+11], 14, 0x265E5A51);
      b = gg(b, c, d, a, x[i+ 0], 20, 0xE9B6C7AA);
      a = gg(a, b, c, d, x[i+ 5], 5 , 0xD62F105D);
      d = gg(d, a, b, c, x[i+10], 9 , 0x02441453);
      c = gg(c, d, a, b, x[i+15], 14, 0xD8A1E681);
      b = gg(b, c, d, a, x[i+ 4], 20, 0xE7D3FBC8);
      a = gg(a, b, c, d, x[i+ 9], 5 , 0x21E1CDE6);
      d = gg(d, a, b, c, x[i+14], 9 , 0xC33707D6);
      c = gg(c, d, a, b, x[i+ 3], 14, 0xF4D50D87);
      b = gg(b, c, d, a, x[i+ 8], 20, 0x455A14ED);
      a = gg(a, b, c, d, x[i+13], 5 , 0xA9E3E905);
      d = gg(d, a, b, c, x[i+ 2], 9 , 0xFCEFA3F8);
      c = gg(c, d, a, b, x[i+ 7], 14, 0x676F02D9);
      b = gg(b, c, d, a, x[i+12], 20, 0x8D2A4C8A);
 
      a = hh(a, b, c, d, x[i+ 5], 4 , 0xFFFA3942);
      d = hh(d, a, b, c, x[i+ 8], 11, 0x8771F681);
      c = hh(c, d, a, b, x[i+11], 16, 0x6D9D6122);
      b = hh(b, c, d, a, x[i+14], 23, 0xFDE5380C);
      a = hh(a, b, c, d, x[i+ 1], 4 , 0xA4BEEA44);
      d = hh(d, a, b, c, x[i+ 4], 11, 0x4BDECFA9);
      c = hh(c, d, a, b, x[i+ 7], 16, 0xF6BB4B60);
      b = hh(b, c, d, a, x[i+10], 23, 0xBEBFBC70);
      a = hh(a, b, c, d, x[i+13], 4 , 0x289B7EC6);
      d = hh(d, a, b, c, x[i+ 0], 11, 0xEAA127FA);
      c = hh(c, d, a, b, x[i+ 3], 16, 0xD4EF3085);
      b = hh(b, c, d, a, x[i+ 6], 23, 0x04881D05);
      a = hh(a, b, c, d, x[i+ 9], 4 , 0xD9D4D039);
      d = hh(d, a, b, c, x[i+12], 11, 0xE6DB99E5);
      c = hh(c, d, a, b, x[i+15], 16, 0x1FA27CF8);
      b = hh(b, c, d, a, x[i+ 2], 23, 0xC4AC5665);
 
      a = ii(a, b, c, d, x[i+ 0], 6 , 0xF4292244);
      d = ii(d, a, b, c, x[i+ 7], 10, 0x432AFF97);
      c = ii(c, d, a, b, x[i+14], 15, 0xAB9423A7);
      b = ii(b, c, d, a, x[i+ 5], 21, 0xFC93A039);
      a = ii(a, b, c, d, x[i+12], 6 , 0x655B59C3);
      d = ii(d, a, b, c, x[i+ 3], 10, 0x8F0CCC92);
      c = ii(c, d, a, b, x[i+10], 15, 0xFFEFF47D);
      b = ii(b, c, d, a, x[i+ 1], 21, 0x85845DD1);
      a = ii(a, b, c, d, x[i+ 8], 6 , 0x6FA87E4F);
      d = ii(d, a, b, c, x[i+15], 10, 0xFE2CE6E0);
      c = ii(c, d, a, b, x[i+ 6], 15, 0xA3014314);
      b = ii(b, c, d, a, x[i+13], 21, 0x4E0811A1);
      a = ii(a, b, c, d, x[i+ 4], 6 , 0xF7537E82);
      d = ii(d, a, b, c, x[i+11], 10, 0xBD3AF235);
      c = ii(c, d, a, b, x[i+ 2], 15, 0x2AD7D2BB);
      b = ii(b, c, d, a, x[i+ 9], 21, 0xEB86D391);
 
      a = add(a, olda);
      b = add(b, oldb);
      c = add(c, oldc);
      d = add(d, oldd);
    }
    return rhex(a) + rhex(b) + rhex(c) + rhex(d);
  }

}


上面是算法的实现代码Q不q我们真正用的时候当然不需要那么复杂了Qjdk里面早就有了高效的算法实玎ͼ

package myreuse.common.util;

import sun.misc.BASE64Encoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public abstract class MessageDigester
{
  static MessageDigest SHA1 ;
  static MessageDigest MD5 ;
  static {
    try{
      SHA1 = MessageDigest.getInstance("SHA1");
      MD5 = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e){
      e.printStackTrace();
    }

  }
  public static byte[] SHA1(String msg)
  {
    return SHA1.digest(msg.getBytes());
  }

  public static byte[] MD5(String msg)
  {
    return MD5.digest(msg.getBytes());
  }

  private static BASE64Encoder bASE64Encoder = new BASE64Encoder();

  public static String hex(byte[] b)
  {
    StringBuffer result = new StringBuffer(b.length);
    for (int i = 0; i < b.length; i++)
      result.append(Integer.toHexString(0xff & b[i]));
    return result.toString();
  }

  public static String base64(byte[] b)
  {
    String result = bASE64Encoder.encode(b);
    int i = result.indexOf('=');
    if (i>-1) result = result.substring(0,i);
    return result;
  }

  public static String base64SHA1(String msg)
  {
    return base64(SHA1(msg));
  }

  public static String base64MD5(String msg)
  {
    return base64(MD5(msg));
  }

  public static String hexSHA1(String msg)
  {
    return hex(SHA1(msg));
  }

  public static String hexMD5(String msg)
  {
    return hex(MD5(msg));
  }

}



emu 2005-05-18 16:16 发表评论
]]>
java版本的escape和unescape函数 http://www.aygfsteel.com/emu/articles/4773.htmlemuemuWed, 18 May 2005 08:14:00 GMThttp://www.aygfsteel.com/emu/articles/4773.htmlhttp://www.aygfsteel.com/emu/comments/4773.htmlhttp://www.aygfsteel.com/emu/articles/4773.html#Feedback12http://www.aygfsteel.com/emu/comments/commentRss/4773.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4773.html java版本的escape和unescape函数

 

class  EscapeUnescape
{
 public static String  escape (String src)
 {
  int i;
  char j;
  StringBuffer tmp = new StringBuffer();
  tmp.ensureCapacity(src.length()*6);

  for (i=0;i<src.length() ;i++ )
  {

   j = src.charAt(i);

   if (Character.isDigit(j) || Character.isLowerCase(j) || Character.isUpperCase(j))
    tmp.append(j);
   else
    if (j<256)
    {
    tmp.append( "%" );
    if (j<16)
     tmp.append( "0" );
    tmp.append( Integer.toString(j,16) );
    }
    else
    {
    tmp.append( "%u" );
    tmp.append( Integer.toString(j,16) );
    }
  }
  return tmp.toString();
 }

 public static String  unescape (String src)
 {
  StringBuffer tmp = new StringBuffer();
  tmp.ensureCapacity(src.length());
  int  lastPos=0,pos=0;
  char ch;
  while (lastPos<src.length())
  {
   pos = src.indexOf("%",lastPos);
   if (pos == lastPos)
    {
    if (src.charAt(pos+1)=='u')
     {
     ch = (char)Integer.parseInt(src.substring(pos+2,pos+6),16);
     tmp.append(ch);
     lastPos = pos+6;
     }
    else
     {
     ch = (char)Integer.parseInt(src.substring(pos+1,pos+3),16);
     tmp.append(ch);
     lastPos = pos+3;
     }
    }
   else
    {
    if (pos == -1)
     {
     tmp.append(src.substring(lastPos));
     lastPos=src.length();
     }
    else
     {
     tmp.append(src.substring(lastPos,pos));
     lastPos=pos;
     }
    }
  }
  return tmp.toString();
 }

 public static void main(String[] args)
 {
  String tmp="~!@#$%^&*()_+|\\=-,./?><;'][{}\"";
  System.out.println("testing escape : "+tmp);
  tmp =escape(tmp);
  System.out.println(tmp);
  System.out.println("testing unescape :"+tmp);
  System.out.println(unescape(tmp));
 }
}



emu 2005-05-18 16:14 发表评论
]]>
解析csv格式的java函数 http://www.aygfsteel.com/emu/articles/4772.htmlemuemuWed, 18 May 2005 08:13:00 GMThttp://www.aygfsteel.com/emu/articles/4772.htmlhttp://www.aygfsteel.com/emu/comments/4772.htmlhttp://www.aygfsteel.com/emu/articles/4772.html#Feedback2http://www.aygfsteel.com/emu/comments/commentRss/4772.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4772.html 解析csv格式的java函数

 

我们l常Excel格式的文件保存ؓcsv格式以方便上传和修改Q可是当数据中包含逗号和双引号的时候Excel会把该字D는双引h住ƈ把数据中?改ؓ""Q从而给解析带来了困难。我写了以下函数来解析这L字符Ԍ

testSplitCSV.java:

import java.util.Vector;
class  testSplitCSV{
 /**
 * Split one line of csv file
 * @return a String array results
 */
 public static String[] splitCSV(String src) throws Exception{
  if (src==null || src.equals("")) return new String[0];
  StringBuffer st=new StringBuffer();
  Vector result=new Vector();
  boolean beginWithQuote = false;
  for (int i=0;i<src.length();i++){
   char ch = src.charAt(i);
   if (ch=='\"'){
    if (beginWithQuote){
     i++;
     if (i>=src.length()){
      result.addElement(st.toString());
      st=new StringBuffer();
      beginWithQuote=false;
     }else{
      ch=src.charAt(i);
      if (ch == '\"'){
       st.append(ch);
      }else if (ch == ','){
       result.addElement(st.toString());
       st=new StringBuffer();
       beginWithQuote = false;
      }else{
       throw new Exception("Single double-quote char mustn't exist in filed "+(result.size()+1)+" while it is begined with quote\nchar at:"+i);
      }
     }
    }else if (st.length()==0){
     beginWithQuote = true;
    }else{
     throw new Exception("Quote cannot exist in a filed which doesn't begin with quote!\nfield:"+(result.size()+1));
    }
   }else if (ch==','){
    if (beginWithQuote){
     st.append(ch);
    }else{
     result.addElement(st.toString());
     st=new StringBuffer();
     beginWithQuote = false;
    }
   }else{
    st.append(ch);
   }
  }
  if (st.length()!=0){
   if (beginWithQuote){
    throw new Exception("last field is begin with but not end with double quote");
   }else{
    result.addElement(st.toString());
   }
  }
  String rs[] = new String[result.size()];
  for (int i=0;i<rs.length;i++){
   rs[i]=(String)result.elementAt(i);
  }
 return rs;
 }

 public static void main(String[] args){
  String src1=  "\"fh,zg\",sdf,\"asfs,\",\",dsdf\",\"aadf\"\"\",\"\"\"hdfg\",\"fgh\"\"dgnh\",hgfg'dfh,\"asdfa\"\"\"\"\",\"\"\"\"\"fgjhg\",\"gfhg\"\"\"\"hb\"";
  try {
   String[] Ret = splitCSV(src1);
   for (int i=0;i<Ret.length;i++){
    System.out.println(i+": "+Ret[i]);
   }
  }
  catch(Exception e) {
   e.printStackTrace();
  }
 }
}



emu 2005-05-18 16:13 发表评论
]]>
写了一个hta来{换资源文件格? http://www.aygfsteel.com/emu/articles/4769.htmlemuemuWed, 18 May 2005 08:07:00 GMThttp://www.aygfsteel.com/emu/articles/4769.htmlhttp://www.aygfsteel.com/emu/comments/4769.htmlhttp://www.aygfsteel.com/emu/articles/4769.html#Feedback0http://www.aygfsteel.com/emu/comments/commentRss/4769.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4769.html

java的资源文件一个设计目的就是方便的提供多语a支持Q可是它本n对unicode的支持是十分搞笑的。很多h见到q样的资源文仉会觉得很熟悉吧:

tongren.oa.system.user.userNameExists=\u7528\u6237\u540D\u5DF2\u7ECF\u5B58\u5728\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u4E00\u4E2A\u7528\u6237\u540D
tongren.oa.system.role.roleNameExists=\u89D2\u8272\u540D\u5DF2\u7ECF\u5B58\u5728\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u4E00\u4E2A\u89D2\u8272\u540D
tongren.oa.system.role.UserAssignedToRole=\u6307\u5B9A\u89D2\u8272\u4E0D\u80FD\u5220\u9664\uFF0C\u56E0\u4E3A\u7528\u6237{0}\u62E5\u6709\u8FD9\u4E2A\u89D2\u8272

我的天,q要怎么读?q要用工h它{回unicode才能看到汉字Q修改完了还要{回去Q?BR>


昨天下午抽了一Ҏ间写了一个hta工具来编辑资源文Ӟ现在l于可以直接修改Q自动{换保存了。直接把下面的代码保存ؓ一个后~为hta的文件即可:

<html>
<head>
<title></title>
</head>
<body>
<input type=file onchange="getFile()" id=fileSelector>
<SCRIPT LANGUAGE="JavaScript">
<!--
var fso;
var OpenFileForReading = 1
var OpenFileForWriting = 2
var OpenFileForAppending = 8
var srcFilePath="";
fso = new ActiveXObject("Scripting.FileSystemObject");

function getFile(){
 if (!fso.FileExists(fileSelector.value)){
  alert("指定文g不存在或已经被移?);
  event.returnValue=false;
  return;
 }
 srcFilePath = fileSelector.value;
 var textStream = fso.OpentextFile(srcFilePath, OpenFileForReading,true);
 viewer.value = textStream.ReadAll();
 textStream.Close();
 document.getElementsByName("showType")[0].checked=true;
 buttonSaveAs.disabled=false;
 buttonSave.disabled=false;
 showAsText();
}
function showAsText(){
 document.getElementsByName("showType")[1].checked=true;
 viewer.value = unescape(viewer.value.replace(/\\u/g,"%u"))
}
function showAsProperties(){
 document.getElementsByName("showType")[0].checked=true;
 viewer.value = unescape(escape(viewer.value).replace(/%u/g,"\\u"))
}
function saveFile(){
 showAsProperties();
 var fileStillExists = fso.FileExists(srcFilePath);
 if (fileStillExists)
  fso.CopyFile (srcFilePath,fileSelector.value+".bak");
 var textStream = fso.OpentextFile(srcFilePath, OpenFileForWriting,true);
 textStream.Write(viewer.value);
 textStream.Close();
 alert("资源文g已经成功保存"+(fileStillExists?("Q原来的文g备䆾为\n"+srcFilePath+".bak"):"!"));
}
function saveAs(){
 showAsProperties();
 var srcFileName = srcFilePath.substr(srcFilePath.lastIndexOf("\\")+1);
 var newFileName = prompt("误入新文g?,srcFileName);
 var newFilePath = srcFilePath.replace(srcFileName,newFileName);
 if (srcFileName == newFileName){
  if (confirm("您确认要覆盖原来的文Ӟ")) saveFile();
 }else if (fso.FileExists(newFilePath)){
  if (confirm("您确认要覆盖文g"+newFilePath+"Q?)) {
   var textStream = fso.OpentextFile(newFilePath, OpenFileForWriting,true);
   textStream.Write(viewer.value);
   textStream.Close();
   srcFilePath = newFilePath;
   alert("资源文g已经成功保存")
  }
 }else {
  var textStream = fso.OpentextFile(newFilePath, OpenFileForWriting,true);
  textStream.Write(viewer.value);
  textStream.Close();
  fileSelector = fileSelector.value;
  srcFilePath = newFilePath;
  alert("资源文g已经成功保存")
 }
}
setInterval("document.title='当前文gQ?+srcFilePath",500);
//-->
</SCRIPT>
<textarea id=viewer style="width:90%;height:90%"></textarea>
<BR>

<input type=radio name="showType" id="asProperties" onclick="showAsProperties()">
<label for="asProperties">资源文g格式</label>

<input type=radio name="showType" id="asText" onclick="showAsText()">
<label for="asText">文本格式</label>
<input type=button value="保存文g" onclick="saveFile()" id=buttonSave disabled>
<input type=button value="另存为文? onclick="saveAs()" id=buttonSaveAs disabled>
</body>
</html>


 

kukoo 发表?SPAN>2004-12-24 4:50 PM  
Looks nice, but Z么不用JDK自己的native2ascii来{换?


emu 发表?SPAN>2004-12-27 12:17 PM  
因ؓnative2ascii只能单向转换Q又没有可视化编辑功能,你要l护一个原始版本,每次修改原始版本后重新native2ascii ...


native2ascii是双向的?nbsp;发表?SPAN>2004-12-29 6:10 PM  
Usage: native2ascii [-reverse] [-encoding encoding] [inputfile [outputfile]]


是双向的 发表?SPAN>2004-12-29 6:10 PM  
Native2Ascii是双向的Q?

Usage: native2ascii [-reverse] [-encoding encoding] [inputfile [outputfile]]


emu 发表?SPAN>2004-12-30 12:08 PM  
哈哈Q原来可以{来{ȝ?/DIV>

 



emu 2005-05-18 16:07 发表评论
]]>
struts 的radio和checkbox标记的增?http://www.aygfsteel.com/emu/articles/4758.htmlemuemuWed, 18 May 2005 06:20:00 GMThttp://www.aygfsteel.com/emu/articles/4758.htmlhttp://www.aygfsteel.com/emu/comments/4758.htmlhttp://www.aygfsteel.com/emu/articles/4758.html#Feedback2http://www.aygfsteel.com/emu/comments/commentRss/4758.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4758.htmlstruts 的radio标记的增?/FONT>

写struts的html标记的h不知道是不是对html标记不熟Q写出来的标记怎么怪里怪气的,id标记被用来绑定bean了,我还以ؓ不支持html的id属性了Q不心看到org.apache.struts.taglib.html.BaseHandlerTag的prepareStyles()Ҏ里面Q居然赫然写着Q?BR>        if (styleId != null) {
            styles.append(" id=\"");
            styles.append(getStyleId());
            styles.append("\"");
        }
原来要用styleId属性来定义html的id属性,不看源码你敢q么猜吗Q怎么看styleId都象是个样式有关的属性嘛Q要定义html的id属性,如果不能用id臛_也应该叫HTMLID或者tagId之类的名字嘛。okQ源码都看了Q那我们用styleId也成Q可是看看radio标记实在是W了Q?BR>    public int doAfterBody() throws JspException {
        if (this.bodyContent != null) {
            String value = this.bodyContent.getString().trim();
            if (value.length() > 0) {
                this.text = value;
            }
        }
        return (SKIP_BODY);
    }
你说我把文字括到<html:radio>text</html:radio>标记中间为的是什么?当然q个文字要和radiol定啦。可是上面的代码居然直接把文字往生成?lt;input type="radio"...>标记后面一丢了事,有这么不负责的嘛Q?BR>q好咱java和html都还懂一炏V自己写个类Q?BR>package com.reuse.common.web.tag;
import javax.servlet.jsp.JspException;
public class RadioTag extends org.apache.struts.taglib.html.RadioTag{
  public int doAfterBody() throws JspException {
    if (this.bodyContent != null) {
        String value = this.bodyContent.getString().trim();
        if (value.length() > 0) {
            this.text = "<label for=\""+getStyleId()+"\" "+
                prepareEventHandlers()+
                prepareStyles().replaceAll(" id=\""+getStyleId()+"\"","")+
                ">"+value+"</label>";
        }
    }
    super.setStyleId(null);
    return (SKIP_BODY);
  }
  static int idCount=0;
  public int doStartTag() throws JspException {
    if (super.getStyleId() == null){
      super.setStyleId("RadioTag_"+ ++idCount);
     }
    return super.doStartTag();
  }
}
然后再在WEB-INF下面扑ֈstruts-html.tld,?BR><tagclass>org.apache.struts.taglib.html.RadioTag</tagclass>
改ؓ
<tagclass>myPackage.RadioTag</tagclass>

q下整个世界都清凉多了。回家过五一了,下次再对checkbox开刀?/P>


struts 的checkbox标记的增?/FONT>

五一q完Qcheckbox也开了刀了:

package com.reuse.common.web.tag;

import javax.servlet.jsp.JspException;
public class CheckboxTag
    extends org.apache.struts.taglib.html.CheckboxTag
{
  public int doAfterBody() throws JspException {
  if (this.bodyContent != null) {
      String value = this.bodyContent.getString().trim();
      if (value.length() > 0) {
          this.text = "<label for=\""+getStyleId()+"\" "+
              prepareEventHandlers()+
              prepareStyles().replaceAll(" id=\""+getStyleId()+"\"","")+
              ">"+value+"</label>";
      }
  }
  super.setStyleId(null);
  return (SKIP_BODY);
}
static int idCount=0;
public int doStartTag() throws JspException {
  if (super.getStyleId() == null){
    super.setStyleId("CheckBoxTag_"+ ++idCount);
   }
  return super.doStartTag();
}

}



emu 2005-05-18 14:20 发表评论
]]>
用junitperf做ƈ发测试带来的问题 http://www.aygfsteel.com/emu/articles/4755.htmlemuemuWed, 18 May 2005 06:13:00 GMThttp://www.aygfsteel.com/emu/articles/4755.htmlhttp://www.aygfsteel.com/emu/comments/4755.htmlhttp://www.aygfsteel.com/emu/articles/4755.html#Feedback0http://www.aygfsteel.com/emu/comments/commentRss/4755.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4755.html

关于junitperf的一点介l?/FONT>

junitperf 是个很小巧的java性能试框架Q可以在http://sourceforge.net/project/showfiles.php?group_id=15278 上下载到。可以很Ҏ的把它结合junit一h试,比如在测试套仉面加q么几行Q?/P>

import com.clarkware.junitperf.TimedTest;
import com.clarkware.junitperf.LoadTest;
.......

    int testTimes = 10;
    int users = 5;

    suite.addTest(new TestUserDAO("test1AddUser")); //基本功能试Q同时初始化环境
  
    suite.addTest(new TimedTest(new TestUserDAO("test1AddUser"), 1000)); //基本性能试:Ҏ应在1U内完成

    suite.addTest(new LoadTest(new TestUserDAO("test1AddUser"), users)); // q发试

  suite.addTest(new LoadTest(new TestUserDAO("test1AddUser"),users,testTimes)); //q发负蝲试

  suite.addTest(new TimedTest(new LoadTest(new TestUserDAO("test1AddUser"),
                                           users,testTimes), 35000)); //q发性能试

如果只需要反复做一个测试而不需要ƈ发测试,可以

suite.addTest(new LoadTest(new TestUserDAO("test1AddUser"),1,testTimes));

当然也可以不用junitperfQjunit.extensions.RepeatedTest是设计来干q个的:

suite.addTest(new RepeatedTest(new TestUserDAO("test1AddUser"), testTimes)); //重复试


用junitperf做ƈ发测试带来的问题


做ƈ发测试的时候junitperf有一个问题。注意看q一行:

new LoadTest(new TestUserDAO("test1AddUser"), users)

我们只传递了一个TestUserDAO实例lLoadTestQ却要求它开启users个线E来试Q这栯users个线E就会只针对同一个TestUserDAO实例q行试。这个时候,我们在TestUserDAO里面׃能存放Q何状态数据了。比如以前我很喜Ƣ这么做Q?/FONT>

public class TestUserDAO extends TestCase{
 private int lastId;
 protected void setUp() throws Exception{
  super.setUp();
  //构造一个测试用的数?BR>  User user = new User("张三");
  //向数据库插入一条记?BR>  userDAO.addUser(user);
  lastId = user.getId();//刚刚插入的记录在数据库中产生的ID;
 }

 public void testUpdateUser() throws DaoException{
  //针对setup中插入的数据q行update操作
  user = userDAO.getUserById(lastId);
  user.setName("李四");
  userDAO.updateUser(user);
 }
 protected void tearDown() throws Exception{
  //删除试数据
  userDAO.deleteUserById(lastId);
  super.tearDown();
 }
q样我是通过一个int变量lastId在各个方法之间传递被试的数据的。如果用junitperf来测试,lastId变量׃被后来的U程覆盖Q导致测试失败?BR>


解决Ҏ


在com.clarkware.junitperf.TestFactory的文档中对这个问题做了说明:

This factory class should be used in cases when a stateful test is intended to be decorated by a <code>LoadTest</code>.  A stateful test is defined as any test that defines test-specific state in its <code>setUp()</code> method.

TestFactory的用方法是q样Q?/P>

import com.clarkware.junitperf.TestFactory;

......

     suite.addTest(new LoadTest(new TestFactory(TestUserDAO.class), users,testTimes)); //q发负蝲试

但是q样只能观察整个试cȝ表现。如果我们要单个的测试测试类中的一个测试,那么可以考虑另一U方法。我们在TestFactory的文档中看到Q?/P>

 This class is dependent on Java 2.  For earlier platforms a  local cache implementation should be changed to use, for example,  a HashMap to track thread-local information.

q个Ҏ同时也适用于我们需要处理的情况Q?/P>

public class TestUserDAO extends TestCase{
 private static final ThreadLocal threadLocal = new ThreadLocal();
 protected void setUp() throws Exception{
  super.setUp();
  //构造一个测试用的数?BR>  User user = new User("张三");
  //向数据库插入一条记?BR>  userDAO.addUser(user);
  //lastId = user.getId();//刚刚插入的记录在数据库中产生的ID;
  threadLocal.set(new Integer(user.getId()));
 }

 public void testUpdateUser() throws DaoException{
  //针对setup中插入的数据q行update操作
  //user = userDAO.getUserById(lastId);
  user = userDAO.getUserById(((Integer)threadLocal.get()).intValue());
  user.setName("李四");
  userDAO.updateUser(user);
 }
 protected void tearDown() throws Exception{
  //删除试数据
  userDAO.deleteUserById(((Integer)threadLocal.get()).intValue());
  super.tearDown();
 }



emu 2005-05-18 14:13 发表评论
]]>
自作聪明的junit.swingui.TestRunner http://www.aygfsteel.com/emu/articles/4754.htmlemuemuWed, 18 May 2005 06:08:00 GMThttp://www.aygfsteel.com/emu/articles/4754.htmlhttp://www.aygfsteel.com/emu/comments/4754.htmlhttp://www.aygfsteel.com/emu/articles/4754.html#Feedback1http://www.aygfsteel.com/emu/comments/commentRss/4754.htmlhttp://www.aygfsteel.com/emu/services/trackbacks/4754.html
自作聪明的junit.swingui.TestRunner

问题
在junit.swingui.TestRunner的时候发现TestRunner启动q程中报错:
log4j:ERROR A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
同时也发C个^时工作正常的cd使用junit.swingui.TestRunnerq行试的时候报告一个奇怪的 ClassCastExceptionQ明明构造的对象的类是实C指定的接口的Q可是就是无法造型到接口上?BR>q一步研I发玎ͼ即造型回原来的cM不行Q虽然调试的时候显C构造的对象是指定的类Q但是就是无法造型成这个类Q一度认为是妖h作祟或者机子被落了降头?/P>

研究
求得庄老大再次出手Q一下指出指出问题在于不同的c装载器装蝲的类无法怺造型的。于是进去junit.swingui.TestRunner里面Lc装载器Q一L腾之后终于找刎ͼ

junit.runner.BaseTestRunner
           |Q-Q-Q-junit.swingui.TestRunner
           |Q-Q-Q-junit.textui.TestRunner

在BaseTestRunner里面定义了这样一个方法:
    public TestSuiteLoader getLoader() {
        if (useReloadingTestSuiteLoader())
            return new ReloadingTestSuiteLoader();
        return new StandardTestSuiteLoader();
    }
不过注意到junit.textui.TestRunner是不会出上面的错误的Q因为它自己重蝲了getLoader()ҎQ?BR>    /**
     * Always use the StandardTestSuiteLoader. Overridden from
     * BaseTestRunner.
     */
    public TestSuiteLoader getLoader() {
        return new StandardTestSuiteLoader();
    }
但是junit.swingui.TestRunner很自作聪明了,Z避免每次在点“run”按钮的时候装载运行器本nQ就直接使用了基cȝҎ去获取装载器Q,q样基类可以调用自qgetLoaderҎ来决定要启用那个classloaderQ?/P>

    public TestSuiteLoader getLoader() {
        if (useReloadingTestSuiteLoader())
            return new ReloadingTestSuiteLoader();
        return new StandardTestSuiteLoader();
    }

如果我们用sun的jdk的话Q这个方法会q回一个TestCaseClassLoader对象Q而这个对象在装蝲class的时候L调用creatLoaderҎQ?BR>    protected TestCaseClassLoader createLoader() {
        return new TestCaseClassLoader();
    }

q回的其实是TestCaseClassLoader。这样如果被试cM用了log4j的话Q会造成org.apache.log4j.Appenderc被 sun.misc.Launcher$AppClassLoader(也就是sun.misc.Launchercȝ嵌入cAppClassLoader)装蝲一ơ(在启动test的过E中vm自动装蝲被引用到的类Q,然后在运行的时候又被junit.runner.TestCaseClassLoader再装载一ơ。由两个装蝲器装载进来的cM是不是来自同一?class文gQ都会被认ؓ是两个不同的cR因此就造成了上面的错误?BR>同样的,如果你在自己的代码里面这栯载类Q?BR>MyClass myClass = (MyClass)Thread.currentThread().getContextClassLoader().loadClass(mClassName);
也会造成相同的问题ƈ抛出ClassCastException。因为MyClass是在q行试的过E由junit.runner.TestCaseClassLoader装蝲的,而Thread.currentThread().getContextClassLoader()却指向的是sun.misc.Launcher$AppClassLoader?/P>

解决Ҏ
1 java -Dlog4j.ignoreTCL junit.swingui.TestRunner
我猜TCL是ThreadClassLoader的羃写,q个参数的意思大概就是让log4j忽略Thread自己的类装蝲器(sun.misc.Launcher$AppClassLoaderQ,改而用当前Class的装载器Qjunit.runner.TestCaseClassLoaderQ来装蝲。但是这个方法只能解决log4j的错误报告(改变了org.apache.log4j.ConsoleAppender的装载方式)Q但是对我们自己写的代码中的问题却没有作用?/P>

2 在我们自qc里面写上一D静态代码:
  static{
        Thread.currentThread().setContextClassLoader(MyClassFactory.class.getClassLoader());
  }
和方法一cMQ这也是在工厂类中用加蝲了当前lass的装载器QTestCaseClassLoaderQ来代替Thread的初始化装蝲器sun.misc.Launcher$AppClassLoader。这个方法可以解x们自׃码中的问题,q且不会带来影响原来的其他代码。结合第一U方法可以解决上面的两个问题。但是如果你有好几个工厂c,或者你用的其他包里面用了这L装蝲方式……那你还可以试试下面的偏门:

3 注意到BaseTestRunner要进行一个useReloadingTestSuiteLoader()判断才决定返回哪个装载器
public TestSuiteLoader getLoader() {
    if (useReloadingTestSuiteLoader())
        return new ReloadingTestSuiteLoader();
    return new StandardTestSuiteLoader();
}
我们来看看这个判断过E:
protected boolean useReloadingTestSuiteLoader() {
    return getPreference("loading").equals("true") && !inVAJava() && fLoading;
}
嗯,里面有个inVAJava()是什么玩意儿Q?BR>public static boolean inVAJava() {
    try {
        Class.forName("com.ibm.uvm.tools.DebugSupport");
    }
    catch (Exception e) {
        return false;
    }
    return true;
}
原来它是惛_断如果当前用的是ibm的虚拟机׃用默认装载器Q但是判断的条g也忒单了点,很容易就吧它l蒙q去了:
在当前工E下创徏com.ibm.uvm.tools包,在其中创建DebugSupportc:
package com.ibm.uvm.tools;
public class DebugSupport{}
没有错,p个空白的c,q样可以把junit.swingui.TestRunnerl蒙倒。这样做据说的副作用是,每次点run按钮的时候,都要重vgui环境Q但是我没有发现有什么区别。不q要是没有区别,人家又干吗费那么多事呢?不解?/P>

参考资?/P>

http://mail-archives.apache.org/mod_mbox/logging-log4j-user/200301.mbox/%3C3E1F1A31.2000605@attbi.com%3E


[点击此处收藏本文]
发表?2005q?4?9?10:48 AM


maggie 发表?SPAN>2005-04-29 11:43 AM  
果然看不不懂

emu 发表?SPAN>2005-04-29 6:02 PM  
你将来长大了懂了呵?/DIV>

Pingback/Trackback 发表?SPAN>2005-04-29 6:36 PM  
试一?/DIV>

linux_china 发表?SPAN>2005-05-12 8:13 PM  
please modify excluded.properties in junit.jar,and execude some package will be all right.


emu 2005-05-18 14:08 发表评论
]]> վ֩ģ壺 | Դ| | | | | | ɽ| | | Դ| ֬| | ³ľ| | ̺| | | | ˫| ʡ| ɳ| | | Ѿ| | ɽ| | Դ| ̶| ̨| ̫| | ԫ| | | | ƶ| | | |