Post

换课工具

记录一个教师换课辅助工具的开发过程

换课工具

引言

很久前就开发了一个用于辅助老师进行“换课”的小工具。之前是以命令行的方式运行的,也因此我的“用户”并不想用。

现在结合 AI,可以在比较短的时间内做出稍微实用的 GUI 程序了。

需求听起来很简单:A 老师想在某天和 B 老师换一节课,系统需要检查冲突,并记录这次变更。

然而,当我们深入实现时,发现从“一张 Excel 课表”到“一个可用的换课系统”,中间跨越了几个有趣的逻辑鸿沟。本文将记录这些核心设计思路,特别是那些非代码层面的决策过程。

1. 架构抉择:CLI 与 GUI 的共生

早就做好的 CLI 版本“不好用”,本质问题是:是做一个给开发者用的命令行工具(CLI),还是做一个给最终用户用的图形界面(GUI)?

  • CLI (Command Line Interface): 开发快,调试方便,容易集成到脚本中。但对于不熟悉终端的老师来说,门槛太高。
  • GUI (Graphical User Interface): 交互友好,直观。但开发成本高,且 Go 语言在 GUI 领域的生态相对小众。

最终决策:我全都要。

为了兼顾开发效率和用户体验,我采用了核心逻辑与表现层分离的架构。

  • Core Layer: client/courseSwap 包封装了所有业务逻辑(解析、计算、冲突检测)。
  • CLI Layer: 使用 cobra 库构建。它负责解析命令行参数,调用 Core Layer,并将结果打印到终端。这成为了我开发过程中最主要的调试入口。
  • GUI Layer: 使用 fyne 库构建(Windows 下默认启动)。它负责渲染窗口、按钮、文件选择器,将用户的点击转化为 Core Layer 的函数调用。

这种设计不仅解决了“谁来用”的问题,还意外地提高了代码的可测试性——我可以在不启动笨重 GUI 的情况下,通过 CLI 快速验证核心逻辑的边界情况。

2. 驯服 Excel:非结构化数据的解析

这部分没有什么设计可言,纯粹根据源文件的格式,硬编码解析逻辑。 换一个学校,换一种格式约定(并没有约定),就直接修改代码,直接定制。

3. 核心难点:从“静态课表”到“动态日程”

这是开发过程中最大的思维转换点。

Excel 里的课表是静态的、周期性的。它描述的是:“每周一第一节,张老师在三班上课”。 但换课操作是针对具体日期的。用户会说:“我想换2025 年 1 月 1 日的那节课”。

如果直接在静态课表上操作,会产生歧义:你是要换掉以后所有周一的课,还是只换这一天?当然是后者。

所以程序需要引入了一个“推演”步骤:

  1. 读取 Excel,构建“静态课表模型”(Key: 周几+节次)。
  2. 获取当前日期,计算未来几周(例如 21 天)的具体日历。
  3. 将“静态课表”投影到这些具体日期上,生成“动态日程实例” (CourseInfo 结构体中包含具体的 Date 字段)。

只有在“动态日程”的维度上,换课操作才是有意义的。这使得系统能够精确处理“只换这一天”的需求,而不影响下周同一时间的课程。

4. 状态管理:为什么不修改 Excel?

当用户执行“换课”时,数据该怎么存? 最直观的想法是:直接修改 Excel 源文件,把 A 老师的名字改成 B 老师。

但我坚决否定了这个方案。

  1. 数据破坏风险: 修改源文件是破坏性的,万一程序 bug 导致数据丢失,后果严重。
  2. 难以回滚: 如果用户想撤销换课,或者想查看“原始安排”,修改后的文件无法提供这些信息。
  3. 并发冲突: Excel 文件通常被锁定,程序写入时容易报错。

因此实际上计算逻辑为:【源课表】转换为【具体课程安排】再叠加【换课记录】

  • 存储: 换课操作不修改 Excel,而是生成一条“换课记录”(Swap Record),存储在 SQLite 或云端。记录包含:{日期, 节次, 老师A, 老师B}
  • 计算: 每次查询课表时,程序先加载 Excel 的原始状态,然后按顺序回放所有换课记录。
  • 回滚: 删除一条换课记录,再次计算时,课表自然就恢复了原状。

这种设计使得系统具有了天然的“审计”能力,并且保证了原始数据的绝对安全。

5. 细节体验:参数缓存

在 CLI 测试和 GUI 使用中,我发现重复输入文件路径和老师姓名非常折磨人。 特别是 Excel 文件路径通常很长,每次重启程序都要重新选择,体验极差。

因此,引入了简单的参数缓存机制 (InputConfig)。 程序退出时,会将当前的配置(文件路径、最后选择的老师等)序列化为 JSON。下次启动时自动加载。

这只是一个微小的功能,但对于每天使用的工具来说,它带来的幸福感提升是巨大的。

结语

这个换课工具虽然规模不大,但它涵盖了软件工程中许多经典问题的缩影: 从接口与实现的分离,到非结构化数据的处理;从领域模型的抽象(静态 vs 动态),到数据一致性的保障(不可变数据源)。

很多时候,工具的好用与否,不仅仅在于核心算法的快慢,更在于这些设计决策是否贴合了实际的使用场景。

This post is licensed under CC BY 4.0 by the author.