[譯文]解開嵌套代碼

譯者:白玉堂php

做者:Jason McCreary程序員

原文:jasonmccreary.me/articles/un…算法

我不相信寫代碼的硬性規則,可是你常常能聽到。好比一個方法不該該超過15行,方法只應該有一個 return 語句,縮進必須是4個空格等等。這些規則太死板了。
實際代碼要靈活的多。這些硬性規則帶來的反作用是讓咱們偏離了實質 可讀性。若是個人關注點徹底放在行數或 return 語句,這隻會阻礙本身寫高可讀性代碼,由於它們有很多都「太長」或多個 return 語句。
不少這些硬性規則試圖解決嵌套代碼問題。嵌套代碼很難跟進邏輯去理解。感官上,這樣更須要用眼睛掃描更多;心理上,每一個嵌套層級都須要付出更多精力跟蹤理解功能,這些都會讓閱讀者費勁。
嵌套代碼主要是條件判斷的結果。自從條件語句成爲編程邏輯的基礎,咱們不能很好的移除它們。咱們必須識別出他們對讀者的影響,採起措施減小這種影響。 編程

回到頂部

爲了提升可讀性,咱們想讓代碼回到頂部。循環和條件語句天生就有一個嵌套結構,在這些代碼塊中沒法避免的要嵌套。然而,咱們能夠避免在此結構以外的嵌套。
咱們一塊兒看幾個嵌套代碼的例子來練習一下提升他們的可讀性。 swift

空代碼塊

可能你不相信我,可是我不止一次的看到這樣的代碼:api

<?php

public function handle($request, Closure $next) {
    if (env('APP_ENV') == 'development') {
        // do nothing...
    } else {
        if ($request->getScheme() != 'https') {
            URL::forceScheme('https');
            return redirect('https://www.example.com/' . $request->getPath());
        }
    }

    return $next($request);
}
複製代碼

就是這,一個空的 if 語句塊,我還看到過另外一面:一個空的 else 代碼塊。沒有規定一個 if 必須和一個 else 成對出現。至少在過去的 20 多年沒有任何一門編程語言規定這樣。空代碼塊是死代碼,刪除他們。 數組

條件值

嵌套代碼常常會返回一個值。若是值是 boolean 型,這是壓縮代碼的機會,直接 return 掉條件判斷。
考慮一下這個 Set 類中 isEmpty 方法的嵌套代碼:安全

<?php

public function isEmpty() {
    if ($this->size === 0) {
        return true;
    } else {
        return false;
    }
}
複製代碼

儘管這個方法塊只有 4 行代碼,還包括多個子塊。即便這麼少的代碼行數,也難於閱讀,由於它讓代碼比它實際要出現更高的複雜度。
經過識別原始 boolean 值的條件返回,咱們有很好的機會經過直接返回條件徹底刪除嵌套代碼。數據結構

<?php

public function isEmpty() {
    return $this->size === 0;
}
複製代碼

結合這個恰當的方法命名和如今的一行代碼塊的上下文,咱們下降了代碼的理解複雜度。儘管這行代碼可能會比較密集,但它仍然比重構前更具可讀性。
注:壓縮條件判斷能夠適用於不少數據類型不只限於布爾類型。例如,你可使用簡單類型的整型做爲條件返回。而後,這也會迅速的增長了複雜度。不少的編程者嘗試使用三目運算來解決這種狀況。可是三目運算可以節省代碼卻沒有下降複雜度,還會下降代碼可讀性。在這種狀況下,衛語句是更好的選擇。架構

衛語句

嵌套代碼常常是邏輯層次遞進的結果。咱們做爲編程者,要寫出每個條件直到能夠安全操做的程度。
可是這個流程對程序執行來講是典範,對代碼閱讀卻不是。由於每一層代碼嵌套,代碼閱讀者必須保持一種持續增長的思惟模式。
細想下面的 Setadd 方法的實現:

<?php

public function add($item) {
    if ($item !== null) {
        if (!$this->contains($item)) {
            $this->items[] = $item;
        }
    }
}
複製代碼

代碼邏輯是這樣:若是item 不是 null 而且 若是 Set 類不包含 item,就把 item 添加到數組裏。問題是:這不只這麼簡單的操做讓人感到複雜,更讓主要操做埋藏在最深層的代碼裏。
理想狀態下,代碼的主要操做位於頂部。咱們能夠把條件語句重構成衛語句,達到解開嵌套語句和突出主要操做的目的。
衛語句只是想保護咱們的方法不受一場路徑的干擾。儘管他們一般初夏你在代碼塊的頂部,他們也能夠出如今任何地方。咱們能夠運用 De Morgan's Laws 把任何的嵌套條件轉換成衛語句並放棄層層的控制。代碼裏,這意味着咱們不用條件語句,並採用 return語句。
把以上思路應用到  add 方法,咱們的實現以下:

<?php

public function add($item) {
  if ($item === null || $this->contains($item)) {
      return;
  }

  $this->items[] = $item;
}
複製代碼

譯者注:其實衛語句也運用了《重構》裏常常提到的儘早返回的思想,把異常狀況直接打回去
這麼作,咱們不只提煉了主邏輯,還突顯出方法的異常路徑。如今對將來的代碼閱讀者少了一些複雜度,這也讓被突顯出的異常狀況更易於測試。

「切換」到 if

switch 語句是一個很是囉嗦的語法結構,switch 有固定的 4 個關鍵字和三個級別。即便代碼塊裏只有幾行代碼仍然要閱讀不少代碼。雖然者在某些狀況下能夠接受,但在其餘狀況下並非。
有的狀況使用 if 語句來替代 switch 語句可能會產生更多較高可讀性代碼。

  • 當只有幾個 case 代碼塊時,switch 語句的固定結構卻比等效的 if 語句產生更多行代碼。
  • 當 case 代碼塊包含嵌套增長了複雜度下降了可讀性到達危險的水平,使用衛語句或大代碼塊的實踐方法可以改進代碼。
  • 當類型轉換或計算須要引導值到 swtich 約束時,這不適用於不支持複雜的 case 比較的編程語言(如:swift,go 等)

switch 語句很是適用於 1 :1 的已有 case 語句而且有他們本身多行的代碼塊場景。不管這些代碼行是賦值,return 語句或者方法調用,比率大體爲 1 :1 則可讀性幾乎保持不變。

<?php

switch ($command) {
    case 'action':
        startRecording();
        break;
    case 'cut':
        stopRecording();
        break;
    case 'lights':
        adjustLighting();
        break;
}
複製代碼

注:當 switch 語句是流線型的狀況下,不少編程者使用字典/數據表,或多態代替。全部這些倒是其餘的替代品。就記住每個方案都有權衡(複雜性),對於大多數代碼,switch 語句一般「足夠好」。

複雜的循環

另外一個嵌套的表現是循環,循環具備自然的複雜度。做爲一個編程者,咱們就像被一我的詛咒了同樣,總想...增量邏輯。一樣,做爲人類,咱們不是計算機,咱們不太可能贏得了和計算機的循環計算。計算機會一直比人類強大,能和複雜惟一與之抗衡的是可讀性。
我不會介紹可能會改進你代碼的數據結構或算法。那比較有特定性。一般,大多數循環處理累加或調用。若是你發現你的代碼庫包含太多循環,看下是否有相似 filter / map / reduce 等的高階函數被使用。雖然這可能沒法幫助全部的閱讀代碼者改進可讀性,但它會提升你的我的技能。

譯者後記

我經常在想,咱們爲何要和代碼作鬥爭?
一、代碼沒有錯,代碼構建的軟件幫助公司創造價值,爭取市場份額,創造了收益。
二、軟件隨着功能和需求的迭代,原始的架構設計確定不能知足後續的變動,結果形成代碼臃腫,維護成本增長,軟件風險增長,也間接加重了產品的風險,甚至對公司營收都會形成風險和影響。
三、軟件的腐爛不可避免,咱們能作的是延緩這種腐爛,不斷重構咱們的代碼,重構不必定是整個接口或功能所有推倒重來,也能夠是一個函數的優化,一行代碼的優化。這些小的動做都能下降代碼腐爛的速度,下降bug數,下降維護成本和擴展成本。於公,能夠爲公司節省開支;與私,開發也能早點下班;於職業規劃,好的代碼也是一個程序員的門面,對本身的代碼質量和編程習慣負責也是之後求職的一個競爭優點。

基於原做者的分享,我也分享我看到的,在代碼編寫初期就能改進的,(若是代碼編寫完成了,再去重構,代價會更大),這些小動做就和「勿以善小而不爲」一個道理,聚沙成塔,咱們天天都能寫出比昨天更好的代碼。

多餘的變量

<?php
class Do {
  	public $api_url = 'http://www.domain.com/api/name/action';
  
        public function requestRemoteApi(array $params) {
            $url = $this->api_url;
           // ... do someting
          
          $res =  HttpHelper::post($url,$data);
          
          return $res;
        }
}
複製代碼

好比這裏有兩點:

  1. 類已經有一個全局變量 $api_url,在函數內部又從新賦值給一個局部變量,這個是徹底不必的。
  2. 響應如數據賦值給變量 $res,卻沒有作任何處理,再後邊才返回這也是沒有必要的,除非你要拿到結果作判斷和處理,不然單獨賦值給一個新的變量是多餘的。

直接寫

<?php
class Do {
  	public $api_url = 'http://www.domain.com/api/name/action';
  
        public function requestRemoteApi(array $params) {
		
           // ... do someting
          
        		return  HttpHelper::post($this->api_url,$data);
        }
}
複製代碼

直接的複製粘貼

在代碼優化的原則裏的確是有一個「延遲決定」的思想,就是說當咱們作代碼抽象的時候,一次是特例,兩次是偶然,三次你就須要考慮抽象封裝了。
可是在代碼裏咱們每每看到完徹底全的複製,只修改了一些簡單的一行配置或者 變量名不一樣,更有甚者懼於生產環境風險直接複製了一個新的文件,甚至一個包含一千多個文件的文件夾,命名後綴只是簡單的加了數字1/2/3/4 等等 folderName1 , folderName2 , folderName3 。

  • 咱們始終要記得:代碼是給人看的,而且不僅是寫給本身,是寫給本身小夥伴。
  • 寫好代碼,寫漂亮代碼,是對本身的負責,對同事和團隊的負責,更是對公司的負責。
  • 重構越早,代價越小

擴展閱讀

軟件的高質量意味着高成本?
技術債

相關文章
相關標籤/搜索