《驯服烂代码:在编程操练中悟道》一第3章 写main()方法测试一下

简介:

本节书摘来自华章出版社《驯服烂代码:在编程操练中悟道》一书中的第3章,作者 伍斌,更多章节内容可以访问云栖社区“华章计算机”公众号查看

第3章 写main()方法测试一下

“类图上的所有类都实现完了。咱们现在可以写个main()方法来测试一下了。”
先创建一个包含main()方法的类HotelWorldClocksRunner。
HotelWorldClocksRunner类的代码如下所示(CM: Added class HotelWorldClocksRunner with a main() method to have a try.):

+public class HotelWorldClocksRunner { 
+}

然后在这个类里面写main()方法。
main()方法的代码如下所示(CM: Added the main() method to class HotelWorldClocksRunner and wrote the expected code there.):

public class HotelWorldClocksRunner { 
+    public static void main(String[] args) { 
+        TimeSubject utcTime = new UtcTime(); 
+        utcTime.attach("beijing", new CityClock(8)); 
+        utcTime.attach("london", new CityClock(0)); 
+        utcTime.attach("moscow", new CityClock(4)); 
+        utcTime.attach("sydney", new CityClock(10)); 
+        utcTime.attach("newYork", new CityClock(-5)); 
+        Clock phoneClock = new PhoneClock(utcTime); 
+ 
+        phoneClock.setLocalTime(9); 
+ 
+        utcTime.printTimeOfAllClocks(); 
+    } 
 }

这段代码分3个部分,第1部分是做准备工作;第2部分是调用手机时钟的setLocalTime()方法来设定时间为北京时间上午9点,以触发所有城市时钟的自动调整;第3部分是打印所有时钟的本地时间。
在第1部分中,我们先创建一个具有TimeSubject类型的UtcTime实例;再把5个城市时钟的实例都分别attach到这个UtcTime实例上,每创建一个城市时钟实例,都把该城市与UTC时间的时差作为构造器的参数传进这个新创建的实例中。比如北京比UTC时间早8小时,所以在attach北京时钟时,用new CityClock(8)来创建北京时钟实例。最后创建手机时钟实例phoneClock,并把上面准备好的UtcTime实例作为构造器的参数传进去,以便在PhoneClock类的setLocalTime()方法中,调用UtcTime类的setUtcZeroTime()方法,来自动调整所有城市时钟的时间。
现在咱们先创建CityClock类的带有时差参数的构造器。
“等等!我觉得带有utcTime参数的创建PhoneClock的实例那句话写得有问题。因为PhoneClock和CityClock都继承同一个父类Clock,为何创建CityClock实例时要提供时差参数,而创建PhoneClock实例时却没有提供时差参数?难道PhoneClock的实例都不需要时差参数吗?”
问得好!PhoneClock的实例在创建时,确实也和创建CityClock实例时一样,需要传入当地时间与UTC时间之间的时差。需要改一改这个main()方法。
修改main()方法中创建PhoneClock实例的代码如下所示(CM: Updated the main() method to make the constructor of PhoneClock is the same with CityClock and add the UtcTime object to the phoneClock using method PhoneClock.setUtcTime().):

utcTime.attach("moscow", new CityClock(4)); 
         utcTime.attach("sydney", new CityClock(10)); 
         utcTime.attach("newYork", new CityClock(-5)); 
-        Clock phoneClock = new PhoneClock(utcTime); 
+        Clock phoneClock = new PhoneClock(8); 
+        phoneClock.setUtcTime(utcTime); 
 
         phoneClock.setLocalTime(9);

在创建PhoneClock实例时,把北京时间距离UTC时间的时差8作为构造器的参数传进去,然后在PhoneClock类上增加一个接口setUtcTime()方法,来把utcTime传给phoneClock实例。
“这样应该没问题了。再更改一下类图,在图中把这个更改标记为7号。”
在细化后的类图中对7号的更改如图3-1所示。

image

这个main()方法有不少编译错误,大概有4处。
HotelWorldClocksRunner类的main()方法的4处编译错误如图3-2所示。

image

现在从上往下看看这4个编译错误。第1个编译错误是CityClock类还没有一个接受该城市与UTC时间的时差的构造器;第2个编译错误是PhoneClock类也没有接受一个其所在城市与UTC时间的时差的构造器;第3个编译错误是PhoneClock类还没有创建setUtcTime()方法;第4个编译错误是UtcTime类还没有创建printTimeOfAllClocks()方法。
接着用Alt+Enter快捷键来让IDEA帮助咱们创建这些默认的构造器和方法。
现在先消除第1个编译错误,创建CityClock类的带有时差参数的构造器。
创建CityClock类的带有时差参数的构造器的代码如下所示(CM: Added constructor CityClock(int utcOffset).):

public class CityClock extends Clock { 
+    public CityClock(int utcOffset) { 
+        super(); 
+    } 
+ 
     @Override 
     public void setLocalTime(int localTime) { 
在CityClock类的带有时差参数的构造器里,时差参数
u```  
tcOffset应该作为参数传进super()方法里,并在父类Clock里也添加一个带时差参数的构造器来接收这个参数。
在CityClock类中将时差参数utcOffset作为参数传进super()方法的代码如下所示(CM: Added constructor Clock(int utcOffset).):

public class CityClock extends Clock {

 public CityClock(int utcOffset) { 
  • super();
  • super(utcOffset);

     }
    
在父类Clock里添加一个带时差参数的构造器的代码如下所示(CM同上):

public abstract class Clock {

  • protected static final int UTC_OFFSET = 0;
  • protected static int UTC_OFFSET;
    protected int localTime = 0;
  • public Clock(int utcOffset) {
  • UTC_OFFSET = utcOffset;
  • }
  • public abstract void setLocalTime(int localTime);
因为Clock类的成员变量UTC_OFFSET需要在其构造器里赋值,所以就不能是final的了。
再消除第2个编译错误,创建PhoneClock类的带有时差参数的构造器。
创建PhoneClock类的带有时差参数的构造器的代码如下所示(CM: Created constructor PhoneClock(int utcOffset).):

public class PhoneClock extends Clock {

 private UtcTime utcTime; 
  • public PhoneClock(int utcOffset) {
  • super(utcOffset);
  • }
    +
现在消除第3个编译错误,创建PhoneClock类的setUtcTime()方法。不过在main()方法中,变量phoneClock被声明为Clock类型了。如果是这样的话,setUtcTime()方法应该在父类Clock中创建,而成为一个公共接口,这使得Clock类的另一个子类CityClock也不得不实现这个它并不需要的接口。这就不大合理了。所以可以在main()方法中,把变量phoneClock声明为PhoneClock类型,这样setUtcTime()方法就只在PhoneClock类上创建了。
在main()方法中,把变量phoneClock声明为PhoneClock类型的代码如下所示(CM: Changed type of varialbe phoneClock in main() to be PhoneClock.):

utcTime.attach("newYork", new CityClock(-5));

  • Clock phoneClock = new PhoneClock(8);
  • PhoneClock phoneClock = new PhoneClock(8);

         phoneClock.setUtcTime(utcTime);
    
“main()方法里还有一个问题,就是变量utcTime的类型应该是UtcTime,而不应该是其父类TimeSubject。因为根据类图来看,PhoneClock类持有一个UtcTime的实例,以便调用后者的setUtcZeroTime()方法。而这个方法只在UtcTime类中定义了,其父类TimeSubject并没有定义。所以为了调用UtcTime类中的setUtcZeroTime()方法,main()方法里的变量utcTime的类型应该是UtcTime。”
好的,这就改过来。
在main()方法中,把变量utcTime声明为UtcTime类型的代码如下所示(CM: Changed type of varialbe utcTime in main() to be UtcTime.):

public class HotelWorldClocksRunner {

 public static void main(String[] args) { 
  • TimeSubject utcTime = new UtcTime();
  • UtcTime utcTime = new UtcTime();

         utcTime.attach("beijing", new CityClock(8));
    
现在可以创建PhoneClock类的setUtcTime()方法来消除第3个编译错误了。
在PhoneClock类中创建setUtcTime()方法的代码如下所示(CM: Created method Phone-Clock.setUtcTime(UtcTime).):

super.localTime = localTime;

     this.utcTime.setUtcZeroTime(localTime - UTC_OFFSET); 
 } 
  • public void setUtcTime(UtcTime utcTime) {
  • this.utcTime = utcTime;
  • }
    }
现在可以创建UtcTime类的printTimeOfAllClocks()方法来消除第4个编译错误了。这个方法专门是为测试用的,所以就不在类图中画出来了。
创建UtcTime类的printTimeOfAllClocks()方法的代码如下所示(CM: Created method UtcTime.printTimeOfAllClocks().):

clock.setLocalTime(Clock.toLocalTime(this.utcZeroTime));

     } 
 } 
  • public void printTimeOfAllClocks() {
  • for (String clockName : super.clocks.keySet()) {
  • System.out.println(clockName + ": " + super.clocks.get(clockName).getTime());
  • }
  • }
    }
因为utcTime能从其父类TimeSubject里继承Map类型的成员变量clocks,所以就能在printTimeOfAllClocks()方法里写一个循环语句,打印所有的时钟名称和时间。
现在就差创建Clock类的getTime()方法了。
创建Clock类的getTime()方法的代码如下所示(CM: Created method Clock.getTime().):

public static int toLocalTime(int utcZeroTime) {

     return utcZeroTime + UTC_OFFSET; 
 } 
  • public String getTime() {
  • return String.valueOf(this.localTime);
  • }
    }
看起来现在终于可以运行一下这个main()方法了。在IDEA里打开那个main()方法,把光标定位到类名上,然后按Ctrl+Shift+F10组合键运行一下。结果出来了,奇怪,所有的城市的时间都是9点。
第一次运行main()方法的结果如图3-3所示。
“现在的问题是,除了北京以外,其他所有城市的当地时间都是错的,而且都是9。加个断点调试一下吧。“
在调试前,咱们先看看这章做了什么工作:
1)开始编写main()方法,并通过打印语句,测试了一下先前按照细化后的类图所编写的代码。
2)在创建PhoneClock的实例时发现并修复了构造器的参数问题。
3)先在main()方法中编写调用了暂时不存在但期望存在的接口的代码(即意图代码),然后循着这些意图代码的红色编译错误,来编写生产代码以消除这些错误。
4)修改细化后的类图以反映设计的修改。

![image](https://yqfile.alicdn.com/edc16bce0cdc59264450a73d9a69504588e1bb33.png)
相关文章
|
3月前
|
测试技术 开发者 Python
Python单元测试入门:3个核心断言方法,帮你快速定位代码bug
本文介绍Python单元测试基础,详解`unittest`框架中的三大核心断言方法:`assertEqual`验证值相等,`assertTrue`和`assertFalse`判断条件真假。通过实例演示其用法,帮助开发者自动化检测代码逻辑,提升测试效率与可靠性。
385 1
|
4月前
|
算法 IDE Java
Java 项目实战之实际代码实现与测试调试全过程详解
本文详细讲解了Java项目的实战开发流程,涵盖项目创建、代码实现(如计算器与汉诺塔问题)、单元测试(使用JUnit)及调试技巧(如断点调试与异常排查),帮助开发者掌握从编码到测试调试的完整技能,提升Java开发实战能力。
496 0
|
2月前
|
安全 Java 测试技术
《深入理解Spring》单元测试——高质量代码的守护神
Spring测试框架提供全面的单元与集成测试支持,通过`@SpringBootTest`、`@WebMvcTest`等注解实现分层测试,结合Mockito、Testcontainers和Jacoco,保障代码质量,提升开发效率与系统稳定性。
|
7月前
|
人工智能 自然语言处理 安全
学不会编程也能写测试?AI让测试更平权
在传统的软件开发体系中,测试常被划分为“技术型测试”(如自动化、性能、安全)和“业务型测试”(如功能验证、用户体验)。前者掌握技术话语权,后者则更多依赖经验和流程规范。然而,随着大语言模型(LLM)等AI技术的迅猛发展,这一固有格局正被悄然打破:
242 10
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
如何让AI更“聪明”?VLM模型的优化策略与测试方法全解析​
本文系统解析视觉语言模型(VLM)的核心机制、推理优化、评测方法与挑战。涵盖多模态对齐、KV Cache优化、性能测试及主流基准,助你全面掌握VLM技术前沿。建议点赞收藏,深入学习。
925 8
|
3月前
|
人工智能 边缘计算 搜索推荐
AI产品测试学习路径全解析:从业务场景到代码实践
本文深入解析AI测试的核心技能与学习路径,涵盖业务理解、模型指标计算与性能测试三大阶段,助力掌握分类、推荐系统、计算机视觉等多场景测试方法,提升AI产品质量保障能力。
|
5月前
|
安全 Java 测试技术
Java 项目实战中现代技术栈下代码实现与测试调试的完整流程
本文介绍基于Java 17和Spring技术栈的现代化项目开发实践。项目采用Gradle构建工具,实现模块化DDD分层架构,结合Spring WebFlux开发响应式API,并应用Record、Sealed Class等新特性。测试策略涵盖JUnit单元测试和Testcontainers集成测试,通过JFR和OpenTelemetry实现性能监控。部署阶段采用Docker容器化和Kubernetes编排,同时展示异步处理和反应式编程的性能优化。整套方案体现了现代Java开发的最佳实践,包括代码实现、测试调试
225 0
|
6月前
|
测试技术 Go 开发者
如何为 gRPC Server 编写本地测试代码
本文介绍了如何使用 Go 语言中的 gRPC 测试工具 **bufconn**,通过内存连接实现 gRPC Server 的本地测试,避免端口冲突和外部依赖。结合示例代码,讲解了初始化内存监听、自定义拨号器及编写测试用例的完整流程,并借助断言库提升测试可读性与准确性。适用于单元及集成测试,助力高效开发。
144 1