国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 語(yǔ)言 > PHP > 正文

給PHP開(kāi)發(fā)者的編程指南 第一部分降低復(fù)雜程度

2024-09-04 11:43:32
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

PHP 是一門自由度很高的編程語(yǔ)言。它是動(dòng)態(tài)語(yǔ)言,對(duì)程序員有很大的寬容度。作為 PHP 程序員,要想讓你的代碼更有效,需要了解不少的規(guī)范。很多年來(lái),我讀過(guò)很多編程方面的書(shū)籍,與很多資深程序員也討論過(guò)代碼風(fēng)格的問(wèn)題。具體哪條規(guī)則來(lái)自哪本書(shū)或者哪個(gè)人,我肯定不會(huì)都記得,但是本文(以及接下來(lái)的另一篇文章) 表達(dá)了我對(duì)于如何寫(xiě)出更好的代碼的觀點(diǎn):能經(jīng)得起考驗(yàn)的代碼,通常是非常易讀和易懂的。這樣的代碼,別人可以更輕松的查找問(wèn)題,也可以更簡(jiǎn)單的復(fù)用代碼。

降低函數(shù)體的復(fù)雜度

在方法或者函數(shù)體里,盡可能的降低復(fù)雜性。相對(duì)低一些的復(fù)雜性,可以便于別人閱讀代碼。另外,這樣做也可以減少代碼出問(wèn)題的可能性,更易修改,有問(wèn)題也更易修復(fù)。

在函數(shù)里減少括號(hào)數(shù)量

盡可能少的使用 if, elseif, else 和 switch 這些語(yǔ)句。它們會(huì)增加更多的括號(hào)。這會(huì)讓代碼更難懂、更難測(cè)試一些(因?yàn)槊總€(gè)括號(hào)都需要有測(cè)試用例覆蓋到)。總是有辦法來(lái)避免這個(gè)問(wèn)題的。

代理決策 ("命令,不用去查詢(Tell, don't ask)")

有的時(shí)候 if 語(yǔ)句可以移到另一個(gè)對(duì)象里,這樣會(huì)更清晰些,例如:

  1. if($a->somethingIsTrue()) { 
  2.  $a->doSomething(); 

可以改成:

$a->doSomething();

這里,具體的判斷由 $a 對(duì)象的 doSomething() 方法去做了。我們不需要再為此做更多的考慮,只需要安全的調(diào)用 doSomething() 即可。這種方式優(yōu)雅的遵循了命令,不要去查詢?cè)瓌t。我建議你深入了解一下這個(gè)原則,當(dāng)你向一個(gè)對(duì)象查詢信息并且根據(jù)這些信息做判斷的時(shí)候都可以適用這條原則。

使用map

有時(shí)可以用 map 語(yǔ)句減少 if, elseif 或 else 的使用,例如:

  1. if($type==='json') { 
  2.   return $jsonDecoder->decode($body); 
  3. }elseif($type==='xml') { 
  4.   return $xmlDecoder->decode($body); 
  5. }else
  6.   throw new /LogicException( 
  7.     'Type "'.$type.'" is not supported' 
  8.   ); 

可以精簡(jiǎn)為:

  1. $decoders= ...;// a map of type (string) to corresponding Decoder objects 
  2.    
  3. if(!isset($decoders[$type])) { 
  4.   thrownew/LogicException( 
  5.     'Type "'.$type.'" is not supported' 
  6.   ); 

這樣使用 map 的方式也讓你的代碼遵循擴(kuò)展開(kāi)放,關(guān)閉修改的原則。

強(qiáng)制類型

很多 if 語(yǔ)句可以通過(guò)更嚴(yán)格的使用類型來(lái)避免,例如:

  1. if($a instanceof A) { 
  2.   // happy path 
  3.   return $a->someInformation(); 
  4. }elseif($a=== null) { 
  5.   // alternative path 
  6.   return 'default information'

可以通過(guò)強(qiáng)制 $a 使用 A 類型來(lái)簡(jiǎn)化:

return $a->someInformation();

當(dāng)然,我們可以通過(guò)其他方式來(lái)支持 "null" 的情況。這個(gè)在后面的文章會(huì)提到。

Return early

很多時(shí)候,函數(shù)里的一個(gè)分支并非真正的分支,而是前置或者后置的一些條件,就像這樣:// 前置條件

  1. if(!$a instanceof A) { 
  2.   throw new /InvalidArgumentException(...); 
  3.    
  4. // happy path 
  5. return $a->someInformation(); 

這里 if 語(yǔ)句并不是函數(shù)執(zhí)行的一個(gè)分支,它只是對(duì)一個(gè)前置條件的檢查。有時(shí)我們可以讓 PHP 自身來(lái)完成前置條件的檢查(例如使用恰當(dāng)?shù)念愋吞崾?。不過(guò),PHP 也沒(méi)法完成所有前置條件的檢查,所以還是需要在代碼里保留一些。為了降低復(fù)雜度,我們需要在提前知道代碼會(huì)出錯(cuò)時(shí)、輸入錯(cuò)誤時(shí)、已經(jīng)知道結(jié)果時(shí)盡早返回。

盡早返回的效果就是后面的代碼沒(méi)必要像之前那樣縮進(jìn)了:

  1. // check precondition 
  2. if(...) { 
  3.   thrownew...(); 
  4.    
  5. // return early 
  6. if(...) { 
  7.   return...; 
  8.    
  9. // happy path 
  10. ... 
  11.    
  12. return...; 

像上面這個(gè)模板這樣,代碼會(huì)變動(dòng)更易讀和易懂。

創(chuàng)建小的邏輯單元

如果函數(shù)體過(guò)長(zhǎng),就很難理解這個(gè)函數(shù)到底在干什么。跟蹤變量的使用、變量類型、變量聲明周期、調(diào)用的輔助函數(shù)等等,這些都會(huì)消耗很多腦細(xì)胞。如果函數(shù)比較小,對(duì)于理解函數(shù)功能很有幫助(例如,函數(shù)只是接受一些輸入,做一些處理,再返回結(jié)果)。

使用輔助函數(shù)

在使用之前的原則減少括號(hào)之后,你還可以通過(guò)把函數(shù)拆分成更小的邏輯單元做到讓函數(shù)更清晰。你可以把實(shí)現(xiàn)一個(gè)子任務(wù)的代碼行看做一組代碼,這些代碼組直接用空行來(lái)分隔。然后考慮如何把它們拆分成輔助方法(即重構(gòu)中的提煉方法)。

輔助方法一般是 private 的方法,只會(huì)被所屬的特定類的對(duì)象調(diào)用。通常它們不需要訪問(wèn)實(shí)例的變量,這種情況需要定義為 static 的方法。在我的經(jīng)驗(yàn)中,private (static)的輔助方法通常會(huì)匯總到分離的類中,并且定義成 public (static 或 instance)的方法,至少在測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的時(shí)候使用一個(gè)協(xié)作類就是這種情形。

減少臨時(shí)變量

長(zhǎng)的函數(shù)通常需要一些變量來(lái)保存中間結(jié)果。這些臨時(shí)變量跟蹤起來(lái)比較麻煩:你需要記住它們是否已經(jīng)初始化了,是否還有用,現(xiàn)在的值又是多少等等。

上節(jié)提到的輔助函數(shù)有助于減少臨時(shí)變量:

  1. public function capitalizeAndReverse(array $names) { 
  2.   $capitalized = array_map('ucfirst'$names); 
  3.   $capitalizedAndReversed = array_map('strrev'$capitalized); 
  4.   return $capitalizedAndReversed

使用輔助方法,我們可以不用臨時(shí)變量了:

  1. public function capitalizeAndReverse(array $names) { 
  2.   return self::reverse( 
  3.     self::capitalize($names
  4.   ); 
  5.    
  6. private static function reverse(array $names) { 
  7.   return array_map('strrev'$names); 
  8.    
  9. private static function capitalize(array $names) { 
  10.   return array_map('ucfirst'$names); 

正如你所見(jiàn),我們把函數(shù)變成新函數(shù)的組合,這樣變得更易懂,也更容易修改。某種方式上,代碼還有點(diǎn)符合“擴(kuò)展開(kāi)放/修改關(guān)閉”,因?yàn)槲覀兓旧喜恍枰傩薷妮o助函數(shù)。

由于很多算法需要遍歷容器,從而得到新的容器或者計(jì)算出一個(gè)結(jié)果,此時(shí)把容器本身當(dāng)做一個(gè)“一等公民”并且附加上相關(guān)的行為,這樣做是很有意義的:

  1. classNames 
  2.   private $names
  3.    
  4.   public function __construct(array $names
  5.   { 
  6.     $this->names = $names
  7.   } 
  8.    
  9.   public function reverse() 
  10.   { 
  11.     return new self( 
  12.       array_map('strrev'$names
  13.     ); 
  14.   } 
  15.    
  16.   public function capitalize() 
  17.   { 
  18.     return new self( 
  19.       array_map('ucfirst'$names
  20.     ); 
  21.   } 
  22. $result = (newNames([...]))->capitalize()->reverse(); 

這樣做可以簡(jiǎn)化函數(shù)的組合。

雖然減少臨時(shí)變量通常會(huì)帶來(lái)好的設(shè)計(jì),不過(guò)上面的例子中也沒(méi)必要干掉所有的臨時(shí)變量。有時(shí)候臨時(shí)變量的用處是很清晰的,作用也是一目了然的,就沒(méi)必要精簡(jiǎn)。

使用簡(jiǎn)單的類型

追蹤變量的當(dāng)前取值總是很麻煩的,當(dāng)不清楚變量的類型時(shí)尤其如此。而如果一個(gè)變量的類型不是固定的,那簡(jiǎn)直就是噩夢(mèng)。

數(shù)組只包含同一種類型的值

使用數(shù)組作為可遍歷的容器時(shí),不管什么情況都要確保只使用同一種類型的值。這可以降低遍歷數(shù)組讀取數(shù)據(jù)的循環(huán)的復(fù)雜度:

  1. foreach($collection as $value) { 
  2.   // 如果指定$value的類型,就不需要做類型檢查 

你的代碼編輯器也會(huì)為你提供數(shù)組值的類型提示:

  1. /** 
  2.  * @param DateTime[] $collection 
  3.  */ 
  4. public function doSomething(array $collection) { 
  5.   foreach($collection as $value) { 
  6.     // $value是DateTime類型 
  7.   } 

而如果你不能確定 $value 是 DateTime 類型的話,你就不得不在函數(shù)里添加前置判斷來(lái)檢查其類型。beberlei/assert庫(kù)可以讓這個(gè)事情簡(jiǎn)單一些:

  1. useAssert/Assertion 
  2.    
  3. public function doSomething(array $collection) { 
  4.   Assertion::allIsInstanceOf($collection, /DateTime::class); 
  5.    
  6.   ... 

如果容器里有內(nèi)容不是 DateTime 類型,這會(huì)拋出一個(gè) InvalidArgumentException 異常。除了強(qiáng)制輸入相同類型的值之外,使用斷言(assert)也是降低代碼復(fù)雜度的一種手段,因?yàn)槟憧梢圆辉诤瘮?shù)的頭部去做類型的檢查。

簡(jiǎn)單的返回值類型

只要函數(shù)的返回值可能有不同的類型,就會(huì)極大的增加調(diào)用端代碼的復(fù)雜度:

  1. $result= someFunction(); 
  2. if($result=== false) { 
  3.   ... 
  4. }else if(is_int($result)) { 
  5.   ... 

PHP 并不能阻止你返回不同類型的值(或者使用不同類型的參數(shù)),但是這樣做只會(huì)造成大量的混亂,你的程序里也會(huì)到處都充斥著 if 語(yǔ)句。

下面是一個(gè)經(jīng)常遇到的返回混合類型的例子:

  1. /** 
  2.  * @param int $id 
  3.  * @return User|null 
  4.  */ 
  5. public function findById($id
  6.   ... 

這個(gè)函數(shù)會(huì)返回 User 對(duì)象或者 null,這種做法是有問(wèn)題的,如果不檢查返回值是否合法的 User 對(duì)象,我們是不能去調(diào)用返回值的方法的。在 PHP 7之前,這樣做會(huì)造成"Fatal error",然后程序崩潰。

下一篇文章我們會(huì)考慮 null,告訴你如何去處理它們。

可讀的表達(dá)式

我們已經(jīng)討論過(guò)不少降低函數(shù)的整體復(fù)雜度的方法。在更細(xì)粒度上我們也可以做一些事情來(lái)減少代碼的復(fù)雜度。

隱藏復(fù)雜的邏輯

通常可以把復(fù)雜的表達(dá)式變成輔助函數(shù)。看看下面的代碼:

  1. if(($a||$b) &&$c) { 
  2.   ... 

可以變得更簡(jiǎn)單一些,像這樣:

  1. if(somethingIsTheCase($a,$b,$c)) { 
  2.   ... 

閱讀代碼時(shí)可以清楚的知道這個(gè)判斷依賴 $a, $b 和 $c 三個(gè)變量,而函數(shù)名也可以很好的表達(dá)判斷條件的內(nèi)容。

使用布爾表達(dá)式

if 表達(dá)式的內(nèi)容可以轉(zhuǎn)換成布爾表達(dá)式,不過(guò) PHP 也沒(méi)有強(qiáng)制你必須提供 boolean 值:

  1. $a=new/DateTime(); 
  2. ... 
  3.    
  4. if($a) { 
  5.   ... 

$a 會(huì)自動(dòng)轉(zhuǎn)換成 boolean 類型。強(qiáng)制類型轉(zhuǎn)換是 bug 的主要來(lái)源之一,不過(guò)還有一個(gè)問(wèn)題是會(huì)對(duì)代碼的理解帶來(lái)復(fù)雜性,因?yàn)檫@里的類型轉(zhuǎn)換是隱式的。PHP 的隱式轉(zhuǎn)換的替代方案是顯式的進(jìn)行類型轉(zhuǎn)換,例如:

  1. if($a instanceof DateTime) { 
  2.   ... 

如果你知道比較的是 bool 類型,就可以簡(jiǎn)化成這樣:

  1. if($b=== false) { 
  2.   ... 

使用 ! 操作符則還可以簡(jiǎn)化:

  1. if(!$b) { 
  2.   ... 

不要 Yoda 風(fēng)格的表達(dá)式

Yoda 風(fēng)格的表達(dá)式就像這樣:

  1. if('hello'===$result) { 
  2.   ... 

這種表達(dá)式主要是為了避免下面的錯(cuò)誤:

  1. if($result='hello') { 
  2.   ... 

這里 'hello' 會(huì)賦值給 $result,然后成為整個(gè)表達(dá)式的值。'hello' 會(huì)自動(dòng)轉(zhuǎn)換成 bool 類型,這里會(huì)轉(zhuǎn)換成 true。于是 if 分支里的代碼在這里會(huì)總是被執(zhí)行。

使用 Yoda 風(fēng)格的表達(dá)式可以幫你避免這類問(wèn)題:

  1. if('hello'=$result) { 
  2.   ... 

我覺(jué)得實(shí)際情況下不太會(huì)有人出現(xiàn)這種錯(cuò)誤,除非他還在學(xué)習(xí) PHP 的基本語(yǔ)法。而且,Yoda 風(fēng)格的代碼也有不小的代價(jià):可讀性。這樣的表達(dá)式不太易讀,也不太容易懂,因?yàn)檫@不符合自然語(yǔ)言的習(xí)慣。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 额济纳旗| 新化县| 资溪县| 太保市| 鸡泽县| 东山县| 西乡县| 抚松县| 紫金县| 甘谷县| 和政县| 黑水县| 东海县| 沧州市| 广宗县| 九江市| 鄂温| 宁南县| 浦江县| 皮山县| 徐州市| 县级市| 西丰县| 金阳县| 峨眉山市| 丹凤县| 沙雅县| 雅安市| 崇阳县| 蓬莱市| 宁晋县| 万山特区| 茶陵县| 二连浩特市| 江阴市| 龙口市| 祁阳县| 白银市| 宜君县| 锦屏县| 岑溪市|