重构一个功能块的总结

简介: 因为Leader不建议占用上班时间搞这个,基本上都是加班搞的 在做这个事的过程中,对IntelliJ idea更加熟悉,回顾下Mocikitto中spy,mock,verify,when的使用,这次重构基本上是小步快走,层层分离 IntelliiJ idea:重构手法:如果想把一个方法移动到另一...

因为Leader不建议占用上班时间搞这个,基本上都是加班搞的

在做这个事的过程中,对IntelliJ idea更加熟悉,回顾下Mocikitto中spy,mock,verify,when的使用,这次重构基本上是小步快走,层层分离

IntelliiJ idea:
重构手法:如果想把一个方法移动到另一个class中,可以通过method增加一个Class类型的参数,然后将光标放在方法名的任何字符上【若选中方法名,则F6会失效】,按F6,在弹出的对话框中选择预期的Class【因为这个方法的参数是其它类型的对象】

更改方法的参数个数时,可以会调用处更改,从上往下进行,或按Ctrl+F6在调用的GUI界面操作。IntelliJ Idea的自动操作往往留一些尾巴,譬如,删除一个构造函数的一个参数,参数赋值的字段和构造中赋值语句不会自动删除。

如果方法参数有多个,且参数类型差别不大,IntelliJ idea alt+Enter提供的修改可能是错误的,譬如你删除了第5个参数,Idea可能会把第4个参数删除了,然后更改实际函数中的参数名,造成参数错位,就会编译报错,这种情况建议使用Ctrl+F6的GUi界面来操作。

把一个方法通过F6移动到另一个Class时,Move操作成功后,移过去的方法如果使用了原来Class中的字段,就会编译不过。这种情况不用担心,一般的步骤是先移动,然后解决编译报错。

移动方法时,方法增加的Class一般不选择抽象方法,这样会把方法移动到抽象方法中,正确的做法是,增加的类型参数是预期的抽象类的子类,这样就可以了

 

在建工厂类或接口时,可以先写Class Name,然后使用Idea的快捷键来创建Class,Interface,Method,Field

去除if else 的常用方法是每个分支就是一个对象,然后实现相同的接口;把if else分支的判断逻辑放到Factory类,这样代码就清净了。
扩展性和可维护性都增加了,扩展性:如果增加或减少一种场景,只修改工厂类或增加或删除新类即可,与此不相关的场景不会影响 ,不用知道其它情景的实现,也不用重新测试这些没有涉及的场景。
并且 业务逻辑和每个场景都可以独立写测试用例。如果有重复代码,不仅看得见,更能去得掉。重复代码抽取到父类中即可。也可以定义一个接口,抽象类来实现,业务类再继承抽象类。重复的字段或方法放在抽象类中。

Idea中的快捷键:
Ctrl+Alt+F12:打开某个package;
Ctrl+F12:打开File Structure
Ctrl+Alt+B:接口的实现类,抽象类的子类
Alt+F7:查找使用字段,方法
Ctrl+E:最近使用的文件,在F6移动操作时很有用
Ctrl+Alt+LeftArrow键:上一个操作代码位置。很遗憾,远程桌面中无法使用,只能使用鼠标点击工具栏上的按钮
Ctrl+Shift:移动方法或字段或语句
在equals方法的左或右边的对象上按Alt+Enter,会出现Flip,来互换
Ctrl+B转到定义处,再按Ctrl+B,返回
Alt+insert:构造函数,get,set,overrider
Ctrl+Alt+T:surround with try catch
在方法或类上按Ctrl+shift+T,用来创建测试用例
Ctrl+P:查看方法参数
Ctrl+Q:查看API
Shift+F6:重命名
Ctrl+Alt+n:内联,即独立的方法整合到调用中,被内联的方法就去掉了
在进行重构操作的Class代码中按Ctrl+Z,即可undo刚才的操作
compare with clipboard:先复制一段文本,然后在idea中选中一段文字,在选中文字上右键,选“Compare with clipboard”即可比较,identical:相同的,一致的
Ctrl+Shift+F9:compile,要做中Module或Project,光标在一个Class文件中,就会只编译这个文件,如果引用其它非APIclass,则编译不过
Idea中有在跑测试用例,编译Module或Project时,才会发现哪些Class中没有编译过。提交代码前一定要先编译下或先跑下测试用例

git:
git fetch//获取origin上的更新
git merge//和本地代码合并,如果有clifict,还要resolve才能提交
git add <some files>//暂存数据(标识要commit的本地所有文件),.代表所有本地文件(除了ignore文件中标识的)。也可以add指定文件名的单个文件
git status -s//查看本地仓库的修改状态,如果没有输出,说明没有需要提交的。
git commit -m "注释"//提交到本地仓库
git push origin master//提交到git服务器origin的master分支
git branch//查看
git log --oneline -5

测试用例中mock使用的回顾:
spy,如果不想执行spy的方法,就使用doReturn,不然,被spy的方法还会被执行
直接贴个例子吧,

import com.download.util.Subnet;
import com.file.utils.StmImManageImp;
import com.file.utils.FileInfoUtil;
import com.api.util.DebugPrn;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.File;
import java.io.IOException;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)//这个必须有,不然@Mock就失效了
public class StmFileExporterTest extends AbstractFileExportTest {//UT也需要去除重复

    private static final DebugPrn LOGGER = new DebugPrn(StmFileExporterTest.class.getName());
    private String targetPath;
    @Mock//使用Mock注解,代码更清晰
    private FileInfoUtil fileStyle;
    @Mock
    private StmImManageImp ImManageImp;

    @Before//准备工作
    public void init() throws IOException {
        targetPath = StmFileExporterTest.class.getResource("/com/file/fileexporter/").getPath();
        if (!new File(targetPath).mkdirs()) {
            LOGGER.info(String.format("path(%s) mkdirs failed!", targetPath));
        }
    }

    @Test
    public void should_doDataClearWork_deleteTempPath_when_getByDataType_is_true_and_saveFile_not_exists() {//方法名体现场景
        try {
            when(fileStyle.getTaskName()).thenReturn("task1");//mock掉方法的返回值
            String localTempPah = targetPath + fileStyle.getTaskName() + File.separator + "" + File.separator;
            Subnet spySubnet = spy(getGv3Subnet());//spy一个对象,因为这个对象中的
            File localTempFle = new File(localTempPah);
            if (!localTempFle.mkdirs()) {
                LOGGER.info(String.format("path(%s) mkdirs failed!", localTempFle));
            }
            StmFileExporter StmFileExporter = new StmFileExporter(targetPath, fileStyle);
            StmFileExporter spy = spy(StmFileExporter);
            spy.setImManageImp(ImManageImp);
             //Mock方法中要匹配某个参数,需使用eq();doReturn,不执行mock的相关方法
doReturn(true).when(spy).getDataByDataType(eq(spySubnet), anyString(), anyString());
            spy.sync(spySubnet);
            verify(ImManageImp).doData();
            verify(ImManageImp).clearWork();//等价于verify(ImManageImp,times(1)).clearWork()
            
           //Mockito的assertThat方法,可以把不匹配的expect和actual数据都打印出来,预期的数据需要使用is(),is()的功能也很强大
assertThat(localTempFle.exists(), is(false));
        } catch (Exception e) {
            throw new IllegalArgumentException("UT Fail", e);
        }
    }

    @Test
    public void should_doDataClearWork_deleteTempPath_when_Type_not_equals_gv3() {
        try {
            Subnet spySubnet = spy(getNotGv3Subnet());
            StmFileExporter StmFileExporter = new StmFileExporter(targetPath, fileStyle);
            StmFileExporter spy = spy(StmFileExporter);

            spy.sync(spySubnet);

            verify(spy, times(0)).doStmSync(spySubnet);
        } catch (Exception e) {
            throw new IllegalArgumentException("UT Fail", e);
        }
    }

}

累并充实着吧。
就想起这么多了  

再总结一下,上面去除if else的重构过程,是LSP的一个体现,即父类出来的地方,都可以用子类替代
测试用例的书写原则,对象完成一个行为后,肯定要改变什么,通过IO操作改变了外设上的数据、改变了对象的状态,测试用例只需要覆盖变化即可。
被测对象中的行为在执行时,使用了其它对象A,对象A中有些API的使用,导致无法正常写测试用例,这种场景,需要将对象A注入到被测对象,即给被测对象增加一个协作者,因为被测对象的行为不能单独完成
多用组合,少用继承。组合时,注入需要协作的对象,即可方便写测试用例。
新增对象时,一般会采用set方法而不在constructor中新增一个入参,这样改动最小,对已有对象影响最小。


LSP:
1.概述: 派生类(子类)对象能够替换其基类(父类)对象被调用
2.概念:
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。(源自百度百科)
 
3.子类为什么可以替换父类的位置?:
当满足继承的时候,父类肯定存在非私有成员,子类肯定是得到了父类的这些非私有成员(假设,父类的的成员全部是私有的,那么子类没办法从父类继承任何成员,也就不存在继承的概念了)。既然子类继承了父类的这些非私有成员,那么父类对象也就可以在子类对象中调用这些非私有成员。所以,子类对象可以替换父类对象的位置。
 
4.里氏代换原则优点:
需求变化时,只须继承,而别的东西不会改变。由于里氏代换原则才使得开放封闭成为可能。这样使得子类在父类无需修改的话就可以扩展





 

相关文章
|
程序员
你的代码需要重构吗?
你的代码需要重构吗?
64 0
|
设计模式 算法
重构,避免重构误区
重构,避免重构误区
41 0
|
设计模式 缓存 算法
关于“重构”的一些思考
本文将从一个新人数次修改CR comments的角度探讨代码重构的定义、目的以及常见的重构方法,并以简单的代码案例来说明代码重构的具体实现。
4891 3
关于“重构”的一些思考
|
开发者
重构的理解
重构的理解
94 0
|
数据处理
《重构2》第六章-重构基础
《重构2》第六章-重构基础
306 0
|
消息中间件 缓存 负载均衡
架构重构的技巧
对软件代码做任何改动以增加可读性或者简化结构而不影响输出结果。
155 0
|
数据格式 索引
日常开发之重构
作为开发人员,应该让重构贯穿于你的开发日常的每一刻。
347 0
|
存储 设计模式 架构师
记一次项目重构
本文主要记录,刚刚步入架构师岗位4个月的我,重构项目的一些经历。
|
算法 Java 容器
狗屎一样的代码!快,重构我!
狗屎一样的代码如何重构? 重构不止是代码整理,它提供了一种高效且受控的代码整理技术。
132 0
|
消息中间件 设计模式 缓存
系统重构的道与术
准备以重构工作中容易产生误区的地方或容易被忽视的重点来聊聊,既不重复网上千篇一律的各种方案资料,也对重构工作有参考价值。
系统重构的道与术