敲碎时间的人的个人专栏
上一篇

扇出

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

扇出

扇出是测量函数直接或者间接依赖的模块或对象的数量。有一个经验上的代码复杂度计算公式就是:

(fan_in * fan_out)的平方

一些研究表明:复杂度的值与软件变更有98%的相关性。比如:越复杂的函数或者模块,该函数或者模块就越有可能发生Bug。

那么扇出的定义到底是什么呢?

过程A的扇出是表示过程A的内部流程数量(指挥别人做的)和过程A所更新的数据结构数量(自己做的)之和。

扇出就衡量一个函数做多少事。类比于人做事情,就是这个人指挥别人做的+他自己做的

内部流程有三类:

1、如果A调用B;

2、如果B调用A,并且A返回一个B随后可以使用的值;

3、如果C调用A和B,并且A的返回值传递给B;

有了复杂度公式,我们可以推论一下内容:

1、高扇入何扇出的代码,可能表示一个函数做的事情太多,需要通过抽象和优化来避免;

2、高扇入和扇出,可以判断出系统的压力点,且这个部分的维护会很困难,因此关联了太多的系统其它部分。

3、这些代码或函数不够精细,需要重构。

下面我们用一个例子来说明:

YUI.use('myModule',function(Y){
	var myModule = function() {
		this.a = new Y.A();
		this.b = new Y.B();
		this.c = new Y.C();
		this.d = new Y.D();
		this.e = new Y.E();
		this.f = new Y.F();
		this.g = new Y.G();
		this.h = new Y.H();
	};
	Y.MyModule = myModule;
}.{ requires: ['a','b','c','d','e','f','g','h'] });

以上这段代码,myModule这个模块的扇出至少是8,因此需要进行优化,下面是我们的一种优化方案,重点就是将一部分相关模块转移到另一个模块中。

YUI.use('mySubModule',function(Y){
	var mySubModule = function() {
		this.a = new Y.A();
		this.b = new Y.B();
		this.c = new Y.C();
		this.d = new Y.D();
	};
	mySubModule.prototype.getA() {
		return this.a;
	};
	mySubModule.prototype.getB() {
		return this.b;
	};
	mySubModule.prototype.getC() {
		return this.c;
	};
	mySubModule.prototype.getD() {
		return this.d;
	};
	Y.MySubModule = mySubModule;
},{ requires: ['a','b','c','d']});

抽取除了一个扇出为4的模块mySubModule。

YUI.use('myModule',function(Y){
	var myModule = function() {
		var sub = new Y.MySubModule();
		this.a = sub.getA();
		this.b = sub.getB();
		this.c = sub.getC();
		this.d = sub.getD();
		this.e = new Y.E();
		this.f = new Y.F();
		this.g = new Y.G();
		this.h = new Y.H();
	};	
	Y.MyModule = myModule;
}, {requires: ['mySubModule','e','f','g','h']});

我们将myModule这个模块的扇出成功的减少到了5,但是我们却付出了另一个代价,那就是增加了测试的代码量,因为新的模块也需要进行测试,这个方法的好处仅仅是让每个模块或者函数更容易测试。

 

下面我们再看一个例子:

function makeChickenDinner(ingredients) {
	var chicken = new ChickenBreast();
	var oven = new ConventionalOven();
	var mixer = new Mixer();
	var dish = mixer.mix(chicken,ingredients);
	return oven.bake(dish, new FDegrees(350), new Timer('50 minites'));
}
var dinner = makeChickenDinner(ingredients);

这个函数的扇出特别高,因为它创建了五个外部对象,并且调用了两个不同对象中的两个方法。如果大家对spring的依赖注入比较了解的话,那么就知道上面的代码为啥耦合会这么大。如果对这个函数进行测试,那么首先需要做的就是mock所有对象,以及模拟这些对象调用方法的返回值。显然这个代码是相当不容易写的。下面我试着模拟一下:

describe('test nake dinner', function(){
	//Mocks
	var Food = function(obj){};
	Food.prototype.attr = {};
 	var MixedFood = function(args) {
		var obj = Object.create(Food.prototype);
		obj.attr.isMixed = true;
		return obj;
	};
 	var CookedFood = function(args) {
		var obj = Object.create(Food.prototype);
		obj.attr.isCooked = true;
		return obj;
	};
 	var FDegrees = function(temp){ this.temp = temp};
 	var Meal = function(dish){this.dish = dish};
 	var Timer = function(timeSpec){ this.timeSpec = timeSpec};
 	var ChickenBreast = function() {
		var obj = Object.create(Food.prototype);
		obj.attr.isChicken = true;
		return obj;
	};
 	var ConventionalOven = function() {
		this.bake = function(dish, degrees ,timer) {
			return new CookedFood(dish, degrees, timer);
		};
	};
	var Mixer = function() {
		this.mix = function(chicken, ingredients) {
			return new MixedFood(chicken,ingredients);
		};
	};
	var Ingredients = function(ings){ this.ings = ings;};
 	//Mocks end
});

看了以上部分测试的Mock,我想大家就知道测试这个函数需要付出多大的代价了吧。下面我们尝试来慢慢优化。

先找到耦合的对象,它们包括ChickenBreastConventionalOvenMixerFDegreesTimer ,先将这些耦合修改成依赖注入,创建一个Facade的oven:创建烤箱,设置温度,设置定时器。

function Cooker(oven){
	this.oven = oven;
}
 
Cooker.prototype.bake = function(dish, deg,timer){
	return this.oven.bake(dish, deg,timer);
} 
Cooker.prototypr.degree_f = function(deg){
	return new FDegrees(deg);
}
Cooker.prototype.timer = function(time) {
	return new Timer(time);
}
function makeChickenDinner(ingredients, cooker) {
	var chicken = new ChickenBreast();
	var mixer = new Mixer();
	var dish = mixer.mix(chicken, ingredients);
	return cooker.bake(dish, cooker.degree_f(350), cooker.timer('50 minutes'));
}
var cooker = new Cooker(new ConventionalOven);
var dinner = makeChickenDinner(ingredients, cooker);

通过对以上代码的重构,我将makeChickenDinner这个方法的紧耦合减少为两个,注入了一个外观cooker,这个外观没有暴露oven,degree,timer,意味着这个外观可以单独进行测试,同时这个函数也可以进行单独测试。

使用同样的方法,我还可以将生下来的耦合通过注入的方式一步一步的收取出来,最后得到一个松耦合的函数,这样的函数显然是方便测试和可维护的。

好了,大家可以按照上面的方法练习。

版权声明:著作权归作者所有。

X

欢迎加群学习交流

联系我们