5个重构原理示例

这篇文章介绍了重构真正的开源代码( Gradle Modules Plugin )时应用的五​​种(最著名的)重构原理。

语境

当我为Gradle Modules Plugin (PR #73 ) 单独编译 module-info.java ,我注意到了一些重构的潜力。 结果,我提交了问题#79 ,后来又通过PR #88 (尚未合并)解决了该问题,在其中重构了代码。

事实证明,重构比我最初想象的要广泛得多。 在这里,我介绍此PR的一部分,作为我在此处应用的重构原则的示例。

重构原理

注意:这里列出的列表绝不是全面的,并且原则不是原创的(不过,我以自己的声音并根据自己的理解提出了这些原则)。 正如我所看到的,这篇文章的最大价值在于遵循这些原则的真实示例。

这里介绍的五项原则是:

  1. 用“什么”隐藏“如何”
  2. 力求一致性
  3. 避免深层嵌套
  4. 单独的关注点(=单一责任原则)
  5. 明智地避免重复(=不要重复自己)

1.用“什么”隐藏“如何”

该原则只是由Robert Martin提出的“ 干净代码”原则的一部分。

对我来说,用“什么”隐藏“如何”意味着在任何时候提取类和方法

  • 我可以识别出由一段代码执行的独特,不平凡的功能,并且
  • 我可以用一个有意义的名称将这种不琐碎的事情隐藏起来。

示例1:

重构之前,这是RunTaskMutator的一个片段:

 mainDistribution.contents(copySpec -> copySpec.filesMatching(patchModuleExtension.getJars(), action -> { RelativePath relativePath = action.getRelativePath().getParent().getParent() .append( true , "patchlibs" , action.getName()); action.setRelativePath(relativePath);  })); 

这是重构后的代码段:

 mainDistribution.contents( copySpec -> copySpec.filesMatching(patchModuleExtension.getJars(), this ::updateRelativePath)  ); 

综上所述,我们:

  • 隐藏如何更新相对路径
  • 与我们有什么 (=我们更新它的事实)。

由于有了这样的重构,掌握mainDistribution发生的事情要容易mainDistribution

作为参考, 这里提供了updateRelativePath的内容。

示例2:

这是重构之前TestTask类的一部分的样子:

 TestEngine.select(project).ifPresent(testEngine -> { args.addAll(List.of( "--add-reads" , moduleName + "=" + testEngine.moduleName)); Set<File> testDirs = testSourceSet.getOutput().getClassesDirs().getFiles(); getPackages(testDirs).forEach(p -> { args.add( "--add-opens" ); args.add(String.format( "%s/%s=%s" , moduleName, p, testEngine.addOpens)); });  }); 

如下所示:

 TestEngine.select(project).ifPresent(testEngine -> Stream.concat( buildAddReadsStream(testEngine), buildAddOpensStream(testEngine)  ).forEach(jvmArgs::add)); 

同样,我们:

  • 隐藏如何 --add-reads--add-opens选项的值
  • 与我们有什么 (=我们指定它们的事实)。

作为参考,可在此处获得buildAddReadsStreambuildAddOpensStream的内容。

2.追求一致性

这很笼统,但是我的意思是我们可以获得任何合理的一致性。

例如, 唐纳德·拉布 ( Donald Raab ) 关于对称的博客文章就是努力保持一致性的一个很好的例子。 不用说,我完全同意他的结论:

具有对称性的大型系统变得更容易理解,因为您可以检测并期望重复出现的模式。

Donald Raab,对称的同情

对于Gradle Modules Plugin,这主要归结为提取AbstractModulePluginTask基类并统一任务查找和配置调度过程。

例如,重构之前的JavadocTaskTestTask是:

 public class JavadocTask { public void configureJavaDoc(Project project) { Javadoc javadoc = (Javadoc) project.getTasks().findByName(JavaPlugin.JAVADOC_TASK_NAME); if (javadoc != null ) { // ... } }  }  public class TestTask { public void configureTestJava(Project project, String moduleName) { Test testJava = (Test) project.getTasks().findByName(JavaPlugin.TEST_TASK_NAME); // ... (no null check) }  } 

之后,它们是:

 public class JavadocTask extends AbstractModulePluginTask { public void configureJavaDoc() { helper().findTask(JavaPlugin.JAVADOC_TASK_NAME, Javadoc. class ) .ifPresent( this ::configureJavaDoc); } private void configureJavaDoc(Javadoc javadoc) { /* ... */ }  }  public class TestTask extends AbstractModulePluginTask { public void configureTestJava() { helper().findTask(JavaPlugin.TEST_TASK_NAME, Test. class ) .ifPresent( this ::configureTestJava); } private void configureTestJava(Test testJava) { /* ... */ }  } 

供参考: JavaDocTask diff和TestTask diff 。

3.避免深度嵌套

我想这很明显。 对我而言,控制结构的深层嵌套非常难以阅读和掌握。

结果,我重构了以下getPackages方法:

 private static Set<String> getPackages(Collection<File> dirs) { Set<String> packages = new TreeSet<>(); for (File dir : dirs) { if (dir.isDirectory()) { Path dirPath = dir.toPath(); try (Stream<Path> entries = Files.walk(dirPath)) { entries.forEach(entry -> { if (entry.toFile().isFile()) { String path = entry.toString(); if (isValidClassFileReference(path)) { Path relPath = dirPath.relativize(entry.getParent()); packages.add(relPath.toString().replace(File.separatorChar, '.' )); } } }); } catch (IOException e) { throw new GradleException( "Failed to scan " + dir, e); } } } return packages;  } 

如下所示:

 private static Set<String> getPackages(Collection<File> dirs) { return dirs.stream() .map(File::toPath) .filter(Files::isDirectory) .flatMap(TestTask::buildRelativePathStream) .map(relPath -> relPath.toString().replace(File.separatorChar, '.' )) .collect(Collectors.toCollection(TreeSet:: new ));  }  private static Stream<Path> buildRelativePathStream(Path dir) { try { return Files.walk(dir) .filter(Files::isRegularFile) .filter(path -> isValidClassFileReference(path.toString())) .map(path -> dir.relativize(path.getParent())); } catch (IOException e) { throw new GradleException( "Failed to scan " + dir, e); }  } 

可在此处找到完整的差异 。

4.单独的问题

SRP( 单一职责原则 )是众所周知的软件设计原则。 在这里,我们可以看到其在从RunTaskMutator中提取StartScriptsMutator应用程序。

之前:

 public class RunTaskMutator { // common fields public void configureRun() { /* ... */ } public void updateStartScriptsTask(String taskStartScriptsName) { /* ... */ } // 12 other methods (incl. 2 common methods)  } 

后:

 public class RunTaskMutator extends AbstractExecutionMutator { public void configureRun() { /* ... */ }   // 2 other methods  }  public class StartScriptsMutator extends AbstractExecutionMutator { public void updateStartScriptsTask(String taskStartScriptsName) { /* ... */ } // 8 other methods  } 

由于提取了StartScriptsMutator ,因此更容易理解以下范围:

  • 本身配置run任务,
  • 配置相关的startScripts任务。

供参考:以上提取的提交 。

5.明智地避免重复

DRY( 请勿重复 )是另一种著名的软件开发原理。 但是,以我的经验,这个原则有时太过复杂,导致代码无法重复,但是也太复杂了。

换句话说,只有在成本/收益比为正时,才应该重复数据删除:

  • 成本 :重构时间,产生的复杂性等
  • 获得 :没有重复(或更严格地说,是唯一的真理来源 )。

Gradle Modules Plugin中的一个这样的例子(在我看来,成本/收益比接近零,但仍然为正)是PatchModuleResolver的引入。

下面是重构的代码片段其中包括:

  1. PatchModuleExtension.configure方法。
  2. 使用它的地方( TestTask )。
  3. 无法使用的地方( RunTaskMutator )。
  4. 不能使用它的另一个地方( JavadocTask )。
 // 1. PatchModuleExtension  public List<String> configure(FileCollection classpath) { List<String> args = new ArrayList<>(); config.forEach(patch -> { String[] split = patch.split( "=" ); String asPath = classpath.filter(jar -> jar.getName().endsWith(split[ 1 ])).getAsPath(); if (asPath.length() > 0 ) { args.add( "--patch-module" ); args.add(split[ 0 ] + "=" + asPath); } } ); return args;  }  // 2. TestTask  args.addAll(patchModuleExtension.configure(testJava.getClasspath()));  // 3. RunTaskMutator  patchModuleExtension.getConfig().forEach(patch -> { String[] split = patch.split( "=" ); jvmArgs.add( "--patch-module" ); jvmArgs.add(split[ 0 ] + "=" + PATCH_LIBS_PLACEHOLDER + "/" + split[ 1 ]); }  );  // 4. JavadocTask  patchModuleExtension.getConfig().forEach(patch -> { String[] split = patch.split( "=" ); String asPath = javadoc.getClasspath().filter(jar -> jar.getName().endsWith(split[ 1 ])).getAsPath(); if (asPath != null && asPath.length() > 0 ) { options.addStringOption( "-patch-module" , split[ 0 ] + "=" + asPath); } }  ); 

引入PatchModuleResolver ,代码如下所示:

 // 1. PatchModuleExtension  public PatchModuleResolver resolve(FileCollection classpath) { return resolve(jarName -> classpath.filter(jar -> jar.getName().endsWith(jarName)).getAsPath());  }  public PatchModuleResolver resolve(UnaryOperator<String> jarNameResolver) { return new PatchModuleResolver( this , jarNameResolver);  }  // 2. TestTask  patchModuleExtension.resolve(testJava.getClasspath()).toArgumentStream().forEach(jvmArgs::add);  // 3. RunTaskMutator  patchModuleExtension.resolve(jarName -> PATCH_LIBS_PLACEHOLDER + "/" + jarName).toArgumentStream().forEach(jvmArgs::add);  // 4. JavadocTask  patchModuleExtension.resolve(javadoc.getClasspath()).toValueStream() .forEach(value -> options.addStringOption( "-patch-module" , value)); 

多亏了重构,现在只有一个地方( PatchModuleResolver ),我们在其中分割了PatchModuleExtension类的config条目。

供参考:DIFFS 1 , 2 , 3 , 4 。

摘要

在这篇文章中,我介绍了以下五个重构原则:

  1. 用“什么”隐藏“如何”
  2. 力求一致性
  3. 避免深层嵌套
  4. 单独关注
  5. 明智地避免重复

每个原则都附有一个真实的示例,希望该示例显示了遵循该原则如何产生简洁的代码。

翻译自: https://www.javacodegeeks.com/2019/05/5-refactoring-principles-example.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/343335.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【渝粤教育】国家开放大学2019年春季 0134-22T劳动法学 参考试题

试卷编号&#xff1a;0134 期末考 劳动法学试题答案 1、劳动法的调整对象 2、竞业限制 3&#xff0e;职工培训 4&#xff0e;行政责任 二、单项选择题&#xff08;每题4分&#xff0c;共20分&#xff09; 考生注意&#xff1a;必须将正确答案填入表格中&#xff0c;否则该…

POE工业交换机的四种接法详解

PoE工业交换机具有电信级性能特征&#xff0c;可耐受严苛的工作环境。PoE交换机产品系列丰富&#xff0c;端口配置灵活&#xff0c;可满足各种工业领域的使用需求。那么&#xff0c;POE工业交换机该怎么接线呢&#xff1f;接下来就由飞畅科技的小编来为大家详细介绍下POE交换机…

【渝粤教育】国家开放大学2019年春季 0691-22T物理化学及实验 参考试题

科目编号&#xff1a;0691 座位号&#xff1a; 2018-2019学年度第二学期期末考试 物理化学及实验试题 2019年7月 一、填空题&#xff08;每空4分&#xff0c;共40分&#xff09; 1、熵与热力学概率之间的函数关系式是 。 2、补全热力学函数关系式&#xff1a;CP (S/T)P 3、一…

线程本地分配缓冲区

最近&#xff0c;我一直在研究遭受严重性能问题的Java应用程序。 在许多问题中&#xff0c;真正引起我注意的一个问题是新对象的分配速率相对较低&#xff08;应用程序分配了大量的相当大的对象&#xff09;。 后来发现&#xff0c;原因是在TLAB之外发生了大量分配。 什么是TL…

java发邮件无主题,使用SpringCloud过程中遇到的一些问题

对SpringCloud做一次简单的问题总结。application.yml 和 bootstrap.yml 有何区别&#xff1f;I have just asked the Spring Cloud guys and thought I should share the info I have here.bootstrap.yml is loaded before application.yml.It is typically used for the foll…

【渝粤教育】国家开放大学2019年春季 1018国际公法 参考试题

试卷代号&#xff1a;1018 2019年春季学期期末统一考试 国际公法试题 2019年7月 一、单项选择题&#xff08;每题1分&#xff0c;共10分。每题只有一项答案正确&#xff0c;请将正确答案的序号填在括号内&#xff09; 1.下列哪位法学家最早将西方国际法著作翻译成中文&#xff…

一号信令是什么?1号信令和7号信令的区别介绍!

1号信令又称为多频互控信令或随路信令。那么&#xff0c;什么是一号信令&#xff1f;一号信令是怎么分类的&#xff1f;1号信令常见问题有哪些&#xff1f;1号信令和7号信令之间有哪些区别呢&#xff1f;接下来我们就跟随飞畅科技的小编一起来详细了解下吧&#xff01; 一、1号…

【渝粤教育】国家开放大学2019年春季 1124流行病学 参考试题

试卷代号&#xff1a;1124 2019年春季学期期末统一考试 流行病学试题&#xff08;开卷&#xff09; 2019年7月 一、单项选择题&#xff08;每题2分&#xff0c;共20分&#xff09; 1&#xff0e;下列哪一个不是流行病学的特征() A.群体特征B&#xff0e;以分布为起点的特征 C&a…

matlab lstm工具箱,深度学习工具箱使用笔记---lstm网络(1)

matlab 深度学习工具箱使用笔记—lstm网络在2017的版本之后&#xff0c;matlab上线了自己的lstm网络工具箱&#xff0c;至此&#xff0c;搭建简单的网络时&#xff0c;就可以只用工具包所提供的函数&#xff0c;并且matlab提供了GUI和训练过程界面&#xff0c;可以方便的使用&a…

RS232、RS485和CAN协议总结与对比

RS232简单实用&#xff0c;缺陷是不支持多设备间的互连&#xff0c;缺少拓扑结构。由此诞生了RS485。RS485最重要的是采用两条差分线代替RS232的单线传输&#xff0c;支持拓扑结构。RS485属于电气层的协议&#xff0c;物理上的实现大都在RS232基础上完成。缺陷是主从轮询的方式…

【渝粤教育】国家开放大学2019年春季 1260软件工程 参考试题

试卷代号&#xff1a;1260 软件工程 试题&#xff08;半开卷&#xff09; 2019年7月 一、选择题&#xff0c;请从四个可选项中选择正确答案。&#xff08;60分&#xff0c;每题3分&#xff09; 1.以下哪一项不是软件危机的表现形式&#xff08; &#xff09;。 A.成本高 B.生产…

【渝粤教育】国家开放大学2019年春季 1362应用语言学 参考试题

试卷代号&#xff1a;1362 应用语言学 试题 2019年7月 注意事项 一、将你的学号、姓名及分校&#xff08;工作站&#xff09;名称填写在答题纸的规定栏 内。考试结束后&#xff0c;把试卷和答题纸放在桌上。试卷和答题纸均不得带 出考场。 二、仔细阅读题目的说明&#xff0c;并…

maven的中央存储库_部署到Maven中央存储库

maven的中央存储库您需要使您的Java库可公开访问吗&#xff1f; 您的项目托管在GitHub上吗&#xff1f; 您是否喜欢“将所有功能都部署到Maven Central Repository”按钮的想法&#xff1f; 我将展示如何使用maven-release-plugin进行设置 。 源代码托管在GitHub上&#xff0c;…

php 回到顶部,jquery如何实现点击网页回到顶部效果?(图文+视频)

本篇文章主要给大家介绍如何用jquery代码实现网页回到顶部的效果。我们在浏览各大网站页面时&#xff0c;想必大家肯定都遇到过&#xff0c;当阅览一个长页面时&#xff0c;拉到下面部分会出现类似回到顶部的按钮特效吧。这种点击回到顶部的功能特效&#xff0c;可以很大程度上…

【渝粤教育】国家开放大学2019年春季 2114人体解剖生理学 参考试题

试卷代号&#xff1a;2114 人体解剖生理学 试题 2019年7月 一、单项选择题&#xff08;每题2分&#xff0c;共80分&#xff09; 1.上皮组织的特点不包括&#xff08; &#xff09;。 A.包括被覆上皮和腺上皮 B.分布于体表及有腔器官的腔面 C.含丰富血管、神经 D.具有保护作用 E…

rs485中继器产品功能特点及应用领域介绍

中继器是连接网络线路的一种装置&#xff0c;常用于两个网络节点之间物理信号的双向转发工作。rs485/422中继器是最简单的网络互联设备&#xff0c;主要完成物理层的功能&#xff0c;负责在两个节点的物理层上按位传递信息&#xff0c;完成信号的复制、调整和放大功能&#xff…

【渝粤教育】国家开放大学2019年春季 2441经济数学基础1 参考试题

试卷代号&#xff1a;2441 2 0 1 9年春季学期期末统一考试 经济数学基础1 试题 2019年7月 导数基本公式&#xff1a; 积分基本公式&#xff1a; ( 一、单项选择题&#xff08;每小题4分&#xff0c;本题共20分&#xff09; 1&#xff0e;下列函数中为奇函数的是( )&#xff0…

使用JDK 13查看TLS配置

JDK 13 Early Access Build 16现在可用&#xff0c;它带来的有趣的功能之一是能够使keytool命令行工具显示当前系统的TLS配置信息 。 这比尝试在单独的文档中查找受支持的TLS信息并使该信息与自己的JDK供应商和版本更容易。 要查看JDK 13 Early Access Build 16的TLS配置详细信…

php只显示一部分文章,typecho同一个页面下调用不同分类的文章但是却只显示一个分类文章...

typecho同一个页面下调用不同分类的文章但是却只显示一个分类文章作者&#xff1a;佚名来源&#xff1a;爱好者时间&#xff1a;2018-04-30问题描述&#xff1a;同页面调用分类下文章&#xff0c;只显示一第一个分类下的文章在一个页面中&#xff0c;反复调用下面这段代码&…

串口服务器常见异常情况排除方法介绍

串口服务器就像一台带CPU、实时操作系统和TCP/IP协议的微型电脑&#xff0c;方便在串口和网络设备中传输数据。在使用串口服务器的过程中&#xff0c;一般按照操作手册进行操作基本上可以解决问题&#xff0c;但是&#xff0c;在实际操作中还是会出现一些异常故障&#xff0c;今…