软件开发中的决策:权衡与取舍

978-7-115-63516-7
作者: [美]托马斯·莱莱克(Tomasz Lelek)[英]乔恩·斯基特 (Jon Skeet)
译者: 陆明刚胡世杰
编辑: 李瑾

图书目录:

第 1 章 引言 1

1.1 决策的后果与模式 2

1.1.1 单元测试 2

1.1.2 单元测试与集成测试的比例 3

1.2 设计模式及其失效分析 5

1.3 架构设计模式及其失效分析 10

1.3.1 可扩展性与弹性 11

1.3.2 开发速度 12

1.3.3 微服务的复杂性 12

小结 14

第 2 章 代码重复不一定是坏事:代码重复与灵活性的权衡 15

2.1 代码库间的通用代码及重复代码 16

2.1.1 添加新需求导致的代码重复 17

2.1.2 实现新的业务需求 17

2.1.3 结果评估 19

2.2 通过库在代码库之间共享代码 19

2.2.1 共享库的取舍与不足 20

2.2.2 创建共享库 21

2.3 抽取代码为一个独立的微服务 22

2.3.1 采用独立微服务方式的取舍与弊端 24

2.3.2 关于独立微服务的总结 27

2.4 通过代码重复改善松耦合 28

2.5 利用继承减少 API 设计中的重复 31

2.5.1 抽取出一个请求处理器作为基类 33

2.5.2 继承与紧耦合的取舍 35

2.5.3 继承与组合的取舍 36

2.5.4 一贯性的重复与偶然性的重复 37

小结 38

第 3 章 异常及其他——代码错误的处理模式 39

3.1 异常的层次结构 40

4

3.2 代码异常处理的最佳模式 44

3.2.1 公共 API 的已检测异常处理 45

3.2.2 公共 API 的未检测异常处理 46

3.3 异常处理的反模式 47

3.3.1 异常时,关闭资源 49

3.3.2 反模式:利用异常控制应用流 51

3.4 源自第三方库的异常 51

3.5 多线程环境中的异常 54

3.6 使用 Try 以函数式的途径处理异常 59

3.6.1 在生产代码中使用 Try 62

3.6.2 混合使用 Try 与抛出异常的代码 64

3.7 异常处理策略的性能对比 65

小结 68

第 4 章 灵活性与复杂性的权衡 70

4.1 一个健壮但无法扩展的API 71

4.1.1 设计一个新组件 71

4.1.2 从最简单的代码开始 72

4.2 允许客户使用自己的指标框架 75

4.3 通过钩子为你的 API提供可扩展性 77

4.3.1 防范钩子 API 的过度使用 79

4.3.2 钩子 API 的性能影响 81

4.4 通过侦听器为你的 API提供可扩展性 83

4.4.1 使用侦听器与钩子的取舍 84

4.4.2 设计的不可修改性 85

4.5 API 的灵活性分析及维护开销的权衡 87

小结 88

第 5章 过早优化 vs 热路径优化:影响代码性能的决策 89

5.1 过早优化是万恶之源 90

5.1.1 构建账户处理管道 90

5.1.2 依据错误的假设进行优化处理 91

5.1.3 对性能优化进行基准测试 92

5.2 代码中的热路径 94

5.2.1 从软件系统的角度理解帕累托法则 96

5.2.2 依据 SLA 配置线程(并发用户)数 97

5.3 具有潜在热路径的 word服务 97

5.3.1 获取每日一词 98

5.3.2 验证单词是否存在 100

5.3.3 使用 HTTP 服务,向外提供WordsService 100

5.4 检测你代码中的热路径 102

5.4.1 使用 Gatling 创建 API 的性能测试 102

5.4.2 使用 MetricRegistry 度量代码路径 105

5.5 改进热路径的性能 107

5.5.1 为现有代码创建 JMH 微基准测试 107

5.5.2 利用缓存优化 word-exists程序 109

5.5.3 调整性能测试,使用更多的输入单词 113

小结 115

第 6 章 API 的简洁性 vs 维护成本 116

6.1 一个为其他工具服务的基础库 117

6.1.1 创建云服务客户端 117

6.1.2 漫谈认证策略 119

6.1.3 理解配置的机制 120

6.2 直接暴露依赖库的配置 123

6.3 一个将依赖库的配置抽象化的工具 127

6.4 为云服务客户端库添加新的配置 129

6.4.1 为批处理工具添加新配置 130

6.4.2 为流处理工具添加新配置 132

6.4.3 方案对比:用户体验的友好性 vs 维护成本 132

6.5 弃用/删除云服务客户端库的某个配置 133

6.5.1 删除批处理工具的某个配置 135

6.5.2 删除流服务中某个配置 137

6.5.3 两种方案用户体验与维护成本的比较 138

小结 139

第 7 章 高效使用日期和时间数据 140

7.1 日期和时间信息的概念 141

7.1.1 机器时间:时间戳、纪元以及持续时间 141

7.1.2 民用时间:日历系统、日期时间以及期间 145

7.1.3 时区、UTC 以及 UTC偏移量 149

7.1.4 让人头疼的日期和时间概念 154

7.2 准备处理日期和时间信息 155

7.2.1 对范畴做限定 155

7.2.2 澄清日期和时间的需求 157

7.2.3 使用恰当的库或者包 161

7.3 实现日期和时间代码 162

7.3.1 保持概念的一致性 162

7.3.2 通过避免使用默认值提升可测试性 164

7.3.3 以文本方式表示日期和时间 170

7.3.4 通过注释解释代码 175

7.4 有必要单独指出并测试的极端情况 178

7.4.1 日历计算 178

7.4.2 发生在午夜时分的时区转换 178

7.4.3 处理不明确或者跳过的时间 179

7.4.4 处理不断变化的时区数据 179

小结 183

第 8 章 利用机器的数据本地性和内存 184

8.1 数据本地性是什么 184

8.1.1 将计算移动到数据处 185

8.1.2 用数据本地性扩展数据处理 186

8.2 数据的分区 188

8.2.1 线下大数据分区 188

8.2.2 分区和分片的区别 190

8.2.3 分区算法 191

8.3 连接多个分区上的大数据集 193

8.3.1 在同一台物理机上连接数据 194

8.3.2 需要数据移动的连接 195

8.3.3 利用广播优化连接 196

8.4 在内存还是磁盘中进行数据处理的权衡 198

8.4.1 基于磁盘的处理 198

8.4.2 我们为什么需要映射-化简? 198

8.4.3 计算访问时间 201

8.4.4 基于内存的处理 202

8.5 用 Apache Spark 实现连接 203

8.5.1 不使用广播的连接 204

8.5.2 使用广播的连接 206

小结 208

第 9 章 第三方库:你用的库成为你的代码 209

9.1 引用一个库就要对它的配置选项负责:小心那些默认配置 210

9.2 并发模型和可扩展性 213

9.2.1 使用异步和同步 API 215

9.2.2 分布式的可扩展性 217

9.3 可测试性 218

9.3.1 测试库 219

9.3.2 用伪造值和模拟函数来进行测试 221

9.3.3 集成测试工具包 224

9.4 第三方库的依赖 225

9.4.1 避免版本冲突 226

9.4.2 太多的依赖 227

9.5 选择和维护第三方依赖 228

9.5.1 第 一印象 228

9.5.2 复用代码的不同方式 229

9.5.3 锁定供应商 229

9.5.4 软件许可证 230

9.5.5 库和框架 230

9.5.6 安全和更新 230

9.5.7 选择第三方库的检查列表 231

小结 232

第 10 章 分布式系统的一致性和原子性 233

10.1 数据源的至少一次传输语义 234

10.1.1 单节点服务之间的网络访问 234

10.1.2 应用程序重试请求 235

10.1.3 生成数据和幂等性 236

10.1.4 理解 CQRS 238

10.2 去重库的简单实现 240

10.3 在分布式系统里实现去重会遇到的常见错误242

10.3.1 单节点环境 242

10.3.2 多节点环境 244

10.4 用原子性的逻辑避免竞争条件 246

小结 250

第 11 章 分布式系统的传输语义 251

11.1 事件驱动应用程序的架构 252

11.2 基于 Apache Kafka 的生产者和消费者应用程序 254

11.2.1 Kafka 消费者 255

11.2.2 理解 Kafka brokers设置 257

11.3 生产者的逻辑 258

11.4 在消费者端实现不同的传输语义 262

11.4.1 消费者手动提交 264

11.4.2 从最早或最晚的偏移量开始重启 266

11.4.3 (最终)恰好一次传输语义 268

11.5 用传输保证提供容错能力 270

小结 271

第 12 章 版本管理和兼容性 272

12.1 版本管理的抽象思考 273

12.1.1 版本的属性 273

12.1.2 向后兼容性和向前兼容性 274

12.1.3 语义版本规范 275

12.1.4 营销版本 277

12.2 库的版本管理 277

12.2.1 源码、二进制和语义兼容性 278

12.2.2 依赖图和菱形依赖 285

12.2.3 处理破坏性改动的技术手段 288

12.2.4 管理内部库 292

12.3 网络 API 的版本管理 293

12.3.1 网络 API 调用的环境 293

12.3.2 用户喜欢公开透明的版本策略 295

12.3.3 常见的版本策略 295

12.3.4 版本管理额外的考虑因素 300

12.4 数据存储的版本管理 303

12.4.1 简要介绍 Protocol Buffers 303

12.4.2 哪些是破坏性改动 305

12.4.3 在存储系统内部迁移数据 306

12.4.4 准备好面对未知 309

12.4.5 分离网络 API 和存储的数据格式 310

12.4.6 评估存储格式 312

小结 313

第 13 章 紧跟最新技术趋势和维护旧代码之间的权衡 315

13.1 什么时候应该使用依赖注入框架 316

13.1.1 DIY 依赖注入 317

13.1.2 使用依赖注入框架 319

13.2 什么时候应该使用响应式编程 321

13.2.1 创建一个单线程阻塞式处理模型 322

13.2.2 使用CompletableFuture 324

13.2.3 实现一个响应式方案 326

13.3 什么时候应该使用函数式编程 328

13.3.1 用非函数式语言写函数式代码 328

13.3.2 尾部递归优化 331

13.3.3 利用不可变性 332

13.4 对比延迟和急切初始化 333

小结 335

详情

本书详细阐述如何在设计、规划和实现软件时做出更好的决策,通过真实的案例,以抽丝剥茧的方式分析那些失误的决策,探讨还有哪些可能的解决方案,并对比各种方案的优缺点,摸索软件设计的常青模式。本书通过实例来说明某些决策的后果,例如代码重复如何影响系统的耦合与演进速度,以及如何在日期和时间信息方面隐藏细微差别。本书还介绍如何根据帕累托法则有效地缩小优化范围,确保分布式系统的一致性。 通过阅读本书,读者很快就可以将作者来之不易的经验应用到自己的项目中,以预防错误并采取更合适的编程决策。

图书摘要

相关图书

AI辅助编程实战
AI辅助编程实战
推荐系统:产品与算法解析
推荐系统:产品与算法解析
流式系统
流式系统
深入理解Prometheus监控系统
深入理解Prometheus监控系统
大模型应用开发:RAG入门与实战
大模型应用开发:RAG入门与实战
面向电子鼻的复合光气体传感方法
面向电子鼻的复合光气体传感方法

相关文章

相关课程