`
Neil_yang
  • 浏览: 131563 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
社区版块
存档分类
最新评论

Java异常处理之陋习展播

阅读更多

 你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗?

 

 

1 OutputStreamWriter out = ...

2 java.sql.Connection conn = ...

3 try { // ⑸

4 Statement stat = conn.createStatement();

5 ResultSet rs = stat.executeQuery(

6 "select uid, name from user");

7 while (rs.next())

8 {

9 out.println("ID:" + rs.getString("uid") // ⑹

10 ",姓名:" + rs.getString("name"));

11 }

12 conn.close(); // ⑶

13 out.close();

14 }

15 catch(Exception ex) // ⑵

16 {

17 ex.printStackTrace(); //⑴,⑷

18 }

 

 

 

  作为一个Java程序员,你至少应该能够找出两个问题。但是,如果你不能找出全部六个问题,请继续阅读本文。

 

  本文讨论的不是Java异常处理的一般性原则,因为这些原则已经被大多数人熟知。我们要做的是分析各种可称为“反例”(anti-pattern)的违背优秀编码规范的常见坏习惯,帮助读者熟悉这些典型的反面例子,从而能够在实际工作中敏锐地察觉和避免这些问题。

 

  反例之一:丢弃异常

 

  代码:15行-18行。

 

  这段代码捕获了异常却不作任何处理,可以算得上Java编程中的杀手。从问题出现的频繁程度和祸害程度来看,它也许可以和C/C++程序的一个恶名远播的问题相提并论——不检查缓冲区是否已满。如果你看到了这种丢弃(而不是抛出)异常的情况,可以百分之九十九地肯定代码存在问题(在极少数情况下,这段代码有存在的理由,但最好加上完整的注释,以免引起别人误解)。

 

  这段代码的错误在于,异常(几乎)总是意味着某些事情不对劲了,或者说至少发生了某些不寻常的事情,我们不应该对程序发出的求救信号保持沉默和无动于衷。调用一下printStackTrace算不上“处理异常”。不错,调用printStackTrace对调试程序有帮助,但程序调试阶段结束之后,printStackTrace就不应再在异常处理模块中担负主要责任了。

 

  丢弃异常的情形非常普遍。打开JDK的ThreadDeath类的文档,可以看到下面这段说明:“特别地,虽然出现ThreadDeath是一种‘正常的情形’,但ThreadDeath类是Error而不是Exception的子类,因为许多应用会捕获所有的Exception然后丢弃它不再理睬。”这段话的意思是,虽然ThreadDeath代表的是一种普通的问题,但鉴于许多应用会试图捕获所有异常然后不予以适当的处理,所以JDK把ThreadDeath定义成了Error的子类,因为Error类代表的是一般的应用不应该去捕获的严重问题。可见,丢弃异常这一坏习惯是如此常见,它甚至已经影响到了Java本身的设计。

 

  那么,应该怎样改正呢?主要有四个选择:

 

  1、处理异常。针对该异常采取一些行动,例如修正问题、提醒某个人或进行其他一些处理,要根据具体的情形确定应该采取的动作。再次说明,调用printStackTrace算不上已经“处理好了异常”。

 

  2、重新抛出异常。处理异常的代码在分析异常之后,认为自己不能处理它,重新抛出异常也不失为一种选择。

 

  3、把该异常转换成另一种异常。大多数情况下,这是指把一个低级的异常转换成应用级的异常(其含义更容易被用户了解的异常)。

 

  4、不要捕获异常。

 

  结论一:既然捕获了异常,就要对它进行适当的处理。不要捕获异常之后又把它丢弃,不予理睬。

 

  反例之二:不指定具体的异常

 

  代码:15行。

 

  许多时候人们会被这样一种“美妙的”想法吸引:用一个catch语句捕获所有的异常。最常见的情形就是使用catch(Exception ex)语句。但实际上,在绝大多数情况下,这种做法不值得提倡。为什么呢?

 

  要理解其原因,我们必须回顾一下catch语句的用途。catch语句表示我们预期会出现某种异常,而且希望能够处理该异常。异常类的作用就是告诉Java编译器我们想要处理的是哪一种异常。由于绝大多数异常都直接或间接从java.lang.Exception派生,catch(Exception ex)就相当于说我们想要处理几乎所有的异常。

 

  再来看看前面的代码例子。我们真正想要捕获的异常是什么呢?最明显的一个是SQLException,这是JDBC操作中常见的异常。另一个可能的异常是IOException,因为它要操作OutputStreamWriter。显然,在同一个catch块中处理这两种截然不同的异常是不合适的。如果用两个catch块分别捕获SQLException和IOException就要好多了。这就是说,catch语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的Exception类。

 

  另一方面,除了这两个特定的异常,还有其他许多异常也可能出现。例如,如果由于某种原因,executeQuery返回了null,该怎么办?答案是让它们继续抛出,即不必捕获也不必处理。实际上,我们不能也不应该去捕获可能出现的所有异常,程序的其他地方还有捕获异常的机会——直至最后由JVM处理。

 

  结论二:在catch语句中尽可能指定具体的异常类型,必要时使用多个catch。不要试图处理所有可能出现的异常。

 

  反例之三:占用资源不释放

 

  代码:3行-14行。

 

  异常改变了程序正常的执行流程。这个道理虽然简单,却常常被人们忽视。如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。为此,Java提供了一个简化这类操作的关键词finally。

 

  finally是样好东西:不管是否出现了异常,Finally保证在try/catch/finally块结束之前,执行清理任务的代码总是有机会执行。遗憾的是有些人却不习惯使用finally。

 

  当然,编写finally块应当多加小心,特别是要注意在finally块之内抛出的异常——这是执行清理任务的最后机会,尽量不要再有难以处理的错误。

 

  结论三:保证所有资源都被正确释放。充分运用finally关键词。

 

  反例之四:不说明异常的详细信息

 

  代码:3行-18行。

 

  仔细观察这段代码:如果循环内部出现了异常,会发生什么事情?我们可以得到足够的信息判断循环内部出错的原因吗?不能。我们只能知道当前正在处理的类发生了某种错误,但却不能获得任何信息判断导致当前错误的原因。

 

  printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程,但只提供了一些最基本的信息,未能说明实际导致错误的原因,同时也不易解读。

 

  因此,在出现异常时,最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息,包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。

 

  结论四:在异常处理模块中提供适量的错误原因信息,组织错误信息使其易于理解和阅读。

 

  反例之五:过于庞大的try块

 

  代码:3行-14行。

 

  经常可以看到有人把大量的代码放入单个try块,实际上这不是好习惯。这种现象之所以常见,原因就在于有些人图省事,不愿花时间分析一大块代码中哪几行代码会抛出异常、异常的具体类型是什么。把大量的语句装入单个巨大的try块就象是出门旅游时把所有日常用品塞入一个大箱子,虽然东西是带上了,但要找出来可不容易。

 

  一些新手常常把大量的代码放入单个try块,然后再在catch语句中声明Exception,而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来了困难,因为一大段代码中有太多的地方可能抛出Exception。

 

  结论五:尽量减小try块的体积。

 

  反例之六:输出数据不完整

 

  代码:7行-11行。

 

  不完整的数据是Java程序的隐形杀手。仔细观察这段代码,考虑一下如果循环的中间抛出了异常,会发生什么事情。循环的执行当然是要被打断的,其次,catch块会执行——就这些,再也没有其他动作了。已经输出的数据怎么办?使用这些数据的人或设备将收到一份不完整的(因而也是错误的)数据,却得不到任何有关这份数据是否完整的提示。对于有些系统来说,数据不完整可能比系统停止运行带来更大的损失。

 

  较为理想的处置办法是向输出设备写一些信息,声明数据的不完整性;另一种可能有效的办法是,先缓冲要输出的数据,准备好全部数据之后再一次性输出。

 

  结论六:全面考虑可能出现的异常以及这些异常对执行流程的影响。

 

  改写后的代码

 

  根据上面的讨论,下面给出改写后的代码。也许有人会说它稍微有点啰嗦,但是它有了比较完备的异常处理机制。

 

 

OutputStreamWriter out = null;
java.sql.Connection conn = null;

try {
	Statement stat = conn.createStatement();
	ResultSet rs = stat.executeQuery("select uid, name from user");
	while (rs.next())
	{
		System.out.println("ID:" + rs.getString("uid") + ",姓名: "	+ rs.getString("name"));
	}
}catch (SQLException sqlex){
	System.out.println("警告:数据不完整");
	throw new ApplicationException(	"读取数据时出现SQL错误", sqlex);
}catch (IOException ioex){
	throw new ApplicationException(	"写入数据时出现IO错误", ioex);
}finally{
	if (conn != null) {
		try {
			conn.close();
		}catch (SQLException sqlex2){

			System.err(this.getClass().getName() +	".mymethod - 不能关闭数据库连接: " +	sqlex2.toString());
		}
	}

	if (out != null) {
		try {
			out.close();
		}catch (IOException ioex2){
			System.err(this.getClass().getName() +".mymethod - 不能关闭输出文件" +ioex2.toString());

		}
	}
}

 

  本文的结论不是放之四海皆准的教条,有时常识和经验才是最好的老师。如果你对自己的做法没有百分之百的信心,务必加上详细、全面的注释。

 

  另一方面,不要笑话这些错误,不妨问问你自己是否真地彻底摆脱了这些坏习惯。即使最有经验的程序员偶尔也会误入歧途,原因很简单,因为它们确确实实带来了“方便”。所有这些反例都可以看作Java编程世界的恶魔,它们美丽动人,无孔不入,时刻诱惑着你。也许有人会认为这些都属于鸡皮蒜毛的小事,不足挂齿,但请记住:勿以恶小而为之,勿以善小而不为。

 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/leek2000/archive/2003/05/15/13495.aspx

分享到:
评论

相关推荐

    JSTL详细标签库介绍

    target=_blank>Java异常处理的陋习展播</A> <LI><A title=java的异常处理机制 href="http://www.jspcn.net/htmlnews/11453815669531884.html" target=_blank>java的异常处理机制</A> <LI><A title=浅析Java...

    医药洁净室空调箱多模式控制程序解析——基于西门子1500 PLC与昆仑通泰触摸屏的应用

    内容概要:本文详细介绍了医药洁净室空调箱的多模式控制程序,涵盖停止模式、生产模式、值班模式、消毒循环模式和消毒排风模式。文中强调了不同模式下的具体控制逻辑及其重要性,并深入探讨了西门子1500 PLC与昆仑通泰触摸屏在博途15编程环境中如何协同工作,实现精准控制。此外,还展示了部分PLC程序代码片段,帮助读者更好地理解和实施这些控制逻辑。 适用人群:从事医药洁净室设计、安装、维护的技术人员,以及对工业自动化控制系统感兴趣的工程师。 使用场景及目标:适用于医药洁净室项目的规划与实施阶段,旨在帮助技术人员掌握空调箱多模式控制的设计思路和技术细节,从而提升系统的可靠性和稳定性。 其他说明:文中提供的示例代码和控制逻辑对于实际工程项目具有较高的参考价值,有助于缩短开发周期,降低调试难度。同时,结合博途15和昆仑通泰触摸屏的实际应用案例,使理论与实践相结合,便于理解和操作。

    【嵌入式系统】STM32F407ZET6驱动HC-SR04超声波模块实现精准测距功能的设计与代码实现文档所属领域(

    内容概要:本文档主要介绍了HC-SR04超声波测距模块的工作原理及其在STM32F407ZET6平台上的应用。HC-SR04超声波测距模块能提供2cm-400cm范围内的非接触式距离感测,精度达3mm。其工作原理是通过TRIG引脚接收触发信号后,自动发送8个40kHz的方波并检测返回信号。若有返回信号,ECHO引脚将输出一个高电平,高电平持续的时间即为超声波往返的时间。根据这个时间可以计算出测试距离。文中还提供了基于STM32F407ZET6平台的C语言代码示例,详细说明了如何配置TRIG和ECHO引脚,以及如何通过测量ECHO引脚高电平持续时间来获取距离值。; 适合人群:对超声波测距模块有一定兴趣,具有嵌入式系统基础知识的研发人员或学生。; 使用场景及目标:①了解HC-SR04超声波测距模块的基本工作原理;②掌握如何在STM32平台上配置和使用HC-SR04模块进行测距。; 其他说明:此文档提供的代码和原理有助于开发者快速上手HC-SR04超声波测距模块的应用开发,建议读者结合实际硬件进行实验以加深理解。

    基于滚动优化的大规模电动汽车充放电策略优化MATLAB代码实现

    内容概要:本文介绍了基于滚动优化的大规模电动汽车充放电策略优化的MATLAB代码实现。文中提出了针对大规模电动汽车调度问题的一种快速优化方法——滚动优化法,并将其与均衡负载法和全局优化法进行了对比。该方法通过将全局问题分解为多个局部子问题,降低了求解复杂度,提高了计算效率。模型考虑了电动汽车的随机到达分布及其充放电策略,目标是最小化电力系统的负荷波动并最大化电动汽车的充放电效益。实验结果显示,滚动优化法在保持电力系统稳定性的前提下显著提升了电动汽车的充放电效益。 适合人群:从事智能交通系统研究、电力系统优化、电动汽车调度等相关领域的研究人员和技术人员。 使用场景及目标:适用于需要优化大规模电动汽车充放电策略的研究项目或实际应用,旨在提升电力系统的稳定性和电动汽车的经济效益。 其他说明:代码基于MATLAB+CVX平台实现,附带详细注释,便于学习和复现。

    基于博途软件的PLC自助洗车机控制系统:全仿真程序、画面、接线图及IO分配表

    内容概要:本文介绍了基于PLC(可编程逻辑控制器)的自助洗车机控制系统的设计与实现。系统采用博途软件编写,实现了从启动到结束的完整洗车流程,包括喷水、刷洗、喷洒清洁剂、吹干等步骤。文中详细描述了洗车机的动作流程、原点复位设计、系统架构、代码与控制逻辑分析,并提供了人机界面、接线图和IO分配表等辅助资料。此外,还强调了系统的稳定性和优质售后服务。 适合人群:从事自动化控制领域的工程师和技术人员,特别是对PLC编程和洗车机控制系统感兴趣的读者。 使用场景及目标:适用于需要了解或开发自助洗车机控制系统的场合,旨在帮助读者掌握PLC编程技巧和洗车机的工作原理,提高系统的稳定性和可靠性。 其他说明:本文不仅提供了详细的硬件和软件设计方案,还涵盖了故障处理和售后服务的内容,确保系统的长期稳定运行。

    【嵌入式系统】STM32F407ZET6 GPIO接口详解:输入输出模式与应用实例介绍文档的主要内容

    内容概要:本文档详细介绍了STM32F407ZET6的GPIO(通用输入输出接口)。GPIO在输出模式下能控制端口输出高低电平,适用于驱动LED、控制蜂鸣器等;在输入模式下可读取端口的高低电平或电压,如读取按键输入、ADC电压采集等。每个通用I/O端口包含多个32位配置寄存器、数据寄存器等。文档列举了GPIO的八个功能模式,重点解析了输出状态中的推挽输出和开漏输出,以及输入状态中的下拉电阻和上拉电阻的概念与工作原理。推挽输出可输出高低电平,由两个互补的晶体管提供较大电流驱动;开漏输出通常只能输出低电平,适合电平转换。输入状态方面,下拉电阻将信号初始化为低电平,上拉电阻则初始化为高电平,其本质分别是输出和注入电流。; 适合人群:嵌入式系统开发人员、电子工程师、对STM32微控制器有兴趣的学习者。; 使用场景及目标:①帮助开发者理解STM32F407ZET6的GPIO工作机制;②为实际项目中GPIO的应用提供理论指导,如控制外部设备、读取传感器数据等。; 其他说明:文档提供了详尽的寄存器配置信息和功能模式介绍,有助于深入理解和灵活运用GPIO接口。建议读者结合实际硬件操作进行学习,以加深理解。

    两电平三相并网逆变器的有限控制集模型预测控制(FCS-MPC)代码实现与波形分析

    内容概要:本文详细介绍了两电平三相并网逆变器的有限控制集模型预测控制(FCS-MPC)的代码实现方法。首先设置了系统的参数,如滤波电感、等效电阻、控制周期等。接着阐述了离散化状态方程和代价函数的设计,其中代价函数考虑了电流跟踪误差和开关损耗。然后展示了主控制循环的具体实现,通过遍历所有可能的开关状态选择最优的动作。最后对仿真效果进行了分析,指出了常见的问题及解决方案,如采样同步、权重系数调整和离散化精度等问题。 适合人群:电力电子领域的初学者和技术爱好者,尤其是对逆变器控制策略感兴趣的读者。 使用场景及目标:适用于需要理解和实现两电平三相并网逆变器FCS-MPC控制的人群。目标是掌握FCS-MPC的工作原理,能够独立完成相关代码编写,并能进行基本的参数调整和优化。 其他说明:文中提供了详细的代码片段和仿真波形图,帮助读者更好地理解理论与实践的结合。同时附有参考文献,方便进一步深入学习。

    CST仿真超表面技术实现线极化转圆极化极化转换器的研究与文献复现

    内容概要:本文详细介绍了利用CST微波工作室进行超表面仿真,将线极化波转化为圆极化波的技术实现过程。首先,构建了一个简单的十字形金属贴片作为超表面单元模型,设置了金属层和基板的具体参数。接着,通过调整X和Y方向的相位差达到90度来实现极化转换,并使用VBA脚本进行参数优化。最终,在12.5GHz频率处实现了低于3dB的轴比,验证了圆极化的成功转换。此外,还讨论了网格划分对仿真的影响,指出六边形网格相比矩形网格能更快收敛。 适合人群:从事电磁仿真、天线设计以及超表面研究的专业技术人员。 使用场景及目标:适用于需要深入了解线极化转圆极化技术原理及其实际应用的研究人员和技术开发者。目标是掌握CST仿真工具的操作技巧,理解极化转换的关键技术和优化方法。 其他说明:文中提供了详细的建模步骤、参数设置和代码片段,有助于读者快速上手并复现实验结果。同时提醒注意网格划分的选择,以提高仿真效率。

    全国大学生电子设计竞赛论文模板(20190717084742).pdf

    电子设计竞赛相关资源

    Modbus RTU协议在STC32G与STC8系列单片机上的优化实现及应用

    内容概要:本文详细介绍了新版Modbus RTU从机源码针对STC32G和STC8系列单片机的优化改进。主要改进包括硬件CRC加速、动态内存分配机制、自动波特率检测等功能特性。文中对比了新旧版本的性能差异,特别是在CRC校验效率提升、内存管理和通信稳定性方面的显著进步。此外,还提供了与多种组态软件的适配方法,如威纶通触摸屏的地址映射技巧,以及ModbusPoll工具的预配置模板,帮助开发者快速搭建和调试系统。 适合人群:嵌入式系统开发工程师,尤其是熟悉STC系列单片机和Modbus协议的开发者。 使用场景及目标:适用于工业自动化控制系统中需要高效稳定通信的应用场景,旨在提高系统的实时性和可靠性,降低开发难度并缩短产品上市周期。 其他说明:文中提到的部分技术细节(如硬件CRC、动态内存分配)可能不适合所有应用场景,在具体项目中可根据实际情况选择是否采用。对于初次接触Modbus协议的新手来说,建议先掌握基础知识再深入研究本文提供的高级特性。

    实训商业源码-工作场所技能必不可少的应用程序主页模板-毕业设计.zip

    实训商业源码-工作场所技能必不可少的应用程序主页模板-毕业设计.zip

    110kV变电站电气一次设计:主接线方案、短路电流计算及设备选型详解

    内容概要:本文详细介绍了110kV变电站电气一次设计的关键步骤和技术要点。首先,通过对原始参数的解读,明确了变电站的基本要求。接着,重点讨论了主接线方案的选择,通过案例分析展示了双母线接线方式的优势。随后,深入探讨了短路电流计算的重要性及其对电网安全的影响。最后,针对电气一次设备的选型进行了全面分析,强调了质量和效率的平衡。整个设计过程最终通过AutoCAD2014绘制出了详细的主接线A0大图。 适合人群:从事电力系统设计、运行和维护的专业技术人员,尤其是对110kV变电站电气一次设计感兴趣的工程师。 使用场景及目标:适用于新建设或改造110kV变电站项目,帮助设计师优化主接线方案,提高电网的安全性和可靠性,同时降低运营成本。 其他说明:文中提供了具体的CAD绘图命令片段作为参考,有助于读者更好地理解和应用相关设计方法。

    实训商业源码-抽奖页面小程序模板-毕业设计.zip

    实训商业源码-抽奖页面小程序模板-毕业设计.zip

    电力系统中基于39节点故障数据的短路分析与负荷仿真实验

    内容概要:本文详细介绍了基于39节点故障数据的短路分析与负荷仿真实验。文中首先概述了故障数据的特点,指出故障主要集中在特定区域的多个节点上,短路持续时间和负荷水平存在显著差异。接着,文章描述了仿真的具体过程和方法,强调了采用先进技术进行多场景下电网运行的模拟。仿真结果显示,短路点分布广泛,短路持续时间从数小时到数天不等,负荷水平变化范围大。此外,还讨论了数据处理技术和仿真模型构建的关键步骤,提出了故障应对中的安全保障措施。最后,作者总结了研究的重要性和对未来工作的建议,如加强电网监控、提升应急响应能力和推进智能化电网建设。 适合人群:从事电力系统研究、电网管理和维护的技术人员及研究人员。 使用场景及目标:适用于希望深入了解电网故障分析和负荷仿真技术的专业人士,旨在提高电网运行的可靠性和稳定性。 阅读建议:读者可以通过本文了解电网故障的具体表现形式及其对电网运行的影响,掌握先进的仿真技术和数据处理方法,从而为实际工作中遇到的问题提供解决方案。

    (addon.py):Blender MCP插件

    MCP插件(addon.py):Blender插件,在Blender中创建一个套接字服务器来接收和执行命令

    基于Qt框架的最小生成树算法(Prim和Kruskal)动态可视化实现及其应用

    内容概要:本文介绍了利用Qt框架实现Prim和Kruskal两种最小生成树算法的动态可视化项目。该项目不仅展示了这两种算法的具体执行过程,还提供了详细的源代码和技术解析。Prim算法通过维护两个节点集合之间的最短边进行构建,而Kruskal算法则借助并查集防止形成环路。两者均实现了动态展示,使算法执行过程如同观看动画般直观。此外,文中还提到了一些实现细节,如优先队列的特殊使用方法、多线程环境下的数据同步以及图形化界面的设计。 适合人群:计算机科学专业学生、算法爱好者、软件开发者。 使用场景及目标:本项目的应用场景主要是在教学环境中用于讲解最小生成树算法的工作机制,帮助学习者更好地理解和掌握相关概念;同时也适用于希望深入研究这两类算法实现方式的研究人员。 其他说明:附带完整的源代码和一份详尽的技术报告,涵盖从理论分析到实际编码过程中遇到的问题及解决方案。

    电力电子领域中基于滞环电流控制的VIENNA整流器技术研究与优化

    内容概要:本文详细介绍了基于滞环电流控制的VIENNA整流器的技术特点和工作原理。首先概述了VIENNA整流器作为一种高效AC-DC转换器的独特优势,如高功率因数、低谐波失真和高效率。接着阐述了滞环电流控制技术的基本原理,即通过实时检测和比较实际电流与给定电流的偏差,控制开关器件的通断,实现电流的精确控制。然后具体解释了基于滞环电流控制的VIENNA整流器的工作机制,强调其快速响应、高精度和低噪声的特点。最后总结了该整流器的多项优点,包括高效率、快速响应、高功率因数和较低的谐波失真,指出其在未来电力电子系统中的广泛应用前景。 适合人群:从事电力电子技术研究和开发的专业人士,以及对高效AC-DC转换器感兴趣的工程技术人员。 使用场景及目标:适用于需要深入了解VIENNA整流器及其控制技术的研究人员和技术人员,旨在提升对电力转换系统的理解和优化能力。 其他说明:本文不仅提供了理论分析,还讨论了实际应用中的优化策略,有助于推动相关技术的进步和发展。

    实训商业源码-单个商品销售系统源码-毕业设计.zip

    实训商业源码-单个商品销售系统源码-毕业设计.zip

    全国大学生电子设计竞赛介绍(20190716151135).pdf

    电子设计竞赛相关资源

Global site tag (gtag.js) - Google Analytics