软件开发中的简单设计与反思性设计实践
在软件开发领域,追求高效、简洁且易于维护的代码是每个开发者的目标。本文将探讨简单设计与反思性设计的相关概念、方法以及实践案例,帮助开发者提升代码质量和开发效率。
1. 自我文档化代码
在编写代码时,简单性的评判标准并非取决于个人,而是团队其他成员或未来的代码维护者。为避免设计过于复杂,应使用团队和语言通用的习惯用法和模式。若引入新想法,需先与团队成员沟通。
命名是编写自我文档化代码的强大工具,变量和函数的名称应清晰反映其意图。当函数复杂时,可使用提取函数重构方法为每个部分命名;条件语句难以理解时,可使用函数或中间变量为其各部分命名。
编写好的命名和简单的代码并非易事,但有三种方法可使其变得更容易:
-
结对编程或集体编程
:提供更多视角和想法,便于讨论和改进代码表达。
-
重构
:当下尽力编写代码,后续再进行优化。
-
集体代码所有权
:当遇到不清晰的代码时,理解原作者意图并进行重构,使意图更明显。
2. 发布接口
发布接口会限制代码的更改能力。一旦接口发布给团队外部人员,更改接口通常需要大量成本和精力,因此要避免破坏他人依赖的内容。
有些团队将每个公共方法都视为发布接口,认为定义后不应更改,这并非良策,会阻碍设计的改进。更好的做法是根据需要更改未发布的接口,并相应更新调用者。
若代码供团队外部使用,则需要发布接口。但应尽量减少发布接口的数量,仔细权衡每个接口的成本和收益,并尽可能推迟发布决策,以优化设计。对于为第三方创建库的团队,需提前精心设计接口,接口越小越好。
3. 性能优化
现代计算机系统复杂,开发者对性能的直觉往往是错误的。基于简短性能测试的优化技巧并不适用,优化现代系统需采用整体方法,即测量代码的实际性能,找出热点并进行优化,而不是凭猜测和假设,要不断进行性能分析。
通常,字符串拼接、函数调用和装箱拆箱等看似消耗性能的操作并非关键问题,性能往往受网络、数据库或文件系统的影响,也可能是二次算法、线程争用或非顺序内存访问等问题导致。
4. 简单设计的相关问题解答
- 已知未来需要某个功能,是否应提前设计钩子? :除非该功能是本周工作的一部分,否则不应提前添加钩子,因为计划可能改变,提前添加会增加不必要的复杂性。而且,进化式设计会随着时间降低更改成本,等待越久,更改成本越低。
- 忽略某个功能会使未来实现更困难怎么办? :可寻找消除风险的方法,而不是直接编码支持该功能,“风险驱动架构”有更详细的介绍。
5. 简单设计的前提和指标
简单设计需要通过重构、反思性设计和增量设计不断改进,否则设计无法随需求演变。但不能将简单设计作为糟糕设计的借口,简单性需要深思熟虑。
创建简单设计时,有以下指标:
|指标|描述|
|----|----|
|不提前编写未来功能代码|团队不会为未来的故事编写代码|
|工作完成更快|不构建当前不需要的东西,提高工作效率|
|支持任意更改|设计能够轻松支持任意更改|
|代码更改局部化|新功能可能需要大量新代码,但对现有代码的更改是局部且直接的|
6. 替代方案和实验
传统的设计方法是预测未来变化并提前构建支持这些变化的设计,称为“预测性设计”。虽然许多团队使用这种方法取得了成功,但它依赖于正确预测新需求和故事。若预测偏差较大,可能需要重写大量基于错误假设的代码,导致代码库出现长期缺陷。
一般来说,本文介绍的简单设计技术比预测性设计更有效,但也可将两者结合。若采用预测性设计,最好聘请在特定行业有丰富经验的程序员,他们更有可能正确预测变化。
7. 反思性设计
传统设计方法假定代码不应更改,通过添加新代码支持新功能,并通过继承、依赖注入等方式构建可扩展性“钩子”,遵循“开闭原则”。而敏捷团队创建的简单设计不预测未来,没有这些钩子,而是具备重构代码和更改设计的能力,这种方法称为反思性设计。
反思性设计与预测性设计相反,它不推测未来,只关注当前的更改。其工作流程如下:
graph LR
A[分析现有代码] --> B[识别设计缺陷]
B --> C[选择改进点]
C --> D[逐步重构代码]
D --> E{是否完成任务且代码干净}
E -- 否 --> B
E -- 是 --> F[结束]
- 分析现有代码 :若不熟悉代码,可进行逆向工程,复杂代码可绘制类图或序列图辅助理解。
- 识别设计缺陷 :思考难以理解、运行不佳的部分,以及当前任务可能遇到的问题。
- 选择改进点 :考虑能清理代码并使当前任务更轻松的设计更改,重大更改需与团队成员讨论。
- 逐步重构代码 :注意设计更改在实践中的效果,若不理想则调整方向。
- 重复直至完成任务 :确保代码比开始时更干净。
8. 反思性设计的实践案例
曾有一次需要替换网站的登录基础设施,从旧的认证提供商 Persona 切换到新的 Auth0。采用反思性设计方法,逐步进行更改:
-
逆向工程代码设计
:从现有登录端点开始,分析文件的导入和依赖关系。发现登录端点依赖 PersonaClient 和 SubscriberAccount,PersonaClient 依赖 HttpsRestClient,SubscriberAccount 依赖 RecurlyClient,而 RecurlyClient 也依赖 HttpsRestClient。
-
识别设计缺陷
:
- 逻辑与基础设施未分离,SubscriberAccount 直接依赖 RecurlyClient。
- SubscriberAccount 功能不明确,用户相关逻辑由单独的 User 类负责。
- 基础设施类(PersonaClient、RecurlyClient 和 HttpsRestClient)和登录端点都没有测试,存在风险。
-
确定改进重点
:由于缺乏可测试性是最大问题,决定先使 HttpsRestClient 可空,并添加窄集成测试,然后逐步使其他类可空,最后实现 Auth0Client。
-
逐步重构代码
:
1. 为 HttpsRestClient 添加窄集成测试并清理边缘情况(3 小时)。
2. 使 HttpsRestClient 可空(1 小时)。
3. 使 RecurlyClient 可空(1.25 小时)。
4. 使 PersonaClient 可空(0.75 小时)。
5. 修改 HttpsRestClient 以更好支持 Auth0Client 的需求(0.75 小时)。
6. 实现 Auth0Client(2 小时)。
完成 Auth0Client 后,实现一个功能标志,用于在生产环境中手动测试 Auth0 登录端点,同时对普通用户隐藏。同样采用反思性设计方法,审查相关类,识别测试问题并进行改进。
9. 逆向工程设计
反思性设计的第一步是分析现有代码并逆向工程其设计。最好的方法是向团队成员请教,通过白板草图进行交流,这种方式学习快且易引发协作改进。
若团队无人了解设计或想自己深入研究,可从思考源文件的职责入手,选择与当前任务最相关的文件,通常可从用户界面开始追踪依赖关系。
打开文件后,浏览方法和函数名,确认或修正对文件职责的猜测,还可查看测试名获取更多线索。分析文件的依赖关系,重复此过程直到依赖与当前更改无关。
了解涉及的文件和职责后,查看它们之间的关系,复杂时可绘制图表,可使用正式建模技术或临时草图,手动绘制能更深入理解代码。
10. 识别改进点
寻找设计改进时,要记住所有代码都有潜在的美感。不要轻易认为代码糟糕,即使代码看似不佳,也可能有其背后的设计意图。开发者应理解代码的原始设计,找出其中的闪光点并进行改进。
综上所述,简单设计和反思性设计在软件开发中具有重要意义。通过合理运用结对编程、重构、集体代码所有权等方法,以及遵循反思性设计的流程,开发者能够提高代码的质量和可维护性,更好地应对不断变化的需求。在实际开发中,应根据具体情况灵活选择设计方法,不断优化代码,以实现高效、简洁的软件开发目标。
软件开发中的简单设计与反思性设计实践
11. 反思性设计的优势和挑战
反思性设计在软件开发中具有显著优势,但也面临一些挑战,以下为你详细分析:
| 方面 | 优势 | 挑战 |
|---|---|---|
| 灵活性 | 只关注当前更改,能快速响应需求变化,避免过度设计 | 需要开发者具备较强的分析和重构能力,对开发者要求较高 |
| 代码质量 | 通过不断改进代码,使代码更简洁、易理解和维护 | 频繁的重构可能引入新的错误,需要完善的测试机制保障 |
| 团队协作 | 促进团队成员交流,有助于知识共享和共同提升 | 可能因成员理解差异导致重构方向不一致,需加强沟通协调 |
12. 反思性设计与其他设计方法的比较
为了更好地理解反思性设计,将其与预测性设计进行对比,具体如下:
| 设计方法 | 设计理念 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 预测性设计 | 预测未来需求,提前构建支持变化的设计 | 需求相对稳定、可预测的项目 | 能提前规划,减少后期大规模更改 | 依赖准确预测,预测偏差会导致大量重写代码 |
| 反思性设计 | 关注当前更改,根据实际情况改进代码 | 需求变化频繁、不确定的项目 | 灵活适应变化,逐步优化代码 | 对开发者能力和团队协作要求高,可能短期效率不高 |
13. 反思性设计的应用建议
在实际应用反思性设计时,可参考以下建议:
-
培养反思习惯
:开发者在日常工作中要时刻关注代码设计,遇到问题及时反思并寻求改进。
-
加强团队协作
:团队成员之间要积极交流,分享对代码设计的看法和经验,共同推动代码优化。
-
结合测试驱动开发
:在重构代码过程中,利用测试驱动开发确保代码功能的正确性,降低引入新错误的风险。
-
持续学习和实践
:不断学习新的设计理念和技术,通过实践积累经验,提高反思性设计的能力。
14. 反思性设计的未来发展趋势
随着软件开发行业的不断发展,反思性设计也将呈现一些新的趋势:
-
与人工智能结合
:借助人工智能技术分析代码,自动识别设计缺陷并提供改进建议,提高反思性设计的效率和准确性。
-
更注重用户体验
:在设计过程中,不仅关注代码的技术层面,还会更加注重用户体验,使设计更贴合用户需求。
-
跨领域融合
:与其他领域的设计方法和理念融合,如敏捷设计、精益设计等,形成更全面、高效的设计体系。
15. 总结
简单设计和反思性设计是软件开发中非常重要的理念和方法。简单设计通过自我文档化代码、合理处理发布接口、科学进行性能优化等,确保代码简洁、易维护;反思性设计则以关注当前更改、不断改进代码为核心,能够灵活应对需求变化,提升代码质量。
在实际开发中,开发者应根据项目的特点和需求,灵活运用这些设计方法。同时,要不断学习和实践,提高自身的设计能力和团队协作水平。通过持续优化代码,我们能够打造出更高效、更稳定的软件系统,满足不断变化的市场需求。
以下是反思性设计的整体流程 mermaid 流程图,帮助大家更清晰地理解:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(分析现有代码):::process
B --> C(识别设计缺陷):::process
C --> D(选择改进点):::process
D --> E(逐步重构代码):::process
E --> F{是否完成任务且代码干净}:::decision
F -- 否 --> C
F -- 是 --> G([结束]):::startend
希望本文能为软件开发人员在设计和开发过程中提供有益的参考,让大家在面对复杂多变的软件开发任务时,能够更加从容地运用简单设计和反思性设计的方法,提升软件的质量和开发效率。
超级会员免费看
10万+

被折叠的 条评论
为什么被折叠?



