应使用const引用传递大对象、显式声明依赖、优先返回值、拆分i/o与纯逻辑。例如:用const std::string&替代std::string传参;将logger::getinstance()改为参数注入;parse_config返回std::optional而非修改引用;parse_json接受std::string_view且不操作文件。

用 const 引用参数替代值传递或非 const 引用
函数参数若需读取大对象(如 std::string、std::vector),传值会触发拷贝,既低效又让调用方难以预测开销;传非 const 引用则隐含修改意图,破坏纯度,也阻碍测试时构造只读输入。改用 const std::string& 或 const MyData& 是最直接的改进。
- 避免意外修改:编译器强制保护输入,函数逻辑更可预测
- 支持字面量和临时对象:比如
process("hello")能直接绑定到const std::string&,而不能绑定到非 const 引用 - 单元测试时可自由传入任意 const 对象,无需准备可变副本
把依赖显式声明为参数,别藏在全局或单例里
如果函数内部调用了 Logger::getInstance() 或读了 g_config,它就无法脱离当前环境运行,测试时就得 mock 全局状态——这既脆弱又增加 setup 成本。把依赖拎出来作为参数,哪怕只是接口指针或函数对象,就能立刻提升可测性与复用边界。
- 例如:把
void send_email(std::string to)改成void send_email(std::string to, EmailSender& sender) - 测试时传入
FakeEmailSender,生产时传SmtpEmailSender,零耦合 - 函数签名即契约:谁调用谁负责提供依赖,不甩锅给环境
优先返回值而非副作用,避免隐藏状态变更
像 bool parse_config(Config& out) 这种“通过引用参数写结果”的写法,表面简洁,实则模糊了函数职责:它既是解析器,又是配置填充器。一旦 out 在调用前已有部分字段被设值,行为就变得不确定;测试时还得反复重置对象状态。
- 改用
std::optional<config> parse_config(const std::string& text)</config>,成功则返回新对象,失败则返回空,调用方完全掌控生命周期 - 返回值天然支持链式调用、
if (auto cfg = parse_config(...))等现代惯用法 - 没有隐式状态污染,同一个输入永远产生相同输出(符合 referential transparency)
小函数 + 纯逻辑,别掺杂 I/O 或 new/delete
一个函数如果同时做解析、分配内存、写文件、抛异常,它就很难被复用——下游模块可能只需要解析,但被迫引入文件系统依赖;测试时还得 mock 文件句柄、拦截 new 调用。真正易复用的函数,应该只做一件事,且这件事是纯计算。
- 把 I/O 拆出去:例如
parse_json只接受std::string_view,不碰std::ifstream - 把内存管理交给调用方:返回
std::vector<int></int>而非int*,由 RAII 保证安全 - 异常策略统一上移:底层函数用错误码或
std::expected,由顶层决定是否转为异常
最容易被忽略的是:复用性和可测性不是靠加注释或写文档换来的,而是由函数签名、参数类型、返回方式这些硬约束决定的。哪怕只改一个 & 变成 const&,或把一个全局变量挪成参数,就足以让原本锁死的函数,在另一个模块里跑起来。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











