[译文]解开嵌套代码

译者:白玉堂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 。

  • 咱们始终要记得:代码是给人看的,而且不仅是写给本身,是写给本身小伙伴。
  • 写好代码,写漂亮代码,是对本身的负责,对同事和团队的负责,更是对公司的负责。
  • 重构越早,代价越小

扩展阅读

软件的高质量意味着高成本?
技术债

相关文章
相关标签/搜索