- 浏览: 1311 次
- 性别:
- 来自: 广州
文章分类
最新评论
代码——你的思想能走多远
相信每一位从事开发的同行,都深有感受,一段代码,可识其人,识其品性,识其思想,识其修为。然而,在快餐式代码兴行的今天,越来越多的人开始迷失,盲目追求简洁的代码,抛弃设计,抛弃思想,直至走火入魔,言必排斥复杂。
没错,设计的境界,应当简洁。可是,不应走火入魔。知其一,不知其三,盲目舍弃,却完全不懂其原理,只会显得肤浅。
我们应该追求的,不是代码的结果,而是其中的思想。
真正的代码,是可以不写,但不可以没有思想。我从不浪费时间重构我的代码,但我绝对知道如何重构自己的代码。
追求境界,不应该是追求几十万几百万行的代码量,而是看重内功,看你的重构思想究竟有多远。点到,即止,又何须真的写代码?
我尝试用一个简单的实际例子来说明一下,究竟什么才是设计,什么才是简洁,什么才是,代码
一个简单的应用场景,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++的基本语法,还能掌握面向对象编程思想,并能够利用STL和其他高级特性编写高效、可靠的代码。同时,这些实例也是解决实际问题的起点,有助于培养学习者的编程思维和...
本篇文章将针对"iOS游戏应用源代码——ArcadaiOs-DrinkingGame-d70487a.zip"进行详细的探讨,旨在揭示游戏背后的编程技术与设计思想。 首先,我们来看一下这个项目的基本结构。"ArcadaiOs-DrinkingGame-d70487a"这...
《C++程序设计教程——设计思想与实现》是一门深入探讨C++编程语言的课程,旨在教授学生如何理解和运用C++的设计原则与实现方法。这门课件包含了丰富的教学资源,不仅有详细的理论讲解,还提供了实践案例,帮助学生...
《iOS游戏应用源代码解析——qtechnologycompany-OrbitPad》 在iOS开发领域,源代码是理解应用程序工作原理和实现技术的关键。本篇将详细探讨"qtechnologycompany-OrbitPad"游戏应用的源代码,揭示其背后的编程思想...
《深入解析iOS游戏应用源代码——mcconkiee-EMHint-b15b0aa》 在iOS开发领域,源代码的学习与分析是提升技术能力的重要途径。本篇将围绕"mcconkiee-EMHint-b15b0aa"这一特定的iOS游戏应用源代码进行深度探讨,旨在...
本篇文章将深入探讨名为“tristan-SetGame-9e52c37”的iOS游戏应用源代码,旨在揭示其背后的编程思想、技术栈以及实现机制。 首先,我们要了解的是iOS游戏开发的核心语言——Objective-C或Swift。此项目中的源代码...
《iOS游戏应用源代码分析——基于mattgemmell-MGSplitViewController》 在iOS开发领域,源代码的分析和学习对于提升开发者的技术水平至关重要。本文将深入探讨一个名为mattgemmell-MGSplitViewController的开源项目...
《iOS游戏应用源代码分析——cammsaul-NAMenu-d4974a0》 在iOS开发领域,源代码是理解应用运行机制的关键。"cammsaul-NAMenu-d4974a0....通过深入探究,不仅可以提高技术水平,还能了解优秀代码的设计思想和实现方式。
通过对jmullen-Brain-Game-47bc418.zip源代码的深入剖析,开发者不仅能学习到iOS游戏开发的具体技术,还能了解到整个游戏开发过程的组织架构和设计思想。通过这种方式,我们可以不断积累经验,提升自己的编程技能,...
【标题】"iOS游戏应用源代码——fastpath-FastSpace-d0831ed.zip" 涉及的是一个iOS游戏开发项目,其中包含了源代码。这个项目的名称为"FastPath",可能是一个以速度或者路径规划为主题的休闲游戏。版本标识为"d0831...
《iOS游戏应用源代码分析——基于andresbonilla-Balloon-Pop-Fever》 在iOS开发领域,源代码分析是提升技术能力、学习新知识的重要途径。本篇将深入探讨名为“andresbonilla-Balloon-Pop-Fever”的游戏应用源代码,...
《iOS游戏应用源代码解析——azamsharp-Space-Demon-1456763》 在iOS游戏开发领域,源代码是开发者们探索、学习和创新的基础。本篇文章将深入探讨“azamsharp-Space-Demon-1456763”这款iOS游戏的源代码,揭示其...
《iOS游戏应用源代码分析——基于drewish-munchem-8f7f405.zip》 在iOS开发领域,源代码是开发者们学习、探索和创新的基础。本篇文章将深入探讨名为“drewish-munchem-8f7f405”的iOS游戏应用源代码,旨在为读者...
同时,通过阅读注释和查阅相关文档,能更深入地掌握代码的设计思想和实现细节。此外,通过运行项目并调试,观察代码执行过程,有助于我们更好地理解实际运行时的情况。 总之,“shawbenWiki-WarGame-f6f748f”是一...
《iOS游戏应用源代码解析——基于rmd6502-monkey-dedc14c.zip》 在iOS开发领域,源代码是开发者探索、学习和创新的关键资源。本篇文章将深入探讨“rmd6502-monkey-dedc14c.zip”这个压缩包中的iOS游戏应用源代码,...
《iOS游戏应用源代码解析——基于wczekalski-CDPieMenu-5bb9688》 在iOS开发领域,源代码的学习是提升技能的关键。本篇将深入探讨一款名为wczekalski-CDPieMenu的游戏应用源代码,通过对这个项目的剖析,我们可以...
《iOS游戏应用源代码分析——基于r3econ-Noughts-and-Crosses》 在iOS开发领域,源代码分析是提升技术理解、学习新知识和优化应用性能的重要手段。本篇文章将深入探讨名为“r3econ-Noughts-and-Crosses”的iOS游戏...
《iOS游戏应用源代码解析——domesticcatsoftware-DCControls》 在iOS开发领域,源代码是理解应用程序工作原理和学习新技术的关键。本篇将详细探讨由domesticcatsoftware开发的DCControls库,该库专注于为iOS游戏...