高质量代码的特征

回想起来,我觉得我们似乎在误读Uncle Bob的Clean Code,至少我们错误地将所谓Clean与可读性代码简单地划上了等号。尤为不幸的是,在Clean Code一书中,从第二章到第五章都围绕着可读性代码做文章,于是加深了这种错误的印象。

许多具有代码洁癖的程序员将代码可读性视为神圣不可侵犯的真理,并奉其为高质量代码的最重要特征,封上了“神坛”。殊不知,Uncle Bob在Clean Code的第一章就通过别人之口对所谓“Clean Code”进行了正名:所谓整洁代码并非仅仅是“清晰”这么简单。

按照Kent Beck的简单设计规则,排在第一位的其实不是可读性,而是“通过所有测试”。其中潜藏的含义是满足用户正确的需求,因为测试可以看做是用户提出的需求。这个需求不仅仅是业务上的,还包括质量属性的需求,例如性能、安全等属性。

消除重复提高表达力这两点,有时候会互相促进,去除了冗余的代码,会让代码变得更加清晰;然而,有时候却又互相冲突,消除重复的成本可能会比较高,导致提取了太多细碎微小的实体,反而增加了阅读障碍。

故而我常常将Uncle Bob提出的“函数的第一规则是要短小。第二条规则是还要更短小。”看做是一种矫枉过正的强迫。对于那种喜欢编写大函数的程序员而言,确实需要时刻铭记这一原则,但切记不要将其视为最高准则。保证函数短小是有前提的,仔细阅读Kent Beck的简单设计原则,依其重要顺序:

能通过所有测试;

没有重复代码;

体现设计者的意图;

若无必要,勿增实体(方法、函数、类等)。

如果程序满足了客户需求,没有重复代码,函数的表达已经足够清晰地体现设计者意图,为何还要不断地提取函数,使得函数变得极为短小呢?真正有意义的原则是“让函数只做一件事情”。

正因为此,在Clean Code书中,Uncle Bob展示的对FitNesse中HtmlUtil.java的第二次重构并无必要。在经过第一次重构后,代码如下所示:

public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception {
    boolean isTestPage = pageData.hasAttribute("Test");
    if (isTestPage) {
        WikiPage testPage = pageData.getWikiPage();
        StringBuffer newPageContent = new StringBuffer();
        includeSetupPages(testPage, newPageContent, isSuite);
        newPageContent.append(pageData.getContent());
        includeTeardownPages(testPage, newPageContent, isSuite);
        pageData.setContent(newPageContent.toString());
    }
    return pageData.getHtml();}

这段代码的结构与层次已经非常清晰,也对实现细节做了足够合理的封装与隐藏。若要说不足之处,或许可以将如下代码再做一次方法提取,使其满足SLAP原则(单一抽象层次原则):

newPageContent.append(pageData.getContent());

//提取为:
includeTestContents(testPage, newPageContent)

而Uncle Bob做的第二次重构,除了将方法变得更加短小,隐藏了太多细节从而引入更多层次之外,究竟给代码的清晰带来了什么呢?

public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception {
    if (isTestPage(pageData))
        includeSetupAndTeardownPages(pageData, isSuite);
    return pageData.getHtml();
}

过犹不及啊!

有时候,为了去除重复,就必须要从相似代码中寻找到一种模式或者某种抽象,进而对其进行提取。过分的提取反而会让代码变得很难阅读,这是因为提取的手段常常会引入“间接”。正如Martin Fowler所说:“间接性可能带来帮助,但非必要的间接性总是让人不舒服”。不必要的间接常常妨碍代码的直截了当和干净利落。倘若去除重复带来的唯一好处仅仅是避免一个类中少许的私有重复,去除这样的重复其实意义真的不大。

我喜欢清晰的代码,但我认为保持代码的正确、健壮与高效同样重要。

因为代码洁癖的缘故,我曾经将大量的非空判断、非法检查与异常处理视为干扰清晰代码的洪水猛兽,但如果不做这些“脏活累活”,代码就可能变得不健壮。在Java中,若真要避免这些判断,可以考虑转移职责,通过定义Checked Exception,将异常处理的职责转移给方法的调用者。然而,职责的盲目转移始终是不负责任的。实现每个方法和每个类的程序员应该保证自己的代码是自治的。

如下代码:

@Override
public void run() {
   if (isFromFile) {
        if (hasQuery) {
            throw new RuntimeException("both --execute and --file specified");
        }
        try {
            query = Files.toString(new File(clientOptions.file), UTF_8);
            hasQuery = true;
        }
        catch (IOException e) {
            throw new RuntimeException(format("Error reading from file %s: %s", clientOptions.file, e.getMessage()));
        }
    }
}

这样的代码确实谈不上优雅,然而足够充分的判断保证了代码的正确性与健壮性。我只能说,在满足了这两点的前提下,可以聪明地利用诸如防御式编程、Optional来规避多余的嵌套或分支,从而提高代码的可读性。

Effective Java总结了高质量代码的几个特征:清晰、正确、可用、健壮、灵活和可维护。我认为这一总结非常中肯。写代码真的不要太偏执,不分任何场景一味地追求代码的可读(清晰),一味地重申DRY,我觉得都是不负责任的态度。

或许是我老了的缘故,我变得不再理想主义;但更多的原因是因为我看到太多追求所谓“整洁代码”的程序,不愿考虑复杂繁琐的异外情况从而导致程序的不健壮;因为去除重复带来的不必要间接影响了代码的简洁与干净,甚至影响了代码运行的性能。

整洁代码是必须的,但不是衡量代码质量的唯一标准!

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2017-07-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员宝库

如何做一个小程序口令红包功能

作者:张先生 原文:https://segmentfault.com/a/1190000011014127 在做小程序后端支持的过程中遇到不少有意思的功能,有...

33110
来自专栏斑斓

对象的自治和行为的扩展与适配

? 在坏的设计中,数据往往是分散的,甚至是杂乱的,这就好像一群失去意识的猛兽,我们无法控制、协调以及管理它们。这种漫无头绪的散乱数据,犹如猛兽的肆意妄为,会给...

3366
来自专栏带你撸出一手好代码

做人肉代码翻译器是什么感受?

程序员编码的常规流程是通过大脑把将要实现的功能切分成一个个逻辑单元,使用编程语言将这些逻辑单元实现并组织在一起,积少成多, 形成电脑软件 缺乏经验的程序员可能...

2898
来自专栏程序猿DD

实现领域事件

当你的系统或者业务变得日益复杂时, DDD的模式是一种非常值得尝试的架构模式。 DDD让你更加关注于你的业务领域,思考你的业务模型,帮组你理清繁杂的业务关系。我...

19110
来自专栏牛客网

贝壳前端面经

当一个构造函数的原型上有一个基本类型的属性a,new 两个实例b和c,改变b.a , c.a是否会跟着变?不变。如果原型上的属性是个数组,改变b实例上的这个数组...

701
来自专栏美团技术团队

移动端性能监控方案Hertz

性能问题是造成App用户流失的罪魁祸首之一。App的性能问题包括崩溃、网络请求错误或超时、响应速度慢、列表滚动卡顿、流量大、耗电等等。而导致App性能低下的原因...

5543
来自专栏Coding迪斯尼

VUE+Webpack游戏设计:增加游戏战略性平衡和实现资源预加载

883
来自专栏IT派

如何让你的代码整洁漂亮?

IT派 - {技术青年圈} 持续关注互联网、大数据、人工智能领域 内容来源: 译者,飞鱼; 译文,http://www.zcfy.cc/article/ho...

3828
来自专栏Coding迪斯尼

VUE+WebPack精美游戏设计:实现微信红包铜钱转动特性和页面数据的本地存储

764
来自专栏一“技”之长

Swift学习第一练——用Swift实现的FlappyBird小游戏

      伴随着apple公司对swift的推广态度深入,swift火的很快,并且swift精简便捷的语法和强大的功能,对于使用Object—C开发iOS的开...

562

扫码关注云+社区