扇出

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

扇出

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

(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,意味着这个外观可以单独进行测试,同时这个函数也可以进行单独测试。

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

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

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

相关推荐

Linux shell脚本获取命令行的输出

在Linux使用命令替换来获取命令行的输出命令替换格式:$(command) 或者`command` 这里的`为反引号。示例OUTPUT="$(ls -1)" echo "${OUTPUT}" 参考:Bash Reference Manual

理解Java的数字溢出

Java各个数字类型所占的存储空间:整型byte:1字节,1*8bit,取值范围-128~127short:2字节,2*8bit,取值范围-32768~32767int:4字节,4*8bit,取值范围-2147483648~2147483647long:8字节,8*8bit,取值访问-2^64~2^64-1浮点型float:4字节,4*8bitdouble:8字节,8*8bitchar类型java

JavaScript跳出forEach循环

JavaScript提供了forEach用于遍历数组。但使用forEach做迭代时有一个问题,它不允许在遍历完所有元素之前终止循环跳出,即不能使用break语句。即使是在函数里使用return false也不行。示例var arr = ["a", "b, "c, "d"

MySQL使用mysqldump导出数据

导出数据主要使用命令mysqldump导出整个数据库命令:mysqldump -u用户名 -p 数据库名 > 导出的文件名 示例mysqldump -udbuser -p mydb > mydb.sql 导出表命令:mysqldump -u用户名 -p 数据

docker-machine配置导入/导出脚本

为了在多个主机同步docker-machine的配置,常常需要对docker-machine配置进行导入导出。这里附上两个脚本分别用于导出/导入docker-machine配置:docker-machine-export.sh和docker-machine-import.sh。这两个脚本需要所有的主机系统有相同的$MACHINE_STORAGE_PATH/certs。用法如下:导出(主机 

WebStorm Navigate弹出框查找文件忽略node_modules

在WebStorm查找文件的快捷键Ctrl+Shift+N(Navigate -> File...),会有一个弹出框出来。如果把node_modules包含在搜索文件里将会很耗时。解决方法:选中node_modules文件夹 -> 右键菜单 -> Mark Directory As -> Excluded如图:

jmeter压测输出HTML

启动jmeter,在cmd的控制台窗口如下:我们可以看到命令行的提示信息,大概的意思是做负载测试时不要使用GUI模式,使用命令行模式来运行Jmeter的测试脚本。GUI模式仅用于创建测试计划,和测试debug使用。同时,命令行提示语也给出了运行jmeter的测试脚本的命令:jmeter -n -t [jmx file] -l [result file] -e -o [Path to output

git commit 中文出现乱码

有的时候我们使用git commit 提交的时候,日志信息有中文的时候会出现乱码,具体表现就是如下图:问题猜想,这个情况应该是编码设置的原因:所以尝试对编码进行重新设置:执行如下设置代码:git config --global i18n.commitencoding utf-8然后执行如下代码:git config --global i18n.logoutputencoding utf-8然后我们