前陣子被同事抓出來一段 code 有些問題。
我之前有一段 code,由於開始的時候對於整個程式的流程應該怎麼做不怎麼有把握,所以先用 Python 寫了 POC,覺得沒什麼問題之後,才用 Objective-C 重寫。裡頭有一段是這樣:我有兩個 array(當然,在 Python 裡頭用的是 list),叫做 a1 與 a2 好了,裡頭都是一堆字串,而我現在要把 a1 中拿掉所有出現在 a2 的內容;用 Python 想事情,自然就會想到使用 list comprehension 或是呼叫 filter()。
用 list comprehension 寫是這樣:
a3 = [x for x in a1 if x not in a2]
呼叫 filter() 則是這樣:
a3 = filter(lambda x: x not in a2, a1)
都是很簡單的程式,我後來選擇用 filter 萊寫。由於在寫 Python POC 的時候,腦袋裡頭想的就是如何對一個 list 下 filter,開始寫 Objective-C code 的時候,也就很自然想到,我應該呼叫 NSArray 的 filteredArrayUsingPredicate:。於是寫了一個用來過濾不在 a2 中物件的 NSPredicate,而且也進入了 production code。
NSPredicate *filter = [NSPredicate predicateWithFormat:@"NOT (SELF in %@)", a2]; NSArray *a3 = [a1 filteredArrayUsingPredicate:filter];
乍看之下沒什麼問題,實際跑起來也可以運作。但產品出貨一陣子之後,才意識到這邊有個很大的問題—如果資料量稍微大一點,不但執行速度慢,在一些 iOS 裝置上甚至可能造成 CPU 滿載,甚至造成應用程式 crash。我們換一種可以做到一模一樣效果的寫法:把 a1 先變成 mutable array,然後對這個 mutable array 呼叫 removeObjects。
NSMutableArray *a3 = [[a1 mutableCopy] autorelease]; [a3 removeObjectsInArray:a2];
兩者執行速度相差十幾倍!
於是我從中獲得了幾個教訓。在 Objective-C 中像是 filteredArrayUsingPredicate: 這類看起來很高階的寫法,或著可能其他 Key-Value Coding 的方式,想來在絕大多數狀況下還是少用為妙,即使似乎比較容易表達原本想要表達的意思,但看來有不小的效能陷阱。
至於多學幾門程式語言,的確可以在寫原本主要工作的語言時,受到來自其他語言的啟發,你也可以從中學到如何寫出精鍊的、富有表達能力、方便日後維護的程式碼,但是,程式碼最重要的意義,還是拿來執行。