`
long13168
  • 浏览: 1311 次
  • 性别: Icon_minigender_1
  • 来自: 广州
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

代码——你的思想能走多远

阅读更多

    相信每一位从事开发的同行,都深有感受,一段代码,可识其人,识其品性,识其思想,识其修为。然而,在快餐式代码兴行的今天,越来越多的人开始迷失,盲目追求简洁的代码,抛弃设计,抛弃思想,直至走火入魔,言必排斥复杂。

 

    没错,设计的境界,应当简洁。可是,不应走火入魔。知其一,不知其三,盲目舍弃,却完全不懂其原理,只会显得肤浅。

    我们应该追求的,不是代码的结果,而是其中的思想。

    真正的代码,是可以不写,但不可以没有思想。我从不浪费时间重构我的代码,但我绝对知道如何重构自己的代码。

追求境界,不应该是追求几十万几百万行的代码量,而是看重内功,看你的重构思想究竟有多远。点到,即止,又何须真的写代码?


    我尝试用一个简单的实际例子来说明一下,究竟什么才是设计,什么才是简洁,什么才是,代码

一个简单的应用场景,A系统需要获得某图片目录下所有图片例表,B系统需要获取某目录下的所有文件列表。

于是,有了下面两段代码

 

	
	@RequestMapping
	public GmModelAndView getGalleries(GmJsonObject request, String cmdString) throws GmException
	{
		GmJsonObject json = new GmJsonObject();		
		
		List<String> directories=null;
		if(cmdString==null){
			cmdString="ls -l /app_data/portal/gallery/manufacturer/"+Long.valueOf((String)request.getParameter("compId"))%1000+"/"+request.getParameter("compId")+"/prod/|grep '^-' | awk '{print $9}'";
		}
		try {
			directories = ShellUtil.runShell(cmdString);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		json.setJsonObject(directories);
		
		return new GmModelAndView(json);
	}


	@RequestMapping
	public GmModelAndView getFacetalkFile(GmJsonObject request) throws GmException
	{
		GmJsonObject json = new GmJsonObject();

		List<String> directories = null;
		String cmdString=null;
		if(StringUtil.isNotEmpty(request.getParameter("type"))&&StringUtil.isNotEmpty(request.getParameter("compId"))){
			 cmdString= "ls -l /app_data/" + request.getParameter("type") + "/"+ Long.valueOf((String) request.getParameter("compId"))% 1000 + "/" + request.getParameter("compId")+ "/comp/|grep '^-' | awk '{print $9}'";
		}else{
			throw new GmException("param error");
		}
		try {
			directories = ShellUtil.runShell(cmdString);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		json.setJsonObject(directories);

		return new GmModelAndView(json);
	}
	
 

 

咋这么一看,这两段代码实现了需求。可是,任何人,都知道如何重构。相信大家都很清楚,两段代码之中,不同的地方,仅仅是 cmdString,其实这是一行linux shell命令。

注意,上面有个方法犯错严重,竟然可以接受来自客户端的cmdString,这是灾难性的人为程序漏洞,用户完全可以通过一句简单的删除命令而令系统崩溃,甚至是不可恢复的灾难

因此,我们必须将这个cmdString参数去掉,任何shell命令,都必须只能靠服务端根据逻辑来生成,绝对不能通过客户端直接注入。

我们再回头看看变化的地方,根据要查看的业务对象不同,会采用不同的shell命令来获得显示列表。于是,按照我们开发的习惯约定,我们所有的系统,均用EntitySource来表达不同的业务对象类型。而且,我们要的功能是列表,于是我们进行重构,并且较为合适的方法名 list, 结果得到下面的代码

 

 

	
	@RequestMapping
	public GmModelAndView list(GmJsonObject request) throws GmException
	{
		GmJsonObject json = new GmJsonObject();
		List<String> directories = null;
		int entitySource = request.getParameter("entity_source")
		
		String cmd = "";
		switch(entitySource)
		{
			case EntitySource.PHOTO:
				cmd = "ls -l /app_data/portal/gallery/manufacturer/"+Long.valueOf((String)request.getParameter("compId"))%1000+"/"+request.getParameter("compId")+"/prod/|grep '^-' | awk '{print $9}'";
				break;
			
			case EntitySource.ATTACHMENT:
				cmd = "ls -l /app_data/" + request.getParameter("type") + "/"+ Long.valueOf((String) request.getParameter("compId"))% 1000 + "/" + request.getParameter("compId")+ "/comp/|grep '^-' | awk '{print $9}'";
				break;
		}
		try {
			directories = ShellUtil.runShell(cmd);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		json.setJsonObject(directories);
		return new GmModelAndView(json);
	}
	
 

重构之后,我们将变化控制在了switch里面,当有新的业务对象需要获取相应的文件列表时,我们只需往switch里增加相应的逻辑代码。看似这个重构很可靠。可是,重构其实并没有完成,因为这里面的分析还不够彻底。

变化仍然不可控。

仔细分析,可以发现,当entitySource不同值时,我们需要查找的路径path也是不同的,而且会根据request接受的参数而不同。而这个变化,似曾相识。没错!!!在文件上传的时候,我们会根据传参的不同而将文件保存在相应的路径下,而这个路径,恰恰是由 uploadService.generatePath() 生成,因此我们应当复用此方法来达到目的。

 

而且, switch已经是可控的变化,其作用主要是根据entitySource产生相应的shell命令,因此应当将其抽离成为独立的方法,暂且将其命名为 cmd()

 

那么,我们重构之后又有了下面的代码

 

 

	
	@RequestMapping
	public GmModelAndView list(GmJsonObject request) throws GmException
	{
		GmJsonObject json = new GmJsonObject();
		Map<String, Object> arguments = request.getParameterMap();
		List<String> directories = null;

		String path = "";
		path = uploadService.generatePath(getEntitySource(arguments,false), getEnSrcType(arguments), getEntityId(arguments), getOptionId(arguments), path, new Date());
		path = uploadService.generateLocation(path, getAppCode(arguments));
		
		String cmd = cmd(arguments, path);		
		try {
			directories = ShellUtil.runShell(cmd);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		json.setJsonObject(directories);
		return new GmModelAndView(json);
	}
	
	private String cmd(Map<String, Object> arguments, String path) throws GmException
	{
		String cmd;
		switch(getEnSrcType(arguments))
		{
			case EntitySource.PHOTO:
				cmd = "ls -1 " + path;
				break;
				
			case EntitySource.ATTACHMENT:
				cmd = "ls -lho --full-time " + path + " | grep -v total | awk '{print $4,$5,$6,$8}' | sort -r -k 2,3";
				break;
				
			default:
				cmd = "ls -l " + path + " | grep -v total";
				break;
		}
		return cmd;
	}
 

到了这里,是否发现整段代码和原来已经非常的不同,我们的path生成是直接调用service完成,这是代码复用的好处,它包装了变化,从而使得这里的path逻辑不再变化

我们又抽离了cmd() 方法出来,从而将业务对象的不同处理这一变化包装起来,因此这个时候看回list() 方法,你会发现已经没有了变化的地方。

 

 

 

可是,往往许多人的脚步在这里停止了。

其实,重构之美,在这里只是开始。而你的思想决定了重构的深度。

仔细分析cmd()方法,我们可以发现,生成相应的shell cmd时,代码阅读起来并不那么美观。如果你真的是个老手,你肯定会第一时间反应,字符串与变量的连接操作,类似的东西是sql。那么,你是否会想起我们的log4j那么优雅的设计。

 

 

	log.info("Getting the " + facetalk + " list, total " + total + " files.");
	log.info("Getting the {0} list, total {1} files", facetalk, total);
	
 

你,是否觉得第二种方式读起来舒服很多。

其实,同样的道理,cmd的生成如果重构成pattern token,我们的代码不仅便于阅读,而且优美多了。还有,更加意想不到的东西,先看重构后的代码

 

 

	
	private String cmd(Map<String, Object> arguments, String path) throws GmException
	{
		String cmd;
		switch(getEnSrcType(arguments))
		{
			case EntitySource.PHOTO:
				cmd = MessageFormat.format("ls -1 {0}", path);
				break;
				
			case EntitySource.ATTACHMENT:
				cmd = MessageFormat.format("ls -lho --full-time {0} | grep -v total | awk '{print $4,$5,$6,$8}' | sort -r -k 2,3", path);
				break;
				
			default:
				cmd = MessageFormat.format("ls -l {0} | grep -v total", path);
				break;
		}
		return cmd;
	}
	
 

 

这个时候,我们又发现变与不变的地方了。不管什么时候,path不变,shell变化。

我们设想,如果要处理的entitySource很多,我们会case没完没了。可是,它们有个共同特点,用entitySource即可定位到相应的shell,因此我们应当将shell与entitySource的对应关系抽离。

相信聪明的你想到了key-value键值对,没错,它们正是这种关系。可是要真正的不变,应当是将其抽离java代码。这时,properties文件隆重登场。

先看重构结果:

 

 

	
	@RequestMapping
	public GmModelAndView list(GmJsonObject request) throws GmException
	{
		GmJsonObject json = new GmJsonObject();
		Map<String, Object> arguments = request.getParameterMap();
		List<String> directories = null;

		String path = "";
		path = uploadService.generatePath(getEntitySource(arguments,false), getEnSrcType(arguments), getEntityId(arguments), getOptionId(arguments), path, new Date());
		path = uploadService.generateLocation(path, getAppCode(arguments));
		
		String cmd = cmd(getEntitySource(arguments,false), path);		
		try {
			directories = ShellUtil.runShell(cmd);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		json.setJsonObject(directories);
		return new GmModelAndView(json);
	}
	
	private String cmd(int entitySource, String path) throws GmException
	{
		return MessageFormat.format(props.getProperty("shell.entity."+entitySource), path);
	}
	
 

 

shell.properties文件

 

# 101 photo
shell.entity.101 = ls -1 {0}

# 102 attachment
shell.entity.102 = ls -lho --full-time {0} | grep -v total | awk '{print $4,$5,$6,$8}' | sort -r -k 2,3

# others
shell.entity.0 = ls -l {0} | grep -v total
	
 

到了这里,shell变成了完全由外部的properties配置,这个时候你会发现它另一个好处。当你想验证shell命令是否写错时,只需在shell.properties文件copy完整的命令,而不用很麻烦的寻找相应的java文件再定位到具体那一行上去小心翼翼地复制出来运行。

 

 

短短十几分钟,一个简单的应用场景经历了几个阶段的重构,由原本的变幻无常,到最后java代码不再变化,而变化只由一个shell.properties文件控制,而且还带来了shell易于维护调试的好处。这,就是重构之美。

 

可是,我们的重构完成了吗?没有,它还可以继续重构。但是,我并不想进行任何重构了,因为,点到即止。在这里完全足够了,再追求重构,只会走火入魔,迷失方向,失去本质。对我们来说,只用一个shell.properties来管理,已经够简洁了。而且,这才是真正简洁的代码。简洁,并不只是代码写得少,它其实应是另一层面的东西。是思想所表达出来的结果足够简洁,才是真正的简洁。

 

 

一个简简单单的重构,你自己,究竟重构到了那个阶段。亦或,你有更优美的方案。但是,代码,不在于写,而在于思想,这才是重构所在。

 

 

 

 

 

 

分享到:
评论

相关推荐

    C++程序设计教程——设计思想与实现习题代码答案

    《C++程序设计教程——设计思想与实现习题代码答案》是一本面向初学者和有一定基础的程序员的教育性资源,旨在帮助读者深入理解和掌握C++编程语言。这本书结合了作者两年的教学实践经验,使得内容既理论严谨又贴近...

    10852C++案例教程源代码——清华大学的

    通过对这些源代码的深入学习,读者不仅可以熟悉C++的基本语法,还能掌握面向对象编程思想,并能够利用STL和其他高级特性编写高效、可靠的代码。同时,这些实例也是解决实际问题的起点,有助于培养学习者的编程思维和...

    iOS游戏应用源代码——ArcadaiOs-DrinkingGame-d70487a.zip

    本篇文章将针对"iOS游戏应用源代码——ArcadaiOs-DrinkingGame-d70487a.zip"进行详细的探讨,旨在揭示游戏背后的编程技术与设计思想。 首先,我们来看一下这个项目的基本结构。"ArcadaiOs-DrinkingGame-d70487a"这...

    c++程序设计教程——设计思想与实现(课件)

    《C++程序设计教程——设计思想与实现》是一门深入探讨C++编程语言的课程,旨在教授学生如何理解和运用C++的设计原则与实现方法。这门课件包含了丰富的教学资源,不仅有详细的理论讲解,还提供了实践案例,帮助学生...

    iOS游戏应用源代码——qtechnologycompany-OrbitPad.zip

    《iOS游戏应用源代码解析——qtechnologycompany-OrbitPad》 在iOS开发领域,源代码是理解应用程序工作原理和实现技术的关键。本篇将详细探讨"qtechnologycompany-OrbitPad"游戏应用的源代码,揭示其背后的编程思想...

    iOS游戏应用源代码——mcconkiee-EMHint-b15b0aa.zip

    《深入解析iOS游戏应用源代码——mcconkiee-EMHint-b15b0aa》 在iOS开发领域,源代码的学习与分析是提升技术能力的重要途径。本篇将围绕"mcconkiee-EMHint-b15b0aa"这一特定的iOS游戏应用源代码进行深度探讨,旨在...

    iOS游戏应用源代码——tristan-SetGame-9e52c37.zip

    本篇文章将深入探讨名为“tristan-SetGame-9e52c37”的iOS游戏应用源代码,旨在揭示其背后的编程思想、技术栈以及实现机制。 首先,我们要了解的是iOS游戏开发的核心语言——Objective-C或Swift。此项目中的源代码...

    iOS游戏应用源代码——mattgemmell-MGSplitViewController.zip

    《iOS游戏应用源代码分析——基于mattgemmell-MGSplitViewController》 在iOS开发领域,源代码的分析和学习对于提升开发者的技术水平至关重要。本文将深入探讨一个名为mattgemmell-MGSplitViewController的开源项目...

    iOS游戏应用源代码——cammsaul-NAMenu-d4974a0.zip

    《iOS游戏应用源代码分析——cammsaul-NAMenu-d4974a0》 在iOS开发领域,源代码是理解应用运行机制的关键。"cammsaul-NAMenu-d4974a0....通过深入探究,不仅可以提高技术水平,还能了解优秀代码的设计思想和实现方式。

    iOS游戏应用源代码——jmullen-Brain-Game-47bc418.zip

    通过对jmullen-Brain-Game-47bc418.zip源代码的深入剖析,开发者不仅能学习到iOS游戏开发的具体技术,还能了解到整个游戏开发过程的组织架构和设计思想。通过这种方式,我们可以不断积累经验,提升自己的编程技能,...

    iOS游戏应用源代码——fastpath-FastSpace-d0831ed.zip

    【标题】"iOS游戏应用源代码——fastpath-FastSpace-d0831ed.zip" 涉及的是一个iOS游戏开发项目,其中包含了源代码。这个项目的名称为"FastPath",可能是一个以速度或者路径规划为主题的休闲游戏。版本标识为"d0831...

    iOS游戏应用源代码——andresbonilla-Balloon-Pop-Fever.zip

    《iOS游戏应用源代码分析——基于andresbonilla-Balloon-Pop-Fever》 在iOS开发领域,源代码分析是提升技术能力、学习新知识的重要途径。本篇将深入探讨名为“andresbonilla-Balloon-Pop-Fever”的游戏应用源代码,...

    iOS游戏应用源代码——azamsharp-Space-Demon-1456763.zip

    《iOS游戏应用源代码解析——azamsharp-Space-Demon-1456763》 在iOS游戏开发领域,源代码是开发者们探索、学习和创新的基础。本篇文章将深入探讨“azamsharp-Space-Demon-1456763”这款iOS游戏的源代码,揭示其...

    iOS游戏应用源代码——drewish-munchem-8f7f405.zip

    《iOS游戏应用源代码分析——基于drewish-munchem-8f7f405.zip》 在iOS开发领域,源代码是开发者们学习、探索和创新的基础。本篇文章将深入探讨名为“drewish-munchem-8f7f405”的iOS游戏应用源代码,旨在为读者...

    iOS游戏应用源代码——shawbenWiki-WarGame-f6f748f.zip

    同时,通过阅读注释和查阅相关文档,能更深入地掌握代码的设计思想和实现细节。此外,通过运行项目并调试,观察代码执行过程,有助于我们更好地理解实际运行时的情况。 总之,“shawbenWiki-WarGame-f6f748f”是一...

    iOS游戏应用源代码——rmd6502-monkey-dedc14c.zip

    《iOS游戏应用源代码解析——基于rmd6502-monkey-dedc14c.zip》 在iOS开发领域,源代码是开发者探索、学习和创新的关键资源。本篇文章将深入探讨“rmd6502-monkey-dedc14c.zip”这个压缩包中的iOS游戏应用源代码,...

    iOS游戏应用源代码——wczekalski-CDPieMenu-5bb9688.zip

    《iOS游戏应用源代码解析——基于wczekalski-CDPieMenu-5bb9688》 在iOS开发领域,源代码的学习是提升技能的关键。本篇将深入探讨一款名为wczekalski-CDPieMenu的游戏应用源代码,通过对这个项目的剖析,我们可以...

    iOS游戏应用源代码——r3econ-Noughts-and-Crosses.zip

    《iOS游戏应用源代码分析——基于r3econ-Noughts-and-Crosses》 在iOS开发领域,源代码分析是提升技术理解、学习新知识和优化应用性能的重要手段。本篇文章将深入探讨名为“r3econ-Noughts-and-Crosses”的iOS游戏...

    iOS游戏应用源代码——domesticcatsoftware-DCControls-722bb9c.zip

    《iOS游戏应用源代码解析——domesticcatsoftware-DCControls》 在iOS开发领域,源代码是理解应用程序工作原理和学习新技术的关键。本篇将详细探讨由domesticcatsoftware开发的DCControls库,该库专注于为iOS游戏...

Global site tag (gtag.js) - Google Analytics