`
zhang_xzhi_xjtu
  • 浏览: 536550 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

实践中的重构16_多即是少

阅读更多
在编写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的代码比较简洁,很容易看清楚重复的地方,因此,对于这些重复代码的重构也一下子简单起来。(未完待续)
分享到:
评论

相关推荐

    重构_重构_改善既有代码_

    《重构:改善既有代码设计》是一本由Martin Fowler所著的经典IT著作,它详细阐述了在软件开发过程中如何通过重构来提升代码质量、可读性和维护性。重构是一种系统性的方法,旨在不改变软件外在行为的前提下,改进其...

    emd.rar_emd信号重构_emd分解重构_emd重构_经验模态分解

    经验模态分解(Empirical Mode Decomposition,简称EMD)是一种强大的数据分析技术,尤其...通过对这些资源的深入理解和实践,我们可以更好地掌握EMD技术,并将其应用到实际问题中,实现非平稳信号的有效分析和重构。

    java并发重构ppt_转温 少

    尽管具体内容未提供,但我们可以推测这篇文章可能分享了作者对于Java并发重构的理解和实践经验,可能包括如何识别和改进并发代码中的问题,以及如何利用Java提供的并发工具和框架来优化多线程环境下的程序。...

    wavetansform1.zip_matlab图像重构_图像重构_图像重构 小波_小波分解和重构_重构误差

    接下来是图像的重构,即利用之前得到的小波系数通过逆小波变换恢复图像。这个过程可以理解为将小波系数重新组合,形成近似原始图像的新图像。通过比较重构后的图像和原始图像,可以评估小波变换的效果。 在本项目中...

    NewPrjName.rar_三维 医学_三维 重构_三维重构_三维重构C++_重构

    在IT行业中,尤其是在医疗影像处理领域,三维重构技术扮演着至关重要的角色。"NewPrjName.rar" 是一个与三维医学图像重构相关的项目文件压缩包,它涉及到的是使用C++编程语言来实现这一复杂的计算过程。这个项目的...

    MATLAB图像重构实战.zip_fanbeam _fanbeam matlab_matlab图像重构_图像重构算法_重构算法m

    在学习和实践中,你可能会接触到如`radon`和`iradon`这样的MATLAB内置函数,它们分别用于执行投影和反投影操作。 首先,`radon`函数用于计算物体的投影数据。在fanbeam变换中,输入参数可能包括图像矩阵、旋转中心...

    几种常见的稀疏重构算法代码.rar_FOCUSS重构_Focuss算法_focuss稀疏重构_压缩感知算法_稀疏重构

    压缩感知是一种革命性的理论,它表明,如果一个信号可以用较少的非零元素(即稀疏表示)来描述,那么可以使用远少于信号原始维度的测量值来恢复这个信号。FOCUSS(Focused Iterative Spherical Undersampling and ...

    omp_重构图像_cs的omp算法_压缩感知_

    《omp算法在压缩感知中的应用及其在图像重构中的实践》 在数字图像处理领域,压缩感知(Compressed Sensing, CS)理论已经成为一种革命性的新技术,它颠覆了传统的信号采样理论,允许我们以远低于奈奎斯特定理所...

    reconfiguration_配电网_配电网络重构_reconfiguration_配电网重构_配电网重构_源码.zip

    在电力系统领域,配电网重构是一项关键的技术,其目的是通过改变配电网络的...总之,配电网重构源码的获取为研究和实践提供了宝贵的工具,通过深入学习和应用,可以提升电力系统的运行效率,为智能电网的发展做出贡献。

    MATLAB.rar_低通采样_信号采样_信号重构_重构_重采样

    在电子工程和数字信号处理领域,信号采样与重构是一个至关重要的概念,它涉及到如何将连续时间信号转换为离散时间信号,以便于在计算机中进行处理,然后再将处理后的信号还原成原始形式。MATLAB是一款强大的数值计算...

    top.rar_动态 重构_动态可重构_动态重构

    在IT行业中,动态重构是一种先进的软件工程实践,它允许开发者在程序运行时对代码结构进行修改,以提高软件性能、可维护性和可扩展性。在标题"top.rar_动态重构_动态可重构_动态重构"中,我们可以推断这是一个关于...

    mutual_information_相空间重构matlab_互信息熵_源码.zip

    这个压缩包中的源码很可能是实现了以上步骤的MATLAB函数或脚本,对于学习和实践互信息和相空间重构的学者来说,这是一个宝贵的资源。用户可以通过阅读和运行这些代码,理解相关算法的原理,并将其应用到自己的项目中...

    xiaobofenxi.rar_多尺度_多尺度 重构_小波 多 分解_小波分解_小波分解重构

    总的来说,这个压缩包提供了一个学习和实践小波分析的平台,涵盖了多尺度分解、小波分解和重构的关键概念和技术。无论是为了学术研究还是工程应用,掌握小波分析都能极大地提升对非平稳信号的理解和处理能力。通过...

    omp.zip_omp信号重构_信号稀疏重构_稀疏信号重构_稀疏度_稀疏重构

    在数学和信号处理中,一个信号被称为“稀疏”如果它可以用相对较少的非零系数来表示。在高维空间中,即使实际信号可能很复杂,但往往可以找到一个合适的基,使得该信号在该基下的表示大部分为零,这就是信号的稀疏性...

    yuanzhui.rar_yuanzhui_三维点云重构_曲面重构_点云_点云特征

    在IT领域,尤其是在计算机图形学和3D建模中,"yuanzhui.rar_yuanzhui_三维点云重构_曲面重构_点云_点云特征"这个标题涉及了多个关键概念和技术,这些都是现代数字几何处理的核心部分。下面我们将深入探讨这些主题: ...

    PSR.zip_matlab PSR_时间序列重构_混沌时间序列_空间重构_重构相空间

    在本文中,我们将深入探讨与"PSR.zip_matlab PSR_时间序列重构_混沌时间序列_空间重构_重构相空间"相关的主题,这主要涉及混沌理论和时间序列分析在MATLAB环境中的应用。时间序列重构是研究复杂系统动态行为的重要...

    重构pdf_chonggou

    在实践中,重构往往需要配合单元测试进行,确保每次修改都不会破坏现有的功能。通过TDD(Test-Driven Development,测试驱动开发)的方式,可以先编写测试,然后进行重构,最后确保所有测试都能通过,从而保证重构的...

    BC.rar_visual c_小波分解重构C_小波重构_重构

    在二维小波分解中,这一过程针对图像或其他二维数据进行,可以揭示图像的多分辨率结构,有助于图像的压缩、去噪、边缘检测等操作。 重构则是小波分解的逆过程,通过重组小波系数来恢复原始信号或图像。在图像处理中...

    Untitled.rar_Untitled_face svd_svd重构_人脸重构_特征提取

    标题 "Untitled.rar_...总的来说,这个项目提供了一个实践性的平台,用于学习和探索如何利用SVD进行人脸识别的特征提取和重构,对于理解SVD在实际问题中的应用,以及提高在图像处理和机器学习领域的技能非常有帮助。

Global site tag (gtag.js) - Google Analytics