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重构原理

raid重构原理_5个重构原理示例相关推荐

  1. 【基础技术】【adb命令】adb原理与常用命令使用示例

    目录 1 adb是什么 1.1 adb组成 1.2 adb运行原理 2 adb常用命令 2.1 adb基础语法 2.2 常用命令 2.3 模拟按键输入 2.4 无线连接 2.5 dumpsys使用 2 ...

  2. 【重构篇js案例解析重构】第一章 重构的原则

    重构的原则 重构代码示例 重构前的代码 var plays = {"hamlet": { "name": "Hamlet", "t ...

  3. 系统重构的原则代码重构的原则

    作者:[美]马丁•福勒(Martin Fowler) 译者:熊节, 林从羽 前一章所举的例子应该已经让你对重构有了一个良好的感觉.现在,我们应该回头看看重构的一些大原则. ##2.1 何谓重构 一线的 ...

  4. .NET重构—单元测试的代码重构

    阅读目录: 1.开篇介绍 2.单元测试.测试用例代码重复问题(大量使用重复的Mock对象及测试数据) 2.1.单元测试的继承体系(利用超类来减少Mock对象的使用) 2.1.1.公用的MOCK对象: ...

  5. python装饰器原理-Python装饰器原理

    装饰器(Decorator)是面向对象设计模式的一种,这种模式的核心思想是在不改变原来核心业务逻辑代码的情况下,对函数或类对象进行额外的修饰.python中的装饰器由python解释器直接支持,其定义 ...

  6. 代码重构(二):类重构规则

    在上篇博客<代码重构(一):函数重构规则(Swift版)>中,详细的介绍了函数的重构规则,其中主要包括:Extract Method, Inline Method, Inline Temp ...

  7. 第十四课 k8s源码学习和二次开发原理篇-调度器原理

    第十四课 k8s源码学习和二次开发原理篇-调度器原理 tags: k8s 源码学习 categories: 源码学习 二次开发 文章目录 第十四课 k8s源码学习和二次开发原理篇-调度器原理 第一节 ...

  8. 【3D视觉原理】2-3D传感器原理

    文章目录 内容概要 3D光学测量方法概述 被动测距 单目立体视觉(精度不高) 聚焦法 离焦法 双目立体视觉(精准) 多目立体视觉(更精准) 主动测距 结构光法 光点法 光条法 光面法 飞行时间法(To ...

  9. 代码重构(一):函数重构规则

    [笔记] 以下是通用的代码重构规则 python代码重构技巧看这里:Python重构代码的一些模式 ========================== 重构是项目做到 一定程度后必然要做的事情.代码 ...

最新文章

  1. 矩阵的卷积核运算(一个简单小例子的讲解)深度学习
  2. 在Linux下gcc缺省编译,在Linux下GCC将文件合起来编译
  3. 洛谷——P3807 【模板】卢卡斯定理
  4. 关于Android Force Close 出现的原因 以及解决方法
  5. ngrx中的memoized函数
  6. 一文告诉你 Java RMI 和 RPC 的区别
  7. Python 之 进程
  8. linux内核怎么修改屏幕旋转方向_运维必备:常见的Linux系统故障及其排查的方法...
  9. Leetcode:convert_sorted_array_to_binary_search_tree
  10. Ext JS学习第二天 我们所熟悉的javascript(一)
  11. 存储虚拟化技术之解读
  12. 令程序员们夜不能寐的“噩梦”除了改需求,还有这些…...
  13. SHA256算法详解及python实现
  14. 卡诺模型(用户需求分析模型)
  15. gitgub利用客户端实现简单的上传和同步
  16. 大数据私房菜--Hadoop完全分布式安装
  17. uc3854 matlab仿真,基于UC3854硬开关PFC变换电路设计课程设计.doc
  18. oracle清除temp表空间,Temp表空间占用长时间不释放,是谁惹的祸
  19. 福特汉姆大学计算机科学专业,福特汉姆大学计算机研究生
  20. 不得不佩服下自己:关于正反双面打印的问题分析

热门文章

  1. C - Insertion Sort Gym - 101955C
  2. Strategic game(树的最小点覆盖)
  3. Function Query(树状数组)
  4. 盲盒(随机概率 + 最大公约数)
  5. 牛客IOI周赛26-提高组(逆序对,对序列,未曾设想的道路) 题解
  6. NOIP2012:疫情控制(二分、贪心、树上倍增)
  7. Loj#2769-「ROI 2017 Day 1」前往大都会【最短路树,斜率优化】
  8. CF396B-On Sum of Fractions【数学】
  9. P5091-[模板]欧拉定理
  10. 【树链剖分】LCA(P4211)