把设计模式用起来(3)用不好的原因之时机不对


上一篇:《把设计模式用起来(3)——用不好的原因 之 实践不足》icon-default.png?t=O83Ahttps://blog.csdn.net/nanyu/article/details/141939342

本篇继续讲设计模式用不好的常见原因,这是第二个:使用设计模式的时机不对。

二、时机不对

这里说的时机并不是单纯指软件研发周期中的时间阶段,而是指条件;更具体一点,指程序员应该满足什么条件,才是使用设计模式的好时机。

第一个条件前面说过了,得有问题。第二个条件,程序员需具备分析问题的能力。毕竟,只有通过分析才能从问题得到模式,如图:

图:  从问题到模式

这张图中最重要的是分析,最不重要的是模式。你甚至可以认为,一个没学过模式的程序员面对某个具体问题,只要分析得当,往往也能应用正确的模式,只是他自己不知道自己组织代码方式,被称作某某模式。

那么,我们要如何获得更强的分析能力呢?其一,对问题所属的业务逻辑越熟悉,对问题就越了解;其二,既然模式是此过程的结果,那么,熟悉模式也有利于我们强化分析问题的能力。由此,我们可以画一张更复杂一点的图:

图:  问题-分析-模式

尽管熟悉模式倒过来有助于理解问题,但程序员的更多努力仍需用在正向路径:熟悉业务→理解问题→分析问题→找到并应用正确的设计模式。

在理解问题的基础上,思考是否需要以及如何使用设计模式,这个结论听起来是如此的自然而然,几乎称得上是一条“公理”,但确实有很多程序员,特别是正在学习设计模式或自诩擅长设计模式的程序员,容易违背,犯“先有模式,然后到处找问题”的错误。

有同学说,“老师,虽然我心里确实装满设计模式,快溢出来的那种,但我并不会犯拿模式硬套问题的低级错误;相反,我总是在看到某个问题和某个模式匹配度很高的情况后,才会放心地套用设计模式。”

“只是放心吗?难道没有开心?”

“当然也开心啊!每当用对一次设计模式,我的内心充满成就感。”

这正是罪之所在:对设计模式的使用,有迫切的期待。这是贪欲,这是心魔,是引发更多软件研发问题的万恶之源。这种心态很容易让我们提倡的“工作逼迫你使用设计模式”,变成“你逼迫工作使用设计模式”。

说这么多,是在表达一个观点:使用设计模式应宁缺勿滥,因为,一段用错设计模式的代码往往比未使用模式的代码,更令后来人头痛。

每一个设计模式,都是在表达特定意图(Intent)的一种组织代码的方式。一段代码用上一个设计模式,意味着这段代码至少多出了两个明显的属性:一是它的意图,二是它的代码结构。一段什么设计模式都没用上代码,它就是一段代码;而一段代码用上了设计模式,代码中的设计模式就像人群有个显眼包,它会一直叫嚷:“看我,看我!我的设计意图是……我的实现结构是……”

那么,阅读者接收到这两个信息,是好事还是坏事?这就得看设计模式用对与否。幸福的代码都是相似的,不幸的代码却各有各的不幸,见表:

功能满足

(业务)

模式选择

(意图)

模式实现

(结构)

不幸的效果

表达了错误的意图且该意图未正确实现,程序不对劲

表达并实现了一个错误的意图,程序不对劲

意图正确,但该意图实现有错,程序不对劲

表达了错误的意图,且意图实现有误,但程序能跑对

用一个错误的模式实现了程序所需的功能

无论模式选对选错,也无论功能满足与否,表中几种应用情况中的设计模式,都会给代码阅读者带来更多干扰甚至误导,毕竟它们会说话。

这里特别讲讲表中最后一类不幸:“用一个错误的模式实现了程序所需功能”。这里最常见的“错误”,就是用了一个并不需要的模式。比如,使用策略模式,但直至系统下线也没用上第二个策略。再如,实现了一个无比强大的解释器,却只用它顺序执行指令。

丁小明严肃地从座位站起来:“老师,您应知道‘防御性编程’?程序员总是预设程序存在问题且将持续需要修改。所以,尽管在刚开始做设计时我们无法预测是否有第二个策略,但我们应该预设它有。至于解释器的例子,确实有点过度设计之嫌,但原则上,假定未来会有复杂的流程脚本需要执行,不对吗?”

这是很多同学都会有的疑惑:防御性编程是对的,可是切忌过度设计也是对的,日常编程做设计,能不能有一个简单明了且有效的方法,能迅速将我们从“加上!”和“不要!”,甚至是“砍掉!”的纠结中拉出来?

具体到何时使用设计模式这个问题,新的疑惑是:假设我还没有完全掌握业务需求就被迫写代码,那这些代码未来一定会变,那么,我现在是不是应该预防性地多加使用设计模式?毕竟,设计模式生来就是为了应对变化。让我们画张图来更清楚的表达:

因为会有变化,所以就马上用模式?

这种想法是错的,它把工作理想化了,并且弄错了工作首要目标。编程工作首先需保证正确实现当下的功能,然后才去考虑如何更好地就应对未来的变化。

为什么是这样先后次序?有两个理由,第一个略显功利:先快点做对,再慢慢做好,这样比较不会挨训,也可以反过来说,你上来就花大力气让代码能应对未来的65536种变化,大概率也得不到领导夸奖。第二个理由很客观:后者依赖前者,即,通常,正是在从不对,到不那么对,到最终做对的过程中,才能对眼下的功能、未来的变化、应有的设计这三者都产生深刻的理解。

丁小明再次发问:“有些功能未来会发生的变化,在程序员一行代码未写时,就可以猜出十之八群,这种情况下,程序员直接用上设计模式,也不合理吗?”这种理想状态当然存在。诸如:

  • 项目前期工作非常棒,需求清晰,设计详细,详细到此处该用什么模式都写出来了;
  • 你对此类问题有深刻理解,或有可靠的经验,此类功能后续的变化路径,你看得清楚、也看得长远;
  • 针对眼前这类问题,业界有教科书般的解决方法。

更多的时候,程序员处在水深火热中:

  • 甲方对现有的功能都说不清楚,遑论未来的变化;
  • 甲方基于防御性心理,罗列了各种各样的,未来可能的变化;
  • 甲方没怎么提,我方(领导、产品经理、程序员自己)设想了一大堆;
  • 甲方没怎么提,我方也没怎么想,但因为业务领域问题,我方并未正确理解甲方的真实需求;
  • 甲方提了一些,我方想了一些,并且我方正确地理解了甲方,只是不管甲方说还是我方想的,都有不对的地方;
  • ……

如果某一块的功能需求还比较模糊,作为乙方,千万不要自信可以通过一个“强大的设计”以做到“以不变应万变”,从而“立于不败之地”。此时的正确做法,应是直接的、快速地把当前所理解的功能做出来,并借助它尽快确定相关需求。

对问题的理解是一切设计的基础,不过,实现设计的过程,能极大帮助程序员全面地,深入地理解问题。多数时候,甲方能告诉你需求,但不能帮你做需求分析,而程序员因为跨领域的原因,所做的需求分析仍然有不少模糊与暧昧。比如,功能F1客观上需要的输入是I1、I2、I3,但程序员有可能在开始时认为是I1、I3和I4。有很多方法有助发现并纠正这个错误认知,其中一种非常高效的方法,就是编写代码尝试实现,在实现的过程,即可验证、强化、纠偏、补充程序员对业务系统的理解,包括厘清对象职责和理清对象关系,这二者是面向对象和用对设计模式的重要基础。

这是本小节的结论:除非你对问题理解到位透彻,否则不要一开始写代码就想着使用设计模式,在功能写对之前,更不要为了该功能“未来可能的变化”去套用设计模式。

这个结论只说了不要,那什么时候可以要呢?两点:

  1. 如果你对问题和业务所在领域非常有经验,那么,可以直接上设计模式以应对未来的变化;
  2. 否则,请在变化发生一次、两次、甚至三次的时候,再开始考虑使用设计模式重构代码。

台下有同学一脸落莫,问:“老师,我赞同第二点,但我们的领导不愿意给我重构所需的资源,比如时间,怎么办?”。这是回到上一小节“实践不足”中的情况B了。你可以屈服,从此不去想设计模式的好;也可以造反,说服领导,或想办法让自己当上领导;还也可以跳槽。无论如何,我都不建议一个程序员在这种恶劣的工作环境下,一边要抓紧完成业务功能,一边要努力用对设计模式。注意,当我说这话时,我站边技术主管。本来,主管只需检查你功能做对做错,现在,他需要同时检查功能和模式,其中后者还有需区分选择和实现上的错误,诸如:(a) 功能做错,模式选错,模式写错;(b) 功能做错,模式选对,模式写错; (c) 功能做对,模式选错,模式写对……(参见表2)。

猜,技术主管在这种场景下,最喜欢说的是哪一句话?答:“你这是要把错误雕成一朵花吗?”

来听一个源于真实案例改编的故事吧。主人公本应是丁小明,为节省篇幅,我们用“你”来代替。

假设问题为Q,正确答案是A;而因为理解有误,你以为需要实现的业务逻辑是B,并且你迅速想到B未来有可能发展成B1、B2、B3。想到这里,你一边啜吸奶茶发出嗞嗞嗞的声音;一边在脑海中构建原始思路C。为了更好地表达思路,也为了更好地应对B1、B2、B3,你果断选用设计模式D,D的意图是I。不过,由于你还是设计模式新人,所以你真实想表达的意图其实是J,而你写的模式很像D但又不是D,称作“D' ”。测试一把,确实能实现B,你充满成就感地提交了代码E。

技术主管开始审查E,他迅速嗅到D的味道;会心一笑,品一口咖啡,感觉自己像羽扇纶巾的诸葛亮,准备欣赏一场漂亮的战役,看看自己的员工如何通过设计模式优雅地干掉Q。10分钟过去,20分钟过去,30分钟过去……经验老到的主管,终于发现D` 和 D 只是长得像而已。

主管放下代码,翻出上次设计模式内训的文档,发现当时你写的答案那叫一个似是而非。他叹一口气,陷入自责。

1个小时过去,主管猛然领悟,你的真实意图是J。

2个小时过去,主管醍醐灌顶:“这家伙想要实现的业务逻辑是B,不是A!”,不过,纵使如此,也不应该用D啊?看一眼时间是夜里11点30分,还早;他拨通了你的电话。

夜深人静,主管的身边人呼吸均匀,偶尔吐一两句梦话。主管轻悄悄地换个姿式,继续听话里的你兴奋地谈着:“B1、B2、B3变化,可能还是保守了,我有一种预感,三个月以后,随着用户量的剧增,B99,B100,B101也是有可能发生的……”

这样的破事来个三四次,故事中主人公的工作类型就会发生变化,参见上一小节“实践不足”的情况D、情况C、情况A。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/875391.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

望繁信科技与华恒生物正式签约,共同开启流程数字化转型新篇章

近日,上海望繁信科技有限公司(简称“望繁信科技”)与安徽华恒生物科技股份有限公司(简称“华恒生物”)成功举行了战略合作签约仪式。作为全球领先的合成生物制造企业,华恒生物将引入望繁信科技的流程智能管…

3分钟带你了解什么是数据目录

什么是数据目录? 数据目录,顾名思义就是“数据的目录”。这里的“数据”指的是元数据。数据目录通过管理这些元数据,形成一个可用的数据清单,使数据开发者、数据分析师等人员能够通过查阅和搜索等操作,快速找到所需的数…

4052A/4052B/4052C/4052D/4052E/4052F/4052G /4052H信号/频谱分析仪

4052A/4052B/4052C/4052D/4052E/4052F/4052G /4052H信号/频谱分析仪 苏州新利通 Ceyear 4052具备出色的测试动态范围、相位噪声、幅度精度和测试速度,具备频谱分析、I/Q分析、实时频谱分析、瞬态分析、矢量信号分析、脉冲分析、音频分析等丰富的测试功能。 Ceyear…

长沙自闭症寄宿学校推荐,为孩子开启光明未来

在长沙这座历史悠久而又充满活力的城市中,自闭症儿童的成长与教育问题牵动着无数家庭的心。家长们渴望为孩子找到一所能够提供专业康复、温馨关怀与全面教育的学校,为他们的未来铺设一条光明之路。虽然本文起始于长沙的期盼,但我们的目光已跨…

SpringSecurity原理解析(二):认证流程

1、SpringSecurity认证流程包含哪几个子流程? 1)账号验证 2)密码验证 3)记住我—>Cookie记录 4)登录成功—>页面跳转 2、UsernamePasswordAuthenticationFilter 在SpringSecurity中处理认证逻辑是在UsernamePas…

Windows10 如何配置python IDE

Windows10 如何配置python IDE 前言Python直接安装(快速上手)Step1.找到网址Step2.选择版本(非常重要)Step3. 安装过程Step4. python测试 Anaconda安装(推荐,集成了Spyder和Pycharm的安装)Step1…

使用功率分析仪测量和分析电抗器(电感器)的方法

高频电抗器用于电动汽车 (EV) 和混合动力汽车 (HEV) 的各种位置。例如,电池和逆变器之间的升压 DC/DC 转换器以及电池充电电路中的 AC/DC 转换器。为了提高整个系统的效率,必须提高每个组成电路的效率,而电抗器是造成这些电路大量损耗的元件之…

Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理

Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理 目录 Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理 一、简单介绍 二、FBO 简单介绍 三、案例实现原理 四、注意事项 五、简…

03 Flask-添加配置信息

回顾之前学习的内容 02 Flask-快速上手 Flask 中最简单的web应用组成 1. 导入核心库 Flask from flask import Flask2. 实例化 web应用 注意:不要漏了 app Flask(__name__) 中的 __name__ 表示:是从当前的py文件实例化 app Flask(__name__)3. 创…

力扣每日一题:1372.二叉树中的最长交错路径

题目 给你一棵以 root 为根的二叉树,二叉树中的交错路径定义如下: 选择二叉树中 任意 节点和一个方向(左或者右)。如果前进方向为右,那么移动到当前节点的的右子节点,否则移动到它的左子节点。改变前进方…

力扣213-打家劫舍 II(Java详细题解)

题目链接:213. 打家劫舍 II - 力扣(LeetCode) 前情提要: 本体是打家劫舍的一个变形题,希望大家能先做198. 打家劫舍 - 力扣(LeetCode),并看一下我上题的讲解力扣198-打家劫舍&…

制证书、制电子印章、签章 -- 演示程序说明

ofd签章系统涉及证书的制作、电子印章制作、签章、验章等环节。关于ofd签章原理,本人写过多篇文章进行了阐述; 见文章《ofd板式文件 电子签章实现方法》、《一款简单易用的印章设计工具》、《签章那些事 -- 让你全面了解签章的流程》。 为了进一步加深对签章过程的理…

RK3229 ADNROID9 hdmi与耳机口同出声音

声卡0怎么配置才能跟HDMI同时输出一样的声音,下面是具体描述: 1、硬件连接 声卡0的连接是芯片的ADC音频输出脚直接接到DA芯片输出 2、cat /proc/asound/cards 0 [rockchiprk3229 ]: rockchip_rk3229 - rockchip,rk3229 rockchip,rk3229 1 [rockchiphdmi …

MFC工控项目实例之十一板卡测试信号输入界面

承接专栏《MFC工控项目实例之十添加系统测试对话框》 相关代码 1、在BoardTest.h文件中添加代码 class CBoardTest : public CDialog { // Construction public:CBoardTest(CWnd* pParent NULL); // standard constructorCButtonST m_btnStart[16];CWinThread* pThread…

FAT32文件系统详细分析 (格式化SD nandSD卡)

FAT32 文件系统详细分析 (格式化 SD nand/SD 卡) 目录 FAT32 文件系统详细分析 (格式化 SD nand/SD 卡)1. 前言2.格式化 SD nand/SD 卡3.FAT32 文件系统分析3.1 保留区分析3.1.1 BPB(BIOS Parameter Block) 及 BS 区分析3.1.2 FSInfo 结构扇区分析3.1.3 引导扇区剩余扇区3.1.4 …

RocketMQ 基础入门

文章内容是学习过程中的知识总结,如有纰漏,欢迎指正 文章目录 前言 RocketMQ 特点 RocketMQ 优势 1. RocketMQ 基本概念 1.1 NameServer 1.1.1 NameServer作用 1.1.2 和zk的区别 1.1.3 高可用保障 1.2 Broker 1.2.1 部署方式 1.2.1.1 单 Master 1.2.1.2 …

C语言 | Leetcode C语言题解之第396题旋转函数

题目&#xff1a; 题解&#xff1a; #define MAX(a, b) ((a) > (b) ? (a) : (b))int maxRotateFunction(int* nums, int numsSize){int f 0, numSum 0;for (int i 0; i < numsSize; i) {f i * nums[i];numSum nums[i];}int res f;for (int i numsSize - 1; i &g…

多维时序 | Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变量时间序列预测

多维时序 | Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变量时间序列预测 目录 多维时序 | Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变…

Docker部署tenine实现后端应用的高可用与负载均衡

采用Docker方式的Tengine 和 keepalived 组合模式可以实现小应用场景的高可用负载均衡需求 目录 网络架构一、环境准备二、软件安装1. 下载Tenine镜像2. 下载Keepalived镜像3. 制作SpringBoot镜像 三、软件配置1. 创建应用容器2. 代理访问应用3. 创建Keepalived4. 测试高可用 网…

CSP-J算法基础 树状结构与二叉树

文章目录 前言树状结构树状结构的基本概念&#xff1a;为什么需要树状结构&#xff1f;优点树状结构的示例 二叉树什么是二叉树&#xff1f;二叉树的类型什么样的树不是二叉树&#xff1f;二叉树的五种形态 完全二叉树相关概念完全二叉树的定义&#xff1a; 相关概念1. **高度&…