raid重构原理_5个重构原理示例

raid重构原理

这篇文章介绍了重构真正的开源代码( 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

raid重构原理

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

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

相关文章

extjs ajax 遮罩层,[Ext JS 4] 实战之Load Mask(加载遮罩)的显示与隐藏

前言Load Mask(遮罩)效果&#xff0c;就是在页面还没有完全显示出来之前&#xff0c; 加上一个转装转的效果。类似&#xff1a;添加这样的效果有两个好处&#xff1a;1. 在页面没完全show出来之前&#xff0c; 把后面的页面给遮罩起来&#xff0c; 防止进行一些非法的操作。2. …

macOS下卸载文件系统_卸载移动硬盘_卸载U盘_推出移动硬盘_推出U盘

先使用命令 df -lh 在终端查看当前系统的所有挂载的文件系统&#xff08;系统硬盘、移动硬盘、U盘等&#xff09;&#xff0c;命令如下&#xff1a; liaowenxiongliaowenxiongdeMacBook-Air ~ % df -h Filesystem Size Used Avail Capacity iused ifree %iused …

python isalnum函数_Python 字符串 (isdigit, isalnum,isnumeric)转

Python isdigit() 方法检测字符串是否只由数字组成。 语法 isdigit()方法语法&#xff1a; str.isdigit() 参数 无。 返回值 如果字符串只包含数字则返回 True 否则返回 False。 Python isdecimal() 方法检查字符串是否只包含十进制字符。这种方法只存在于unicode对象。 注意:定…

zing jvm_Zing加快了JVM应用程序的预热

zing jvmJava虚拟机&#xff08;JVM&#xff09;提供了托管运行时环境&#xff0c;用于安全部署应用程序&#xff0c;其性能通常可以超过本机编译语言&#xff08;如C和C &#xff09;。 通过即时&#xff08;JIT&#xff09;编译进行垃圾回收和自适应编译的内存管理是两个最突…

黎明觉醒测试服服务器维护怎么办,黎明觉醒测试资格进不去怎么办

黎明觉醒测试资格进不去怎么办&#xff1f;黎明觉醒游戏在在9月16日迎来曙光测试&#xff0c;相信不少玩家都去玩了发现自己进不去游戏&#xff0c;这是怎么回事呢&#xff1f;和小编一起来看看吧。黎明觉醒测试资格进不去怎么办一、测试资格进不去获得测试资格的玩家官方已提前…

查看Linux命令_搜索Linux命令_查找Linux命令

站点1&#xff1a;https://tool.lu/command/ 站点2&#xff1a;https://www.linuxcool.com/

蜂鸣器音乐代码 天空之城_潮玩 | 艺术展览,乐队live现场,网红小黑泥,贩卖“美好”的市集……一场未来公共生活,天空之城和你一起探索!...

第一次打卡这样的新媒体艺术作品&#xff0c;不是画作&#xff0c;也不是艺术品陈列&#xff0c;而是一场看的见的引力交响曲~错落的磁场具象成看得见的流动痕迹&#xff0c;不动声响却震撼的感官体验。很容易让人沉浸其中&#xff0c;去捕捉流动的方向和瞬间。虽然UFO是没看到…

自动部署 管道 ci cd_自动化测试在CI CD管道中的作用

自动部署 管道 ci cd业界广泛采用的软件开发实践&#xff1a;持续集成和持续部署可确保良好地交付产品并经常交付。 常规代码提交需要常规/连续测试&#xff0c;而如果忽略它&#xff0c;则可能导致非弹性基础结构。 如何交付坚固的CI CD管道&#xff1f; 对于许多公司来说&…

Maven的maven-install-plugin插件详解

文章目录将下载到本地的jar文件安装到Maven本地仓库中将当前项目安装到Maven本地仓库中参考maven-install-plugin 插件的作用&#xff1a;1.可以将当前项目安装到 Maven 本地仓库&#xff0c;供本地其它 Maven 项目使用 执行 mvn install 命令将当前项目安装到 Maven 本地仓库…

python字典_Python 字典

1、什么是 dict(字典)上一章节&#xff0c;我们学习了列表(List) 和 元组(tuple) 来表示有序集合。而我们在讲列表(list)的时候&#xff0c;我们用了列表(list) 来存储用户的姓名。name [一点水, 两点水, 三点水, 四点水, 五点水]那么如果我们为了方便联系这些童鞋&#xff0c…

hazelcast_Java:如何在不到5分钟的时间内通过Hazelcast提高生产力

hazelcast如果要使用Hazelcast内存数据网格&#xff08;IMDG&#xff09;来加快数据库应用程序的速度&#xff0c;但是要处理数百个表怎么办&#xff1f; 手动编码所有Java POJO和序列化支持将需要数周的工作&#xff0c;完成后&#xff0c;手动维护该域模型将很快成为一场噩梦…

Maven的maven-help-plugin插件详解

执行下面的命令查看指定插件的详细信息&#xff1a; [~/Documents/IdeaProjects/demo02]$ mvn help:describe -Dpluginorg.apache.maven.plugins:maven-site-plugin:3.9.0 -Ddetail上述命令执行的是 maven-help-plugin 的 describe 目标&#xff0c;在参数 plugin 中输入需要描…

android官方文档中文版_最全实至名归,NumPy 官方早有中文教程,结合深度学习,还有防脱发指南...

点击 机器学习算法与Python学习 &#xff0c;选择加星标精彩内容不迷路本文转自机器之心在 Github 上一度蝉联最流行的机器学习和数据科学包 NumPy&#xff0c;已经有了非常之系统的中文文档&#xff0c;回想起当初细啃 NumPy 之时&#xff0c;不少人不得不徘徊于各大搜索引擎及…

tls jdk_使用JDK 13查看TLS配置

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

执行Plugins下的install:install报错:The packaging for this project did not assign a file to the build artif

文章目录导致错误的原因分析解决方法直接执行生命周期的某个阶段命令阶段和插件目标一起执行参考导致错误的原因分析 在 IDEA 中使用 Plugins 下的 install:install &#xff08;或者在命令行下执行命令 mvn install:install&#xff09;&#xff0c;代表执行的是 install 插件…

javascript等待异步线程完成_前端:什么是单线程,同步,异步?彻底弄懂 JavaScript 执行机制...

javascript是按照语句出现的顺序执行的。js是一行一行执行的&#xff1a;let a 1;console.log(a);let b 2;console.log(b);然而实际上js是这样的&#xff1a;setTimeout(function(){ console.log(定时器开始啦)});new Promise(function(resolve){ console.log(马上执行for循环…

react回调_回调地狱和React模式

react回调我可以更好地了解a的用途的一种方式 基于React流的方法是它简化了无阻塞IO调用的方式。 这篇文章将快速讲解进行同步远程调用所涉及的那种代码&#xff0c;然后说明如何在非阻塞IO中分层&#xff0c;尽管在资源&#xff08;尤其是线程&#xff09;的使用方面非常高效…

Maven插件列表_Maven插件查询_Maven插件查看

Maven 官方插件列表&#xff1a;https://maven.apache.org/plugins/index.html。 Maven 官方插件下载地址&#xff1a;https://repo1.maven.org/maven2/org/apache/maven/plugins/

jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2

喜欢排队吧&#xff0c;它能保护你的时间和精力 - 排队纪律维护员Event LoopPromise和事件循环概览图请注意上面这张图&#xff0c;Promise和事件循环的那些事&#xff0c;将在这个图上缓缓展开。微任务和(宏)任务好了&#xff0c;(经过上一节对Promise的理解)现在我们对如何创…

azure api 管理_具有Azure功能的无服务器API

azure api 管理在这篇文章中&#xff0c;我将研究一个非常简单的用例。 在执行部署管道时&#xff0c; FlexDeploy可能会产生一些应被批准或拒绝的人工任务。 例如&#xff0c;某人必须批准对生产环境的部署。 可以在FlexDeploy UI中或通过某些外部通信渠道来完成。 今天&#…