我研究過數百個因“拷貝-粘貼”導緻的錯誤。可以肯定的是,程式員常常會在一大段代碼的最後一段裡犯錯。好像還沒有任何程式設計書讨論過這種現象,是以我決定自己寫點什麼。我稱之為“末行效應”。
我叫andrey karpov,我的工作有點不尋常:我借助靜态分析工具研究各種應用程式代碼,并描述從中找到的錯誤或者缺陷。我這麼做既有實際效益也因為工作需要。使用的方法正是基于我們公司所推廣的pvs-studio和cppcat工具的原理。套路很簡單:找bug,然後寫文章分析bug,文章吸引到潛在使用者的注意,接着就是收益。但今天這篇文章不是介紹這些工具的。
在分析各種軟體項目的過程中,我把找到的bug以及相關代碼存入一個特殊的資料庫。順便說一下,有興趣的話各位可以看一看這個資料庫。我們把它轉換成網頁格式并上傳到了公司網站的“detected errors”欄下。
這個資料庫獨一無二!目前它收錄了1500塊問題代碼片,正等着程式員們去研究,從中總結出特定規律。為将來的研究,手冊和文章奠定一個基礎。
我還沒認真地分析過目前搜集到的材料。但是過程中我發現有一個明顯的模式反複出現,決定深入研究一下。你大概看到了,文中我反複使用短語“注意最後一行”。在我看來,這一定有某種規律。
程式設計的時候,程式員常常需要寫一系列相似的結構。逐行敲鍵盤輸入無聊且低效。這就是為什麼他們會使用奧義-“拷貝-粘貼”大法:一段代碼被拷貝粘貼幾次,然後修改。誰都知道這樣做的壞處:你很容易在粘貼後忘記修改某些内容最後滋生出問題。不幸的是,常常找不到比這更好的方法。
那麼我發現了什麼規律呢?我發現錯誤常常發生在最後的一塊粘貼代碼裡。
下面是一個簡短的例子:
注意這一行:”z += other.y;”。程式員忘記把‘y’替換成‘z’了。
也許你以為這是個假設的例子,然後它其實來自一個真實的應用程式。接下來,我會讓你相信這是高頻常見的一種錯誤。程式員們經常在一連串相似操作的結尾犯這種錯誤。
我聽說攀岩者常常在最後的幾十米中滑落下來。并不是因為他們累了,而正是由于他們對即将到達的終點過于興奮,他們想象着成功後的喜悅,變得疏忽大意,最後失足。我猜想程式員們也是這樣的。
接下來看一組資料。
研究了資料庫後,我分離出了84個代碼段由“拷貝-粘貼”大法生成。其中41段中錯誤發生在中間的某些粘貼塊。比如:
“threads=”字元串的長度是8個字元,而非6。
另外的43段代碼中,錯誤發生在最後的粘貼塊。
當然,43比41大不了多少。但是請注意,一段程式中,可能有很多類似的代碼塊,是以錯誤可能發生在第一,第二,第五甚至第十塊中。是以在其他代碼塊中我們有一個相對均勻的分布,而最後一塊卻存在一個峰值。
平均而言,相似代碼塊總數為5。
于是前面4個代碼塊中均勻分布了41處錯誤,平均每塊代碼有10個錯誤。
然而最後一塊代碼中有43個錯誤!
下面的分布概圖凸顯出這個現象:
圖1. 五塊類似代碼段中的錯誤分布概圖
是以我們可以總結出一個規律:
在最末的粘貼代碼塊中出錯的機率是其他代碼塊的4倍。
這個規律可能并沒有普适性。它隻是個有趣的發現,其實際效用在于:提醒在你寫最後一塊的時候保持警覺。
下面我要證明這并不是我的胡思亂想而是有真實的趨勢的。請看下面的執行個體。
當然,我不會列出所有例子,僅列舉簡單而有代表性的。
最後一行應該是setw()。
最後兩行相同。
最後一行備援代碼來自于慣性粘貼。數組的大小是3。
程式員忘記把最後一行的中的“backgroundcolor.y”改成“backgroundcolor.z”。
程式員忘記把最後一個判斷中的“prob > max6”改為“prob > max7”。
最後一行應該用rglfslider。
最後一塊少了‘patternrepeatx’。正确的代碼應該是:
‘mjstride’永遠等于1。最後一行應該是:
最後的“ftp”很可疑,它之前已經被比較過了。
i<code>`</code>javascript
f (fabs(dir[0]) > test->radius ||
return (containerbegline <= containeebegline &&
bool operator==(const membercfg& r) const {
....
return _id==r._id && votes == r.votes &&
}
static bool positionisinside(....)
{
return
qreal x = ctx->calldata->args[0].tonumber();
qreal y = ctx->calldata->args[1].tonumber();
qreal w = ctx->calldata->args[2].tonumber();
qreal h = ctx->calldata->args[3].tonumber();
if (!qisfinite(x) || !qisfinite(y) ||
if (!strncmp(vstart, "ascii", 5))
arg->format = asn1_gen_format_ascii;
else if (!strncmp(vstart, "utf8", 4))
arg->format = asn1_gen_format_utf8;
else if (!strncmp(vstart, "hex", 3))
arg->format = asn1_gen_format_hex;
else if (!strncmp(vstart, "bitlist", 3))
arg->format = asn1_gen_format_bitlist;