设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
19512 人已学习
课程目录
已更新 38 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (12讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?
23 | 实战一(上):针对业务系统的开发,如何做需求分析和设计?
24 | 实战一(下):如何实现一个遵从设计原则的积分兑换系统?
25 | 实战二(上):针对非业务的通用框架开发,如何做需求分析和设计?
26 | 实战二(下):如何实现一个支持各种统计规则的性能计数器?
设计原则与思想:规范与重构 (9讲)
27 | 理论一:什么情况下要重构?到底重构什么?又该如何重构?
28 | 理论二:为了保证重构不出错,有哪些非常能落地的技术手段?
29 | 理论三:什么是代码的可测试性?如何写出可测试性好的代码?
30 | 理论四:如何通过封装、抽象、模块化、中间层等解耦代码?
31 | 理论五:让你最快速地改善代码质量的20条编程规范(上)
32 | 理论五:让你最快速地改善代码质量的20条编程规范(中)
33 | 理论五:让你最快速地改善代码质量的20条编程规范(下)
34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题
35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题

王争 2020-01-20
在前面几节课中,我们讲了一些跟重构相关的理论知识,比如:持续重构、单元测试、代码的可测试性、解耦、编码规范。用一句话总结一下,重构就是发现代码质量问题,并且对其进行优化的过程。
前面的内容相对还是偏理论。今天,我就借助一个大家都很熟悉的 ID 生成器代码,给你展示一下重构的大致过程。整个内容分为两节课。这一节课我们讲述如何发现代码质量问题,下一节课讲述如何针对发现的质量问题,对其进行优化,将它从“能用”变得“好用”。
话不多说,让我们正式开始今天的学习吧!

ID 生成器需求背景介绍

“ID”中文翻译为“标识(Identifier)”。这个概念在生活、工作中随处可见,比如身份证、商品条形码、二维码、车牌号、驾照号。聚焦到软件开发中,ID 常用来表示一些业务信息的唯一标识,比如订单的单号或者数据库中的唯一主键,比如地址表中的 ID 字段(实际上是没有业务含义的,对用户来说是透明的,不需要关注)。
假设你正在参与一个后端业务系统的开发,为了方便在请求出错时排查问题,我们在编写代码的时候会在关键路径上打印日志。某个请求出错之后,我们希望能搜索出这个请求对应的所有日志,以此来查找问题的原因。而实际情况是,在日志文件中,不同请求的日志会交织在一起。如果没有东西来标识哪些日志属于同一个请求,我们就无法关联同一个请求的所有日志。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(26)

  • 失火的夏天
    争哥,老实说,这个小王是不是曾经的你
    2020-01-20
    5
    11
  • 李小四

    设计模式_34
    Logger的方法一般不会有未决行为,不影响可测试性,不用依赖注入。

    以前看代码质量是看感觉,现在开始有章法了。
    2020-01-20
    3
  • 辣么大
    小wang同学的代码还是有小问题。随机字符的代码是无法取到小写字母z的。
    因为业务要求随机字符包括大小写和数字,在代码中写到 int randomAscii = random.nextInt(122)
    java中nextInt取的是[lower, upper),z的ascii是122,所以程序是取不到z字符的。
    争哥的例子告诉我们尽量不要用魔数,不但可读性不好,有时我们自己都糊涂,边界容易搞错。

    我试着重写了一下,一起讨论讨论!
    https://github.com/gdhucoder/Algorithms4/blob/master/designpattern/u34/IdGenerator.java

      public static final String BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
      private static final char[] CHAR_SET = BASE62.toCharArray();

      public static String generate() {

        // get host name
        String hostName = null;
        try {
          hostName = InetAddress.getLocalHost().getHostName();
          String[] tokens = hostName.split("\\.");
          hostName = tokens[tokens.length - 1];
        } catch (UnknownHostException e) {
          hostName = "UNKNOWN";
        }

        // random chars
        char[] randomChars = new char[8];
        Random random = new Random();
        for (int i = 0; i < randomChars.length; i++) {
          int rndIdx = random.nextInt(CHAR_SET.length);
          randomChars[i] = CHAR_SET[rndIdx];
        }

        // generate id
        String id = String.format("%s-%d-%s", hostName,
            System.currentTimeMillis(), new String(randomChars));

        return id;
      }
    2020-01-20
    2
  • liu_liu
    没有必要,不需要在这里测试 logger
    2020-01-20
    2
  • pedro
    争哥这一节跟马丁那本<重构>的第一章有异曲同工之妙。
    如果大家觉得意犹未尽,可以尝试动手去学学重构的第一章,然后动手实践一下🤪
    2020-01-20
    2
  • 下雨天
    课堂讨论
        日志系统设计属非业务相关模块,从可复用性,单一职责,高内聚低耦合考虑,最好设计成单独模块,以便其他类复用!
        Logger对象是一个单例全局对象才合理,如果通过依赖注入到IdGenerator中就显得有点多余了!
    2020-01-20
    2
  • 黄林晴
    打卡✔
    2020-01-20
    1
  • 程斌
    很好的一篇checklist,算是对我们前边学的整理起来了。棒棒。
    2020-01-20
    1
  • Jackey
    我要把这个checklist打印出来贴桌子上
    2020-01-20
    1
  • 代码搬运工
    我做的项目开发都是这样定义的,这个有问题吗?
    2020-01-20
    1
    1
  • seckiller
    王争大佬以后课多带点实战代码
    2020-01-20
    1
  • 小晏子
    不必要把logger依赖注入进去,logger是可以直接初始化的业务无关的,不依赖于外部接口,不影响单元测试,可以忽略掉,所以不需要改成依赖注入的。
    2020-01-20
    1
  • whistleman
    滴滴。打卡~
    2020-01-20
    1
  • 0bug
    catch包裹的代码太多了
    2020-01-20
    1
  • 平风造雨
    slf4j本身已经支持的不同Logger的实现,配合lombok,自己直接定义static logger是没有必要的,可以替换掉。Logger本身并不是业务需求关注的点,也不是非功能性需求特别关注的点,没必要为了依赖注入而注入,更何况Logger本身可以通过配置的方式改变处理log的方式,已经很灵活了。
    2020-01-20
    1
  • 牧名
    1. try-catch 范围过大,只需要try-cache InetAddress.getLocalHost().getHostName();即可
    2. 随机数范围调整为74(122-48)可适度减少无谓的循环。另外生成数字,大小写字母其实全部使用'0'+randomInt即可(randomInt取值范围 0-74)
    2020-01-21
  • Frank
    学完这篇文章后,体会到之前学习的东西逐渐影响到自己后面的学习。这也验证了“你努力走过的路,每一步都算数”。在读文章的过程中,自己也思考了文中提供的代码有哪些问题,但是在读完文章后,发现自己并没有像文章中罗列的那套路来思考,自己都是按照之前学习的知识想到一点是一点,没有条理,没有章法。这也提醒了我该回炉重新去回顾以前学过的知识,要不断的进行思考总结将它们在自己的大脑里留下更深的记忆,并产生相关的连接。文中罗列的两个checkList,个人感觉很好用,通过这两个checkList,从“通用的代码关注点”和“业务功能、非功能需求”这两个维度思考将之前学习到的知识点都串联起来了,使我们能够学以致用。回顾自己以前在看自己的代码和别人的代码的时候,只是简单的看一些命名是否规范,是否可读等。感觉有内功又深了一层的感觉。
    对于课堂讨论,Logger对象,个人觉得并不影响测试性,它只是一个辅助手段用于打印日志。也没必要通过依赖注入的方式构造对象,这一点耦合还是可以接受的。使用依赖注入为了是解耦,将依赖类在外部创建好,通过构造函数,函数参数方式传递来给类使用。
    2020-01-20
  • JOsuny
    我来啦
    2020-01-20
  • Jxin
    回答问题:
    1.不需要注入。因为日志工具与该业务要验证功能的现象无关。如果要挑选日志工具实现类,可以单独为其写测试类,验证各方面指标差异。

    demo代码还存在的问题:

    1.try太大了,应该局限在hostname那块,大于自己的try范围会引人误解。

    2.为取host最后一个字段而生成arr没必要,直接sub取最后一个字段语义更强。(jdk的split和sub都有点问题,条件允许还是用工具好点)。

    3.异常比空字符串语义其实更准确些,一般我偏向于把异常处理抛给api调用方,而不是这里的捕捉打印日志。(另外,异常堆栈耗性能,像这种异常(不需要看调用链追查异常原因的),健全的异常信息足已)
    2020-01-20
  • halweg
    说实话,我觉得小王写的还行,
    要是我也会这样写
    2020-01-20
收起评论
26
返回
顶部