当前位置: 首页 > news >正文

程序的“通用性”和“过度设计”困境

在软件工程的实际操作中,我常常遇到这样一种现象:本可以用简单代码解决的问题,却因为设计者过分关注“通用性”、“可维护性”和“可扩展性”而变得不必要地复杂,难以理解。

他们的思维方式是这样的:“这段代码未来可能会在更多场景中使用,所以我现在应该考虑它的可扩展性。”结果,他们在代码中加入了各种“框架结构”,以便在未来有新需求时,无需修改就能在新的地方使用。

“通用性”的价值与误区

作者并不否认“通用性”的价值,我的一些程序也具有很高的通用性。然而,很多人所谓的“通用性”实际上适得其反,这种现象通常被称为 “过度设计” 。关于过度设计,有一个有趣的故事:

传说在上世纪60年代美国和俄罗斯的“太空竞赛”期间,NASA 遇到了一个严重的技术问题:宇航员需要一支能在外太空真空环境中书写的笔。最终,NASA 花费了150万美元开发了这样一支笔。不幸的是,这种笔在市场上并不畅销。

俄罗斯人也面临同样的问题,他们则用了铅笔。

虽然这个故事是虚构的,但它具备了伊索寓言的力量。现在让我们看看软件行业,可能会发现:

代码需要“重用”的场合比您想象的要少得多

许多人在写程序时,连“当前异常”都处理不好,却关注“未来的需求”。他们总是想象别人会重用这段代码。然而,实际上,由于设计过于复杂,理解这些设计所需的心智努力已经超过了从头开始的成本。因此,大多数人根本不会使用他们的代码,而是重新写一个。有些人最终会发现自己甚至看不下去之前写的代码,更愿意删除它并重新开始,而不是谈论重用。

我们用一个简单的例子来说明这一点。假设我们需要写一个简单的程序来计算两个整数的和:

def add(a, b):return a + bresult = add(3, 5)
print(result)  # 输出 8

这个函数非常直观,可以很好地满足当前需求。然而,一些程序员可能会考虑未来可能需要更多的计算能力,因此设计了一个复杂的框架:

class Operation:def execute(self, a, b):raise NotImplementedErrorclass AddOperation(Operation):def execute(self, a, b):return a + bclass OperationFactory:@staticmethoddef get_operation(op_type):if op_type == 'add':return AddOperation()# 可以添加更多操作类型。raise ValueError("不支持的操作类型")operation = OperationFactory.get_operation('add')
result = operation.execute(3, 5)
print(result)  # 输出 8

虽然这个设计提供了可扩展性,但在当前只需计算两个整数和的场景中,这个设计无疑增加了代码的复杂性和理解成本。

实际修改代码所需的工作量比你想象的少

还有一种情况是,这些设计为“共享”而写的代码在很多地方并没有被使用,因此即使你完全手动修改它们,也不会花费很多时间。现在,随着 IDE 技术的发展和各种高级重构工具的出现,批量代码修改不再特别麻烦。过去需要在逻辑层面设计可维护性,现在只需在 IDE 中点击几下就能轻松完成。因此,在考虑设计框架之前,你还应该考虑这些因素。

例如,在上面提到的复杂设计中,如果我们需要修改加法操作,我们需要修改多个类和文件。在这种情况下,我们可能会发现简单的函数实现更容易维护和修改。

“考虑”通用性并不意味着你已经准确“掌握”了通用性

许多人考虑通用性,但他们并不准确地看到未来可能需要修改的部分,因此他们的设计往往错失重点。当新需求出现时,发现最初认为可能变化的部分并没有变化,而那些认为不变的部分却发生了变化。

能够准确预测未来需求并从代码中抽象出真正通用的框架是非常困难的任务。它不仅需要编程技能,还需要强大的观察现实世界事物的能力。许多人设计的框架只是复制他人的经验,无法适应实际需求。Java 世界中的许多设计模式就是由这些半吊子的人创造的。

例如,假设我们需要添加一个新的操作,如减法。如果我们没有准确掌握哪些部分需要通用设计,我们可能会发现现有框架不适用于新需求:

class SubtractOperation(Operation):def execute(self, a, b):return a - boperation = OperationFactory.get_operation('subtract')
result = operation.execute(10, 3)
print(result)  # 输出 7

在这里,我们需要修改 OperationFactory 类以支持减法操作。这表明,虽然我们考虑了通用性,但我们没有准确掌握未来的需求,导致框架的灵活性有限。

初始设计的复杂性

如果在初始设计中过早地考虑未来需求,可能会导致不必要的复杂性和问题。因此,这种对未来变化的考虑阻碍了进展。原本如果我们专注于解决当前问题,可以取得很好的结果。然而,由于“通用性”带来的复杂性,设计者每次都要多费一些心思,无法创建优雅的程序。

例如,在上面提到的复杂设计中,如果我们只需要一个简单的加法操作,复杂的框架反而使初始设计变得臃肿且难以理解:

class Operation:def execute(self, a, b):raise NotImplementedErrorclass AddOperation(Operation):def execute(self, a, b):return a + bclass OperationFactory:@staticmethoddef get_operation(op_type):if op_type == 'add':return AddOperation()raise ValueError("不支持的操作类型")# 初始设计的复杂性。
operation = OperationFactory.get_operation('add')
result = operation.execute(3, 5)
print(result)  # 输出 8

相比之下,只需执行一个简单的函数即可。

def add(a, b):return a + bresult = add(3, 5)
print(result)  # 输出 8
理解和维护框架代码的开销

如果你设计了一个框架式的代码,每个程序员都需要理解这个框架的构建,才能在这个框架下编写代码,这带来了学习成本。一旦发现这个框架有设计问题,依赖它的代码可能需要修改,这带来了修改成本。因此,在设计中加入“通用性”的初衷是为了节省未来的修改成本,但可能会增加当前的开发和维护成本。

例如,在复杂的框架设计中,添加新操作类型需要理解多个类及其关系:

class MultiplyOperation(Operation):def execute(self, a, b):return a * b# 理解和维护的成本。
operation = OperationFactory.get_operation('multiply')
result = operation.execute(3, 5)
print(result)  # 输出 15

而在简单的函数实现中,添加新功能相对简单:

def multiply(a, b):return a * bresult = multiply(3, 5)
print(result)  # 输出 15
结论

在软件工程中,设计的“通用性”确实是一个重要的考虑因素,但我们必须谨慎。过度设计和过度工程不仅不会提高代码的可维护性,反而会增加开发和维护成本。在实际项目中,我们应该根据当前需求采用最简单直接的解决方案,而不是为了未来的可能性增加当前的复杂性。

总的来说,简洁直观的代码往往比复杂的框架更能满足实际需求。我们应在设计中保持平衡,避免过度工程,专注于解决当前问题,同时为未来扩展留有余地。

相关文章:

  • zookeeper学习、配置文件参数详解
  • SSM旅游系统
  • WDF驱动开发-WDF总线枚举(一)
  • obsidian中用check list 打造待办清单
  • 在阿里云使用Docker部署MySQL服务,并且通过IDEA进行连接
  • 软件介绍—Fluent Reader (RSS阅读器)
  • SparkSQL的分布式执行引擎-Thrift服务:学习总结(第七天)
  • Java学习 - 网络TCP,UDP协议讲解
  • 基于uni-app和图鸟UI开发上门服务小程序
  • linux库函数 gettimeofday() localtime 使用demo
  • 达梦8 通过日志解释数据守护系统的关停顺序
  • 【Java算法】滑动窗口 下
  • 基于JSP的“塞纳河畔左岸”的咖啡馆管理系统
  • Linux通配符总结
  • 【初体验 threejs】【学习】【笔记】hello,正方体 3!
  • Akka系列(七):Actor持久化之Akka persistence
  • Android框架之Volley
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • exports和module.exports
  • github从入门到放弃(1)
  • GitUp, 你不可错过的秀外慧中的git工具
  • HTTP 简介
  • JAVA 学习IO流
  • java2019面试题北京
  • JavaScript 一些 DOM 的知识点
  • maya建模与骨骼动画快速实现人工鱼
  • Median of Two Sorted Arrays
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • overflow: hidden IE7无效
  • python3 使用 asyncio 代替线程
  • 动态规划入门(以爬楼梯为例)
  • 构造函数(constructor)与原型链(prototype)关系
  • 开发基于以太坊智能合约的DApp
  • 那些被忽略的 JavaScript 数组方法细节
  • 实战|智能家居行业移动应用性能分析
  • 数据仓库的几种建模方法
  • ionic异常记录
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​Java并发新构件之Exchanger
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ​业务双活的数据切换思路设计(下)
  • #git 撤消对文件的更改
  • #微信小程序:微信小程序常见的配置传旨
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • $(selector).each()和$.each()的区别
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)STM32单片机上位机
  • (23)Linux的软硬连接
  • (Qt) 默认QtWidget应用包含什么?
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (二)换源+apt-get基础配置+搜狗拼音
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐