亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 語言 > PHP > 正文

給PHP開發者的編程指南 第一部分降低復雜程度

2024-09-04 11:43:32
字體:
來源:轉載
供稿:網友

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

降低函數體的復雜度

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

在函數里減少括號數量

盡可能少的使用 if, elseif, else 和 switch 這些語句。它們會增加更多的括號。這會讓代碼更難懂、更難測試一些(因為每個括號都需要有測試用例覆蓋到)??偸怯修k法來避免這個問題的。

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

有的時候 if 語句可以移到另一個對象里,這樣會更清晰些,例如:

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

可以改成:

$a->doSomething();

這里,具體的判斷由 $a 對象的 doSomething() 方法去做了。我們不需要再為此做更多的考慮,只需要安全的調用 doSomething() 即可。這種方式優雅的遵循了命令,不要去查詢原則。我建議你深入了解一下這個原則,當你向一個對象查詢信息并且根據這些信息做判斷的時候都可以適用這條原則。

使用map

有時可以用 map 語句減少 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.   ); 

可以精簡為:

  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 的方式也讓你的代碼遵循擴展開放,關閉修改的原則。

強制類型

很多 if 語句可以通過更嚴格的使用類型來避免,例如:

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

可以通過強制 $a 使用 A 類型來簡化:

return $a->someInformation();

當然,我們可以通過其他方式來支持 "null" 的情況。這個在后面的文章會提到。

Return early

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

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

這里 if 語句并不是函數執行的一個分支,它只是對一個前置條件的檢查。有時我們可以讓 PHP 自身來完成前置條件的檢查(例如使用恰當的類型提示)。不過,PHP 也沒法完成所有前置條件的檢查,所以還是需要在代碼里保留一些。為了降低復雜度,我們需要在提前知道代碼會出錯時、輸入錯誤時、已經知道結果時盡早返回。

盡早返回的效果就是后面的代碼沒必要像之前那樣縮進了:

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

像上面這個模板這樣,代碼會變動更易讀和易懂。

創建小的邏輯單元

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

使用輔助函數

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

輔助方法一般是 private 的方法,只會被所屬的特定類的對象調用。通常它們不需要訪問實例的變量,這種情況需要定義為 static 的方法。在我的經驗中,private (static)的輔助方法通常會匯總到分離的類中,并且定義成 public (static 或 instance)的方法,至少在測試驅動開發的時候使用一個協作類就是這種情形。

減少臨時變量

長的函數通常需要一些變量來保存中間結果。這些臨時變量跟蹤起來比較麻煩:你需要記住它們是否已經初始化了,是否還有用,現在的值又是多少等等。

上節提到的輔助函數有助于減少臨時變量:

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

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

  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); 

正如你所見,我們把函數變成新函數的組合,這樣變得更易懂,也更容易修改。某種方式上,代碼還有點符合“擴展開放/修改關閉”,因為我們基本上不需要再修改輔助函數。

由于很多算法需要遍歷容器,從而得到新的容器或者計算出一個結果,此時把容器本身當做一個“一等公民”并且附加上相關的行為,這樣做是很有意義的:

  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(); 

這樣做可以簡化函數的組合。

雖然減少臨時變量通常會帶來好的設計,不過上面的例子中也沒必要干掉所有的臨時變量。有時候臨時變量的用處是很清晰的,作用也是一目了然的,就沒必要精簡。

使用簡單的類型

追蹤變量的當前取值總是很麻煩的,當不清楚變量的類型時尤其如此。而如果一個變量的類型不是固定的,那簡直就是噩夢。

數組只包含同一種類型的值

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

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

你的代碼編輯器也會為你提供數組值的類型提示:

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

而如果你不能確定 $value 是 DateTime 類型的話,你就不得不在函數里添加前置判斷來檢查其類型。beberlei/assert庫可以讓這個事情簡單一些:

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

如果容器里有內容不是 DateTime 類型,這會拋出一個 InvalidArgumentException 異常。除了強制輸入相同類型的值之外,使用斷言(assert)也是降低代碼復雜度的一種手段,因為你可以不在函數的頭部去做類型的檢查。

簡單的返回值類型

只要函數的返回值可能有不同的類型,就會極大的增加調用端代碼的復雜度:

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

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

下面是一個經常遇到的返回混合類型的例子:

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

這個函數會返回 User 對象或者 null,這種做法是有問題的,如果不檢查返回值是否合法的 User 對象,我們是不能去調用返回值的方法的。在 PHP 7之前,這樣做會造成"Fatal error",然后程序崩潰。

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

可讀的表達式

我們已經討論過不少降低函數的整體復雜度的方法。在更細粒度上我們也可以做一些事情來減少代碼的復雜度。

隱藏復雜的邏輯

通常可以把復雜的表達式變成輔助函數??纯聪旅娴拇a:

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

可以變得更簡單一些,像這樣:

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

閱讀代碼時可以清楚的知道這個判斷依賴 $a, $b 和 $c 三個變量,而函數名也可以很好的表達判斷條件的內容。

使用布爾表達式

if 表達式的內容可以轉換成布爾表達式,不過 PHP 也沒有強制你必須提供 boolean 值:

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

$a 會自動轉換成 boolean 類型。強制類型轉換是 bug 的主要來源之一,不過還有一個問題是會對代碼的理解帶來復雜性,因為這里的類型轉換是隱式的。PHP 的隱式轉換的替代方案是顯式的進行類型轉換,例如:

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

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

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

使用 ! 操作符則還可以簡化:

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

不要 Yoda 風格的表達式

Yoda 風格的表達式就像這樣:

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

這種表達式主要是為了避免下面的錯誤:

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

這里 'hello' 會賦值給 $result,然后成為整個表達式的值。'hello' 會自動轉換成 bool 類型,這里會轉換成 true。于是 if 分支里的代碼在這里會總是被執行。

使用 Yoda 風格的表達式可以幫你避免這類問題:

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

我覺得實際情況下不太會有人出現這種錯誤,除非他還在學習 PHP 的基本語法。而且,Yoda 風格的代碼也有不小的代價:可讀性。這樣的表達式不太易讀,也不太容易懂,因為這不符合自然語言的習慣。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品国产乱码久久久久久婷婷| 欧美视频第一页| 亚洲激情视频在线观看| 国产精品小说在线| 国产免费久久av| 国产亚洲视频在线| 在线视频免费一区二区| 亚洲欧美日韩区| 毛片精品免费在线观看| 国内精品在线一区| 91夜夜揉人人捏人人添红杏| 91在线精品播放| 亚洲欧美一区二区三区情侣bbw| 国产日韩欧美视频| 91日韩在线视频| 日韩精品在线私人| 日韩中文字幕久久| 亚洲精品视频中文字幕| 国产精品自拍视频| 6080yy精品一区二区三区| 亚洲欧美999| 色婷婷av一区二区三区久久| 国产精品精品视频| 亚洲精品福利在线观看| 91在线视频精品| 国产一区二区免费| 日韩中文字幕第一页| 一本色道久久88亚洲综合88| 成人美女免费网站视频| 精品久久在线播放| 国产精品极品美女在线观看免费| 国产精品av电影| 日本国产一区二区三区| 国产精品高精视频免费| 日韩欧美成人区| 久久精品国产欧美激情| 国产一级揄自揄精品视频| 亚洲国模精品私拍| 性欧美xxxx交| 国产啪精品视频| 91精品国产综合久久久久久蜜臀| 国产精品电影观看| 91日本在线观看| 亚洲女人天堂av| 亚洲精品日产aⅴ| 91av国产在线| 亚洲精品福利免费在线观看| 成人免费淫片aa视频免费| 欧美一区二三区| 欧美激情综合色综合啪啪五月| 亚洲精品动漫久久久久| 97视频在线观看免费高清完整版在线观看| 国产精品视频自在线| 亚洲精品国产美女| 91网在线免费观看| 2019国产精品自在线拍国产不卡| 日韩午夜在线视频| 国产最新精品视频| 国产精品成人免费视频| 国产精品福利小视频| 91青草视频久久| 中文字幕欧美日韩va免费视频| 国产欧美精品日韩| 亚洲精品久久久久中文字幕欢迎你| 成人欧美一区二区三区在线湿哒哒| 亚洲3p在线观看| 国产一区二区三区在线播放免费观看| 亚洲xxxxx电影| 亚洲国产成人久久综合一区| 亚洲日本中文字幕免费在线不卡| 亚洲精品免费av| 奇米一区二区三区四区久久| 国产亚洲aⅴaaaaaa毛片| 91日韩在线视频| 668精品在线视频| 国产日韩欧美影视| 日韩av色在线| 一区二区三区四区视频| 668精品在线视频| www国产亚洲精品久久网站| www.亚洲人.com| 日韩精品在线视频观看| 91亚洲午夜在线| 日韩最新在线视频| 亚洲男人天堂久| 国产精品久久久久久久久久久久久| 欧美电影免费观看高清| 992tv在线成人免费观看| 国产精品美女久久久久久免费| 在线观看精品自拍私拍| 亚洲午夜精品视频| 色视频www在线播放国产成人| 亚洲第一福利在线观看| 成人精品视频在线| 亚洲欧美在线免费观看| 91亚洲精品视频| 亚洲国产高清福利视频| 日韩在线中文视频| 亚洲精品乱码久久久久久按摩观| 久久免费国产精品1| 日韩成人在线网站| 亚洲色图美腿丝袜| 国产女精品视频网站免费| 欧美日韩国产一中文字不卡| 国产精品对白刺激| 国产专区欧美专区| 亚洲成人久久久| 久久精品国产99国产精品澳门| 欧美大秀在线观看| 久久精品欧美视频| 日韩av片免费在线观看| 日韩女优在线播放| 91九色精品视频| 国产美女扒开尿口久久久| 亚洲老司机av| 91国产精品电影| 精品亚洲国产成av人片传媒| 国产一区av在线| 91国产视频在线播放| 亚洲最大在线视频| 91国自产精品中文字幕亚洲| 欧美亚州一区二区三区| 在线观看日韩www视频免费| 91在线中文字幕| 日韩av在线播放资源| 国产精品美乳一区二区免费| 久久久精品电影| 国产视频丨精品|在线观看| 97精品一区二区视频在线观看| 国内精久久久久久久久久人| 国产97在线观看| 精品亚洲精品福利线在观看| 欧美黑人性生活视频| 国产精品视频不卡| 国产国产精品人在线视| 国外色69视频在线观看| 啊v视频在线一区二区三区| 国产精品福利无圣光在线一区| 欧美日韩一区二区在线播放| 精品成人国产在线观看男人呻吟| 狠狠躁夜夜躁人人爽天天天天97| 成人妇女免费播放久久久| 欧美怡红院视频一区二区三区| 久热精品视频在线观看一区| 亚洲香蕉伊综合在人在线视看| 亚洲最大福利网| 亚洲免费人成在线视频观看| 亚洲精品一区二区在线| 7777精品久久久久久| 亚洲精品www久久久久久广东| 2019中文字幕全在线观看| 在线观看久久久久久| 欧美性色xo影院| 亚洲jizzjizz日本少妇| 91性高湖久久久久久久久_久久99| 欧美激情久久久久久| 欧美久久精品午夜青青大伊人| 黄色一区二区三区| 欧美亚洲成人免费| 一本一本久久a久久精品牛牛影视| 精品福利免费观看| 欧美高清videos高潮hd| 91久久精品视频| 68精品久久久久久欧美|