译者:白玉堂

作者:Jason McCreary

原文:jasonmccreary.me/articles/un…

我不相信写代码的硬性规则,但是你经常能听到。比如一个方法不应该超过15行,方法只应该有一个 return 语句,缩进必须是4个空格等等。这些规则太死板了。
实际代码要灵活的多。这些硬性规则带来的副作用是让我们偏离了实质 可读性。如果我的关注点完全放在行数或 return 语句,这只会阻碍自己写高可读性代码,因为它们有不少都“太长”或多个 return 语句。
很多这些硬性规则试图解决嵌套代码问题。嵌套代码很难跟进逻辑去理解。感官上,这样更需要用眼睛扫描更多;心理上,每个嵌套层级都需要付出更多精力跟踪理解功能,这些都会让阅读者费劲。
嵌套代码主要是条件判断的结果。自从条件语句成为编程逻辑的基础,我们不能很好的移除它们。我们必须识别出他们对读者的影响,采取措施减少这种影响。

回到顶部

为了提高可读性,我们想让代码回到顶部。循环和条件语句天生就有一个嵌套结构,在这些代码块中无法避免的要嵌套。然而,我们可以避免在此结构之外的嵌套。
我们一起看几个嵌套代码的例子来练习一下提高他们的可读性。

空代码块

可能你不相信我,但是我不止一次的看到这样的代码:

<?phppublic 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 方法的嵌套代码:

<?phppublic function isEmpty() {if ($this->size === 0) {return true;} else {return false;}
}
复制代码

尽管这个方法块只有 4 行代码,还包括多个子块。即使这么少的代码行数,也难于阅读,因为它让代码比它实际要出现更高的复杂度。
通过识别原始 boolean 值的条件返回,我们有很好的机会通过直接返回条件完全删除嵌套代码。

<?phppublic function isEmpty() {return $this->size === 0;
}
复制代码

结合这个恰当的方法命名和现在的一行代码块的上下文,我们降低了代码的理解复杂度。尽管这行代码可能会比较密集,但它仍然比重构前更具可读性。
注:压缩条件判断可以适用于很多数据类型不仅限于布尔类型。例如,你可以使用简单类型的整型作为条件返回。然后,这也会迅速的增加了复杂度。很多的编程者尝试使用三目运算来解决这种情况。但是三目运算能够节省代码却没有降低复杂度,还会降低代码可读性。在这种情况下,卫语句是更好的选择。

卫语句

嵌套代码经常是逻辑层次递进的结果。我们作为编程者,要写出每一个条件直到可以安全操作的程度。
但是这个流程对程序执行来说是典范,对代码阅读却不是。因为每一层代码嵌套,代码阅读者必须保持一种持续增加的思维模式。
细想下面的 Setadd 方法的实现:

<?phppublic function add($item) {if ($item !== null) {if (!$this->contains($item)) {$this->items[] = $item;}}
}
复制代码

代码逻辑是这样:如果item 不是 null 并且 如果 Set 类不包含 item,就把 item 添加到数组里。问题是:这不仅这么简单的操作让人感到复杂,更让主要操作埋藏在最深层的代码里。
理想状态下,代码的主要操作位于顶部。我们可以把条件语句重构成卫语句,达到解开嵌套语句和突出主要操作的目的。
卫语句只是想保护我们的方法不受一场路径的干扰。尽管他们通常初夏你在代码块的顶部,他们也可以出现在任何地方。我们可以运用 De Morgan's Laws 把任何的嵌套条件转换成卫语句并放弃层层的控制。代码里,这意味着我们不用条件语句,并采用 return语句。
把以上思路应用到  add 方法,我们的实现如下:

<?phppublic 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 则可读性几乎保持不变。

<?phpswitch ($command) {case 'action':startRecording();break;case 'cut':stopRecording();break;case 'lights':adjustLighting();break;
}
复制代码

注:当 switch 语句是流线型的情况下,很多编程者使用字典/数据表,或多态代替。所有这些却是其他的替代品。就记住每一个方案都有权衡(复杂性),对于大多数代码,switch 语句通常“足够好”。

复杂的循环

另一个嵌套的表现是循环,循环具有天然的复杂度。作为一个编程者,我们就像被一个人诅咒了一样,总想...增量逻辑。同样,作为人类,我们不是计算机,我们不太可能赢得了和计算机的循环计算。计算机会一直比人类强大,能和复杂唯一与之抗衡的是可读性。
我不会介绍可能会改进你代码的数据结构或算法。那比较有特定性。通常,大多数循环处理累加或调用。如果你发现你的代码库包含太多循环,看下是否有类似 filter / map / reduce 等的高阶函数被使用。虽然这可能无法帮助所有的阅读代码者改进可读性,但它会提高你的个人技能。

译者后记

我常常在想,我们为什么要和代码做斗争?
1、代码没有错,代码构建的软件帮助公司创造价值,争取市场份额,创造了收益。
2、软件随着功能和需求的迭代,原始的架构设计肯定不能满足后续的变更,结果造成代码臃肿,维护成本增加,软件风险增加,也间接加剧了产品的风险,甚至对公司营收都会造成风险和影响。
3、软件的腐烂不可避免,我们能做的是延缓这种腐烂,不断重构我们的代码,重构不一定是整个接口或功能全部推倒重来,也可以是一个函数的优化,一行代码的优化。这些小的动作都能降低代码腐烂的速度,降低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 sometingreturn  HttpHelper::post($this->api_url,$data);}
}
复制代码

直接的复制粘贴

在代码优化的原则里的确是有一个“延迟决定”的思想,就是说当我们做代码抽象的时候,一次是特例,两次是偶然,三次你就需要考虑抽象封装了。
但是在代码里我们往往看到完完全全的复制,只修改了一些简单的一行配置或者 变量名不同,更有甚者惧于生产环境风险直接复制了一个新的文件,甚至一个包含一千多个文件的文件夹,命名后缀只是简单的加了数字1/2/3/4 等等 folderName1 , folderName2 , folderName3 。

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

扩展阅读

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

[译文]解开嵌套代码相关推荐

  1. 嵌套访问_利用Idea重构功能及Java8语法特性——优化深层嵌套代码

    当遇到深层嵌套代码,如for,if,lambda表达式或内部类及这些代码的组合,这时我们可以通过Java 8的语法特性来进行优化. 下面的代码是一个嵌套循环的示例. public MappedFiel ...

  2. python多个if_Python之条件判断/if嵌套/如何写嵌套代码

    一.条件判断 条件判断是计算机沟通的逻辑,其作用就是明确地让计算机知道:在什么条件下,该去做什么.对于Python也是同样.Python之所以能完成自动化任务,比如自动抓取网页关键词,自动下载小电影等 ...

  3. 网易极客战记官网codecombat|当算法进入游戏,解开用代码画画的奥秘!

    什么是算法? 算法二字看起来玄而又玄,许多初入门的编程新手更是始终摸不清算法的门道,但其实,算法并没有你们想像的那么复杂玄奥. 百科对算法的解释为:算法(Algorithm)是指解题方案的准确而完整的 ...

  4. else if mybatis 嵌套_新手如何书写C++代码,远离深度嵌套的if-else

    有人喜欢if-else,称之为程序结构化的灵魂.确实,就计算机逻辑来讲,if-else最符合计算机非0即1的逻辑.就连在工作时,项目经理在给我们描述业务逻辑时也会来两句if如何,else如何!但是,也 ...

  5. 十年程序员的告诫:千万不要重写代码!

    对重写代码说不. 作者 | Roman Luzgin 译者 | 苏本如 责编 | 屠敏 出品 | CSDN(ID:CSDNNews) 以下为译文: 重写代码消耗了12个月! 我们从头开始重写代码浪费的 ...

  6. 前端代码规范(es6,eslint,vue)

    2019独角兽企业重金招聘Python工程师标准>>> 前端开发规范 一.HTML 1.语义化标签 HTML5 提供了很多语义化元素,更好地帮助描述内容.希望你能从这些丰富的标签库中 ...

  7. 在dw中如何调试html代码,如何在 Dreamweaver 中优化和调试代码 - Dreamweaver 用户指南...

    了解如何在 Dreamweaver 中清除代码.检查浏览器兼容性.验证 XML 文档并使页面符合 XHTML 规范. 清理代码 您可以自动删除空标签,合并嵌套 font 标签,以及通过其它方法改善杂乱 ...

  8. python代码块-Python 代码块

    代码块 骏马金龙https://www.cnblogs.com/f-ck-need-u/p/9925021.html 代码块可以使得一段python代码作为一个单元.一个整体执行. 几种代码块 模块文 ...

  9. HTML之表格篇——表格的嵌套

    表格的嵌套一方面是为使页面(贴子)的外观更为漂亮,利用表格嵌套来编辑出复杂而精美的效果,另一方面是出于布局需要,用一些嵌套方式的表格来做精确的编排,或者二者兼而有之.熟练地掌握表格的嵌套技巧并不是很困 ...

最新文章

  1. Boosting、Adaboost、AdaBoost模型的优缺点、提升树、梯度提升树GBDT
  2. CCNP路由实验---12、配置分发列表和被动接口
  3. java8 多行字符串_Java8(2):Java8 对字符串连接的改进
  4. 转载 Android解决java.lang.OutOfMemoryError: bitmap size exceeds VM budget
  5. Qt: QTableView如何获取(行)选中、行切换信息
  6. 【Python】体育竞技分析
  7. python requests库api_python利用requests库进行接口测试的方法详解
  8. ElasticSearch核心基础之索引管理
  9. (Twinkle Tray)快速调整外接显示器屏幕亮度
  10. 公司内网环境下部署流量监控服务器的初步方案
  11. Xshell6 Xftp6 官方永久免费版下载
  12. IDEA怎么换背景颜色
  13. VSCode 使用code runner
  14. unity不规则碰撞_Unity中的刚体和碰撞器
  15. gmsl摄像头Android平台调试思路
  16. 符号拓展指令CBW、CWD、CDQ、CWDE、CDQE
  17. 华为高性能服务器实验室,1000平米全球唯一!华为神秘实验室首公开
  18. Flash Socket 的基本通讯协议流程例子
  19. 小飞的电梯调度算法,光影切割问题编程之美
  20. MySQL的用途(转)

热门文章

  1. Quartus II实现D触发器
  2. 木槿校园影院估值5亿:90后追梦人叶少翔的不破不立
  3. python基础教程读书笔记——第三章 字符串
  4. P2500 - 【DP合集】背包 bound
  5. 在线客服兼容谷歌Chrome、苹果Safari、Opera浏览器的修改
  6. 百度蜘蛛最新UA及各大搜索引擎蜘蛛爬虫UA汇总
  7. ABAP-小工具之Program的导出与导入
  8. bochs2.6.9 配置文件详解.和相关调试到虚拟机运行
  9. 打开MorphVOX 报错及解决办法。
  10. 7-7 jmu-分段函数l (20 分)(PTA Python版本)