說(shuō)PHP入門門檻低,其中一個(gè)原因是我們不需要關(guān)心變量的類型,PHP為我們做了自動(dòng)的轉(zhuǎn)化。但事實(shí)上是這樣嗎?下面就是一個(gè)隱蔽的bug。
1234 |
|
很神奇吧,php在這里自動(dòng)將字符串作為整數(shù)0來(lái)比較了。由此引發(fā)了一系列問(wèn)題:
1234567891011 |
|
所以當(dāng)一個(gè)表達(dá)式中同時(shí)包含0和字符串的時(shí)候就要特別留意了,以免被奇葩的bug坑了。
==
與不等!=
嚴(yán)格的來(lái)說(shuō),PHP中的==
符號(hào)完全沒(méi)有作用,因?yàn)?code style="margin: 0px 3px; padding: 1px 3px; background: rgb(250, 250, 250); border: 1px solid rgb(238, 238, 238); font-family: Monaco, Menlo, "Andale Mono", "lucida console", "Courier New", monospace; font-size: 0.9em; border-radius: 5px;">'string' == true,而且'string' == 0
,但是,true != 0
。
然后是123 == "123foo"
,但是當(dāng)你用引號(hào)將123包起來(lái)以明確說(shuō)明這是一個(gè)字符串時(shí),'123' != "123foo"
,但是在現(xiàn)實(shí)中,誰(shuí)會(huì)用常量比較呢,這個(gè)123換成了一個(gè)變量,而根據(jù)php的哲學(xué),誰(shuí)會(huì)在意這個(gè)變量是什么類型。
在使用其他進(jìn)制的時(shí)候這種混淆尤其明顯,像下面的例子:
12345678 |
|
>
小于<
被搞糊涂的話,下面還有:
1234 |
|
使用==
時(shí),參照javascript中的做法,我們可以使用===
讓比較更規(guī)范一些,但是>
,<
這些怎么辦?只能盡量避免。
很多語(yǔ)言都提供了三元運(yùn)算符?:
,因?yàn)樗銐蚝?jiǎn)潔,也夠geek。但是php中的表現(xiàn)與其他語(yǔ)言中又不同:
12345678910 |
|
猜猜結(jié)果是什么?’horse’,而我們預(yù)料中的(在其他語(yǔ)言中)應(yīng)該是’train’。這種做法被稱為”left associative”(左結(jié)合),也即上面的表達(dá)式在php看來(lái)等同于$vehicle = (condition) ? 'horse' : 'feet'
,所以你永遠(yuǎn)不可能得到中間的結(jié)果,這有違人的第一感覺(jué),也與其他語(yǔ)言正好相反。
empty是我在php中非常喜歡的一個(gè)方法,這里我說(shuō)錯(cuò)了,其實(shí)它是一個(gè)語(yǔ)言結(jié)構(gòu),而不是一個(gè)方法,但是它的使用方式又像一個(gè)方法。于是,empty()中包含運(yùn)算式的話是會(huì)報(bào)錯(cuò)的,這也造成了它的局限性。
php中的數(shù)組應(yīng)該是一個(gè)’數(shù)組’,’hash表’,’集合’的結(jié)合體,這在使用上有它的方便之處,但是也造成了一些不易理解的地方,尤其體現(xiàn)在一些array相關(guān)的方法上。
1234 |
|
好吧,其實(shí)php中的array就是一個(gè)hash表,無(wú)論什么情況下。array('bar', 'foo')
就是array('0'=>'bar', '1'=>'foo')
,這樣對(duì)理解array的一些奇怪表現(xiàn)應(yīng)該會(huì)有幫助,但是下面的方法,有推翻了上面的論述,我將它算做一個(gè)bug:
123456 |
|
看到了吧,如果array是個(gè)hash表的話,$first和$second顯然是不一樣的。但是diff的結(jié)果卻認(rèn)為這是兩個(gè)一樣的數(shù)組。所以在愉快的使用array時(shí),別忘了停下來(lái),測(cè)試一下這些隱含的bug。
++
與--
123456 |
|
解決這種++
和--
中的不一致的辦法就是根本不用它們,用+=
和-=
代替。
下面這些就與bug無(wú)關(guān)了,而是php中過(guò)于隨意的命名規(guī)則和變量順序,像htmlentities
和html_entity_decode
,你能想象這兩個(gè)方法是一張紙的兩面嗎?
其實(shí)方法的命名不規(guī)范問(wèn)題也不大,但是變量順序混亂就要人命了,比如array中的一大堆方法,看起來(lái)都以array打頭,相當(dāng)清晰。但是你能料到array_filter($input, $callback)
和array_map($callback, $input)
兩個(gè)方法的回調(diào)方法位置正好相反嗎?我就經(jīng)常記錯(cuò)strpos
中哪個(gè)參數(shù)才是該被搜索的字符串。
還有一個(gè)要命的地方就是很多參數(shù)的引用傳遞不明,比如上面的array_filter
是對(duì)源對(duì)象的一個(gè)拷貝,但是array_walk
卻是一個(gè)引用。這些在php.net上有“粗略”的說(shuō)明,我想誰(shuí)也不希望一邊翻文檔一邊寫(xiě)代碼吧。
關(guān)于php的錯(cuò)誤控制其實(shí)是一個(gè)關(guān)于“配置優(yōu)于約定”還是“約定優(yōu)于配置”的討論,而顯然php選擇了前者。在php中,有一個(gè)全局的配置文件“php.ini”,里面關(guān)于錯(cuò)誤控制的兩個(gè)選項(xiàng)error_reporting
和display_errors
分別代表錯(cuò)誤等級(jí)和是否顯示錯(cuò)誤。
這沒(méi)有問(wèn)題,問(wèn)題是這些配置在運(yùn)行時(shí)是可以修改的。有些框架或有些人為了掩蓋無(wú)處不在的notice
,將error_reporting
等級(jí)調(diào)高,或者索性將display_errors
設(shè)置為off
,這會(huì)讓其他開(kāi)發(fā)者困惑,讓錯(cuò)誤無(wú)跡可尋。
這也不是什么問(wèn)題,最大的問(wèn)題是php中還有set_error_handler
和set_exception_handler
,看字面意思,這兩個(gè)都是用來(lái)控制錯(cuò)誤的。但是在php中,error
和exception
卻是不一樣的。于是你要么在框架里寫(xiě)上兩套一樣的handler
方法,要么就索性都不寫(xiě),由php去決定這些錯(cuò)誤該以什么形式表現(xiàn)。最糟糕的是只寫(xiě)一個(gè)或者寫(xiě)兩套不一樣的handler
,這回讓人困惑為什么有些錯(cuò)誤以一種形式表現(xiàn),而一些錯(cuò)誤又以別的方式出現(xiàn),而且要找到這些handler
也不是一件容易的事,他們可能出現(xiàn)在任何一個(gè)文件的任何一個(gè)角落,除非你用debug_backtrace
將堆棧信息都打印出來(lái)。
以上大部分內(nèi)容和例子來(lái)自PHP: a fractal of bad design,一個(gè)狂熱的python教徒。我不是python教徒,甚至連愛(ài)好者都說(shuō)不上,所以寫(xiě)這篇文章只是為了記錄php中存在的bug,以防一不小心被坑了。只有有了這些意識(shí),才能在這門語(yǔ)言中更好的發(fā)揮,物盡其用。
聯(lián)系客服