先说结论
如果非要找一个最贴近的软件工程名词,我觉得最该说的是两个词:
- 模块化(modularization)
- 命名空间管理(namespace management)
如果再说得更贴一点,这类目录组织往往是在做:
- 按特性分包 / 按领域分包,也就是常说的
package by feature - 如果业务边界已经很强,还会带一点 bounded context 的味道
也就是说,它不是一个只有单一标准译名的小概念,更像是几套设计原则叠在一起。
这事先解决的,其实不是“好看”
很多人一看到 namespace,第一反应是避免重名。这个当然对,C++ 标准里的 namespace 本来就是拿来防止大型项目里名字冲突的 cppreference。但真到业务代码里,它的价值远不止这个。
目录和 namespace 一旦对齐,类名就不用再把上层语义重复一遍了。
比如以前可能会写成这样:
class TwapOrderManager;
class TwapScheduleEngine;
class VwapOrderManager;
class VwapScheduleEngine;
换成目录和命名空间以后,更自然的写法通常是:
namespace algo::twap {
class OrderManager;
class ScheduleEngine;
}
namespace algo::vwap {
class OrderManager;
class ScheduleEngine;
}
这一下,重复前缀没了,语义反而更清楚。因为“它属于哪个上下文”不再塞进类名里,而是交给目录和 namespace 来表达。
所以这套做法,本质上是在把上下文信息从名字里抽出去,交给结构来承载。
这背后更像“按特性分包”
如果你的目录是这样长的:
strategy/
twap/
order_manager.h
schedule_engine.h
slicer.h
vwap/
order_manager.h
schedule_engine.h
slicer.h
那你其实已经不是在做传统的“按技术层分包”了。
传统分法更像这样:
models/
services/
utils/
controllers/
这种分法前期看着整齐,后面业务一长,service 目录就会像垃圾回收站。跟一个功能相关的类会被打散到不同目录里,读代码的时候脑子一直在跳。
而 twap、vwap 这种分法,更接近 package by feature。也就是一个目录先回答“这块业务是干什么的”,再在里面放这块业务自己需要的对象和流程。Java 世界里这个说法更常见,但放到 C++ 一样成立,只是承载它的不是原生 package,而是“目录 + namespace + 头文件边界”。
说白了,你是在按变化原因组织代码,而不是按代码长相组织代码。
再往上走一步,就是高内聚低耦合
软件工程里老生常谈的那句“高内聚、低耦合”,其实就落在这种地方。
twap 下面的对象彼此协作频繁,就应该放得近一点,命名空间也靠得近一点;vwap 和它相似,但又不是一回事,就该给它单独边界。这样做有几个很直接的好处:
- 同一个模块里的类名可以短一点
- 模块之间的依赖更容易看出来
- 重构时更容易判断改动会不会串到别的地方
- 以后真要把某块能力拆成独立库,代价也小一些
这也是为什么很多成熟项目最后都会长成“目录边界 + 命名空间边界 + 编译边界”尽量一致的样子。QuickFIX 这类项目里,你也能看到类似思路,类型和扩展点都尽量放在清晰的命名空间下面,而不是靠巨长的类名前缀去硬区分 QuickFIX GitHub。
什么时候它又不够了
不过这事也别神化。
文件夹分层再套 namespace,只能说明你在往模块化方向走,不代表架构就自动好了。几个常见坑也很明显:
目录和命名空间对不上
目录是 twap/,代码里却还是一堆散着的全局类,或者 namespace common 到处乱飞,这种基本等于白分。
模块边界是假边界
表面上有 twap、vwap,实际上互相 #include,公共逻辑随便穿透,最后只是把文件挪了位置,耦合一点没降。
common 目录越长越胖
这个最常见。很多项目前面切得挺好,后面嫌麻烦,开始往 common、base、util 里丢东西。丢到最后,真正稳定的抽象没沉淀下来,反而多了一个谁都能动、谁都依赖的大坑。
结构只按层,不按业务
如果你的目录永远是 api、service、dao、model,那很多业务概念其实没有家。类名只好越来越长,把本该由结构表达的东西全塞回名字里。
那它到底该叫啥
如果是在团队里沟通,我觉得可以分三档说法:
- 只想说人话:按目录和命名空间做模块化
- 想说得更工程一点:按特性分包的组织方式,配合层级命名空间
- 如果这套边界已经和业务语义强绑定了:带 bounded context 味道的模块划分
我自己更倾向第二种。因为它最贴近你描述的场景。
你说的不是 C++ 语法技巧,也不是单纯命名规范,而是在做一件更实在的事:用结构承载语义,让名字回归本义。
类名短下来,文件名短下来,读代码时先看到的是 twap::OrderManager 这种上下文明确的对象,而不是 TwapOrderManagerImpl 这种把目录职责、命名职责、实现职责全搅在一起的东西。
这时候代码才有点像 Java 里包结构成熟之后那种感觉。
最后一句
所以,软件工程上当然有对应概念,但不是只有一个标准答案。
往大了说,它叫模块化。往代码组织上说,它叫按特性分包,再加命名空间管理。往领域边界上说,它又有点 bounded context 的意思。
怎么说呢,这种实践最值钱的地方,从来不是“名字看起来整齐了”,而是你终于不用靠超长前缀去弥补结构缺位了。
参考资料
- cppreference: Namespaces
- QuickFIX/C++ 官方站点
- QuickFIX GitHub 仓库
- Java Practices: Package by feature, not layer
- Martin Fowler: Bounded Context
写作附记
原始提示词
提示词:在编码开发的时候,C++一种好的实践规范,那就是按照文件夹分层,然后每个类上面套个 namespace,类似 Java 的 package,能有效减少文件名称和类名字里面掺杂冗余的内容,有个经典的项目就是 quickfix,近期开发算法服务也用到了类似的设计,twap vwap 很多功能模块都是类似的,软件工程学上有没有专门的概念?
写作思路摘要
- 从算法服务里
twap、vwap的真实模块触发点切入,先把判断亮出来。 - 没把问题硬解释成单一名词,而是拆成模块化、命名空间管理、按特性分包几个更准确的层次。
- 保留了 QuickFIX 和 Java package 的对照关系,用来说明 C++ 经常要靠目录和命名空间补结构语义。
- 重点落在“用结构承载语义”,而不是停留在“避免重名”这种表层解释。
- 补了
namespace、package by feature、bounded context相关参考链接,方便后续继续展开。