代码可读性,前置检查、注释及总结

以addWater方法为例讲解代码可读性的改进方案

/** Adds water to this container.
 * A negative <code>amount</code> indicates removal of water.
 * In that case, there should be enough water in the group
 * to satisfy the request.
 *
 * @param amount the amount of water to be added
 */
public void addWater(double amount) {
     double amountPerContainer = amount / group.size();
     for (Container c: group) {
         c.amount += amountPerContainer;
     }
}

前置条件:如果参数是负值,则组中要有足够的水量。

后置条件:将添加的水平均分配给该组中的所有容器。

惩罚:抛出IllegalArgumentException。

注意,代码清单中的注释并没有提到如果输入内容违反了前置条件会有怎样的反应,即需要移除的水量大于实际存在的水量。这是因为该实现并没有检查前置条件,而是允许容器中的水量为负值。Javadoc风格规范和 Effective Java一书都建议使用@throws或@exception标签(这两个标签是等价的)来说明方法可能抛出的非检查型异常。将下面这行代码加到方法注释里面就可以了。

@throws IllegalArgumentException
 if an attempt is made to remove more water than actually
present

快速浏览一下官方的Java API文档,就会发现这确实是标准做法。举 个例子,ArrayList类的get(int index)方法返回列表中给定 index位置的元素,该方法的文档注释说明了如果索引越界,则会抛出非检查型异常IndexOutOfBoundsException。

可读性的终极思考

你可以很容易地把本章中的建议应用到大多数甚至所有真实场景中。可读性可能会与其他质量目标(如时间效率或空间效率)有冲突,但在大多数场景 中,可读性应该占上风。当一款软件由于要修复bug或实现新功能而需要不断演进时,保持良好的可读性会让我们受益匪浅。 但是,不应该把代码的整洁性和算法的简洁性混为一谈。

我们并不提倡为了追求可读性而抛弃高效的算法,反而选择低效的算法。恰恰相反,应该选择最适合的算法来完成任务,然后努力编写最整洁的代码。整洁的代码当然优于为追求性能而采取的“奇技淫巧”,但软件工程的合理做法才是更高的追求。 只在少数场景下,可读性要么是一种奢侈,要么是需要刻意避免的。 典型例子是时间紧张的编程挑战赛,比如编程马拉松或编程大赛。此时,参赛者需要以最快的速度写出代码。代码只要能工作就行,写完即可扔掉。任何延迟都是一种开销,也就不必考虑编码风格了。

另一个例子是,一些公司不希望其他任何人能分析其源代码,包括软件的合法用户。他们希望通过隐藏或混淆源代码来隐藏其算法或数据。此时,他们会很自然地放弃代码的可读性,刻意编写非常晦涩难懂的代码来完成工作。事实上,有一种名为混淆器(obfuscator)的特殊软件,用途正是将一个程序翻译成另一个功能相同但人类极难理解的程序。

你可以混淆用任何编程语言开发的程序 ,从机器代码到Java字节码或源代码。在网上搜索“Java混淆器”就可以找到大量的开源和商业混淆工具。因为这类工具的存在,即使最敏感的公司也可以在开发过程中保持代码是整洁、可读的(有利于提高软件质量), 然后在公开发布之前使用混淆工具把它们变得晦涩难懂。

来点儿新鲜的

把编写可读代码的原则应用到一个不同的例子中。这是一个单独的方法,它接收一个double类型的二维数组,然后对其做一些事情。我们故意把该方法的代码写得很随意,虽然算不上晦涩,但也不太可读。作为一个练习,在继续阅读之前,请试着理解它做了什么。

public static void f(double[][] a) {
     int i = 0, j = 0;
     while (i<a.length) {
         if (a[i].length != a.length)
             throw new IllegalArgumentException();
         i++;
     }
     i = 0;
     while (i<a.length) {
         j = 0;
         while (j<i) {
             double temp = a[i][j];
             a[i][j] = a[j][i];
             a[j][i] = temp;
             j++;
         }
         i++;
     }
}

你感觉到痛苦了吗?那些while循环和毫无意义的变量名真的让人头昏脑涨。想象一下,用这样的风格编写整个程序有多么可怕吧! 或许你已经看出来了,这个谜一样的方法会转置(transpose)一个方阵,即交换其行与列。第一个while循环检查传入的矩阵是否是方形的(行数和列数相同)。由于Java二维数组表示的矩阵可能是不规则的(每一行的长度可能不同),就需要检查每一行的长度是否都与行数相同。下面是添加了注释的版本,帮助你理解代码的各个部分。

public static void f(double[][] a) {
     int i = 0, j = 0;
     while (i<a.length) { ❶ 遍历每一行
         if (a[i].length != a.length) ❷ 如果该行的长度是“错误”的
             throw new IllegalArgumentException();
         i++;
     }
     i = 0;
     while (i<a.length) { ❸ 遍历每一行
         j = 0;
         while (j<i) { ❹ 遍历前i列
             double temp = a[i][j]; ❺ 交换a[i][j]和a[j][i]
             a[i][j] = a[j][i];
             a[j][i] = temp;
             j++;
         }
         i++;
     }
}

现在使用本章介绍的原则来提高此方法的可读性。首先,开始部分检查矩阵是否为方阵的代码特别适合使用“提取方法”这个重构原则: 它是具有明确契约定义的一个连贯操作。一旦将其提取到单独的方法中,就可能在其他地方使用它。因此我将它声明为公有的,并给它添加了完整的Javadoc注释。 由于检查是否为方阵的操作不会修改矩阵,因此可以在其主循环中使用增强型for循环。

然后,转置矩阵的方法会调用isSquare方法,并使用两个直白的for 循环来执行转置操作。这时候就不能使用增强型for循环了,因为需要行和列的索引来完成交换工作。 同时,还可以改进变量和方法本身的命名,让其更可读。可以保留i和 j作为行和列的索引,因为它们是数组索引的标准命名。

/** Transposes a square matrix
 *
 * @param matrix a matrix
 * @throws IllegalArgumentException if the given matrix is not
square
 */
public static void transpose(double[][] matrix) {
     if (!isSquare(matrix)) {
         throw new IllegalArgumentException(
         "Can't transpose a nonsquare matrix.");
     }
     for (int i=0; i<matrix.length; i++) { ❶ 遍历每一行
         for (int j=0; j<i; j++) { ❷ 遍历前i列
             double temp = matrix[i][j]; ❸ 交换a[i][j]和a[j][i]
             matrix[i][j] = matrix[j][i];
             matrix[j][i] = temp;
         }
     }
}

真实世界的用例

你已经学习并实际应用了一些非常重要的提高代码可读性的原则。这里列举几个案例来帮助你理解提高代码可读性在真实世界中的重要性。 想象一下,你是一家小型初创公司的创始人之一,并且成功帮公司中标,要为一家天然气基础设施管理公司开发软件,项目目标是实施监管法律。一切看起来都很好:你拿到了一个好项目,而且意识到,由于法律很少改变,软件交付后,你们将能在维护合同存续期间继续享受劳动果实获取收益。

你和同事们做出了一个战略性的决策,要尽可能快地交付解决方案,给客户留下深刻的印象。为此,你决定减少一些非必需的工作,比如提高代码可读性、维护文档、进行单元测试等。几年后,你的公司发展壮大, 但原来的团队中有一半人离开了公司,而公司还和天然气运营商续签着合同。

然后有一天,概率极小的事情发生了:法律法规发生了变化,需要修改软件以满足新的需求。这时你才意识到理解现有代码的工作原理比实现新需求更难。代码的可读性非常重要,是软件公司团队运作的决定性因素。 你是一个有热情、有才华的开发者,渴望为开源社区做出贡献。 你有一个伟大的想法(至少你自己这么认为):要在GitHub上分享代码,希望它能吸引贡献者,并最终被用于真正的项目中。你意识到可读性是吸引贡献者的关键因素,因为他们一开始对你的代码库并不熟悉,而且很可能不愿意询问相关的问题。

下面的例子显示了编程界对可读性的重视程度。

无论你使用哪种编程语言,都应该尽可能地让代码更可读。然而,对于某些编程语言而言,可读性是语言层面的一种设计特性。Python是最流行的语言之一,可以说原因之一就是它天然的可读性。事实上,可读性公认十分重要,以至于语言设计者提出了旨在提高可读性的著名编码风格规范PEP 8(Python改进提案)。

我们再来谈谈Python。(是的,本书主要使用Java,但这些原则是通用的。)Python是一种动态类型的语言,所以不必指定函数参数和返回值的类型。然而,PEP 484在Python 3.5版本中引入了 可选的类型提示,提供了一种声明这些类型的标准方法。这些提示对性能完全没有影响,也不提供运行时类型推断。它们的目的是提高可读性,支持更多的静态类型检查,从而提高了可靠性。

小结

可读性是提高可靠性和可维护性的重要因素。 可以通过结构性优化和外表优化等方式提高可读性。 提高可读性是一个常见的重构目标。 自描述的代码优于实现注释。 应该以标准方式编写详细的、格式化的文档注释,使其易于阅读。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容