NSAssert
這個應該都比較熟悉,他的名字叫做“斷言”。斷言(assertion)是指在開發期間使用的、讓程序在運行時進行自檢的代碼(通常是一個子程序或宏)。斷言為真,則表明程序運行正常,而斷言為假,則意味著它已經在代碼中發現了意料之外的錯誤。斷言對于大型的復雜程序或可靠性要求極高的程序來說尤其有用。而當斷言為假的時候,幾乎所有的系統的處理策略都是,讓程序死掉,即Crash掉。方便你知道,程序出現了問題。
斷言其實是“防御式編程”的常用的手段。防御式編程的主要思想是:子程序應該不因傳入錯誤數據而被破壞,哪怕是由其他子程序產生的錯誤數據。這種思想是將可能出現的錯誤造成的影響控制在有限的范圍內。斷言能夠有效的保證數據的正確性,防止因為臟數據讓整個程序運行在不穩定的狀態下面。
關于如何使用斷言,還是參考《代碼大全2》中“防御式編程”一章。這里簡單的做了一點摘錄,概括其大意:
用錯誤處理代碼來處理預期會發生的狀況,用斷言來處理絕不應該發生的狀況。
- 避免把需要執行的代碼放到斷言中
- 用斷言來注解并驗證前條件和后條件
- 對于高健壯性的代碼,應該先使用斷言再處理錯誤
- 對來源于內部系統的可靠的數據使用斷言,而不要對外部不可靠的數據使用斷言,對于外部不可靠數據,應該使用錯誤處理代碼。 而在IOS編程中,我們可以使用NSAssert來處理斷言。比如:
- (void)printMyName:(NSString *)myName { NSAssert(myName == nil, @"名字不能為空!"); NSLog(@"My name is %@.",myName); }
我們驗證myName的安全性,需要保證其不能為空。NSAssert會檢查其內部的表達式的值,如果為假則繼續執行程序,如果不為假讓程序Crash掉。
每一個線程都有它自己的斷言捕獲器(一個NSAssertionHanlder的實例),當斷言發生時,捕獲器會打印斷言信息和當前的類名、方法名等信息。然后拋出一個NSInternalInconsistencyException異常讓整個程序Crash掉。并且在當前線程的斷言捕獲器中執行handleFailureInMethod:object:file:lineNumber:description:以上述信息為輸出。
當時,當程序發布的時候,不能把斷言帶入安裝包,你不想讓程序在用戶機器上Crash掉吧。打開和關閉斷言可以在項目設置中設置assert ,在release版本中設置了NS_BLOCK_ASSERTIONS之后斷言失效。
盡可能不要用Try-Catch
并不是說Try-Catch這樣的異常處理機制不好。而是,很多人在編程中,錯誤了使用了Try-Catch,把異常處理機制用在了核心邏輯中。把其當成了一個變種的GOTO使用。把大量的邏輯寫在了Catch中。弱弱的說一句,這種情況干嘛不用ifelse呢。
而實際情況是,異常處理只是用戶處理軟件中出現異常的情況。常用的情況是子程序拋出錯誤,讓上層調用者知道,子程序發生了錯誤,并讓調用者使用合適的策略來處理異常。一般情況下,對于異常的處理策略就是Crash,讓程序死掉,并且打印出堆棧信息。
而在IOS編程中,拋出錯誤的方式,往往采用更直接的方式。如果上層需要知道錯誤信息,一半會傳入一個NSError的指針的指針:
- (void) doSomething:(NSError* __autoreleasing*)error { ... if(error != NULL) { *error = [NSError new]; } .... }
而能夠留給異常處理的場景就極少了,所以在IOS編程中盡量不要使用Try-Catch。
(PS:見到過使用Try-Catch來防止程序Crash的設計,如果不是迫不得已,盡量不要使用這種策略)
盡量將沒有Crash掉的BUG,讓它Crash掉
上面主要講的是怎么知道Crash的“BUG”。對于合理的制造“BUG”還有一條就是盡量把沒有Crash掉的“BUG”,讓他Crash掉。這個沒有比較靠譜的方法,靠暴力吧。比如寫一些數組越界在里面之類的。比如那些難調的多線程BUG,想辦法讓他Crash掉吧,crash掉查找起來就比較方便了。
總之,就是抱著讓程序“死掉”的心態去編程,向死而生。