- 浏览: 522137 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
飞天奔月:
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 1338看到这样一段代码,功能为读取一个指定文件的内容然后返回。 ... -
实践中的重构31_结果类两种实现的比较
2011-09-13 19:58 1059在查询接口结果类设计 ... -
实践中的重构30_不做油漆匠
2011-08-15 23:42 1219油漆匠的故事是编程文化中的一个著名故事。本地化如下。 小强毕业 ... -
实践中的重构29_不自动的自动化测试
2011-07-31 18:00 1029测试的精髓之一就是自 ... -
实践中的重构28_小心怀疑类库
2011-07-24 10:25 1034一般而言,类库的使用频率较高,场景较多,隐藏的bug就较少。 ... -
实践中的重构27_不要忘了内存空间
2011-06-06 18:31 1158方法在设计中,一般关注的是方法的功能契约,即方法需要什么样的参 ... -
实践中的重构26_奇怪的接口注释
2011-06-06 16:10 1325最近又看到奇怪的注释。 /** * 用户查询服务。 ... -
实践中的重构25_UT也需要持续重构
2011-05-01 11:20 946UT是个好东西,在对代 ... -
实践中的重构24_持续的方法重构
2011-05-01 02:20 1059很少有人可以一遍就写出好的代码。写代码和写文章差不多,大部分人 ... -
实践中的重构23_详尽的注释未必是好注释
2011-03-20 17:37 1494注释一直是软件开发中的一个老大难问题。 代码中一个注释都没有是 ... -
实践中的重构22_不要垃圾
2011-03-20 13:31 1030Java引入了GC当然很好,减轻了程序员手工管理内存的负担,但 ... -
实践中的重构21_给她一个好名字
2011-03-20 13:03 891名字的重要性实在是再怎么强调都不为过的。 为什么名字这么重要呢 ... -
实践中的重构20_一段可笑的异常处理逻辑
2011-03-06 20:32 1655Code review也是一个充满 ... -
实践中的重构19_脱裤子放屁
2011-03-03 23:17 2000每当看到代码中有一个 ... -
实践中的重构18_不对称的美
2011-02-26 22:30 963一般而言,自然界是以 ... -
实践中的重构17_表驱动法
2011-02-22 00:10 833代码以及初始的单元测试见 http://zhang-xzhi- ... -
实践中的重构15_null的意义和集合类作为方法结果类型
2011-01-12 22:16 622在编程中,估计null应该是一个很常写的词汇了。 实践中,经常 ... -
实践中的重构14_用方法设计保证正确性
2011-01-04 21:40 982一般来说,方法的调用方遵循方法的契约调用某方法来完成某功能。每 ... -
实践中的重构13_利用递归提高代码的可维护性
2010-12-30 01:38 718有这么一段代码,是用来解析国内的地址信息的。 AddressI ... -
实践中的重构12_不要乱用异常
2010-12-30 00:36 1451code review的时候,发现了如下代码。 /** ...
相关推荐
书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。 声明:本电子版仅为方便个人学习和研究,请勿用于商业用途,版权归原作者所有,如有能力请购买正版。
重构_改善既有代码的设计 ...书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。本书提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。
书中给出了70 多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。《重构 改善既有代码的设计》提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。
书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。《重构:改善既有代码的设计》提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。《重构:改善既...
同样,一个好的程序员要求能够主动自然地重构代码,虽不应翻着重构手册干活,但需对《重构:改善既有代码的设计》中提到的70多个重构方法成竹在胸。然而,在达到这一境界之前,需要不断的实践和经验积累,并且要先读...
书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。《重构:改善既有代码的设计》提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。 《重构:改善...
书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。《重构:改善既有代码的设计》提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。 《重构:改善...