[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
最后更新:2026-01-01
原始链接:https://henrywu.netlify.app/2025/04/18/cppcon24-refactor-with-di/
版权声明:转载请注明出处。CC BY-NC-SA 4.0
