- 浏览: 536598 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
飞天奔月:
public List<String> gener ...
实践中的重构30_不做油漆匠 -
在世界的中心呼喚愛:
在世界的中心呼喚愛 写道public class A {
...
深入理解ReferenceQueue GC finalize Reference -
在世界的中心呼喚愛:
在世界的中心呼喚愛 写道在世界的中心呼喚愛 写道在classB ...
深入理解ReferenceQueue GC finalize Reference -
在世界的中心呼喚愛:
在世界的中心呼喚愛 写道在classB的finalize上打断 ...
深入理解ReferenceQueue GC finalize Reference -
在世界的中心呼喚愛:
iteye比较少上,如果可以的话,可以发e-mail交流:ch ...
深入理解ReferenceQueue GC finalize Reference
在编写UT的过程中,随处可见重复,硬编码等等使得代码僵化的代码。
UT也是代码,并且其重要性和产品代码相比,只高不低。
以下是一段产品代码的框架。
这段代码提供了用户分页查询功能。但是用户的信息是保存在不同的数据库里面的。所以,要由应用程序来处理历史库和生产库的查询定向和分页组装功能。
稍稍分析一下,这里主要有如下情况需要处理:
1 时间范围在历史库中,只查询历史库。
2 时间范围在生产库中,只查询生产库。
3 时间范围跨库,但是结果数据只在历史库中。
4 时间范围跨库,结果数据一部分在历史库中,一部分在生产库中,需要做拼接处理。
5 时间范围跨库,但是结果数据只在生产库中,视页面边界是否和数据边界对齐的情况要分别处理。
原始的UT的思路是使用JMock,不同的场景mock出不同的manager。导致UT的可读性比较差。代码中充斥着硬编码,重复等不好的味道。
mock本来是为了简化生活的,但是这个地方的mock却是越看越别扭。
这个时候,我想起了“少即是多”这句名言,少即是多,那么多即是少。如果不使用mock,直接写一个简单的manager实现,这样一来,UT应该更简明。
manager的实现如上,其思路如下:
1 在内存中存储用户对象。模拟一个简单的用户数据库。
2 使用构造函数来构造该manager的数据,包括历史库时间分界点,历史库和生产库用户数多少,自动构建用户。
3 构建用户的同时,设置一个特殊值,用来验证返回结果。
4 实现了manager的接口方法。
构思和实现这个UT的架构比较费时间,但是一旦完成了,UT的编写就是简单到了极点。下面的UT写起来基本不用费什么脑力。这样,我们通过编写一个相对复杂的内存实现,简化了UT的编写。
UT如下:
每一个UT都很简洁,唯一不足的地方在于重复。
但是由于UT的代码比较简洁,很容易看清楚重复的地方,因此,对于这些重复代码的重构也一下子简单起来。(未完待续)
UT也是代码,并且其重要性和产品代码相比,只高不低。
以下是一段产品代码的框架。
public interface UserQueryManager { /** * 获得历史库和生产库的分界时间点。 * */ public Date getHisDate(); /** * 统计两个时间点之间的用户数。由调用方保证该时间范围不是跨库时间范围。 * */ public int countUsersNumber(Date start, Date end); /** * 查找两个时间点之间的用户。由调用方保证该时间范围不是跨库时间范围。 pageNum从1开始计数。 * */ public List<User> findUsers(Date start, Date end, int pageSize, int pageNum); }
public class UserQueryService { /** * 时间段是否跨库。 * */ private static boolean isCross(Date start, Date end, Date hisDate) { return start.before(hisDate) && end.after(hisDate); } private UserQueryManager manager; /** * 分页查询,查找start和end之间的用户。 pageNum从1开始计数。 * */ public List<User> findUsers(Date start, Date end, int pageSize, int pageNum) { Date hisDate = manager.getHisDate(); // 是否跨库查询 boolean isCross = isCross(start, end, hisDate); if (isCross) { return findUsers(start, end, hisDate, pageSize, pageNum); } else { return manager.findUsers(start, end, pageSize, pageNum); } } /** * 跨库分页查询,查找start和end之间的用户。 pageNum从1开始计数。 * */ private List<User> findUsers(Date start, Date end, Date his, int pageSize, int pageNum) { // 历史库中的用户数。 int hisTotalSize = manager.countUsersNumber(start, his); int startNum = (pageNum - 1) * pageSize + 1; int endNum = pageNum * pageSize; // 全部在历史库中。 if (endNum <= hisTotalSize) { return manager.findUsers(start, his, pageSize, pageNum); } // 全部在生产库。 if (startNum > hisTotalSize) { int remainder = hisTotalSize % pageSize; // 页面边界整齐或者不整齐。 if (remainder == 0) { int newPageNum = pageNum - hisTotalSize / pageSize; return manager.findUsers(his, end, pageSize, newPageNum); } else { int newPageNum = pageNum - hisTotalSize / pageSize - 1; List<User> firstUserList = manager.findUsers(his, end, pageSize, newPageNum); List<User> secondUserList = manager.findUsers(his, end, pageSize, newPageNum + 1); return combinePagedUserList(firstUserList, secondUserList, pageSize - hisTotalSize % pageSize, pageSize); } } // 跨库查询 List<User> firstUserList = manager.findUsers(start, his, pageSize, pageNum); List<User> secondUserList = manager.findUsers(his, end, pageSize, 1); return combinePagedUserList(firstUserList, secondUserList, 0, pageSize); } /** * 合并分页用户列表,删除最前面的deleteSize个元素。如果结果list的大小大于maxSize,删除尾部的元素使list的 * size==maxSize. * * <pre> * deleteSize>=0 * maxSize>0 * </pre> * * */ private List<User> combinePagedUserList(List<User> list1, List<User> list2, int deleteSize, int maxSize) { List<User> userList = new ArrayList<User>(); userList.addAll(list1); userList.addAll(list2); int fromIndex = deleteSize; int toIndex = Math.min(userList.size(), deleteSize + maxSize); return userList.subList(fromIndex, toIndex); } public void setManager(UserQueryManager manager) { this.manager = manager; } }
这段代码提供了用户分页查询功能。但是用户的信息是保存在不同的数据库里面的。所以,要由应用程序来处理历史库和生产库的查询定向和分页组装功能。
稍稍分析一下,这里主要有如下情况需要处理:
1 时间范围在历史库中,只查询历史库。
2 时间范围在生产库中,只查询生产库。
3 时间范围跨库,但是结果数据只在历史库中。
4 时间范围跨库,结果数据一部分在历史库中,一部分在生产库中,需要做拼接处理。
5 时间范围跨库,但是结果数据只在生产库中,视页面边界是否和数据边界对齐的情况要分别处理。
原始的UT的思路是使用JMock,不同的场景mock出不同的manager。导致UT的可读性比较差。代码中充斥着硬编码,重复等不好的味道。
mock本来是为了简化生活的,但是这个地方的mock却是越看越别扭。
这个时候,我想起了“少即是多”这句名言,少即是多,那么多即是少。如果不使用mock,直接写一个简单的manager实现,这样一来,UT应该更简明。
class MockUserQueryManager implements UserQueryManager { private int hisSize; private int prodSize; private Date hisDate; private List<User> hisUserList; private List<User> prodUserList; private void initUserList() { hisUserList = new ArrayList<User>(); for (int i = 1; i <= hisSize; i++) { User user = new User(); user.setName("his_" + i); hisUserList.add(user); } prodUserList = new ArrayList<User>(); for (int i = 1; i <= prodSize; i++) { User user = new User(); user.setName("prod_" + i); prodUserList.add(user); } } public MockUserQueryManager(Date hisDate, int hisSize, int prodSize) { this.hisDate = hisDate; this.hisSize = hisSize; this.prodSize = prodSize; initUserList(); } @Override public Date getHisDate() { return hisDate; } @Override public int countUsersNumber(Date start, Date end) { if (!hisDate.before(end)) { return hisSize; } else { return hisSize + prodSize; } } @Override public List<User> findUsers(Date start, Date end, int pageSize, int pageNum) { if (!hisDate.before(end)) { int fromIndex = (pageNum - 1) * pageSize; int toIndex = Math.min(pageSize * pageNum, hisSize); return hisUserList.subList(fromIndex, toIndex); } else { int fromIndex = (pageNum - 1) * pageSize; int toIndex = Math.min(pageSize * pageNum, prodSize); return prodUserList.subList(fromIndex, toIndex); } } }
manager的实现如上,其思路如下:
1 在内存中存储用户对象。模拟一个简单的用户数据库。
2 使用构造函数来构造该manager的数据,包括历史库时间分界点,历史库和生产库用户数多少,自动构建用户。
3 构建用户的同时,设置一个特殊值,用来验证返回结果。
4 实现了manager的接口方法。
构思和实现这个UT的架构比较费时间,但是一旦完成了,UT的编写就是简单到了极点。下面的UT写起来基本不用费什么脑力。这样,我们通过编写一个相对复杂的内存实现,简化了UT的编写。
UT如下:
public class TestUserQueryService { /** * 解析"yyyyMMdd"形式的字符串为日期。 * */ private static Date parseDate(String dateStr) { Date date = null; try { DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); date = dateFormat.parse(dateStr); } catch (Exception e) { throw new RuntimeException(e); } return date; } private UserQueryService queryService; private MockUserQueryManager mockUserQueryManager; /** * 初始化测试环境。 * */ private void setUpEnv(String hisDate, int hisSize, int prodSize) { queryService = new UserQueryService(); mockUserQueryManager = new MockUserQueryManager(parseDate(hisDate), hisSize, prodSize); queryService.setManager(mockUserQueryManager); } /** * 验证返回的结果。 * * @param userList * 用户列表。 * @param size * 总用户个数。 * @param hisSize * 历史库返回的用户个数。 * @param hisFrom * 历史库返回用户的起始index。 * @param prodSize * 生产库返回的用户个数。 * @param prodFrom * 生产库返回的用户起始index。 * * */ public void assertUserList(List<User> userList, int size, int hisSize, int hisFrom, int prodSize, int prodFrom) { Assert.assertNotNull(userList); Assert.assertEquals(size, hisSize + prodSize); Assert.assertEquals(size, userList.size()); for (int i = 0; i < hisSize; i++) { User user = userList.get(i); Assert.assertEquals("his_" + (hisFrom + i), user.getName()); } for (int i = 0; i < prodSize; i++) { User user = userList.get(hisSize + i); Assert.assertEquals("prod_" + (prodFrom + i), user.getName()); } } /** * 用户查询(只查历史库,满页) * */ @Test public void testQuery_00() { setUpEnv("19820110", 40, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 1); assertUserList(userList, 10, 10, 1, 0, 0); } /** * 用户查询(只查历史库,不满页) * */ @Test public void testQuery_01() { setUpEnv("19820110", 43, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 5); assertUserList(userList, 3, 3, 41, 0, 0); } /** * 用户查询(只查生产库,满页) * */ @Test public void testQuery_10() { setUpEnv("19810804", 40, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 2); assertUserList(userList, 10, 0, 0, 10, 11); } /** * 用户查询(只查生产库,不满页) * */ @Test public void testQuery_11() { setUpEnv("19810801", 43, 23); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 3); assertUserList(userList, 3, 0, 0, 3, 21); } /** * 用户查询(跨库,满页) * */ @Test public void testQuery_20() { setUpEnv("19820103", 43, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 5); assertUserList(userList, 10, 3, 41, 7, 1); } /** * 用户查询(跨库,不满页) * */ @Test public void testQuery_21() { setUpEnv("19820103", 43, 4); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 5); assertUserList(userList, 7, 3, 41, 4, 1); } /** * 用户查询(只查生产库,对齐满页) * */ @Test public void testQuery_30() { setUpEnv("19820103", 40, 60); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 6); assertUserList(userList, 10, 0, 0, 10, 11); } /** * 用户查询(只查生产库,对齐不满页) * */ @Test public void testQuery_31() { setUpEnv("19820103", 40, 17); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 6); assertUserList(userList, 7, 0, 0, 7, 11); } /** * 用户查询(只查生产库,不对齐满页) * */ @Test public void testQuery_40() { setUpEnv("19820103", 43, 40); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 6); assertUserList(userList, 10, 0, 0, 10, 8); } /** * 用户查询(只查生产库,不对齐不满页) * */ @Test public void testQuery_41() { setUpEnv("19820103", 43, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 7); assertUserList(userList, 3, 0, 0, 3, 18); } }
每一个UT都很简洁,唯一不足的地方在于重复。
但是由于UT的代码比较简洁,很容易看清楚重复的地方,因此,对于这些重复代码的重构也一下子简单起来。(未完待续)
发表评论
-
实践中的重构32_使用标准的IO操作写法。
2012-07-14 18:42 1436看到这样一段代码,功能为读取一个指定文件的内容然后返回。 ... -
实践中的重构31_结果类两种实现的比较
2011-09-13 19:58 1093在查询接口结果类设计 ... -
实践中的重构30_不做油漆匠
2011-08-15 23:42 1297油漆匠的故事是编程文化中的一个著名故事。本地化如下。 小强毕业 ... -
实践中的重构29_不自动的自动化测试
2011-07-31 18:00 1068测试的精髓之一就是自 ... -
实践中的重构28_小心怀疑类库
2011-07-24 10:25 1070一般而言,类库的使用频率较高,场景较多,隐藏的bug就较少。 ... -
实践中的重构27_不要忘了内存空间
2011-06-06 18:31 1196方法在设计中,一般关注的是方法的功能契约,即方法需要什么样的参 ... -
实践中的重构26_奇怪的接口注释
2011-06-06 16:10 1361最近又看到奇怪的注释。 /** * 用户查询服务。 ... -
实践中的重构25_UT也需要持续重构
2011-05-01 11:20 1024UT是个好东西,在对代 ... -
实践中的重构24_持续的方法重构
2011-05-01 02:20 1093很少有人可以一遍就写出好的代码。写代码和写文章差不多,大部分人 ... -
实践中的重构23_详尽的注释未必是好注释
2011-03-20 17:37 1552注释一直是软件开发中的一个老大难问题。 代码中一个注释都没有是 ... -
实践中的重构22_不要垃圾
2011-03-20 13:31 1067Java引入了GC当然很好,减轻了程序员手工管理内存的负担,但 ... -
实践中的重构21_给她一个好名字
2011-03-20 13:03 924名字的重要性实在是再怎么强调都不为过的。 为什么名字这么重要呢 ... -
实践中的重构20_一段可笑的异常处理逻辑
2011-03-06 20:32 1715Code review也是一个充满 ... -
实践中的重构19_脱裤子放屁
2011-03-03 23:17 2072每当看到代码中有一个 ... -
实践中的重构18_不对称的美
2011-02-26 22:30 997一般而言,自然界是以 ... -
实践中的重构17_表驱动法
2011-02-22 00:10 865代码以及初始的单元测试见 http://zhang-xzhi- ... -
实践中的重构15_null的意义和集合类作为方法结果类型
2011-01-12 22:16 654在编程中,估计null应该是一个很常写的词汇了。 实践中,经常 ... -
实践中的重构14_用方法设计保证正确性
2011-01-04 21:40 1025一般来说,方法的调用方遵循方法的契约调用某方法来完成某功能。每 ... -
实践中的重构13_利用递归提高代码的可维护性
2010-12-30 01:38 743有这么一段代码,是用来解析国内的地址信息的。 AddressI ... -
实践中的重构12_不要乱用异常
2010-12-30 00:36 1532code review的时候,发现了如下代码。 /** ...
相关推荐
《重构:改善既有代码设计》是一本由Martin Fowler所著的经典IT著作,它详细阐述了在软件开发过程中如何通过重构来提升代码质量、可读性和维护性。重构是一种系统性的方法,旨在不改变软件外在行为的前提下,改进其...
经验模态分解(Empirical Mode Decomposition,简称EMD)是一种强大的数据分析技术,尤其...通过对这些资源的深入理解和实践,我们可以更好地掌握EMD技术,并将其应用到实际问题中,实现非平稳信号的有效分析和重构。
尽管具体内容未提供,但我们可以推测这篇文章可能分享了作者对于Java并发重构的理解和实践经验,可能包括如何识别和改进并发代码中的问题,以及如何利用Java提供的并发工具和框架来优化多线程环境下的程序。...
接下来是图像的重构,即利用之前得到的小波系数通过逆小波变换恢复图像。这个过程可以理解为将小波系数重新组合,形成近似原始图像的新图像。通过比较重构后的图像和原始图像,可以评估小波变换的效果。 在本项目中...
在IT行业中,尤其是在医疗影像处理领域,三维重构技术扮演着至关重要的角色。"NewPrjName.rar" 是一个与三维医学图像重构相关的项目文件压缩包,它涉及到的是使用C++编程语言来实现这一复杂的计算过程。这个项目的...
在学习和实践中,你可能会接触到如`radon`和`iradon`这样的MATLAB内置函数,它们分别用于执行投影和反投影操作。 首先,`radon`函数用于计算物体的投影数据。在fanbeam变换中,输入参数可能包括图像矩阵、旋转中心...
压缩感知是一种革命性的理论,它表明,如果一个信号可以用较少的非零元素(即稀疏表示)来描述,那么可以使用远少于信号原始维度的测量值来恢复这个信号。FOCUSS(Focused Iterative Spherical Undersampling and ...
《omp算法在压缩感知中的应用及其在图像重构中的实践》 在数字图像处理领域,压缩感知(Compressed Sensing, CS)理论已经成为一种革命性的新技术,它颠覆了传统的信号采样理论,允许我们以远低于奈奎斯特定理所...
在电力系统领域,配电网重构是一项关键的技术,其目的是通过改变配电网络的...总之,配电网重构源码的获取为研究和实践提供了宝贵的工具,通过深入学习和应用,可以提升电力系统的运行效率,为智能电网的发展做出贡献。
在电子工程和数字信号处理领域,信号采样与重构是一个至关重要的概念,它涉及到如何将连续时间信号转换为离散时间信号,以便于在计算机中进行处理,然后再将处理后的信号还原成原始形式。MATLAB是一款强大的数值计算...
在IT行业中,动态重构是一种先进的软件工程实践,它允许开发者在程序运行时对代码结构进行修改,以提高软件性能、可维护性和可扩展性。在标题"top.rar_动态重构_动态可重构_动态重构"中,我们可以推断这是一个关于...
这个压缩包中的源码很可能是实现了以上步骤的MATLAB函数或脚本,对于学习和实践互信息和相空间重构的学者来说,这是一个宝贵的资源。用户可以通过阅读和运行这些代码,理解相关算法的原理,并将其应用到自己的项目中...
总的来说,这个压缩包提供了一个学习和实践小波分析的平台,涵盖了多尺度分解、小波分解和重构的关键概念和技术。无论是为了学术研究还是工程应用,掌握小波分析都能极大地提升对非平稳信号的理解和处理能力。通过...
在数学和信号处理中,一个信号被称为“稀疏”如果它可以用相对较少的非零系数来表示。在高维空间中,即使实际信号可能很复杂,但往往可以找到一个合适的基,使得该信号在该基下的表示大部分为零,这就是信号的稀疏性...
在IT领域,尤其是在计算机图形学和3D建模中,"yuanzhui.rar_yuanzhui_三维点云重构_曲面重构_点云_点云特征"这个标题涉及了多个关键概念和技术,这些都是现代数字几何处理的核心部分。下面我们将深入探讨这些主题: ...
在本文中,我们将深入探讨与"PSR.zip_matlab PSR_时间序列重构_混沌时间序列_空间重构_重构相空间"相关的主题,这主要涉及混沌理论和时间序列分析在MATLAB环境中的应用。时间序列重构是研究复杂系统动态行为的重要...
在实践中,重构往往需要配合单元测试进行,确保每次修改都不会破坏现有的功能。通过TDD(Test-Driven Development,测试驱动开发)的方式,可以先编写测试,然后进行重构,最后确保所有测试都能通过,从而保证重构的...
在二维小波分解中,这一过程针对图像或其他二维数据进行,可以揭示图像的多分辨率结构,有助于图像的压缩、去噪、边缘检测等操作。 重构则是小波分解的逆过程,通过重组小波系数来恢复原始信号或图像。在图像处理中...
标题 "Untitled.rar_...总的来说,这个项目提供了一个实践性的平台,用于学习和探索如何利用SVD进行人脸识别的特征提取和重构,对于理解SVD在实际问题中的应用,以及提高在图像处理和机器学习领域的技能非常有帮助。