把书读薄 | 《设计模式之美》规范与重构(上)(一)

简介: 节后第一天,本文是 规范与重构 (15-33) 的浓缩总结,同上,把实战部分(34-37) 拆到下节,这部分主要是一些编码建议和规范,过一遍,自己写代码注意下就好,比较简单。二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。

0x1、重构四问


① 重构的目的 → 为什么重构(Why)?


软件设计大师Martin Fowler对重构的定义:


重构是一种对软件内部结构的改善,目的是 在不改变软件的可见行为 的情况下,使其更易理解,修改成本更低。


可以把重构理解为:


在保持功能不变的前提下,利用设计思想、原则、模式、编程规范等理论来优化代码,修改设计上的不足,提高代码质量。


为什么要进行代码重构


  • 时刻保证代码质量的有效手段,不至于让代码腐化到无可救药的地步;
  • 优秀的代码或架构都是迭代出来的,无法100%预见未来的需求,随着系统演进,重构不可避免;
  • 避免过度设计的有效手段,维护代码过程真正遇到问题再对代码进行重构,有效避免前期过度设计投入大量时间;
  • 对工程师本身技术的成长有重要意义,将学习到的理论知识应用到实践中一个很好的练兵场;


初级工程师在维护代码,高级工程师在设计代码,资深工程师在重构代码 (发现代码存在问题,保证代码质量处于可控的状态)。


② 重构的对象 → 重构什么(What)?


根据重构的规模,笼统地分为 大规模高层次重构小规模低层次重构,简称为大型、小型重构。


大型重构


对顶层代码设计的重构系统、模块、代码结构、类与类之间的关系 等的重构,手段有:分层、模块化、解耦、抽象可复用组件 等。工具:设计思想、原则和模式。涉及代码变动较多,影响面大,难度大,耗时长,引入bug风险也大。


小型重构


对代码细节的重构类、函数、变量等代码级别 的重构,比如:规范命名、规范注释、消除超大类或函数、提取重复代码等。手段主要是:编码规范。修改地方较集中,比较简单、可操作性强、耗时短,引入bug风险也相对小一些。


③ 重构的时机 → 什么时候重构(When)?


不要等代码烂到一定程度再去重构,提倡 持续重构,闲暇时看下项目中有哪些写的不够好、可以优化的代码,主动去重构下,或者在修改、添加某功能代码时顺手把不符合编码规范、不好的设计重构一下,就是要有 持续重构的意识


④ 重构的方法 → 如何重构(How)?


大型重构


提前做好完善的重构计划,分阶段进行,每个阶段只完成一小部分代码的重构,然后提交、测试、运行,发现没问题后,再继续进行下一阶段的重构,保证代码仓库中的代码一直处于可运行,逻辑正确的状态。


每个阶段,都要控制好重构影响到的代码范围,考虑好如何兼容老的代码逻辑,必要的时候还要写一些兼容过渡代码。只有这样,才能让每个阶段的重构不至于耗时太长(最好一天就能完成),不至于和新的功能开发相冲突。


大型重构一定是有组织、有计划且非常谨慎的,需要有经验、熟悉业务的资深同事来主导。


小型重构


随时可以去做,除了人工去发现低层次的代码质量问题,还可以借助一些成熟的静态代码分析工具(如:CheckStyle、FindBugs、PMD等),来自动发现代码中的问题,然后针对性地进行重构优化。


对于重构这件事,资深工程师、团队Leader要负起责任,没事就重构下代码,时刻保持代码质量处于一个良好的状态。否则一旦出现 "破窗效应",一个人往里堆了一些烂代码,之后就会有更多的人往里面堆更烂的代码,毕竟往项目堆砌烂代码的成本太低了。保持代码质量最好的方法还是:打造一种好的技术氛围,以此驱动大家主动关注代码质量,持续重构代码。


0x2、如何保证重构不出错


保证重构不出错,你需要熟练掌握各种设计原则、思想、模式,还有对所重构的业务和代码有足够的了解。除去这些个人能力因素外,最可落地执行、最有效的保证重构不出错的技术手段就是 单元测试(Unit Testing)。


① 单元测试与集成测试


  • 单元测试由研发工程师自行编写,用来测试自己写的代码的正确性,测试对象是 类或函数,测试是否都按照预期的逻辑执行,代码层级的测试,粒度较小;


  • 集成测试 (Integration Testing) 的测试对象是  整个系统或某个功能模块,如测试用户注册、登陆功能是否正常,一种端到端(End To End) 的测试。


② 单元测试编写示例


import java.util.regex.Pattern;
public class Text {
    private String content;
    private final Pattern pattern = Pattern.compile("[0-9]*");
    public Text(String content) {
        this.content = content;
    }
    public Integer toInt() {
        if (content == null || content.isEmpty()) return null;
        String temp = content.replace(" ", "");
        if(pattern.matcher(temp).matches()) {
            return Integer.parseInt(temp);
        }
        return null;
    }
}


比如要对上面这个Text类的toInt()方法进行测试,先设计测试用例(输入 → 期望输出):


  • "123" → 123
  • null或空字符串 → null
  • " 123"、" 123 "、"123 "、"1 23 "、"1 2 3 "、" 1 2 3 " → 123
  • "123a"、"1*23"、"abc" → null
  • "1234567890" → 1234567890


用例设计更多考验程序员思维的缜密程度,看能否设计出覆盖各种正常/异常情况的测试用例,来保证代码在任何预期或非预期情况下都能正确运行。写完用例,接着就是将其翻译成带么了(此处没用任何测试框架)


// 结果校验类
public class Assert {
    public static void assertEquals(Integer expectedValue, Integer actualValue) {
        if (actualValue.intValue() != expectedValue.intValue()) {
            System.out.println(String.format("测试失败:期待值:%d,实际值: %d", expectedValue, actualValue));
        } else {
            System.out.println("测试成功");
        }
    }
    public static boolean assertNull(Integer actualValue) {
        boolean isNull = actualValue == null;
        if (isNull) {
            System.out.println("测试成功");
        } else {
            System.out.println("测试失败,实际值不为null:" + actualValue);
        }
        return isNull;
    }
}
// 测试用例类
public class TextTest {
    public void testToNumber() {
        Assert.assertEquals(123, new Text("123").toInt());
    }
    public void testToNumber_nullOrEmpty() {
        Assert.assertNull(null);
        Assert.assertNull(new Text("").toInt());
    }
    public void testToNumber_containsSpace() {
        Assert.assertEquals(123, new Text(" 123").toInt());
        Assert.assertEquals(123, new Text(" 123 ").toInt());
        Assert.assertEquals(123, new Text("123 ").toInt());
        Assert.assertEquals(123, new Text("1 23 ").toInt());
        Assert.assertEquals(123, new Text("1 2 3 ").toInt());
        Assert.assertEquals(123, new Text(" 1 2 3 ").toInt());
    }
    public void testToNumber_containsInvalidCharacters() {
        Assert.assertNull(new Text("123a").toInt());
        Assert.assertNull(new Text("1*23").toInt());
        Assert.assertNull(new Text("abc").toInt());
    }
    public void testToNumber_large() {
        Assert.assertEquals(Integer.MAX_VALUE, new Text("" + Integer.MAX_VALUE).toInt());
    }
}
// 运行用例类
public class TestCaseRunner {
    public static void main(String[] args) {
        TextTest test = new TextTest();
        test.testToNumber();
        test.testToNumber_nullOrEmpty();
        test.testToNumber_containsSpace();
        test.testToNumber_containsInvalidCharacters();
        test.testToNumber_large();
    }
}


相关文章
|
1月前
|
设计模式 Java API
重构旧代码的秘诀:用设计模式 - 适配器模式(Adapter)给Java项目带来新生
【4月更文挑战第7天】适配器模式是解决接口不兼容问题的结构型设计模式,通过引入适配器类实现目标接口并持有不兼容类引用,实现旧代码与新接口的协作。适用于处理兼容性问题、整合遗留代码和集成第三方库。应用时,识别不兼容接口,创建适配器类转换方法调用,然后替换原有引用。注意保持适配器简单、使用组合和考虑扩展性。过度使用可能导致系统复杂和维护成本增加,应谨慎使用。
|
8月前
|
设计模式 算法 Java
设计模式第十五讲:重构 - 改善既有代码的设计(下)
设计模式第十五讲:重构 - 改善既有代码的设计
249 0
|
8月前
|
设计模式 Java Apache
设计模式第九讲:常见重构技巧 - 去除不必要的!=
设计模式第九讲:常见重构技巧 - 去除不必要的!=
|
8月前
|
设计模式 算法 Java
设计模式第八讲:常见重构技巧 - 去除多余的if else
设计模式第八讲:常见重构技巧 - 去除多余的if else
|
8月前
|
设计模式 Java 测试技术
设计模式第十五讲:重构 - 改善既有代码的设计(上)
设计模式第十五讲:重构 - 改善既有代码的设计
268 0
|
设计模式 SQL 算法
【Java设计模式 规范与重构】 六 代码重构小结
【Java设计模式 规范与重构】 六 代码重构小结
155 0
|
设计模式 前端开发 Java
【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
155 0
|
设计模式 存储 Java
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(下)
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(下)
183 0
|
设计模式 存储 SQL
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(上)
【Java设计模式 规范与重构】 五 重构实战:基于ID生成器case(上)
110 0
|
1月前
|
设计模式 Java API
【设计模式】JAVA Design Patterns——Combinator(功能模式)
【设计模式】JAVA Design Patterns——Combinator(功能模式)