高质量代码的特征

简介: 高质量代码的特征

回想起来,我觉得我们似乎在误读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,将异常处理的职责转移给方法的调用者。然而,职责的盲目转移始终是不负责任的。实现每个方法和每个类的程序员应该保证自己的代码是自治的。

如下代码:

@Overridepublic 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,我觉得都是不负责任的态度。

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

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

相关文章
|
7月前
|
存储 供应链 算法
《数学模型(第五版)》学习笔记(2)第3章 简单的优化模型 第4章 数学规划模型
《数学模型(第五版)》学习笔记(2)第3章 简单的优化模型 第4章 数学规划模型
71 1
|
13天前
|
存储 数据挖掘
R语言用WinBUGS 软件对学术能力测验建立层次(分层)贝叶斯模型
R语言用WinBUGS 软件对学术能力测验建立层次(分层)贝叶斯模型
11 0
|
2月前
|
机器学习/深度学习 数据采集 人工智能
大模型开发:解释特征工程的重要性以及你如何进行特征选择。
特征工程对机器学习和深度学习至关重要,涉及数据清洗、转换和特征选择,以提升模型预测和泛化能力。它能提高数据质量、浓缩信息、优化模型性能及增强解释性。特征选择是关键步骤,包括过滤法、递归特征消除、嵌入式(如L1正则化)、包裹式和基于模型的方法。此过程通常迭代进行,结合多种工具和业务知识,并可通过自动化技术(如AutoML)简化。
|
2月前
|
机器学习/深度学习 算法
大模型开发:解释反向传播算法是如何工作的。
反向传播算法是训练神经网络的常用方法,尤其适用于多层前馈网络。它包括前向传播、计算损失、反向传播和迭代过程。首先,输入数据通过网络层层传递至输出层,计算预测值。接着,比较实际输出与期望值,计算损失。然后,从输出层开始,利用链式法则反向计算误差和权重的梯度。通过梯度下降等优化算法更新权重和偏置,以降低损失。此过程反复进行,直到损失收敛或达到预设训练轮数,优化模型性能,实现对新数据的良好泛化。
|
2月前
|
机器学习/深度学习 自然语言处理 决策智能
8分SCI | 揭示随机森林的解释奥秘:探讨LIME技术如何提高模型的可解释性与可信度!
8分SCI | 揭示随机森林的解释奥秘:探讨LIME技术如何提高模型的可解释性与可信度!
61 1
|
4月前
|
Java 测试技术 程序员
2024提升计划|优秀程序员的10大共性特征
在设计代码时,很多开发者并没有考虑到"容易测试"这个因素,可测试性不强。如果工程师在开发逻辑的过程中同时考虑如何轻松地进行测试,那么编写出的代码将具备良好的可读性和简单性,并经过精心设计,而不仅仅是"能工作"而已。测试所得到的主要好处发生在考虑测试和编写测试的时候,而不是在运行测试的时候!
|
9月前
|
监控 架构师 程序员
第八章 思维模型
第八章 思维模型
|
12月前
|
机器学习/深度学习 算法 数据可视化
基于 R 语言的判别分析介绍与实践(2)
基于 R 语言的判别分析介绍与实践(2)
107 0
|
机器学习/深度学习
机器学习数学基础三:线代基础和特征分解
对于给定矩阵A,寻找=个常数入和非零向量x,使得向量x被矩阵A作用后所得的向量Ax.与原向量x平行,并且满足Ax=λx
83 0
机器学习数学基础三:线代基础和特征分解
|
机器学习/深度学习 算法
条件随机场|机器学习推导系列(二十一)
条件随机场|机器学习推导系列(二十一)
121 0
条件随机场|机器学习推导系列(二十一)