重构01、组合方法:代码重构的基础动作
软件设计和重构是从软件结构上对代码的简化,其中组合方法是我自己认为最为基础和关键的方法,没有之一!
1、什么是组合方法?
组合方法是对将函数实现为处于同一抽象层次的多个小函数组合调用的形式。简单来说,就是将长且复杂的大函数替换成多个小函数组合调用。
这里有一个关键词:同一抽象层次。举个例子来理解一下它,比如:和朋友一起出去玩,一般会讨论:去哪个景点?什么时间到?出行方式是什么?总共有几个人?总的来看,这几个点都属于“出行计划”这个场景(scenario)。写成函数:
void SetTravelPlan() { SetDestination() SetArrivalTime() SetTransportation() SetNumOfPeople() }
|
在游玩计划这个特定的场景下,这些要素就属于同一抽象层次。
反过来,我们不需要讨论的是:大家各自几点起床,到达时间是否精确到具体的分钟,打车是用高德还是滴滴… 这些细节在我们出行过程中会不是涉及?会。但是影不影响出去玩?完全不影响!这些生活中习以为常的细节,其实已经包含了我们大脑的工作方式——分离关注点:把同一层次的问题放到一起,不同层次的分离开,简化要解决的问题。
2、一个应用组合方法的例子
以常见的Array容器Add()方法为例,来看怎么用组合方法重构C++代码:
原始的代码(来源说明:《重构与模式》7.1示例,原例是Java实现):
template <typename T> class Array { public: void Add(const T &element) { if (!readOnly_) { int newSize = size_ + 1; if (newSize > elements_->size()) { auto newElements = std::make_unque<vector<T>>(newSize + 10); for (int i = 0; i < size_; i++) { newElements[i] = elements_[i]; } elements_ = newElements; } elements_[size_++] = element; } }
private: bool readOnly_; int size_; std::unique<vector<T>> elements_; }
|
从Add()实现看,包含了elements扩容,检查只读等所有的细节,阅读起来有压力。接下来,我们使用接近自然语言的表达,来重新概括这个函数的内容:
template <typename T> class Array { public: void Add(const T &element) { if (readOnly_) { return; } if (AtCapacity()) { Grow(); } AddElement(element); }
private: bool AtCapacity() { return size_ + 1 >= elements_->size(); }
constexpr int kGrowSize = 10; void Grow() { auto newElements = std::make_unque<vector<T>>(newSize + kGrowSize); for (int i = 0; i < size_; i++) { newElements[i] = elements_[i]; } elements_ = newElements; }
void AddElement(const T &element) { elements_[size_++] = element; }
private: bool readOnly_; int size_; std::unique<vector<T>> elements_; }
|
通过AtCapacity()
、Grow()
、AddElement()
三个子函数的组合调用,可以让Add()
的实现更清晰简洁,这就是组合方法的应用。
3、方法分析
在分析设计模式和重构时,水球潘提供了一种概念:Force,我将它浅显地解读为:从函数的A实现到B实现进行变更的因素。对于“组合方法”来说,驱动以上例子中改造的Force是:大量的细节掩盖了作者核心要表达的功能单元。只需花10s就能理解重构后Add()
的内容,但是可能需要5min才能弄明白原始代码的逻辑。
参考
[1] 《重构与模式》第7.1节
[2] 水球潘:Force