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

实践中的重构10_平铺直叙的代码

阅读更多
    很多应用程序的主要目的就是用计算机来代替人处理真实世界中的问题。真实世界和计算机世界之间有着巨大的差异。编程语言,编程方法一直以来都有一个重要的发展方向,提供一个平台,尽量减少这个差异。面向对象语言,面向领域编程,为解决该问题在不同层次提供了解决方案。
    在代码级别,也可以通过种种方式减少这个差异。
先看下面一段代码,需求和场景是这样的。
    查询用户列表,传入一个整数,指定一次查询返回用户数的上限。
    有3个不同的数据源可以用来查询用户信息,这3个数据源在查询的时候是有优先级的。即先查询第1个数据源,如果得到的用户数少于上限,则到第2个数据源查询,如果还是少于上限,则到第3个数据源查询。
	public List<User> getUserList_0(int maxSize) {

		List<User> userList;
		int selectSize = 0;
		userList = getUserListFromSource1(maxSize);

		if (null != userList) {
			if (userList.size() < maxSize) {
				selectSize = maxSize - userList.size();
				List<User> userList2 = getUserListFromSource2(selectSize);

				if (null != userList2 && !userList2.isEmpty()) {
					userList.addAll(userList2);
					if (userList.size() < maxSize) {
						selectSize = maxSize - userList.size();
						List<User> userList3 = getUserListFromSource3(selectSize);
						if (null != userList3 && !userList3.isEmpty()) {
							userList.addAll(userList3);
						}
					}
				}
			}

		} else {
			userList = getUserListFromSource2(maxSize);
			if (userList.size() < maxSize) {
				selectSize = maxSize - userList.size();
				List<User> userList4 = getUserListFromSource3(selectSize);
				if (null != userList4 && !userList4.isEmpty()) {
					userList.addAll(userList4);
				}
			}
		}

		return userList;
	}

    如果你耐心的看完了这段代码,我只能说佩服了。一眼看到5层的if嵌套,我已经是半晕状态了。我承认,我没有耐心看完这段代码。
    该方法的需求和场景都是比较简单的,因此应该存在简单的写法。
重写的代码如下:
	public List<User> getUserList_1(final int maxSize) {

		List<User> userList = new ArrayList<User>();
		List<User> temList;

		int selectSize = maxSize - userList.size();
		temList = getUserListFromSource1(selectSize);
		if (temList != null) {
			userList.addAll(temList);
		}
		if (userList.size() >= maxSize) {
			return userList;
		}

		selectSize = maxSize - userList.size();
		temList = getUserListFromSource2(selectSize);
		if (temList != null) {
			userList.addAll(temList);
		}
		if (userList.size() >= maxSize) {
			return userList;
		}

		selectSize = maxSize - userList.size();
		temList = getUserListFromSource3(selectSize);
		if (temList != null) {
			userList.addAll(temList);
		}

		return userList;
	}

    这里首先对参数加上final修饰符,明确表明方法中不会修改该参数。其次,去除掉了if嵌套,用相似的代码结构试图更清晰更直白的表明这段代码的意图。
    这段代码有这样一些小的问题,可以持续改进。
    1 如果可以预期该方法返回的用户列表大小的话,可以设置初始的列表大小。
    2 如果遵循查询结果为空,返回空列表而不是null的话,可以是代码更紧凑。
	public List<User> getUserList_2(final int maxSize) {

		List<User> userList = new ArrayList<User>(maxSize);

		int selectSize = maxSize - userList.size();
		userList.addAll(getUserListFromSource1(selectSize));
		if (userList.size() >= maxSize) {
			return userList;
		}

		selectSize = maxSize - userList.size();
		userList.addAll(getUserListFromSource2(selectSize));
		if (userList.size() >= maxSize) {
			return userList;
		}

		selectSize = maxSize - userList.size();
		userList.addAll(getUserListFromSource3(selectSize));

		return userList;
	}

    这样看上去好多了。这里遵循的原则就是平铺直叙的用编程语言翻译需求,试图用接近自然语言的表达方式实现需求,从而减少两个世界的差异。
    更好的一种方式是先用自然语言(或者伪代码)描述需求的实现,然后翻译成真正的代码。
    用自然语言描述这个需求的实现如下:
    1 新建一个空的用户列表。
    2 计算下一次查询的上限。
    3 到数据源1去查询。
    4 添加结果到用户列表。
    5 如果用户列表达到上限则返回。
    6 计算下一次查询的上限。
    7 到数据源2去查询。
    8 添加结果到用户列表。
    9 如果用户列表达到上限则返回。
    10 计算下一次查询的上限。
    11 到数据源3去查询。
    12 添加结果到用户列表。
    13 返回。
    这个实现的描述和真实的实现很像,即使没有实现,看着这个自然语言的实现翻译成对应的代码实现也很容易。
    当然,计算机编程是复杂的,这里的实现还是有一个潜在的问题。代码中有重复的结构。鉴于这里的实现已经比较简单了,所以没有进一步去掉这个重复的结构。但是,如果需求改变,需要查询更多的数据源,或者可配置的数据源,代码可以做以下演进。
	interface UserQueryService {
		List<User> queryUserList(int maxSize);
	}

	class UserQueryServiceImpl_1 implements UserQueryService {

		@Override
		public List<User> queryUserList(int maxSize) {
			return getUserListFromSource1(maxSize);
		}
	}

	class UserQueryServiceImpl_2 implements UserQueryService {

		@Override
		public List<User> queryUserList(int maxSize) {
			return getUserListFromSource2(maxSize);
		}
	}

	class UserQueryServiceImpl_3 implements UserQueryService {

		@Override
		public List<User> queryUserList(int maxSize) {
			return getUserListFromSource3(maxSize);
		}
	}

	public List<User> getUserList_3(final int maxSize) {

		// 这里的userQueryServices可以从配置文件中生成,也可以用户传入。
		// 这里为了演示,硬编码一下。
		List<UserQueryService> userQueryServices = new ArrayList<UserQueryService>();
		userQueryServices.add(new UserQueryServiceImpl_1());
		userQueryServices.add(new UserQueryServiceImpl_2());
		userQueryServices.add(new UserQueryServiceImpl_3());

		List<User> userList = new ArrayList<User>(maxSize);

		// 方式1
		for (UserQueryService service : userQueryServices) {
			int selectMaxSize = maxSize - userList.size();
			userList.addAll(service.queryUserList(selectMaxSize));

			if (userList.size() >= maxSize) {
				break;
			}
		}

		// 方式2
		for (int i = 0; i < userQueryServices.size()
				&& userList.size() < maxSize; i++) {
			UserQueryService service = userQueryServices.get(i);
			int selectMaxSize = maxSize - userList.size();
			userList.addAll(service.queryUserList(selectMaxSize));
		}

		return userList;
	}

    在方法体中,方式1和方式2都是可行的。对于简单的循环,尾部的一个判断跳出看上去比一个多行的条件判断更清晰一点。
分享到:
评论
5 楼 tuti 2010-12-19  
建议楼上,找个同事来看这2种代码。
听听对方的反馈,哪一种更容易理解。
4 楼 zhang_xzhi_xjtu 2010-12-19  
tuti 写道
zhang_xzhi_xjtu 写道

line6是多余的,只是为了保持结构一致才这么写的,个人行为。


既然是讲“重构”,多余的代码就需要清理掉,否则就不是“平铺直叙”。


这个有一定的道理,但是我觉得还是有商量的余地。

int selectSize = size - userList.size(); 


这个和下面的代码块的结构保持一致。从语义上保持一致。

翻译一下三个代码块对当前要查询的用户数的处理
1 为用户数上限数减去已经查找到的用户数。
2 为用户数上限数减去已经查找到的用户数。
3 为用户数上限数减去已经查找到的用户数。

而省掉这个,是建立在这个是第一次查询,已经知道已经查找到的用户数为0。
等于说,
1 为用户数上限数(第一次默认知道已经查找到的用户数为0)。
2 为用户数上限数减去已经查找到的用户数。
3 为用户数上限数减去已经查找到的用户数。

我选择第一种,是因为看上去虽然有一点冗余,但是这个冗余增强了代码含义的一致性。
3 楼 tuti 2010-12-18  
zhang_xzhi_xjtu 写道

line6是多余的,只是为了保持结构一致才这么写的,个人行为。


既然是讲“重构”,多余的代码就需要清理掉,否则就不是“平铺直叙”。
2 楼 zhang_xzhi_xjtu 2010-12-18  
tuti 写道
Line 6, 多余。
返回UserList的方法都设计的不好, 应该不需要判null。


line6是多余的,只是为了保持结构一致才这么写的,个人行为。

get底层的服务,别人未必想的那么周全,如果它的注释中,没有声明一定不返回null的话,我觉得判一下null是比较安全的做法。
1 楼 tuti 2010-12-18  
Line 6, 多余。
返回UserList的方法都设计的不好, 应该不需要判null。

相关推荐

    MATLAB图像处理_能力提高与应用案例

    在文字叙述上,本书摒弃了枯燥的平铺直叙,采用案例与问题引导式;同时,本书还增加了“温馨提示”、“例程一点通”、“经验分享”、“一语中的”等版块,彰显了本书以读者为本的人性化的特点。

    人事面试题150问_人事面试题大全_net程序员面试题__软件开发工程师面试题.xls

    【思路】:建议大家用2分钟得自我介绍,面试官较喜欢的自我介绍(1)有亮点,每一小段都有一个亮点,而不是平铺直叙(2) 有互动:每一小段都会和面试官互动,而不是自说自话,但是切记,这种互动并不需要面试官配合...

    重庆市第一中学2018_2019学年高一语文上学期期中试题含解析

    “平铺直叙”则形容一种直接叙述的方式,符合语境;“言不及义”通常指言语空洞、无关紧要,但若用于翻译不准确则有失偏颇;“山高水低”本指意外的不幸,若用于描述领略路途的美景则不太恰当;“目不交睫”形容人因...

    《C语言程序设计》教材改革探讨.pdf

    C语言作为一门基础且重要的编程语言,其教学方式长期以来多采用传统的平铺直叙方式,导致学生难以产生浓厚的学习热情。针对这一问题,文章提出了以案例为中心的教材改革策略。 C语言自1972年诞生以来,因其功能强大...

    基于《PLC技术及应用》课程的大学生创新能力的培养与实践.pdf

    由于学生的基础层次不一,教师平铺直叙的讲解方式难以激发学生的兴趣,导致学生对PLC的工作原理和程序设计的理解不深入,难以做到熟练应用和独立思考。这直接影响了学生在实验中的操作能力和创新能力。 2. 缺乏实际...

    细推敲、巧衔接,一枝一叶总关“技”——Python教学背景下的IT课堂实策.pdf

    信息技术学科包含许多专业性的知识和术语,传统的平铺直叙往往难以激发学生的学习兴趣。作者建议在课前设置自主体验任务,例如让学生为Python制作自我介绍,这样既可以使学生带着问题走进课堂,又可以在教师的引导下...

    Head First HTML与CSS、XHTML (英文版)

    书中内容并非平铺直叙,而是将神经生物学、认知心理学和学习理论等研究领域的最新成果融入教学之中,使读者在探索网页设计的过程中,通过生动有趣的方式,激发学习HTML、CSS和XHTML的兴趣。 HTML是构建网页的骨架,...

    导游资格考试知识点:导游业务10.docx

    5. **概述法**:这是一种按照时间顺序、逻辑层次或因果关系进行讲解的方法,也称作平铺直叙,常用于概括性介绍景点或事件。 6. **触景生情法**:此方法要求导游根据眼前的景色引发情感,借题发挥进行讲解,要求自然...

    初中语文语文论文我要的是葫芦中一句话教学的启示

    反问句往往用于强调说话者的强烈情感或明确立场,而陈述句则平铺直叙,更为客观冷静。在实际教学中,教师应注重句式的语境分析,让学生通过实例来理解这些句式的不同用途,而不仅仅是停留在表面形式上的比较。 综上...

    2021年小学教育初等教育实习总结.docx

    初期的习题课和新课教学让实习生积累了宝贵经验,同时也暴露了一些问题,如紧张、讲解过于平铺直叙、课堂控制不够灵活等。通过指导教师的反馈和自我反思,实习生逐渐改进,提高自己的教学水平。 在班主任实习方面,...

    任务驱动教学法在SQL Server数据库课程教学中的应用.pdf

    教学内容不再仅仅是知识点的平铺直叙,而是围绕核心教学目标和任务进行合理地组织与展开。这种方法有助于学生明确学习目标,减少学习的盲目性,让学生在学习过程中更有方向感和目的性。 对于学生而言,任务驱动教学...

    201611徐盈盈评课.doc

    崔老师在未来的教学中可以更加注重语言表达的生动活泼,避免平铺直叙。在讲解作画方法和欣赏作品时,应该注意逻辑顺序,减少语言上的冗余,以提高教学效率。只有这样,才能确保每个学生都能在愉快的氛围中有效学习。...

    生死攸关的烛光2.doc

    比如,伯瑙德夫人的智斗故事被详尽描绘,而敌人的简单搜查则一带而过,通过这样的案例分析,学生能够理解如何根据文章主题的需要来确定内容的详略,避免文章内容平铺直叙,缺乏波澜。 同时,教师还提供了多个写作...

    听践行新课程小学语数阶段性交流与研讨活动心得体会.docx

    传统教学中,教师往往平铺直叙地讲解,而忽视了激发学生兴趣的关键点。例如,李明芳老师在教授《丑小鸭》这一课时,选择了丑小鸭的“不幸”话语作为导入,成功地吸引了学生的注意力,并引导他们深入文本,挖掘背后的...

    新人教统编版一年级上册语文 6比尾巴 课堂教学反思.docx

    教师在引导学生进行课堂活动时,有时语言可能过于平铺直叙,缺乏感染力,这可能会让学生们的兴趣有所下降。体态语言作为无声的教育手段,其运用也需要更为精准和多样化,这样才能更有效地抓住学生的眼球,使他们更...

    安徽省滁州市定远县育才学校2020学年高二语文下学期第三次月考试题(普通班) (2).doc

    他的小说不满足于平铺直叙地讲述故事,而是将历史的背景和个体的遭遇紧密地结合起来。在这样的叙述中,历史不再是单纯的时间和事件的叠加,而是转化为小说中人物的生活经验和心理状态的投影。双雪涛通过个人命运的...

    人教五年级下册习作七PPT学习教案.pptx

    通过这样的学习,学生可以领悟到在写作中如何运用细节来突出人物性格,而不仅仅是平铺直叙地描述事件。这种写作技巧的学习,是语文教学中不可或缺的部分,它能够有效地提升学生的写作能力和对人物描绘的敏感度。 ...

    如何提升演讲汇报能力_自我管理与提升_求职职场_实用文档.pptx

    4. **突出重点和亮点**:在汇报中,强调方案的独特性和价值点,避免平铺直叙。这可以通过使用案例、数据和视觉工具来增强说服力。 5. **共鸣与互动**:尝试与听众建立共鸣,让他们参与到讨论中来,而不是单方面的...

    《马伶传》教案.pdf

    此外,侯方域在描述两位演员较量时,并没有平铺直叙,而是巧妙地通过观众的反应来凸显双方技艺的高下,细节的描写让角色形象更加立体。比如李伶的第一次胜利后,观众的热烈反响以及第二次较量后李伶的谦卑态度,这些...

    Head First Python_ A Brain-Frie - Barry, Paul

    本书的标题“Head First Python”暗示了其教学方法,即通过直观、互动的方式,以问题和答案的形式引导读者思考和学习,与传统的平铺直叙的教学方法有所不同。这种方法也被其他“Head First”系列书籍所采用,例如在...

Global site tag (gtag.js) - Google Analytics