简介《软件工程课程设计指导书》是一份面向计算机科学与技术专业学生及软件工程初学者的实践指导文档围绕课程设计如何从需求分析走向可运行的小型系统展开。资源包共1个doc文件约539KB内容以选课系统为贯穿案例给出10学时上机安排、C Builder 6.0与Access 2000开发环境以及ODBC数据源建立、数据库连接等操作指引。文档还细化登录、查询、选课、退课与成绩管理等功能要求并列出用例图、序列图、对象模型、状态图和测试数据设计等报告组成部分便于读者按软件工程规范完成开发与文档撰写。已有99人学习适合需要对照课程设计任务、搭建实验环境、整理提交报告和查漏补缺的学生参考。1. 从一份选课系统课设说起10 个学时也要走完软件工程全流程十个上机学时要交一套能跑起来的选课系统还要附上用例图、序列图、对象模型、状态图以及二十组不同的测试序列。这是软件工程课程设计指导书里给出的任务书也是很多人第一次被要求把开发流程从头到尾走完整。多数人的第一反应是先打开 C Builder 把窗体拖出来功能调通以后再回头补文档结果报告写到需求分析就卡住因为代码里根本找不到与用例对应的实现。这份指导书的价值不在于教会你某个控件怎么用而在于把“先立需求、再定结构、最后落代码”的顺序固定下来。选课系统业务熟悉、实体清晰、状态可枚举是练完整流程最省力的载体适合计算机专业做课设、毕设起步的同学也适合手里有老式 C Builder 项目、想重新梳理一遍的开发者。2. 需求先立住从字段清单倒推实体与用例边界2.1 三张主表加一张关系表把散落的字段收敛成四个实体指导书给出的字段是一张平铺清单学生姓名、学号、登陆密码、性别、出生年月、籍贯、地址、学生电话、家庭地址教师号、教师姓名、教研室、职称、性别、教师电话课名、课号、学分、先行课号、课时、开课教室、人数限制、选课人数再加上考试成绩、平时成绩、总评成绩。照抄这份清单去建一张大表写代码的时候会很爽但会碰到三个很典型的问题。第一是更新异常。一个学生选了五门课他的家庭地址就在表里重复了五行改一个电话号码要改五处漏改一处就出现两个版本的地址。第二是插入异常。如果主键包含学号一门刚开、还没人选的课压根插不进去因为学号是空的。第三是删除异常。学生把最后一门课退掉整行删掉他的基本档案也跟着消失了。这三个异常在报告的需求分析章节里正好是拆表的依据写进去比空谈“满足第三范式”有说服力得多。按依赖关系收敛之后得到四个实体实体主键主要属性外键关系学生学号姓名、登陆密码、性别、出生年月、籍贯、地址、学生电话、家庭地址无教师教师号姓名、教研室、职称、性别、教师电话无课程课号课名、学分、先行课号、课时、开课教室、人数限制、选课人数教师号→教师先行课号→课程自引用选课记录学号课号考试成绩、平时成绩、总评成绩、状态学号→学生课号→课程有两处值得单独说明。一个是“选课人数”它是可以从选课记录表算出来的派生字段理论上不该存。但课程列表页每刷新一次就COUNT(*)一遍几百条记录时滚动条会明显卡顿所以我一般在课程表里冗余存一份同时要求写入必须在同一个事务里同步更新再留一个“重算人数”的维护按钮做核对。这就是一笔需要提前想清楚的取舍报告里写出来就是设计决策不写就是一个说不清的疑点。另一个是“先行课号”它是课程表指向自己的外键。选修一门课之前要先修完它的先行课这个规则在数据库层面建自关联在业务层做一次带深度限制的递归校验防止课程表里出现 A 的先修是 B、B 的先修是 A 这种环。深度限制取 3 层就够了再多说明课程体系本身设计有问题。2.2 六类用例的边界查询和选课不是一回事指导书列出的功能是登录、查询开课情况、查询选课情况、查询成绩、选课、退课到教师侧还要补一个录入成绩。把这七个动作直接画成七个用例放进用例图看着齐全但评审提一个问题就会露出破绽“查询开课情况和查询选课情况有什么区别”如果答不上来说明用例边界没想清楚。我的划法是按数据视角分查询开课情况是课程维度的回答“这学期开了哪些课、谁教、在哪间教室、还剩几个名额”查询选课情况是学生维度的回答“我选了哪几门、学分合计多少、有没有和别的课撞时间”。两者的数据来源、刷新时机、参与者关注点都不同所以拆成两个用例是合理的合并反而是偷懒。编号用例参与者前置条件后置条件UC-01登 录学生、教师无建立会话记录角色UC-02查询开课情况学生已登录无状态变更UC-03查询选课情况学生已登录无状态变更UC-04选课学生已登录、名额未满、未选过、先行课已通过新增选课记录课程人数1UC-05退课学生已登录、记录存在且未录成绩记录置为已退课程人数-1UC-06查询成绩学生已登录、成绩已录无状态变更UC-07录入成绩教师已登录、该课由本人讲授写平时成绩、考试成绩、总评成绩UC-05 的前置条件里“未录成绩”这四个字很容易被忽略但它是必须加的。允许学生退掉一门已经录了成绩的课成绩记录就成了没有归属的数据如果同时又物理删除了选课记录那这条成绩直接从库里消失教师在成绩单上看到的人数比实际少直接就是一个说不清的问题。加这个前置条件代价只是多一条SELECT判断。2.3 用例规约与序列图一次选课跨了四层用例图画完只是骨架评审真正会看的是用例规约写没写细。以 UC-04 选课为例规约至少要包含下面这些内容缺一项都会被追问项目内容用例名选课参与者学生前置条件已登录课程存在选课人数小于人数限制未选过该课先行课已通过或该课无先行课基本流1. 学生在课程列表选中一行 2. 点击“选课” 3. 系统执行三重校验 4. 写入选课记录 5. 课程选课人数加一 6. 刷新列表并提示成功异常流3a 名额已满提示并回滚3b 已选过该课提示“不能重复选课”3c 先行课未通过提示先行课名称后置条件选课记录加一课程人数加一二者保持一致序列图则要画清楚这一次点击到底经过了哪些对象。我在报告里用的是四层消息表比纯图形更好核对序号发起方接收方消息说明1学生CourseForm点击“选课”界面层取当前选中行2CourseFormCourseManagerSelect(stuId, courseId)控制层不带控件引用3CourseManagerCourseDAOGetCourse(courseId)读人数限制、已选人数、先行课号4CourseManagerCourseDAOExistsSelection(...)重复选课校验5CourseManagerCourseDAOHasPassedPre(...)先行课校验6CourseManagerCourseDAOBeginTrans / Insert / IncCount / Commit事务边界在控制层7CourseForm学生刷新列表、提示结果回显不参与判断序列图画到这一层需求分析章节就不再是凑页数的插图而是能跟后面的分层设计一一对应上的东西。课程列表页用到的查询视图直接在 Access 里建成一个查询对象界面层只负责绑定-- 课程列表视图剩余名额由人数限制减去已选人数得出列表页只读这个查询 SELECT c.[课号], c.[课名], t.[教师姓名], c.[学分], c.[课时], c.[开课教室], c.[人数限制], c.[选课人数], c.[人数限制] - c.[选课人数] AS [剩余名额] FROM [课程] AS c LEFT JOIN [教师] AS t ON c.[教师号] t.[教师号] ORDER BY c.[课号];这里用LEFT JOIN而不是INNER JOIN是为了让还没排教师的课程也能出现在列表里否则新课一开、教师没定学生在列表上根本看不到这门课。剩余名额作为计算列返回界面层就不用再写一遍减法避免了两处算法不一致。3. 总体设计三层职责、对象模型与 Access 表结构3.1 三层职责划分窗体不写 SQLDAO 不碰控件C Builder 6.0 这类 RAD 工具最大的诱惑是把所有逻辑都写在按钮的OnClick里双击按钮、写三行 SQL、跑通了看起来效率极高。但一个选课系统的窗体数量在五到八个之间每个窗体三到五个按钮事件函数全堆在 Form 的.cpp里文件很快过千行改一个校验规则要在里面翻半天。更麻烦的是报告里的对象模型画不出来因为压根没有“对象”只有窗体和控件。我一般按三层切层典型类允许做的事禁止做的事表现层TLoginForm、TCourseForm、TScoreForm取控件值、非空校验、回显结果、按钮启用禁用拼 SQL、开事务控制层TLoginManager、TCourseManager、TScoreManager业务规则判断、事务边界、组装返回消息引用任何控件数据访问层TStudentDAO、TCourseDAO、TScoreDAOSQL 与参数绑定、结果集转结构体弹 MessageBox、读界面状态数据访问层对外只暴露方法不把TADOQuery指针传出去这样上层想绕开 DAO 直接查库都没有路径// CourseDAO.h数据访问层只暴露业务方法内部持有连接不暴露查询对象 class TCourseDAO { public: TCourseDAO(TADOConnection *conn); ~TCourseDAO(); bool GetCourse(const AnsiString courseId, CourseInfo info); bool ExistsSelection(const AnsiString stuId, const AnsiString courseId); bool HasPassedPre(const AnsiString stuId, const AnsiString preId); bool InsertSelection(const AnsiString stuId, const AnsiString courseId); bool IncChosenCount(const AnsiString courseId, int delta); bool SetSelectionStatus(const AnsiString stuId, const AnsiString courseId, const AnsiString status); bool BeginTrans(); bool Commit(); bool Rollback(); private: TADOConnection *FConn; // 连接由外部注入DAO 不负责创建和释放 TADOQuery *NewQuery(); };CourseInfo是一个只有字段的结构体约有课号、课名、学分、人数限制、选课人数、先行课号六个成员。连接对象由外部注入而不是在 DAO 构造函数里自己创建好处是全程序共用一个连接事务才能跨 DAO 生效如果每个 DAO 各建一个连接选课事务里“插入记录”和“人数加一”就落在两个连接上回滚只能回滚一半。NewQuery()是私有工厂方法负责创建、挂连接、设置LockType避免每个方法重复三行样板代码。3.2 对象模型与状态图选课记录的五个状态对象模型在报告里通常画成类图落到文字上可以这样表述Student与Course是多对多关系中间由Selection承载同时Selection带上成绩属性Teacher与Course是一对多Course与自身通过先行课号形成自关联。控制层的三个 Manager 分别对应三个业务域各自依赖若干 DAO。比类图更容易被评审追问的是状态图。选课记录不是只有“存在”和“不存在”两种状态加上成绩就有了完整的生命周期当前状态事件守卫条件下一状态动作无记录选课名额未满、先行课通过已选插入记录人数1已选退课未录成绩已退状态置为已退人数-1已选录入成绩教师为任课教师成绩已录写平时成绩、考试成绩成绩已录计算总评两项成绩均非空总评已定计算并写总评成绩已录退课无保持成绩已录拒绝并提示已退选课名额未满已选状态改回已选人数1这张表决定了一个实现选择退课到底做物理删除还是置状态位。做物理删除很省事但“已退”这个状态就无处安放状态图上画不出来学生重复选同一门课时的唯一性判断也得靠删除后的记录是否还存在来做逻辑会绕。我的做法是选课记录表加一个状态字段取值“已选/已退”退课只改状态、不删行查询选课情况时过滤掉已退记录。代价是表会慢慢变大课设规模下几十上百条记录完全无感。3.3 Access 建表与约束一条 SQL 建完四张表字段名用中文跟指导书的字段清单对齐报告里写数据字典时直接抄不用做一次中英映射。建表 SQL 通过 ADO 连接执行也可以在 Access 的查询设计器里切成 SQL 视图直接跑-- 学生表学号做主键登陆密码存摘要而不是明文长度按 SHA1 十六进制 40 位留 CREATE TABLE [学生] ( [学号] TEXT(12) NOT NULL, [姓名] TEXT(20) NOT NULL, [登陆密码] TEXT(40) NOT NULL, [性别] TEXT(2), [出生年月] DATETIME, [籍贯] TEXT(30), [地址] TEXT(60), [学生电话] TEXT(20), [家庭地址] TEXT(60), CONSTRAINT [PK_学生] PRIMARY KEY ([学号]) ); -- 课程表教师号与先行课号都是外键先行课号指向本表 CREATE TABLE [课程] ( [课号] TEXT(10) NOT NULL, [课名] TEXT(40) NOT NULL, [学分] INTEGER, [先行课号] TEXT(10), [课时] INTEGER, [开课教室] TEXT(20), [人数限制] INTEGER NOT NULL, [选课人数] INTEGER NOT NULL, [教师号] TEXT(10), CONSTRAINT [PK_课程] PRIMARY KEY ([课号]) ); -- 选课记录联合主键保证同一学生同一课程只有一条记录 CREATE TABLE [选课记录] ( [学号] TEXT(12) NOT NULL, [课号] TEXT(10) NOT NULL, [平时成绩] DOUBLE, [考试成绩] DOUBLE, [总评成绩] DOUBLE, [状态] TEXT(4) NOT NULL, CONSTRAINT [PK_选课记录] PRIMARY KEY ([学号], [课号]) );人数限制和选课人数都设了NOT NULL是因为它们参与条件更新允许为空会让WHERE 选课人数 人数限制在空值行上直接判假出现“有名额也选不上”的异常现象排查起来很费时间。成绩字段用DOUBLE而不是整型总评保留一位小数时不用再做类型转换。外键约束在 Access 里通过 ADO 执行ALTER TABLE有时不生效稳妥做法是建完表以后在“关系”窗口里手动拖连线并勾选“实施参照完整性”。这一步报告里要留截图因为它是“数据库设计”章节里唯一能证明约束真的建了的东西。3.4 界面原型与事务边界哪些操作必须打包界面不用做得多漂亮但要能一眼看出角色差异。我的窗体清单一般是五张窗体主要控件关键交互TLoginForm两个 Edit、两个 Button回车即登录区分学生/教师入口TCourseFormDBGrid、选课/退课/刷新按钮选中行后按钮才启用防止空选TSelectionFormDBGrid、学分合计 Label只读过滤掉已退记录TScoreQueryFormComboBox、DBGrid选课号即刷新不做“查询”按钮TScoreInputFormDBGrid、两个 Edit成绩列可编辑其余列只读事务边界只有三处需要严格打包选课时“插入记录”和“课程人数加一”退课时“状态置已退”和“课程人数减一”批量录入成绩时的整批提交。C Builder 里BeginTrans / CommitTrans / RollbackTrans挂在TADOConnection上不能嵌套所以控制层要保证同一时刻只有一个事务在跑做法是在 Manager 里加一个FBusy标志进入事务前置位、__finally里复位。4. 关键实现ODBC 打通链路与业务代码落地4.1 数据源与连接DSN 与无 DSN 两条路怎么选指导书给的附录走的是老路子先用 Access 建 mdb再到控制面板建 ODBC 数据源然后在 BDE Administrator 里新建一个 ODBC 别名指过去最后在 C Builder 里用 BDE 组件连。这条链路能通但步骤多、依赖本机配置答辩换一台机器就断。现在更省事的是直接用 ADO 组件连 Access两种写法// 写法一走 DSN换机器只要重新建一个同名数据源代码不动 ADOConnection1-LoginPrompt false; ADOConnection1-ConnectionString ProviderMSDASQL.1;Persist Security InfoFalse;Data Source选课数据源; ADOConnection1-Connected true; // 写法二无 DSN 直连mdb 放 exe 同目录的 data 子目录整个文件夹拷走就能跑 AnsiString dbPath ExtractFilePath(Application-ExeName) data\\选课.mdb; ADOConnection1-LoginPrompt false; ADOConnection1-ConnectionString ProviderMicrosoft.Jet.OLEDB.4.0;Data Source dbPath ;; ADOConnection1-Connected true;两条路的差别很实际DSN 方式要求每台机器都配一遍数据源好处是路径不写死在代码里无 DSN 方式把路径拼在运行时整个项目目录拷到哪都能跑代价是 mdb 必须跟着 exe 走。课设要交的是“能运行的系统”我一般选无 DSN并在启动时做一次自检// 启动自检连不上就在主窗体出现前说清楚别等点“选课”才抛异常 bool __fastcall TMainForm::CheckDb() { try { ADOConnection1-Connected true; return true; } catch (Exception e) { ShowMessage(数据库连接失败请检查 data 目录下的 mdb 文件\n e.Message); return false; } }自检放在OnCreate里返回 false 就直接Application-Terminate()。这样答辩现场如果环境不对看到的是明确提示而不是一个能打开、点哪儿都报错的空壳。4.2 登录口令摘要、失败计数与角色分流登录有三个容易做浅的点。第一是口令不能明文比对第二是 SQL 不能拼接第三是角色要能分流到不同主界面。// 登录校验参数化查询 摘要比对返回角色0 失败 / 1 学生 / 2 教师 int TLoginManager::Login(const AnsiString userId, const AnsiString pwd) { AnsiString digest Sha1Hex(pwd); // 与入库时同一套摘要算法 TADOQuery *q FStudentDAO-NewQuery(); try { q-SQL-Text SELECT [登陆密码], [角色] FROM [学生] WHERE [学号] :id; q-Parameters-ParamByName(id)-Value userId; q-Open(); if (q-Eof) return 0; // 用户不存在不区分提示 if (q-FieldByName(登陆密码)-AsString ! digest) { IncFailCount(userId); // 失败次数落库不落内存 return 0; } ResetFailCount(userId); return q-FieldByName(角色)-AsInteger; } __finally { q-Close(); delete q; } }Sha1Hex用 Indy 自带的TIdHashSHA1实现入库时对明文算一次十六进制串存进登陆密码字段登录时对输入再算一次比对。IncFailCount和ResetFailCount分别对失败次数、最后失败时间两列做更新连续失败五次就把锁定时间置为当前时间加十分钟登录前先判这个时间。失败计数落库而不放内存是因为 C Builder 的程序关掉就清零内存计数等于没有。参数化这件事值得单独强调。用q-SQL-Text ... WHERE [学号] userId 这种拼法学生姓名里出现一个单引号录入时手滑、或者姓名本身带撇号就会直接抛 SQL 语法异常界面上弹一串英文答辩时很难解释。4.3 选课与退课名额、重复、先行课三道校验选课是整个系统里唯一需要事务的地方也是唯一会被并发影响的地方。虽然课设是单机但“先查后写”这个习惯必须在这里纠正过来。// 选课校验在控制层事务边界也在控制层DAO 只管执行 bool TCourseManager::Select(const AnsiString stuId, const AnsiString courseId, AnsiString msg) { CourseInfo info; if (!FCourseDAO-GetCourse(courseId, info)) { msg 课程不存在; return false; } if (FCourseDAO-ExistsSelection(stuId, courseId)) { msg 不能重复选课; return false; } // 先行课校验只查一层多级先行课靠事先录入时保证顺序 if (!info.PreCourseId.IsEmpty() !FCourseDAO-HasPassedPre(stuId, info.PreCourseId)) { msg 请先修完先行课 info.PreCourseId; return false; } if (!FCourseDAO-BeginTrans()) { msg 事务开启失败; return false; } try { if (!FCourseDAO-InsertSelection(stuId, courseId)) { FCourseDAO-Rollback(); msg 写入选课记录失败; return false; } // 条件更新影响行数为 0 说明名额在写入瞬间被占满 if (FCourseDAO-IncChosenCount(courseId, 1) 0) { FCourseDAO-Rollback(); msg 选课人数已满; return false; } FCourseDAO-Commit(); msg 选课成功; return true; } catch (...) { FCourseDAO-Rollback(); msg 选课过程出现异常已回滚; return false; } }IncChosenCount内部的 SQL 是条件更新不是简单加一-- 名额判断交给数据库受影响行数为 0 就说明满了由调用方回滚 UPDATE [课程] SET [选课人数] [选课人数] 1 WHERE [课号] ? AND [选课人数] [人数限制];IncChosenCount返回q-RowsAffected。把判断压进WHERE的好处是从“读到还剩一个名额”到“写回加一”之间不存在窗口哪怕将来改成多机运行这一句也不会超卖。退课则是它的镜像-- 退课只有状态为“已选”的记录能退重复点按钮时第二次影响行数为 0 UPDATE [选课记录] SET [状态] 已退 WHERE [学号] ? AND [课号] ? AND [状态] 已选; UPDATE [课程] SET [选课人数] [选课人数] - 1 WHERE [课号] ? AND [选课人数] 0;第二条带[选课人数] 0是为了防脏数据把人数减成负数。两条语句放在同一个事务里任一条失败就回滚这样才不会出现“记录退了、人数没减”的错位。4.4 成绩录入与总评计算公式只写一处总评的计算规则很简单平时三成、考试七成但写法上有三个地方会出错界面里算一遍、DAO 里又算一遍两处保留小数的规则不一样缺考按 0 分算还是按空值算四舍五入用Round时对.5的处理不符合预期。// 总评计算唯一入口界面和报表都调它不在别处重写公式 double TScoreManager::CalcFinal(double daily, double exam, bool absent) { if (absent) return 0.0; // 缺考直接记 0不参与加权 double total daily * 0.3 exam * 0.7; // 先放大十倍取整再缩回去避开浮点直传 Round 时 0.5 的边界抖动 return Floor(total * 10.0 0.5) / 10.0; }absent由界面上的“缺考”勾选框传入勾了就不看两个分数。如果只是把考试成绩留空总评会按 0 分加权算出一个非零结果成绩单上看起来像“参加考试但考砸了”跟实际缺考是两回事报告里的测试用例正好可以覆盖这一条。录入时的事务边界按“一批”划教师在一张表里改十几行点一次保存整批提交中间任何一行失败全部回滚避免出现半批数据。具体做法是遍历 DBGrid 的每一行、逐行调用UpdateScore全部成功才Commit失败就Rollback并把出错的行号返回给界面高亮。5. 避坑与排查五个让课设当场翻车的点5.1 环境与部署类换台机器就跑不起来现象一在自己电脑上双击 exe 一切正常拷到答辩用的机器上提示“未找到数据源名称”或者“未找到提供程序”。原因连接串写的是 DSN而 DSN 是注册表里的本机配置不会跟着项目走或者目标机器上装的是 64 位 Office没有 32 位的 Jet 引擎Microsoft.Jet.OLEDB.4.0直接找不到。解决改用无 DSN 连接串把 mdb 放到 exe 同目录的data子目录里路径用ExtractFilePath(Application-ExeName)运行时拼。如果确实要用 DSN就在答辩前把建数据源的步骤做成一个说明页附在报告后面现场照着配一遍两分钟能搞定。现象二编译没有任何问题双击运行报“BDE 初始化失败”或弹出一堆别名配置对话框。原因附录里那套 BDE 方案依赖运行期的 dll 和注册表项只在装过 C Builder 的机器上有效。把 exe 单独拷出来就是个空壳。解决整个项目统一走 ADO不用 BDE 组件。如果指导书要求必须演示 BDE就老老实实现场用装了开发环境的机器跑不要试图把 exe 拷来拷去。5.2 数据一致性类人数对不上、成绩被覆盖现象一课程列表显示还剩 2 个名额连续点五次选课全部成功选课人数超出人数限制。原因校验用的是界面上的缓存值或者“查人数”和“写人数”是两条独立语句中间没有事务也没有把判断压进WHERE。解决按 4.3 的写法用条件更新WHERE 选课人数 人数限制看RowsAffected判断成败失败就回滚。同时在选课成功后立刻重刷列表不要让界面继续显示旧值。现象二学生退课后课程人数没减或者减了两次变成负数。原因退课按钮没做幂等用户手快点了两下或者退课只改了记录状态忘了同步人数。解决UPDATE 选课记录的WHERE里带状态 已选第二次点击影响行数为 0直接跳过人数更新按钮点击后立即Enabled false事务结束再恢复。现象三某门课的总评成绩被别的课覆盖学生查成绩时两门课分数一样。原因更新成绩的WHERE只写了学号漏了课号UPDATE命中该学生所有选课记录。解决所有更新语句的WHERE必须同时带学号和课号写完以后拿一条测试数据专门验一次“改一门课的成绩另一门不受影响”。5.3 代码与安全类SQL 拼接和明文口令现象一录入一个带单引号的学生姓名登录或者查询直接报 SQL 语法错误弹出一长串英文异常。原因用字符串拼接构造 SQL用户输入里的特殊字符直接改变了语句结构。解决全部改成Parameters-ParamByName(...)绑定包括看起来“肯定是数字”的学号字段。数字字段拼进去虽然不会报错但一旦有人输入1 OR 11这类内容登录校验就会被绕过这在报告的安全小节里是可以写一条的。现象二mdb 文件被同学拷走用 Access 一打开所有账号口令一览无余。原因口令明文入库。解决入库前算 SHA1 摘要登录时对输入再算一次比对。这个改动量很小但报告里“安全性考虑”这一节就从空话变成了有代码支撑的设计点。现象三编译时提示找不到某个单元或者改了头文件之后另一处报重复定义。原因C Builder 6.0 的预编译头机制比较老.h里直接#include了别的窗体头文件形成循环引用。解决头文件里尽量用前置声明只在.cpp里#include具体头文件实在绕不开就重新Build All Projects不要只按 F9 增量编译。6. 测试与答辩20 组序列怎么凑齐报告怎么写得像真做过指导书要求书面提交至少 20 个不同的对象测试序列这个数字听着多用三套方法交叉一铺就够了。等价类负责覆盖“合法/非法”两大类合法学号、不存在的学号、格式错的学号、空输入四个输入就换出四组。边界值盯着两个地方人数限制刚好等于已选人数时第 N 个选课应该成功、第 N1 个应该失败成绩的 0 分、100 分、101 分三个点验证校验有没有卡住上限。状态迁移是最容易被忽略、但最能体现“做过”的一类先选课再退课再选同一门课验证状态位改回“已选”而不是插出两条记录先录成绩再退课验证被拒绝录完成绩改分数验证总评跟着重算而不是留着旧值。这三条写进测试表评审一看就知道状态图不是画着玩的。编号前置状态输入预期输出覆盖方法T-01未登录正确学号正确口令进入学生主界面等价类-合法T-02未登录正确学号错误口令提示失败失败次数1等价类-非法T-03连续失败 5 次正确口令提示账号锁定边界值T-04名额已满点击选课提示人数已满人数不变边界值T-05名额剩 1点击选课成功人数达到上限边界值T-06已选该课再次点击选课提示不能重复选课等价类-非法T-07先行课未通过选后续课提示先行课名称等价类-非法T-08已选未录成绩点击退课状态置已退人数-1状态迁移T-09已退再次选同一门课状态改回已选不新增行状态迁移T-10已录成绩点击退课拒绝并提示状态迁移表格列十行作样例剩下十组按同样格式补成绩录入的边界0、60、100、101、总评计算的舍入59.95、60.05、缺考勾选、查询结果的空集与非空集、教师越权录入他人课程。每组都要写清“输入—预期—实际—是否通过”四列实际结果那列不要全部填“通过”留两三组写“首次不通过、修改后通过”并注明改了什么这才是真实测试记录的样子。报告章节和答辩问题的对应关系可以提前做成一张自检表报告章节评审想看什么常见扣分点需求分析用例图有边界、有参与者、有包含/扩展关系只画图不写用例规约软件设计对象模型有类、有属性、有关系状态图有守卫条件类图里全是窗体没有业务类数据库设计表结构、主外键、字段类型和约束一张大表没有范式说明软件测试20 组序列、覆盖方法、实际结果全是“通过”没有失败记录源代码只贴手写部分有注释把本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
企业资源文件管理:从零搭建高效管控体系 1. 项目资源管控的底层逻辑企业数字化转型过程中,资源文件管理往往是最容易被忽视的基建环节。我经历过三个从零搭建的IT项目,发现资源管理失控会导致至少30%的开发时间浪费在文件检索、版本冲突和协作混乱上。一个典型的案例是:某电商项目上… · 2026/11/3 19:56:35
汽车电子AI测试:挑战、策略与实践 1. AI测试在汽车电子领域的特殊挑战汽车电子系统与消费电子产品存在本质差异,这直接影响了AI测试的实施策略。传统车载ECU的故障率要求通常低于百万分之一,而智能驾驶系统的AI模型迭代周期已缩短至两周一次。这种严苛的可靠性要求与快速迭代需求形成了独… · 2026/11/3 19:55:35
离散数学期末拿分顺序:关系矩阵、传递闭包与图论证明模板 简介:面向计算机及相关专业本科生、专升本与考研复习人群的离散数学期末备考材料,以一份附答案与解析的期末试卷为核心,帮助读者在考前系统检验集合论、数理逻辑、图论、代数结构与格论等模块的掌握程度。压缩包内共1个PDF文件,约… · 2026/11/3 19:55:35
uniapp海外游戏分发工程:PG电子集成与Switch卡带区域适配 简介:这是一套面向海外游戏业务开发者的全栈前端解决方案,基于UniApp框架实现跨平台兼容,专为构建海外游戏交易平台、手游下载站及Switch游戏卡带销售系统设计。资源包含完整可二次开发的前端源码(非编译后产物)&#… · 2026/11/4 0:00:17
Qoder NEXT:智能代码补全工具的技术解析与应用 1. 项目概述Qoder NEXT作为新一代智能编程辅助工具,其核心突破在于通过深度优化代码补全算法,将AI生成代码的开发者采纳率提升了65%。这个数字背后代表着编程效率的实质性飞跃——开发者不再需要花费大量时间在重复性代码编写和机械性调试上,… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46