流程控制设计比功能实现更重要,需明确用户操作触发的对象协作、数据流转与异常拦截;主循环应分层解耦,命令调度与业务逻辑分离,输入/输出边界清晰,易变点封装为小函数,错误处理分级且不静默。

直接从“让别人(包括半年后的自己)能快速看懂、安全修改”这个目标出发,流程控制设计比写对功能更重要。对控制台图书管理系统这类项目来说,流程不是指界面跳转顺序,而是用户操作如何触发对象协作、数据如何流转、异常在哪拦截——这些决定了代码日后好不好改。
把主循环拆成职责明确的调度层
别把所有逻辑塞进一个 while(true) 里。主函数只做三件事:接收命令、分发给对应模块、统一处理退出和未知指令。其余逻辑全部下沉到具体类中。
- 用字符串映射到函数指针或简单命令枚举,避免长长的 if-else 嵌套
- 每个命令对应一个独立方法,比如 handleAddBook()、handleSearch(),不混杂输入解析、业务校验、文件保存
- 命令方法内部只调用 Library 或 User 类的公开接口,不直接操作 vector
或 fstream
让每一步操作都有清晰的输入/输出边界
面向对象不是堆类,而是让每个类知道自己“该响应什么、能返回什么、失败时说什么”。例如借书流程:
- 用户输入 ISBN → 控制层校验格式 → 转给 Library.findBook(ISBN) → 返回 Book& 或 nullopt
- 拿到 Book 后,调用 book.checkout() → 它只负责状态变更和前置判断,不负责提示用户或写文件
- 成功后,由控制层决定是否调用 Library.saveToFile(),失败则统一打印“借阅失败:{原因}”
这样改借阅规则,只需动 Book::checkout;换存储方式,只改 Library::saveToFile;加日志,只在调度层加一行 log。
用小函数封装易变点,而不是靠注释说明“这里以后要改”
哪些地方最可能变?用户输入格式、错误提示语、文件路径、默认值。把这些抽成独立函数,哪怕只有两行。
- getInputISBN() 封装输入+正则校验,未来支持条形码扫码只需重写它
- printError(const string& msg) 统一加颜色或前缀,不散落在二十个 cout
- getDataFilePath() 返回配置路径,不用全局 const string 硬编码
这些函数名本身就是文档,比 // TODO: 改成配置文件 更可靠。
让错误不静默,也不泛滥
控制台程序最容易出问题的是输入非法、文件打不开、内存不足。但维护性差的代码往往两种极端:要么全用 try-catch 包住 main,要么完全不处理。
- 输入类错误(如输入非数字ID)在调度层捕获,提示“请输入有效数字”,不抛异常
- 系统级错误(如 ofstream 失败)由 Library::saveToFile 抛出自定义异常,调度层统一转为用户可读提示并记录到 error.log
- 绝不出现 if (!file.is_open()) { exit(1); } —— exit 会跳过析构,导致未保存数据丢失
错误路径和正常路径一样有明确出口,后续加监控或重试机制才有基础。











