为什么我们需要设计模式?

简介: 设计模式解决什么问题设计模式一直被认为是一门难以学习的课程。究其原因是因为我们不清楚设计模式在解决哪些问题方面发挥作用。简言之,设计是为了实现特定的目标,基于不断更新的局部知识来获得解决方案的过程。我们通常熟悉的范式是在几乎掌握所有知识的情况下解决问题,例如解数学题、物理题等。然而,在软件编程过程中,我们掌握的知识往往不充分,而且会不断更新,因此需要我们关注有哪些知识,没有哪些知识,可以获取哪些知

设计模式解决什么问题

设计模式一直被认为是一门难以学习的课程。究其原因是因为我们不清楚设计模式在解决哪些问题方面发挥作用。简言之,设计是为了实现特定的目标,基于不断更新的局部知识来获得解决方案的过程。我们通常熟悉的范式是在几乎掌握所有知识的情况下解决问题,例如解数学题、物理题等。然而,在软件编程过程中,我们掌握的知识往往不充分,而且会不断更新,因此需要我们关注有哪些知识,没有哪些知识,可以获取哪些知识,如何获取知识等问题。当我们面对这些问题时,往往会感到困惑,而这种困惑稍不留神就可能带来很多漏洞,这对我们系统的稳定性构成了极大的挑战。

一些例子

怎么做错误处理

在我们的编码过程中,经常需要对于错误进⾏处理,例如Java中的Try-Catch模式,Golang中的强制错误捕捉模式。这两种模式基于两种关于“错误”的认知假设:

  1. Try-Catch:我们知道错误的范围,根据错误的影响范围,抛弃错误范围内的相应操作。
  2. 强制错误捕捉模式;我们知道错误的特性,根据错误的分类与特性,进⾏相应的恢复操作。

在这两种模式中,需要根据我们的知识来处理错误才能够使得系统稳定地运行。如果我们的错误处理不匹配我们的知识,会使得错误处理并不能cover产生的错误,此时BUG就产生了。

对于Try-Catch模式来说,合理的错误处理应该有如下特性:Try和Catch中的内容是等价的。因为我们不知道Try中的代码和Catch中的代码哪⼀部分会被执⾏,⾃然我们就应该要求Try和Catch中的内容是是等价的(⾄少是同等可接受的)。例如:

				//错误
        Resource r1 = new Resource();
        Resource r2 = new Resource();
        try {
            r1.init();
            r2.init();
        } catch (Exception e) {
            r2 = defaultResourceForR2;
        }

        //正确
        Resource r1 = new Resource();
        Resource r2 = new Resource();
        try {
            r1.init();
            r2.init();
        } catch (Exception e) {
            r1 = defaultResourceForR1;
            r2 = defaultResourceForR2;
        }

对于错误捕捉模式,我们一定要搞清楚我们的代码遇到的问题是什么,并确保我们的错误处理能够解决掉相应的问题。当我们不清楚会发生何种错误时,应当采取兜底方案,彻底放弃对报错代码的执行结果的预期。也就是说:使⽤特定处理⼿段时,判断条件应该为包含判断如ErrorType.contains(err);当使⽤了err!=nil时,必须使⽤使⽤兜底⽅案,例如取消执行,继续向上报错等。⼀个典型的反例:

	//错误代码
	err := sendData(data)
	if err != nil {
		reSendData(data)
	}
	//正确代码
	err := sendData(data)
	//已知错误为⽹络波动时,重试
	if NetworkFluctuations.contains(err) {
		reSendData(data)
	}else if err != nil {
    //不知道错误类型时,采⽤兜底⽅案报错并返回
		record(err)
		return
	}

这是一段关于ETL软件处理错误的示例,采用了三段式回滚设计,以确保数据能够传输到下游。对于网络波动,使用重试处理是可行的。为了确保数据不丢失,不设置重试上限也是正确的。然而,问题在于err不仅仅是网络故障这一种情况。例如,当err本身是由于下游拒绝接收数据时,这个错误处理逻辑会形成一个死循环。

什么时候应该记录⽇志:

我们关于系统的知识并不是完备的,这⼀点在软件编码中体现地很明显。处理这种不完备的重要⼿段之一是获取新知识。我们在代码的Debug过程中,经常会碰到⼀整个调⽤链路上,不知道哪⼀点出现了问题,导致Debug的⼈效极低。那么,如何留日志才能够高效的解决故障?

⽇志本⾝是为了将代码运⾏过程中的未知点变成已知部分的内容。当我们不知道代码运⾏会发⽣什么时,就应该留⽇志。特别是在进⾏与他人进行交互,例如⽤户上传数据或调⽤其他部门的SDK等情况时,更应该留下日志。因为我们难以完全预期他⼈的⾏为,如果没有⽇志,我们就无法了解系统发⽣了什么。

通过⽇志,我们应该能够获取处理未知情况,特别是异常状况的相关知识。例如,为了弄清访问下游网络时未收到正确回复数据的问题,我们应该编写日志。但是,如果我们只记录了我们访问下游的日志,仅能确定我们发送了相关请求,并无法处理未收到正确回复数据的问题。只有记录了下游的响应时间,我们才能知道上下游之间的网络通信是否畅通;只有记录了下游返回的响应编码,我们才能知道我们的请求是否符合格式;只有记录了下游返回的响应内容,我们才能知道下游回复的内容是否符合我们的预期。

为什么要先写单元测试,再去进行具体的编程

这是因为单元测试是一种描述编码需求的知识,是关于目标的知识,而不是关于路径的知识。当然,确定目标后才能完成路径的实现。研发人员经常抱怨需求不清晰,那么是否能反问一个问题:什么是明确的需求?我认为明确的需求就是给出一个函数F,对于我们的代码x,如果F(x)=1则完成了需求;如果F(x)=0则未完成需求。因此,一个明确的需求自然包含单元测试。

举个例子,同学A分配给同学B和C两个子任务。B同学需要编写一个函数,能够把服务器上指定位置的临时文件传递到永久存储中,并返回永久存储中的地址。C同学需要根据永久存储地址将内容同步到一个指定的Redis中。如果我们不写单元测试,直接进行编码开发,就会面临以下问题:

  1. 永久存储是什么,是对象存储还是MySQL?
  2. 服务器上指定位置是否需要特殊权限?Redis中是否需要特殊权限?
  3. Redis中的存储格式是什么?选择什么作为RowKey?

这些问题不一定能在编码之前想清楚。如果出现不对齐的情况,可能会导致代码功能失效,需要重新编写。而如果我们先写单元测试,就能在写单元测试的过程中搞清楚很大一部分之前没有注意到的问题。

展望:

虽然本文在工程实践中解决了一些问题,但远远没有完善。还有很多现实和理论上需要处理的问题。例如,运用设计模式本身就会带来成本,如何平衡成本与带来的收益?我们从经验中总结出的设计模式只是一种“博物馆”式的解决方案,能否建立一种规则化的方案,对所有知识,而不仅仅是代码知识进行形式化建模?这些问题仍有待解决。

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
西门子S7-1200有什么功能特点?应用范围有哪些?CPU型号及模块类型有哪些?
S7-1200是西门子公司新推出的一款面向离散自动化系统和独立自动化系统的低端PLC。S7-1200采用了模块化设计,具备强大的工艺功能,适用于多种场合,可以满足不同的自动化需求。
西门子S7-1200有什么功能特点?应用范围有哪些?CPU型号及模块类型有哪些?
|
2月前
|
存储 缓存 人工智能
Agent运行机制拆解:上下文、检查点、任务恢复以及循环执行、资源管控完整流程23.3
本文系统解析大模型Agent的核心运行机制,涵盖执行循环、上下文与状态管理、工具反馈、检查点、停止条件、资源管控及人工接管等十大模块,揭示其“自主迭代、容错可溯、可控可兜底”的本质,助力开发者构建稳定智能体应用。
298 2
|
物联网 网络安全 iOS开发
Cisco Packet Tracer 9.0 新增功能简介
Cisco Packet Tracer 9.0 (macOS, Linux, Windows) - 思科网络模拟工具Cisco Packet Tracer 9.0 (macOS, Linux, Windows) - 思科网络模拟工具
1240 3
Cisco Packet Tracer 9.0 新增功能简介
|
2月前
|
人工智能 API 数据处理
在线工作流,让非结构化数据处理随叫随到
Dataphin V6.3推出“在线工作流”,将非结构化数据处理能力升级为按需调用的HTTP API,支持秒级/分钟级响应。适用于销售录音分析、合同智能审查、工单图片诊断等即时场景,无需批处理或复杂实时架构,画布编排、一键发布,真正实现“来一个、处理一个、马上出结果”。
186 0
|
存储 Java 数据库
如何开发人事及OA管理系统的会议管理板块?(附架构图+流程图+代码参考)
人事及OA系统是现代企业管理的重要工具,整合人力资源与办公流程,提升效率。其会议管理板块可优化会议室预约、冲突检测、审批流程及数据统计,助力企业高效协作。本文详解功能设计、开发技巧与实现方案。
|
9月前
|
数据采集 Go
轻量Golang爬虫:带HTTP代理实现
Golang爬虫仅用标准库即可高效运行。本文分两步演示:先实现无代理的百度首页抓取;再通过http.Transport配置HTTP代理,替换真实IP、降低封禁风险,保障隐私与稳定性。
315 11
|
机器学习/深度学习
《揭秘机器学习中的交叉验证:模型评估的基石》
交叉验证是机器学习中评估模型性能的关键技术,旨在提高模型的可靠性和泛化能力。通过将数据集划分为多个子集,交叉验证有效抵御过拟合风险,最大化数据利用效率,并精准筛选最优模型。常见的方法包括K折交叉验证、留一交叉验证、分层交叉验证和嵌套交叉验证,每种方法适用于不同场景,确保模型在实际应用中表现优异。
1824 1
|
传感器 人工智能 安全
《把握人机共融设计要点,重塑人机协作格局》
机器人已融入生活与工作的方方面面,从医疗到物流,其身影无处不在。实现人机共融,关键在于深度融合人与机器的优势,确保安全、高效、自然的交互。通过碰撞检测、安全距离设定和紧急制动系统保障安全;借助语音、手势、眼神交互实现自然沟通;智能协作发挥人机各自特长;个性化定制满足不同需求;情感交互让机器人更具“温度”。这一跨学科领域涉及机械、电子、AI与心理学,是未来机器人发展的核心方向。
640 0