“每逢春夏,千鶴雲集”的青城山,在此棲居了上千年的仙鶴紛紛飛走,從此失蹤了。當地居民紛紛指責:這都是房地產大開發惹的禍!也許不是這個原因,也許是;全球多個國家出現氣候異常,有沒有根本的方法來防止呢,可能沒有。
人類的最大BUG就是癌症,以及艾滋病、SARS等的出現。沒有根本的防治方法,也找不出根本的原因。
搞軟件的也總會碰到許多疑難雜症,有的解決了,有的無法解決。我們來對比分析幾個原因以及解決:
1. 不良習慣
有背自然規律,不良生活習慣的積累導致自然生病、人生病。不良的編程習慣也會導致程序出現疑難雜症。
例1.1 中間件內存問題
一Delphi中間件執行批量數據處理時內存劇增,幾個小時後內存占到近1G,處理完了內存也不下降。用Turbo MemorySluth等內存工具查沒查到問題,最後采用最原始的方法:在一段代碼前後執行AllocMemSize,看其差值,正常應該為零,查出一段代碼每處理一條記錄就會洩漏100K左右內存,只是因為該代碼自己創建的類實例沒有釋放,釋放了就好了。這個問題查了很久,如果該段代碼的作者習慣於自己創建的對象就自己釋放,就不會需要多個人費勁心機來跟蹤查找,還懷疑到ADO、Midas、三層結構有問題。
對於程序員,很多事情都沒有對錯,只是習慣,比如遵循命名慣例書寫慣例、寫注釋、使用try...finally...end來保證資源釋放等,壞習慣的累積最後導致軟件出現一些不容易查找的疑難雜症,好的生活習慣會意味著好的生活質量,好的編程習慣也會意味者好的產品質量。
2. 存在未知
相對於大自然、人來說,軟件簡直是太簡單了,但都可稱為復雜系統。一個人在一個特定的時間只可能了解一個復雜系統的一部分,如果要用未知的東西,就很可能出現問題,就象人,雖然我們對自己很多未知,卻活著,天天用,當然也是經常生病,經常出問題。
對於軟件的未知東西,我們能夠能做的就是信任並按自然的方式用之並了解之轉未知為已知、或者直接轉用已知的東西、再有就是直覺判斷該不那麼做、應這麼做。
例2.1 MDB不能打開問題
一單機版軟件使用Access數據庫發布到用戶後運行總是提示不能獨占打開數據庫,但確實沒有其他人使用,問題也是找了很長時間,懷疑過病毒、殺毒程序等,最後知道只是因為MDB文件屬性是只讀的,發布時刻的光盤,直接拷貝到用戶的機器後就仍然是只讀的。
不知道是因為只讀的原因,問題就很難搞定。當然這也可以說是軟件的缺陷,它應該有更友好的提示,也許寫Jet引擎這段代碼的作者也沒考慮到這麼多,也許文檔裡寫有,但我們不知道。程序員的未知總是很多,遇到的問題總是很多,但是我們還是要用這些自己無法全部把握的東西。
這個世界,什麼都必須去重用,什麼都重用,比如女人、還有男人。
例2.2 safecall問題
一中間件遠程模塊采用Wrapper模式(或者說委托模式),把調用轉給相應的類來處理,如
// 遠程模塊代碼不實現方法
procedure TrdmMyRemoteDataModule.MyProc; // 在 interface 中必須聲明為safecall
begin
FMyClass.MyProc;
end;
// 具體的實現在另一個類中
procedure TMyClass.MyProc; // 在 interface 中也聲明為safecall
begin
raise Exception.Create('Just a exception');
end;
結果發現異常無法返回給客戶端。另一個類寫同樣類似的代碼則沒有問題,異常能正常拋到客戶端,結果發現差別在於沒問題的類的方法沒有聲明為safecall,把有問題的方法聲明中safecall去掉就對了。寫正確代碼的寫出來時不明白自己為什麼都不加safecall,可能只是因為直覺做了正確的事情。
最後查Delphi幫助,才都懂了:“The safecall convention implements exception "firewalls." ...”。
在同一個時間點,不同程序員可能存在相同的未知,有的能直接走正確的路,有的走錯誤的路,不知道是什麼道理,只能靠多年經驗積累的直覺。
例2.3 ADO問題
v2.7或更低版本的ADO,Delphi中執行以下帶參數的SQL會出錯:“select * (select * from t where f=:p) a”,2.8的就沒有問題。查找出這個問題費了很多周折。先是一個很復雜的SQL總報錯,然後簡化為上述的還是報錯,懷疑跟ADO版本有關,發現主文件版本2.70的有問題,2.71的就沒問題,想直接替換ADO的支持文件(Program FilesCommon FilesSystemado),結果替換成功了,再試時發現文件又被恢復成老版本了,最後在去掉shell後在命令行中替換文件成功,操作系統真是一個巨大的病毒。直接升級到ADO2.8也可以。
其實開發人員、用戶、客服人員都一樣,發現有問題時都是想者升級軟件到高版本,不明白是什麼原因,也許問題就解決了。不明白的事情太多,但未知不能阻止程序員去解決問題,許多時候解決問題都是不需要也不知道道理的,只有說有效果比有道理重要。
所述問題確認是ADO處理參數問題後,我們換成基於Zeos的代碼來處理參數把參數值直接插入到SQL中形成完整字符串,這樣也方便調試、優化SQL,同時解決另一個更復雜的SQL在ADO2.8中執行時導致MSVCRT.dll出現Access Violatioin錯的問題。
3. 結構問題
一個大的自然工程,如大壩等,如果結構存在問題,那影響的可能不只是環境,也是很難改的。如果人的結構存在問題,比如生下來就缺少點什麼,那就很痛苦。軟件如果存在結構問題,也很痛苦,不過,軟件與自然、人不一樣的就是軟件容易推倒重來。
例3.1 98內存問題
一Delphi程序在98下運行出現內存不足提示無法運行,但在其它操作系統都沒問題;另一個程序可執行文件更大但沒問題。發現是程序中表單太多的問題,減掉幾個就可以。後來找出原因是可執行文件中資源太多,資源需要占系統的句柄空間,而98只有64K的User Resource句柄空間,這樣,只要把可執行文件中不用的其它位圖等資源刪除一些也就可以了。
Delphi提供的RAD開發模式,除了VCL是很不錯的東東,其它都不是好東西:表單使用單獨的資源保存、事件代碼直接寫在表單的單元中等,後者直接導致界面和邏輯不分離,前者在Delphi8和.Net中都已不這麼做。界面與邏輯分離可以為程序帶來優良的結構,實際上Delphi已經提供了這樣的機制,比如基於Action。我們在新的開發中,還引入了動態表單,把界面和界面邏輯分離到表單中,業務邏輯寫在Action中,更清晰的結構會意味著更大的擴展空間和更少的問題、更小的維護成本。
4. 環境變化
動物和人在環境變化時都會出現異常的行為,人在新的環境中可能水土不服,軟件的錯誤在不同環境中也會有不同的表現。
例4.1 “災難性故障”
三層軟件在客戶端報中文的“災難性故障”錯誤,查出都是因為中間件出現AV(Access violatioin)錯誤,AV錯大都是因為不良習慣或者不安全的代碼引起的,比如空對象(指針)訪問、釋放空對象或對象重復釋放等。
另一個可能導致客戶端報“災難性故障”的是中間件出現浮點數操作錯誤,比如除零錯,還有浮點值太大無法填寫到數據庫字段的“Invalid floating point Operation”錯,這兩種錯也是因為不安全的代碼,前者是浮點數除操作前沒做判斷,後者可能因為使用堆棧中的變量沒做初始化等。
寫安全的代碼就像人練就金剛不壞之身,在外部環境變化時都不會出故障。