ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

软件工程课程设计:选课系统从需求分析到验收的完整技术路线

软件工程课程设计:选课系统从需求分析到验收的完整技术路线

简介:《软件工程课程设计指导书》是一份面向计算机科学与技术专业学生的软件工程实践入门指南,围绕一门10学时的上机课程设计展开,帮助初学者运用软件工程思想与面向对象方法,从零完成一个选课系统的开发。资源包仅包含1个doc文档,大小539KB,属于纯文本指导材料,适合学生在课程设计前通读。内容从实验目的、面向专业、先修课程到实验环境均有说明,并以选课系统为例,给出数据字段、功能要求和设计规范。报告部分明确了需求分析、软件设计、软件测试、源代码与个人总结的写作要点,附录还整理了通过ODBC建立数据库连接及C++ Builder 6.0的基本操作步骤,对缺乏实践经验的读者尤为实用。全文已服务97位学习者,可作为软件工程课程设计的高效参考和完整操作指引。

1. 软件工程课程设计指导书:选课系统要从需求分析一路做到验收

每年做软件工程课程设计,最常见的翻车方式不是代码写不出来,而是拿到“选课系统”这类题目就直接开窗体、拖控件,等到交报告的时候才发现需求分析、用例图、序列图、状态图全都没画,测试数据也凑不够二十条,最后只能熬夜补文档。这套课程设计的核心矛盾在于:上机只有 10 学时,但交付物却要求一个能运行的面向对象小型系统加一份完整报告,没有一套从需求到验收的固定打法,很容易顾此失彼。这份指导书的价值,就是把“用 C++ Builder 6.0 + Access 2000 开发选课系统”这件事拆成一条可执行的技术路线,适合计算机科学与技术专业的同学照着做,也适合指导老师当作检查学生进度的依据。我接下来的方案,完全围绕“先立需求、再定设计、最后能落地验收”这条主线展开。

2. 需求分析与总体架构:先画用例图和序列图,再谈代码实现

2.1 需求分析阶段到底要交付什么

很多同学拿到题目就急着建工程,实际上需求分析阶段要交付的内容并不复杂,但必须完整。指导书里点名的字段至少包括学生姓名、学号、登录密码、性别、出生年月、籍贯、地址、学生电话、家庭地址、教师号、教师姓名、教研室、职称、教师电话、课名、课号、学分、先行课号、课时、开课教室、人数限制、选课人数、考试成绩、平时成绩、总评成绩。这些字段不是随便列的,它们决定了后续数据库表怎么拆分。

把字段归归类,就会发现天然分成三张表:学生表、教师表、课程表。选课关系呢,可以用一张选课记录表来表达,选课记录表里关联学生号和课号,另外再带考试成绩、平时成绩、总评成绩。这个拆分方式符合第三范式的思路,也是指导书要求“采用面向对象方法”但数据层仍然要关系化的原因。

我一般会建议学生先别碰代码,用一页纸画出系统的用例图。选课系统的参与者其实就两类:学生和管理员(也可以叫教师端)。学生能做的操作是登录、查询开课情况、查询选课情况、查询成绩、选课、退课;管理员能做的是维护课程信息、维护学生信息、录入成绩。用例图画完,数据流就清楚了一半。

序列图是这个阶段的第二个关键产物。我通常要求学生至少画两个序列图:一个是学生选课的完整流程——登录系统、验证身份、查询开课列表、选择课程、系统检查人数限制、写入选课记录、返回结果;另一个是学生查询成绩的流程——登录、选择查询成绩、系统从选课记录表读取成绩字段、展示结果。序列图画完,后面写代码时函数调用顺序基本就定死了。

2.2 总体架构和三层职责划分

选课系统的架构不需要做得花哨,三层足矣:界面层负责窗体展示和用户交互,业务层负责选课、退课、成绩计算这些规则,数据层负责通过 ODBC 访问 Access 数据库。C++ Builder 6.0 里,界面层就是 Form 上的组件,业务层可以封装成独立的类,比如选课管理器类、登录验证类,数据层用 ADOConnection 或者 BDE 别名来访问 Access 文件。

架构上最容易犯的错误,是把 SQL 语句直接写在按钮的 Click 事件里。一个窗体里塞十几个数据感知组件和 SQL 语句,系统确实能跑,但测试的时候就会发现耦合太紧,换一个数据库结构就要重写一片代码;更真实的问题是,状态图根本没法画——业务规则散落在界面代码里,对象的状态无法追踪。

通常的做法是给系统拆出四个核心类:用户验证类(负责登录验证和密码处理)、课程查询类(封装开课情况和选课情况的查询逻辑)、选课处理类(负责选课、退课、人数校验)、成绩处理类(负责成绩的读取和总评成绩计算)。每个类对应一组独立的函数,界面层只调用这些类的公开方法,不直接操作数据库。

2.3 数据库表设计和 Access 建模

Access 2000 里建表,我建议按下面这个结构来设计,字段名可以自己改,但语义要一致。

表名关键字段说明
Student学号、姓名、密码、性别、出生年月、籍贯、地址、电话主键是学号
Teacher教师号、教师姓名、教研室、职称、性别、电话主键是教师号
Course课号、课名、学分、先行课号、课时、开课教室、人数限制、选课人数、教师号主键是课号,教师号外键关联 Teacher
Enrollment选课记录ID、学号、课号、考试成绩、平时成绩、总评成绩学号+课号联合唯一

Access 里建表有个坑,就是默认的主键类型是“自动编号”,但学号这种业务字段必须用文本类型,而且在建立表关系时要把“实施参照完整性”勾上。我当时帮同学排查数据不一致的问题,最后发现就是建表时没建立表间关系,学生退了一门课,选课记录删了,但课程的选课人数字段没跟着减,这种低级错误在验收演示时特别容易暴露。

3. 关键技术路线:C++ Builder 6.0 + Access 2000 + ODBC 的取舍与连接

3.1 为什么是 C++ Builder 6.0 而不是其他工具

选课系统之所以默认用 C++ Builder 6.0,不是因为它是选课系统的最佳工具,而是因为指导书的上机环境就是这么定的:Windows 操作系统、C++ Builder 6.0、Access 2000。C++ Builder 6.0 的 VCL 组件库让界面开发效率很高,TADOQuery、TDataSource、TDBGrid 这几个组件配合使用,几分钟就能搭出一个数据展示窗体,这对 10 学时的上机时间来说非常关键。

但 C++ Builder 6.0 有个明显的短板:它的 BDE 和 ODBC 配置在 Windows 新版本上经常出问题,而且 IDE 崩溃后恢复工程比较麻烦。所以我在做技术路线验证时建议学生先在小范围内跑通“窗体 + 数据库连接 + 单表查询”,再扩展成完整功能,不要一上来就搭十几个窗体。

另外要强调的是,面向对象方法在这个项目里不是让你把所有逻辑都做成类继承树。课程设计考核的重点是你能不能按软件工程的过程走下来——需求有没有分析、设计有没有建模、测试有没有设计数据。技术选型反而是次要的,够用就行。

3.2 ODBC 数据源配置与 BDE 别名设置

这份指导书附录给了非常详细的 ODBC 配置步骤,实际执行时核心就三步:Access 里建好数据库文件、控制面板里配置 ODBC 数据源、BDE Administrator 里把别名指到该数据源。下面这个配置流程是从指导书附录压缩出来的:

  1. 用 Access 2000 建立选课.mdb,建好表结构并录入少量测试数据。
  2. 打开控制面板里的 ODBC 数据源管理器,添加一个“Microsoft Access Driver (*.mdb)”的用户数据源,数据源名填“选课数据源”,数据库选择刚才的 mdb 文件。
  3. 打开 BDE Administrator,新建一个 Database 别名,驱动选 Microsoft Access Driver,然后将 ODBC DSN 指向刚才创建的数据源名称。
  4. 右键别名 Apply,再右键 Open,输入数据库登录信息后如果字体变黑,说明连接成功。

提示:BDE 和 ODBC 的配置在部分 Windows 环境下会互相“抢”配置。如果 Open 时提示驱动不认识,先检查系统是否缺少 Access 驱动,再检查数据源名是否完全一致,包括空格和大小写。

3.3 ADOConnection 连接数据库的两种方式

C++ Builder 6.0 里连接数据库有两种思路:一种是通过 BDE 别名,在 ADOConnection 的连接字符串里写 Provider 和 Data Source;另一种是直接用 ODBC 数据源名连接。给学生的建议是:上机环境如果统一配置好了 BDE,用指导书里的方式走一遍;如果自己电脑上装的环境有问题,可以直接用 ODBC 数据源名,少一层 BDE 反而更稳。

我一般建议用下面这种连接字符串格式写在 ADOConnection 的 ConnectionString 里,这样程序启动时会自动读配置,不用每次上机都重新配:

// ADOConnection1->ConnectionString 示例 "Provider=MSDASQL.1;Persist Security Info=False;Data Source=选课数据源"

设计说明:用 MSDASQL 提供程序走 ODBC 桥接,Data Source 必须和系统里配置的 ODBC 数据源名称完全一致。Persist Security Info 设为 False,表示不把密码保存在连接字符串里。这样写的好处是,代码里不暴露数据库路径,换一台机器只要重新配置同名 ODBC 数据源就能直接跑。

4. 模块设计与核心实现:登录、查询、选课、退课与成绩管理

4.1 登录模块与权限校验

登录模块是整个系统的入口,也是最容易出低级问题的地方。功能很简单:用户在登录窗体输入学号和密码,系统去 Student 表里校验,匹配成功则进入主窗体,失败则提示错误信息。C++ Builder 里可以这样写核心逻辑:

// 登录验证核心函数 bool LoginCheck(AnsiString userId, AnsiString password) { // 通过 TADOQuery 查询学生表 ADOQuery1->SQL->Clear(); ADOQuery1->SQL->Add("SELECT COUNT(*) AS cnt FROM Student WHERE 学号=:uid AND 密码=:pwd"); ADOQuery1->Parameters->ParamByName("uid")->Value = userId; ADOQuery1->Parameters->ParamByName("pwd")->Value = password; ADOQuery1->Open(); // 查询结果计数大于 0 说明匹配成功 int count = ADOQuery1->FieldByName("cnt")->AsInteger; return count > 0; }

这段代码用了参数化查询而不是字符串拼接,核心考虑是防止输入引号或特殊字符时破坏 SQL 语句。另一个细节是密码字段在 Access 里不要用加密,课程设计阶段明文存储可接受,但报告中要提及这是简化处理,并说明真实系统的密码应该做哈希。

4.2 查询模块:开课情况、选课情况和成绩查询

查询开课情况是学生使用频率最高的功能。界面可以用一个 TDBGrid 显示课程列表,数据源来自 Course 表关联 Teacher 表的查询语句。这里要特别注意,查询语句必须带 WHERE 条件,比如按课名模糊查询、按开课教室筛选,否则学生要在一大张表里翻数据,体验很差。

-- 查询开课情况(含任课教师信息) SELECT c.课号, c.课名, c.学分, c.课时, c.开课教室, c.人数限制, c.选课人数, t.教师姓名 FROM Course c LEFT JOIN Teacher t ON c.教师号 = t.教师号 WHERE c.课名 LIKE '%' + :kw + '%'

这里用了 LEFT JOIN 是为了避免教师信息缺失的课程显示不出来。教职工表里录入数据时如果漏填教师号,用 INNER JOIN 会直接把课程记录过滤掉,用 LEFT JOIN 至少还能看到课程本身,对调试阶段更友好。查询选课情况和查询成绩的逻辑类似,区别在于查询选课情况要用学号关联 Enrollment 和 Course 两张表,查询成绩则直接读 Enrollment 表的成绩字段。

4.3 选课与退课的并发与限制校验

选课模块的核心规则是:同一学生不能重复选同一门课;课程选课人数不能超过人数限制。这两条规则必须在业务层判断,不能只靠数据库约束。C++ Builder 里选课操作按事务方式处理最稳妥:

// 选课处理逻辑 void ProcessEnroll(AnsiString studentId, AnsiString courseId) { // 第一步:检查课程是否已满 ADOQuery1->SQL->Clear(); ADOQuery1->SQL->Add("SELECT 人数限制, 选课人数 FROM Course WHERE 课号=:cid"); ADOQuery1->Parameters->ParamByName("cid")->Value = courseId; ADOQuery1->Open(); int limit = ADOQuery1->FieldByName("人数限制")->AsInteger; int current = ADOQuery1->FieldByName("选课人数")->AsInteger; if (current >= limit) { ShowMessage("该课程已选满,无法选课"); return; } // 第二步:检查是否重复选课 ADOQuery1->SQL->Clear(); ADOQuery1->SQL->Add("SELECT COUNT(*) AS cnt FROM Enrollment WHERE 学号=:sid AND 课号=:cid"); ADOQuery1->Parameters->ParamByName("sid")->Value = studentId; ADOQuery1->Parameters->ParamByName("cid")->Value = courseId; ADOQuery1->Open(); if (ADOQuery1->FieldByName("cnt")->AsInteger > 0) { ShowMessage("您已选过该课程"); return; } // 第三步:写入选课记录并更新选课人数 ADOConnection1->BeginTrans(); try { ADOQuery1->SQL->Clear(); ADOQuery1->SQL->Add("INSERT INTO Enrollment(学号, 课号) VALUES(:sid, :cid)"); ADOQuery1->Parameters->ParamByName("sid")->Value = studentId; ADOQuery1->Parameters->ParamByName("cid")->Value = courseId; ADOQuery1->ExecSQL(); ADOQuery1->SQL->Clear(); ADOQuery1->SQL->Add("UPDATE Course SET 选课人数 = 选课人数 + 1 WHERE 课号=:cid"); ADOQuery1->Parameters->ParamByName("cid")->Value = courseId; ADOQuery1->ExecSQL(); ADOConnection1->CommitTrans(); } catch (...) { ADOConnection1->RollbackTrans(); ShowMessage("选课失败,已回滚"); } }

这里的核心设计思路是事务保护:先检查再写入,写入时同时更新选课记录和课程人数,两步操作放在同一个事务里,任何一步失败都回滚。退课逻辑是选课的镜像操作,删除 Enrollment 记录后把 Course 表选课人数减一,同样放在事务里。最容易被忽略的是并发场景——两个学生在同一时间选最后一门课,单机模式很难复现,但报告里要提到这个问题,说明事务是解决方案。

4.4 成绩管理与总评计算

成绩管理模块面向教师端:录入平时成绩和考试成绩,系统自动计算总评。总评成绩的计算规则按实际情况设计,常见的是平时成绩占 30%、考试成绩占 70%,这个比例在报告的需求分析里就要写明,代码里用常量定义,方便后续修改。

// 计算总评成绩 float CalculateFinalScore(float usualScore, float examScore) { const float USUAL_WEIGHT = 0.3f; // 平时成绩权重 const float EXAM_WEIGHT = 0.7f; // 考试成绩权重 return usualScore * USUAL_WEIGHT + examScore * EXAM_WEIGHT; }

代码里的权重参数为什么用常量而非直接写在表达式里?因为课程设计的成绩计算规则有可能在验收前被老师临时调整,如果权重散落在代码各处,改起来容易漏。成绩模块的界面通常是一个 TDBGrid 直接绑定 Enrollment 表,教师在表格里输入成绩后点击“保存”按钮统一写库,这样比一条条更新更高效,也方便批量操作。

5. 常见问题与避坑指南:BDE/ODBC 配置、乱码与数据不一致

5.1 BDE 连接字体变红或黑、连接失败

现象:在 BDE Administrator 里右键 Open 后,别名项一直显示红色,或者弹出 Database Login 对话框后输入任何密码都提示失败。

原因:最常见的是 ODBC 数据源名称和 BDE 别名指向的 DSN 不一致,多了一个空格或大小写不同;其次是系统缺少 Microsoft Access Driver 驱动,特别是 64 位系统默认装的是 64 位驱动,而 32 位的 C++ Builder 6.0 需要 32 位驱动。

解决:打开 ODBC 数据源管理器,确认数据源名称是纯文本且无特殊字符,再回到 BDE Administrator 双击别名检查 ODBC DSN 下拉框里选中的是不是同一个名称。如果提示驱动问题,重新安装 Microsoft Access Database Engine,并确保把 32 位版本装到了正确路径。

5.2 界面显示中文乱码

现象:TDBGrid 里查询结果显示中文变成问号或者乱码,但 Access 里数据本身是正常的。

原因:C++ Builder 6.0 的组件默认字符集是 ANSI,而 Access 2000 的数据库可能是按系统区域设置存储的,两者在字符编码上不一致;还有可能是 ODBC 连接时没有正确指定字符集。

解决:最简单的方式是在 ADOConnection 的连接字符串里面加上“Character Set”相关参数,或者在每个 TADOQuery 的 SQL 执行前设置 Session 的字符集。实际操作中我一般先检查 Access 表字段的文本类型是不是文本,如果字段类型是备注或者 OLE 对象,读取时也容易出现显示问题。避免踩坑的办法是建表时所有中文文本字段统一用“文本”类型,长度给够,不要用备注。

5.3 选课人数和选课记录数量对不上

现象:Course 表里选课人数是 10,但 Enrollment 表里关联该课程的记录只有 8 条,或者反过来记录比人数多。

原因:选课或退课操作没有做成事务,插入选课记录成功后更新课程人数的 SQL 执行失败,导致两边数据不一致;或者退课时只删了 Enrollment 记录,忘了更新人数。

解决:检查所有选课和退课的代码路径,必须按事务方式执行——先写 Enrollment 再更新 Course 人数,任何一步失败就回滚。另外可以在 Access 层面建立一个简单的检查查询,定期比对两张表的数据,这个操作可以在报告测试部分展示为数据完整性验证用例。从那以后,我每次给学生检查选课系统,第一件事就是数数选课人数和选课记录是否对得上,这个习惯救过不少翻车的演示。

5.4 工程文件换电脑后打不开

现象:在自己电脑上开发正常的工程,拷贝到另一个同学电脑上打开,提示找不到 BDE 别名或数据库文件路径无效。

原因:C++ Builder 6.0 的工程文件里保存了绝对路径,数据库连接字符串里引用的数据源是配置在某台机器上的 ODBC 名称,换机器后这个名称不存在。

解决:把数据库文件和工程放在同一级目录下,连接字符串里使用相对路径或者通过在程序启动时动态配置数据源名称来规避绝对路径依赖。验收前,我要求学生在另一台机器上完整走一遍部署流程——配置 ODBC、设置 BDE 别名、启动程序。能复现就说明部署步骤是完整的,答辩被问起时也更有底气。

5.5 报告里的图与代码对不上

现象:报告里的序列图画的是“学生选课后自动更新课程人数”,但代码里选课功能根本没有更新人数。

原因:先写代码后补文档,画图时凭想象补流程,导致图与实现脱节。这是评审老师最反感的硬伤。

解决:报告里每一张用例图、序列图、状态图,都要跟代码的调用链一一对应。画完图后,拿着图走一遍代码,图上有一步而代码里没有的,要么补代码,要么改图。我见过不少系统其实功能齐全,就是图对不上,被老师认定为“文档抄袭”,这个坑提前排查比事后解释划算得多。

6. 测试设计与答辩验收:20 条测试序列和演示技巧

测试数据的要求很明确:至少要有 20 个不同的对象测试序列。这里“对象”指的是业务操作实例,比如一个学生完成“登录→查询课程→选课→退课→再次选课→查询成绩”算一个测试序列。我建议按功能模块来设计测试矩阵,而不是随机点一点界面。

编号测试序列预期结果
01正确密码登录进入主窗体
02错误密码登录提示密码错误
03查询全部开课课程返回课程列表
04按课程名模糊查询返回匹配课程
05选择未满课程选课成功
06选择人数已满课程提示已满
07重复选择同一课程提示已选过
08退掉已选课程选课记录删除
09退课后重新选同一门课可以选课成功
10查询选课情况显示已选课程
11录入平时成绩保存成功
12录入考试成绩保存成功
13计算总评成绩按权重计算正确
14课程人数超限后选课被拒绝
15未登录直接访问主窗体被拦截
16选课后查询课程人数人数加 1
17退课后查询课程人数人数减 1
18修改密码后重新登录新密码生效
19查询已退课程成绩无记录
20多条件组合查询结果准确

每条测试序列都要记录输入数据、操作步骤、实际输出和测试结果,这个表格直接放进报告测试部分,比写“系统测试通过”这种一句话有说服力得多。测试时注意覆盖正常流和异常流——异常流才更能体现软件工程素养,比如选满课程、重复选课、密码错误这些反向用例,评审老师通常会盯着这些场景提问。

答辩演示的节奏也很重要,我带的项目组通常按这条线走:先演示登录,再查一门具体的课程,现场选课,然后查选课情况确认,再退课,最后查成绩。整个过程控制在三分钟以内,每一步都干净利落。不要漫无目的地在界面上乱点,更不要在演示开始前才重新编译工程——答辩前最后一次完整编译和运行测试必须提前完成,现场大概率没有时间修 bug。

报告装订顺序按指导书来:选题、报告个人信息、课程设计目的、设计要求、设计内容(需求分析、软件设计、软件测试、源代码)、总结。需求分析部分一定要有你的用例图和序列图,软件设计部分要有对象模型和至少一个重要对象的状态图,源代码部分只放手动编写的代码,自动生成的部分不要塞进去凑页数。总结里系统评价和自我心得分开写,自我心得不要写空话,“学会了事务处理”“理解了状态图的作用”这种具体收获比“学到了很多”有价值得多。希望这份完整解决方案能帮到你,更希望你拿着这套方案走一遍流程之后,能感受到软件工程不是一堆文档的堆砌,而是让代码从一开始就少犯错的工程习惯。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进