Java使用connectTo方法提高代码可续性详解

目录
  • 关于可读性的一些想法
  • 整理connectTo方法

源代码有两种不同的用户:程序员和计算机。一方面,计算机既能处理干净、结构良好的代码,也能处理混乱的代码。另一方面,程序员对代码的可读性很敏感。甚至是代码中的空白、正确使用缩进(这与计算机完全无关)也决定了代码容易理解或难以理解。

此外,代码的可读性也提高了可靠性,因为通常不容易隐藏一些bug。并且提高了可维护性,因为它更容易修改。

关于可读性的一些想法

编写可读的代码是一门被低估的技术,学校很少教授这种技术,但它却与软件的可靠性、维护和发展密切相关。程序员通常学习用机器容易理解的东西来实现所需功能的代码。这个编码过程需要添加一层又一层的抽象来将功能分解为更小的单元。

Java语言中,这些抽象是包、类和方法。如果整个系统足够大,就没有程序员可以单独控制整个代码库。有些开发者对某个特定的业务有一个纵深的认识。其他开发人员可能只负责一个抽象层并维护它的API。他们都需要经常阅读和理解别人编写的代码。提高可读性意味着将程序员理解一段代码所需的时间最小化。

如何编写可读的程序?用一句关于表现力的格言来总结就是,简单而直接地说出你的意思。事实上,可读性意味着清楚地表达代码意图。统一建模语言(UML)设计师之一格雷迪·布克(Grady Booch)给出了一个自然的类比:干净的代码读起来就像优美的散文。

写好散文不是简单地遵循一套固定的规则,而是需要练习和阅读著名作家的伟大文章,这一过程可能需要数年时间。幸运的是,与自然语言相比,计算机代码的表达能力非常有限,所以编写出干净的代码比写优美的散文更容易,或者至少更有条理。业界对重构和编写干净的代码越来越感兴趣。可读性已经成为敏捷开发中最重要的关注点之一。

试图使用一组简单的数字指标(如标记)来评估可读性。标识符的长度、表达式中出现的括号的数量,等等。这项工作仍在进行中,要达成一个稳定的共识还有很长的路要走,我们接下来将通过一个例子来说明和解释代码可读性的一些改进点。

整理connectTo方法

现在我们将注意力转向一个名叫connectTo的方法,该方法会将两个组里面的容器进行合并,并且容器里面的水会被均分。对其进行重构以提高可读性。首先查看初始版本的实现:

public void connectTo(Container other) {
   // 如果两个容器已经连接,则不做任何事情
   if (group==other.group) return;
   int size1 = group.size(),
   size2 = other.group.size();
   double tot1 = amount * size1,
   tot2 = other.amount * size2,
   newAmount = (tot1 + tot2) / (size1 + size2);
   // 合并两个组
   group.addAll(other.group);
   // 更新要连接的所有容器的组
   for (Container c: other.group) { c.group = group; }
   // 更新所有新连接的容器
   for (Container c: group) { c.amount = newAmount; }
}

这里有一个缺陷:它包含了大量的注释,试图解释每一行代码的含义。有些程序员关心他们的同事,想要他们更好的理解代码,自然会添加这样的注释。然而,这并不是实现容易理解这一目标的最有效的方法。更好的选择是使用提取方法的方式来进行重构。

可读性提示:“提取方法”重构规则——提取可以实现某一个小功能的代码块转到一个新方法并使用描述性名称。

我们可以在connectTo方法中应用这种技术。事实上,我们可以拆分5个新的方法,以及获得一个新的、可读性更强的connectTo方法:

/** Connects this container with another.
 *
 * @param other The container that will be connected to this one
 */
public void connectTo(Container other) {
   if (this.isConnectedTo(other)) return;
   double newAmount = (groupAmount() + other.groupAmount()) /
   (groupSize() + other.groupSize());
   mergeGroupWith(other.group);
   setAllAmountsTo(newAmount);
}

这个方法更短,可读性更强。如果你试着把这个方法大声读出来,你会发现它几乎可以变成可以被理解的一个短文。为此,我们引入了五种适当的支持方法。事实上,很多业内的大佬都认为长方法是一种不好的代码味道,提取方法来消除这种坏味道是普遍被采纳的一种重构技术。

添加注释只能解释部分代码,而提取方法既解释代码又隐藏生成过程代码——将代码提取到单个方法中。在这个例子中,它会使原来的方法抽象级别保持在更高、更统一的高度,避免了旧版本代码中的高层API解释和底层实现错综复杂地交织在一起。

用查询替换局部变量是另一种可用于connectTo方法的重构技术。

可读性提示:“用查询替换局部变量”重构规则——更改局部变量,通过调用一个计算其值的新方法来替换该量。你可以将此技术应用于局部变量newAmount,该变量只分配一次,然后用作setAllAmountsTo方法的参数。应用该技术可以直接删除变量newAmount,并将connectTo方法的最后两行替换为以下内容。

mergeGroupWith(other.group);
setAllAmountsTo(amountAfterMerge(other));

amountAfterMerge是一个计算合并后的每个容器水量的新方法。但是,稍加思考就会发现,amountAfterMerge方法需要克服很多困难才能完成任务,因为在调用方法时,两个group已经完成了合并。group已经包含了other的group。一个很好的折衷方案是将计算新水量的表达式封装到一个新方法中,同时保留局部变量,以便在合并组之前计算出新的量。

final double newAmount = amountAfterMerge(other);
mergeGroupWith(other.group);
setAllAmountsTo(newAmount);

总而言之,我不建议进行这种重构,如抽出5个方法版本中的代码所示newAmount表达式是可读的,不需要隐藏在单独的方法中。当它替换的表达式很复杂或在类中多次出现时,“用查询替换局部变量”规则通常更有用。

现在看看可读版本中connectTo方法的五个新支持方法。在这五个方法中,有两个最好声明为私有的,因为它们可能导致容器对象处于不一致的状态,不应该从类外部调用。他们是mergeGroupWith方法和setAllAmountsTo方法。

mergeGroupWith方法合并两组容器而不更新它们的水量。如果有人单独从外部调用它,很可能使一些或所有容器的水量发生错误。这个方法只有在使用它的上下文中才有意义:在connectTo方法的末尾,然后调用setAllAmountsTo方法。事实上,它是否真的应该独立成一个方法是有争议的。

一方面,让它独立可以通过给予它一个好名字来解释它的用途,而不是像开始的版本那样使用注释解释。另一方面,独立出来的方法可能在错误的上下文中被调用。因为我们是为了可读性而优化的,所以创建独立的方法会更好一点。类似的权衡setAllAmountsTo方法也适用。

private void mergeGroupWith(Set<Container> otherGroup) {
   group.addAll(otherGroup);
   for (Container x: otherGroup) {
     x.group = group;
   }
}
private void setAllAmountsTo(double amount) {
   for (Container x: group) {
     x.amount = amount;
   }
}

私有方法不值得用Javadoc注释。它们只在类内部使用,所以很少有人觉得有必要了解他们的细节。因此,添加注释不是太有必要的。注释的成本并不限于编写它们所需的时间。就像其他源代码一样,它需要维护,否则可能会过时。也就是说,随着版本的迭代,注释和它所描述的代码不同步了。

记住:过时的评论比没有评论更糟糕! 用描述性名称代替注释并不能避免这种风险。如果编写的代码功能和名称不符了,然后最终仍然可能产生一些过时的名称,这和过时的注释同样糟糕。

其他三种新的支持方法都是只读特性,不会带来任何不良影响。我们不应该轻易做出让他们公有化的决定。添加到类中任何公共成员的后续维护成本都要比添加相同的私有成员的成本大得多。公共方法的额外成本包括:

  • 描述其功能的适当注释;
  • 条件检查,以处理可能不正确的输入内容;
  • 一套完整的测试,以确保其正确性。

connectTo方法的三个新的公有支持方法:

/** Checks whether this container is connected to another one.
 *
 * @param other the container whose connection with this will be
checked
 * @return <code>true</code> if this container is connected
 * to <code>other</code>
 */
public boolean isConnectedTo(Container other) {
 return group == other.group;
}
/** Returns the number of containers in the group of this
container.
 *
 * @return the size of the group
 */
public int groupSize() {
 return group.size();
}
/** Returns the total amount of water in the group of this
container.
 *
 * @return the amount of water in the group
 */
public double groupAmount() {
 return amount * group.size();
}

顺便说一下,isConnectedTo方法还改进了类的可测试性,因为它使以前在实现中需要推测的内容都变成了直接可测试的。实现connectTo的六个方法都非常短,其中connectTo是最长的方法本身只有6行。简洁是干净代码的主要原则之一。

到此这篇关于Java使用connectTo方法提高代码可续性详解的文章就介绍到这了,更多相关Java connectTo内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中实现代码优化的技巧分享

    目录 1.用String.format拼接字符串 2.创建可缓冲的IO流 3.减少循环次数 4.用完资源记得及时关闭 5.使用池技术 1.用String.format拼接字符串 不知道你有没有拼接过字符串,特别是那种有多个参数,字符串比较长的情况. 比如现在有个需求:要用get请求调用第三方接口,url后需要拼接多个参数. 以前我们的请求地址是这样拼接的: String url = "http://susan.sc.cn?userName="+userName+"&a

  • Java使用connectTo方法提高代码可续性详解

    目录 关于可读性的一些想法 整理connectTo方法 源代码有两种不同的用户:程序员和计算机.一方面,计算机既能处理干净.结构良好的代码,也能处理混乱的代码.另一方面,程序员对代码的可读性很敏感.甚至是代码中的空白.正确使用缩进(这与计算机完全无关)也决定了代码容易理解或难以理解. 此外,代码的可读性也提高了可靠性,因为通常不容易隐藏一些bug.并且提高了可维护性,因为它更容易修改. 关于可读性的一些想法 编写可读的代码是一门被低估的技术,学校很少教授这种技术,但它却与软件的可靠性.维护和发展

  • java 中同步方法和同步代码块的区别详解

    java 中同步方法和同步代码块的区别详解 在Java语言中,每一个对象有一把锁.线程可以使用synchronized关键字来获取对象上的锁.synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁). 问题的由来: 看到这样一个面试题: //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized (obj){} } synchronized用于解决同步问

  • Java中的static静态代码块的使用详解

    一.与静态方法的比较 一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,静态方法在类加载的时候 就已经加载 可以用类名直接调用 比如main方法就必须是静态的 这是程序入口.两者的区别就是:静态代码块是自动执行的; 静态方法是被调用的时候才执行的. 二.静态方法注意事项 使用类的静态方法时,注意: a.在静态方法里只能直接调用同类中其他的静态成员(包括变量

  • Java多线程join方法实例代码

    本文研究的主要是Java多线程中join方法的使用问题,以下文为具体实例. Thread的非静态方法join()让一个线程B"加入"到另外一个线程A的尾部.在A执行完毕之前,B不能工作.例如: Thread t = new MyThread(); t.start(); t.join(); 另外,join()方法还有带超时限制的重载版本. 例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态. 线程的加入join()对线程栈导致的结果是线程

  • 简单了解Java方法的定义和使用实现详解

    这篇文章主要介绍了简单了解Java方法的定义和使用实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.概念 Java语言中的"方法"(Method)在其他语言当中也可能被称为"函数"(Function).对于一些复杂的代码逻辑,如果希望重复使用这些代码,并且做到"随时任意使用",那么就可以将这些代码放在一个大括号"{}"当中,并且起一个名字.使用代码的时候,直接找到名

  • java方法及this关键字原理分析详解

    目录 步骤1 .给顾客增加一个吃饭的方法 步骤 2 . 没有加static的属性和方法,一定需要先new对象 步骤 3 . 用new出来的对象去执行eat方法 步骤 4 . 怎么理解c.eat() 步骤 5 . 消息接受器 步骤 6 . 如果有两个顾客? 步骤 7 . 答案 步骤 8 .其实有个this 步骤 9 . 在eat方法里面直接使用this 步骤 10 . 构造方法 步骤 11 . 总结:this的意义是什么? 步骤 12 . 道理我都懂,那static又是什么? 步骤 13 . 本节

  • java中的静态代码块、构造代码块、构造方法详解

    运行下面这段代码,观察其结果: package com.test; public class HelloB extends HelloA { public HelloB() { } { System.out.println("I'm B class"); } static { System.out.println("static B"); } public static void main(String[] args) { new HelloB(); } } cla

  • Java toString方法重写工具之ToStringBuilder案例详解

    apache的commons-lang3的工具包里有一个ToStringBuilder类,这样在打日志的时候可以方便的打印出类实例中的各属性的值. 具体用法如下: import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; public class Message { private String from; private Stri

  • Java 判断字符串中是否包含中文的实例详解

    Java 判断字符串中是否包含中文的实例详解 Java判断一个字符串是否有中文是利用Unicode编码来判断,因为中文的编码区间为:0x4e00--0x9fbb, 不过通用区间来判断中文也不非常精确,因为有些中文的标点符号利用区间判断会得到错误的结果.而且利用区间判断中文效率也并不高,例如:str.substring(i, i + 1).matches("[\\一-\\?]+"),就需要遍历整个字符串,如果字符串太长效率非常低,而且判断标点还会错误.这里提高 一个高效准确的判断方法,使

  • java中常见的6种线程池示例详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如下 线程池名称 描述 FixedThreadPool 核心线程数与最大线程数相同 SingleThreadExecutor 一个线程的线程池 CachedThreadPool 核心线程为0,最大线程数为Integer. MAX_VALUE ScheduledThreadPool 指定核心线程数的定时

随机推荐