[Video] Refactoring C++ Code for Unit testing with Dependency Injection - Peter Muldoon - CppCon 2024
条评论Conclusion
Refactoring C++ Code for Unit testing with Dependency Injection - Peter Muldoon - CppCon 2024
评价:⭐⭐⭐⭐
依赖注入就是约定接口,通常就是几派:继承、模板、类型擦除,总结得很好。
Refactoring for DI 那一节很不错,是一个从屎山开始拆解业务、理解业务和抽象业务的好例子。
- 归类、分解代码是为了单独测试,那一但有了明确的分类,那么就有替换、注入的可能性了。
相比起把所有的东西全部塞到一个 struct 里面,把意义关联的、对称的概念用 template struct 标明出来,能获得更好的内聚性、更好的类型安全(没有隐式转换!!!)。
1
2
3
4
5
6
7
8template<typename T>
struct OptionalPairT {
std::optional<T> bid_;
std::optional<T> ask_;
};
using SidePair = OptionalPairT<Side>;
using BrokerPair = OptionalPairT<Broker>;
using YieldPair = OptionalPairT<Yield>;
Method of injection
Linking
Pro
- No code change
Con
- UB/ODR violation
- Brittle and confusing
链接是个奇妙的东西,小心玩火。
Inheritance
Pro
- Can handle a lot of methods
- Well understood mechanism
- Easier to add to old code
Con
- Numerous functions with testing functions inside
- Data mixed in
- V-table extra hop
经典,但是什么都往 interface 里面加,还不如 CV。
Template
Pro
- Only define methods that actually need
- No running overhead
- Use concepts(C++20) as an “interface”
Con
Hard to add to old code
Compile time
没有 Concept 就是难写一些;而且类型不同的话没法方便用容器。
Type erasure
Calling any thing satisfying a function signature via std::function std::invoke
Pro
- Invokable on any callable target
- Versatile
Con
- Can handle only one method
- Runtime overhead
80% 情况下的通解了。
Null valued objects/stubs
A stub with no functionality - only satisfy type requirements.
- Disable part of the system
- No actual logic: discard arguments, return fixed values
Types of Dependency Injection
Setter DI
1 | void SetSender(std::unique_ptr<Sender> sender) |
Do not use this !!!
Class could be unusable for some time: sender not set.
Method DI
1 | Resp Send(Com& com, ...) { |
The function signature is changed.
e.g. may not work for an external library.
Constructor DI
1 | class Processor{ |
The constructor is changed.
Roadblocks of DI
Object creation hidden
- No handle to inject
- Constructors via Singletons or globals
Reaching through multiple objects
- Long chain of process breaks the principle of least knowledge
Disentangle getting from setting
Dig out pure functions
1
2
3
4
5
6
7
8
9
10if (handle.getBidPrice(&decimalPrice))
bid_.setPrice(Tickers::PriceVariant(decimalPrice));
else if (handle.getBidPrice(&doublePrice))
bid_.setPrice(Tickers::PriceVariant(doublePrice));
else if (handle.getBidPrice(&floatPrice))
bid_.setPrice(Tickers::PriceVariant(floatPrice));
const boost::optional<Tickers::PriceVariant> &bid_price = getBidPrice(handle);
if(bid_price)
bid_.setPrice(*bid_price);
Having too many dependencies in one place
- Impractical to pass
Class packed with huge chunks of data/functionality
- God class
- Too many dependencies
Functionality splintered and spread around
- Fragmented inheritance chain
- Duplicated code
- Blended into many utility classes
Lack of data structure
Ungrouped data
1
2
3
4
5
6
7
8
9Data getData(const Tick&,
const std::optional<Side>& bid,
const std::optional<Side>& ask,
const std::optional<Side>& localBid,
const std::optional<Side>& LocalAsk,
const std::optional<Broker>& bidBroker,
const std::optional<Broker>& askBroker,
const std::optional<Yield>& bidYield,
const std::optional<Yield>& askYield) const;Gather data into coherent data structures
Highway Express of DI
Object creation done outside the logic of functions
Pass in Dependencies directly
Pass in Dependency suppliers
让信息从外部输入
Invoke methods on immediate objects
Avoid invoking methods on an object returned by other methods
Disentangle information retrieval/calculation from state changing
Find the const or pure functions
寻找无状态的函数
Refactor God classes
Functionality clustered and pushed into tiered abstraction layers
Lessen unnecessary dependencies
分解大坨的屎
Refactor fragmented functionality
Cluster splintered functionality together
Lessen dependencies
内聚耦合分明
Refactor data/state
Gather into coherent data structures
抽取有意义、耦合的数据结构
Immutable APIs
API is used wide so interface cannot change
Transparent DI using:
- Default arguments
- Delegating functions
- Delegating constructors
Lazy initialization
Use a dependency provider/creator.
DI unexpected snags
这个没看懂
- Turn into regular function
- Add type erasure at call point
- Use template DI
Conclusion
DI Myths
- It’s simple? Only for simple systems parts
- Overkill on small projects
- Easy to add later
DI Truths
- It’s hard for real production systems
- Properly factored code is the KEY to DI
- Give weight to local refactoring prior to DI
- Poor code needs more work for DI
- Improves the flexibility/re-usability/testability of a system
- Better long term maintainability of code
DI Finally
Lessening number of dependencies needing injection into an interface
- Horizontal abstraction : Refactoring code into decoupled functional chunks
- Vertical abstraction : Refactoring code into tiered layers
横向解耦,纵向分层
Extras
Retiring The Singleton Pattern : Concrete Suggestions on What to Use Instead
Redesigning Legacy Systems : Keys to success
Managing External APIs in Enterprise Systems
Exceptionally Bad : The Story on the Misuse of Exceptions and How to Do Better (Exceptions in C++ : Better Design Through Analysis of Real-World Usage)
Software Development Completeness : Knowing when you are done and why it matters
本文标题:[Video] Refactoring C++ Code for Unit testing with Dependency Injection - Peter Muldoon - CppCon 2024
文章作者:Henry Wu
发布时间:2025-04-18
最后更新:2025-11-13
原始链接:https://henrywu.netlify.app/2025/04/18/cppcon24-refactor-with-di/
版权声明:转载请注明出处。CC BY-NC-SA 4.0
