设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
19667 人已学习
课程目录
已更新 42 讲 / 共 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 | 实战二(下):如何实现一个支持各种统计规则的性能计数器?
设计原则与思想:规范与重构 (11讲)
27 | 理论一:什么情况下要重构?到底重构什么?又该如何重构?
28 | 理论二:为了保证重构不出错,有哪些非常能落地的技术手段?
29 | 理论三:什么是代码的可测试性?如何写出可测试性好的代码?
30 | 理论四:如何通过封装、抽象、模块化、中间层等解耦代码?
31 | 理论五:让你最快速地改善代码质量的20条编程规范(上)
32 | 理论五:让你最快速地改善代码质量的20条编程规范(中)
33 | 理论五:让你最快速地改善代码质量的20条编程规范(下)
34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题
35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”
36 | 实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?
37 | 实战二(下):重构ID生成器项目中各函数的异常处理代码
设计原则与思想:总结课 (1讲)
38 | 总结回顾面向对象、设计原则、编程规范、重构技巧等知识点
不定期加餐 (3讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
春节特别加餐 | 王争:如何学习《设计模式之美》专栏?
设计模式之美
登录|注册

37 | 实战二(下):重构ID生成器项目中各函数的异常处理代码

王争 2020-01-27
平时进行软件设计开发的时候,我们除了要保证正常情况下的逻辑运行正确之外,还需要编写大量额外的代码,来处理有可能出现的异常情况,以保证代码在任何情况下,都在我们的掌控之内,不会出现非预期的运行结果。程序的 bug 往往都出现在一些边界条件和异常情况下,所以说,异常处理得好坏直接影响了代码的健壮性。全面、合理地处理各种异常能有效减少代码 bug,也是保证代码质量的一个重要手段。
在上一节课中,我们讲解了几种异常情况的处理方式,比如返回错误码、NULL 值、空对象、异常对象。针对最常用的异常对象,我们还重点讲解了两种异常类型的应用场景,以及针对函数抛出的异常的三种处理方式:直接吞掉、原封不动地抛出和包裹成新的异常抛出。
除此之外,在上一节课的开头,我们还针对 ID 生成器的代码,提出了 4 个有关异常处理的问题。今天,我们就用一节课的时间,结合上一节课讲到的理论知识,来逐一解答一下这几个问题。
话不多说,让我们正式开始今天的内容吧!

重构 generate() 函数

首先,我们来看,对于 generate() 函数,如果本机名获取失败,函数返回什么?这样的返回值是否合理?
public String generate() {
String substrOfHostName = getLastFiledOfHostName();
long currentTimeMillis = System.currentTimeMillis();
String randomString = generateRandomAlphameric(8);
String id = String.format("%s-%d-%s",
substrOfHostName, currentTimeMillis, randomString);
return id;
}
ID 由三部分构成:本机名、时间戳和随机数。时间戳和随机数的生成函数不会出错,唯独主机名有可能获取失败。在目前的代码实现中,如果主机名获取失败,substrOfHostName 为 NULL,那 generate() 函数会返回类似“null-16723733647-83Ab3uK6”这样的数据。如果主机名获取失败,substrOfHostName 为空字符串,那 generate() 函数会返回类似“-16723733647-83Ab3uK6”这样的数据。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(15)

  • Jxin
    还学到什么:
    1.一下子想搞个例子讲这些真的太难了,拍着脑子想demo。栏主这个demo背景简单,也将要讲的内容串起来了,实属不易,幸苦栏主了。

    个人见解:
    1.按我的习惯,我会尽量把入参和中间不可靠变量的异常校验都放在public方法,所有私有方法都以契约的方式不再做参数校验。也就是说 public方法干 1.参数校验 2. 系统一级流程编排 3.统一异常处理 这三件事。所以对private方法的提炼会和栏主有点出入。

    2.如果这个id生成器还要带有业务key,比如分表路由key之类的东西。那么这个实现就还得大动干戈。但凡这种涉及持久数据的玩意,很可能需要考虑新老版本兼容的问题,也就是如何平滑过度老数据。所以需要在id生成算法上引入版本或者类型的标记,把标记打在持久化的数据上,以备平滑过度老数据。

    作者回复: 我觉得你懂我~

    2020-01-27
    6
  • your problem?
    打卡,也祝大家新年快乐,身体健康,另外我始终觉得generateRandomAlphameric这个函数里,随机获取这个写法很不利于性能测试,假如这个函数会被百万,甚至千万次的调用,不可控性也太强了,我觉得可以改成随机生成0-26的数字,对应去加到字母的位置,不知道老师和大家有什么想法吗
    2020-01-27
    2
  • 皮卡皮卡
    争哥这种设计思路考虑了一下,但是在业务中往往获取唯一ID的地方,不关心ID内部生成错误,需要的只是能够返回出来ID即可。目前我们的处理是异常在generate内部自己解决,同时返回ID
    2020-01-28
    1
    1
  • Yang
    还学到了:
    1.函数出错时是返回NULL还是异常对象?
    要看获取不到数据是正常行为,还是异常行为,如果业务上来说是异常行为就抛出异常,反之返回NULL。

    2.是直接返回出错的异常还是重新封装成新的异常?
    要看函数跟异常是否有业务相关性。相关的话就直接抛出。不相关就包装成与函数相关的异常类型,而且这样也能隐藏实现细节。

    3.NULL值或空字符串在什么时候需要判断?
    a.如果函数是 private 类私有的,只在类内部被调用,完全在你自己的掌控之下,自己保证在调用这个 private 函数的时候,不要传递 NULL 值或空字符串就可以了。
    b.如果函数是 public 的,你无法掌控会被谁调用以及如何调用(有可能某个同事一时疏忽,传递进了 NULL 值,这种情况也是存在的),为了尽可能提高代码的健壮性,我们最好是在 public 函数中做 NULL 值或空字符串的判断。
    c.但是单元测试会测试一些corner case,所以,最好也加上判断。

    4.个人的一点思考
    如果代码中报的错是受检异常就可以针对具体情况来处理是throws出去、吞掉还是包装新的异常。如果报的错是非受检异常我还是习惯内部自己处理,因为非受检异常throws出去的话,调用方不处理,编译器也不会报错,所以,为了防止调用方未处理的情况,还是自己内部处理吧。
    2020-01-28
    1
  • Frank
    今天学习了异常代码处理思路。在处理到异常时,通常会将上层关心的异常直接包装成RuntimeException往上抛,没有根据业务域定义相关的自定义异常。通过今天的学习,了解到处理异常的基本思路:是往上抛还是吞掉,主要看调用者是够关心该异常。是否要包装成新的异常主要看调用者是否理解该异常,该异常是否业务相关。如果能理解、业务相关可以直接抛,否则重新包装。
    在这4节课的持续迭代过程中,除了文章中提到的开发思想,自己总结了如下一些个人想法:
    1. 科比说过“我现在所做的一切,都是为了追求更加完美” - 缅怀逝去的伟大的科比。我们对生活,工作都要尽量追求完美。
    2. 人生是个不断重构自己的过程,自己写的代码也要不断持续重构,优化。这样自己才能不断进步。
    3. 参考优秀的开发思想,方法论,不断地将之实践,总结,改进,逐渐形成合适自己的方法论。
    2020-01-27
    1
  • 高源
    希望老师每节课举的代码有下载的地方,自己下载下来结合老师讲解的,自己理解体会其中的解决问题

    作者回复: 好的,等我俩月,我整理好,一块放到github上:
    https://github.com/wangzheng0822

    2020-01-27
    1
  • Geek_kobe
    果然还是看技术文章能让恐慌的心静下来
    2020-01-27
    1
  • 黄林晴
    打卡
    2020-01-28
  • liu_liu
    写代码不是糊弄,写出好的有水平的代码需要下一番功夫。对代码保持敬畏之心,有追求极致的思想,才会越来越好。
    2020-01-28
  • javaadu
    我再提一点自己的改进想法:修改后的代码里,generate方法还应该处理掉8这个魔法数字,如果需要让用户定制长度,则需要提供另一个不带默认值的方法,并在generate方法里处理随机方法抛出的参数非法异常
    2020-01-27
  • 辣么大
    学到什么:
    1、动手实践。争哥的这4节看着简单,实际信息量很大,动手实践一下能有更深的体会。
    2、跑去研究了Java Random的源码的构造函数如何实现的。
    搞清random做为变量可以放在哪里使用,以及random.rndInt()的取值范围。
    3、异常如何处理:什么时候吞掉、抛出去,或者包装后抛出去。
    4、学了理论还是要多在实际中尝试应用。
    5、重构时先写好测试。每次baby step的重构,refactor build commit。 从《重构2》里学的。
    2020-01-27
  • ちよくん
    滴滴打卡
    2020-01-27
  • 小晏子
    感觉generate函数失败后报错信息是host name empty会有点奇怪,意味着用户还要去设置服务器的hostname,程序和服务器设置有了依赖性了,如果用户不会设置或者是基于容器的,那还要用户花费时间去设置主机名,感觉不好。
    2020-01-27
  • Harvey
    设计之所以难是因为没有标准答案,很多权衡是依赖于具体业务的。这就是DDD的思想所在,要先想清楚问题域是什么在思考解决方案。很多开发讨论问题的时候没有层次,上来就陷入技术细节,这就叫缺乏抽象。下游系统要想清楚哪些是上游系统给你提供的服务?哪些是人家的内部技术实现?比如ID生成,作为上游系统,ID生成服务提供的是有小概率重复的随机ID服务,至于随机算法,下游系统不必关心,这是上游系统的内部实现,这样上游系统才有空间更换算法而不影响下游系统。
    2020-01-27
  • Jeff.Smile
    大年初三,抢占沙发!
    2020-01-27
收起评论
15
返回
顶部