高质量代码的特征

简介: 高质量代码的特征

回想起来,我觉得我们似乎在误读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,我觉得都是不负责任的态度。

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

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

相关文章
|
8月前
|
机器学习/深度学习 算法
大模型开发:解释随机森林算法以及它是如何做出决策的。
随机森林是集成学习方法,利用多棵决策树提升性能。通过随机抽样和特征选择创建弱分类器,减少模型相关性。每个决策树基于子数据集和特征子集构建,预测时集成所有决策树结果,分类问题采用投票,回归问题取平均值。这种方法降低过拟合风险,提高准确性和稳定性,对噪声和异常值容忍度高,广泛应用。
112 0
|
4月前
|
机器学习/深度学习 存储 算法
Transformer、RNN和SSM的相似性探究:揭示看似不相关的LLM架构之间的联系
通过探索大语言模型(LLM)架构之间的潜在联系,我们可能开辟新途径,促进不同模型间的知识交流并提高整体效率。尽管Transformer仍是主流,但Mamba等线性循环神经网络(RNN)和状态空间模型(SSM)展现出巨大潜力。近期研究揭示了Transformer、RNN、SSM和矩阵混合器之间的深层联系,为跨架构的思想迁移提供了可能。本文深入探讨了这些架构间的相似性和差异,包括Transformer与RNN的关系、状态空间模型在自注意力机制中的隐含作用以及Mamba在特定条件下的重写方式。
195 7
Transformer、RNN和SSM的相似性探究:揭示看似不相关的LLM架构之间的联系
|
4月前
|
机器学习/深度学习 自然语言处理
如何让等变神经网络可解释性更强?试试将它分解成简单表示
【9月更文挑战第19天】等变神经网络在图像识别和自然语言处理中表现出色,但其复杂结构使其可解释性成为一个挑战。论文《等变神经网络和分段线性表示论》由Joel Gibson、Daniel Tubbenhauer和Geordie Williamson撰写,提出了一种基于群表示论的方法,将等变神经网络分解成简单表示,从而提升其可解释性。简单表示被视为群表示的“原子”,通过这一分解方法,可以更好地理解网络结构与功能。论文还展示了非线性激活函数如何产生分段线性映射,为解释等变神经网络提供了新工具。然而,该方法需要大量计算资源,并且可能无法完全揭示网络行为。
49 1
|
8月前
|
数据可视化 数据挖掘
singleCellNet(代码开源)|单细胞层面对细胞分类进行评估,褒贬不一,有胜于无
`singleCellNet`是一款用于单细胞数据分析的R包,主要功能是进行细胞分类评估。它支持多物种和多分组分析,并提供了一个名为`CellNet`的类似工具的示例数据集。用户可以通过安装R包并下载测试数据来运行demo。在demo中,首先加载查询和测试数据,然后训练分类器,接着进行评估,包括查看准确率和召回率的曲线图、分类热图和比例堆积图等。此外,`singleCellNet`还支持跨物种评估,将人类基因映射到小鼠直系同源物进行分析。整体而言,`singleCellNet`是一个用于单细胞分类评估的综合工具,适用于相关领域的研究。
108 6
|
8月前
|
机器学习/深度学习 数据采集 人工智能
大模型开发:解释特征工程的重要性以及你如何进行特征选择。
特征工程对机器学习和深度学习至关重要,涉及数据清洗、转换和特征选择,以提升模型预测和泛化能力。它能提高数据质量、浓缩信息、优化模型性能及增强解释性。特征选择是关键步骤,包括过滤法、递归特征消除、嵌入式(如L1正则化)、包裹式和基于模型的方法。此过程通常迭代进行,结合多种工具和业务知识,并可通过自动化技术(如AutoML)简化。
496 0
|
8月前
|
Java 测试技术 程序员
2024提升计划|优秀程序员的10大共性特征
在设计代码时,很多开发者并没有考虑到"容易测试"这个因素,可测试性不强。如果工程师在开发逻辑的过程中同时考虑如何轻松地进行测试,那么编写出的代码将具备良好的可读性和简单性,并经过精心设计,而不仅仅是"能工作"而已。测试所得到的主要好处发生在考虑测试和编写测试的时候,而不是在运行测试的时候!
|
设计模式 Java C#
面向对象四大特征-系统学习二
经历了三大结构的学习之后,对于事务本质的理解增加了很多的维度;进而开启了面向对象方法的研究。
|
机器学习/深度学习 自然语言处理 算法
预测蛋白质间相互作用更准确、更细致,一个基于基因本体术语集的Transformer框架
预测蛋白质间相互作用更准确、更细致,一个基于基因本体术语集的Transformer框架
139 0
预测蛋白质间相互作用更准确、更细致,一个基于基因本体术语集的Transformer框架
|
机器学习/深度学习 人工智能 算法
NeurIPS 2022 | 直面图的复杂性,港中文等提出面向图数据分布外泛化的因果表示学习(1)
NeurIPS 2022 | 直面图的复杂性,港中文等提出面向图数据分布外泛化的因果表示学习
119 0
NeurIPS 2022 | 直面图的复杂性,港中文等提出面向图数据分布外泛化的因果表示学习(1)
|
机器学习/深度学习 算法 数据可视化
基于 R 语言的判别分析介绍与实践(2)
基于 R 语言的判别分析介绍与实践(2)
164 0