1、烂代码是怎么定义的?
简单地说,延续就是对函数参数进行连续的回调。这个东东呢,在较新的函数式语言范式中都是支持的。为了本文中的这个例子,我单独地写个版本来分析之。我称之为tail()方法,意思是指定函数参数的尾部,它被设计为函数Function上的一个原型方法。
[javascript] view plaincopy
Function.prototype.tail = function() { return this.apply(this, [].slice.call(arguments,0).concat([].slice.call(this.arguments, this.length))); }
注意这个tail()方法的有趣之处:它用到了this.length。在javascript中的函数有两个length值,一个是foo.length,它表明foo函数在声明时的形式参数的个数;另一个是arguments.length,它表明在函数调用时,传入的实际参数的个数。也就是说,对于函数foo()来说:
[javascript] view plaincopy
function foo(a, b) { alert([arguments.length, arguments.callee.length]); } foo(x); foo(x,y,z);
第一次调用将显示[1,2],第二次则会显示[3,2]。无论如何,声明时的参数a,b总是两个,所以foo.length == arguments.callee.length == 2。
回到tail()方法。它的意思是说:
[javascript] view plaincopy
Function.prototype.tail = function() { return this.apply( // 重新调用函数自身 this, // 以函数foo自身作为this Object [].slice.call(arguments,0) // 取调用tail时的全部参数,转换为数组 .concat( // 数组连接 [].slice.call(this.arguments, // 取本次函数foo调用时的参数,由于tail()总在foo()中调用,因此实际是取最近一次foo()的实际参数 this.length) // 按照foo()声明时的形式参数个数,截取foo()函数参数的尾部 ) ); }
那么tail()在本例中如何使用呢?
[javascript] view plaincopy
// v3.1,使用tail()的版本 function e_xor(a, b) { if (arguments.length == arguments.callee.length) return !a != !b; return (!a == !b ? arguments.callee.tail(b) : true); }
这里又用到了arguments.callee.length来判断形式参数个数。也就是说,递归的结束条件是:只剩下a,b两个参数,无需再扫描tail()部分。当然,return中三元表达式(?:)右半部分也会中止递归,这种情况下,是已经找到了一个不相同的条件。
在这个例子中,我们将e_xor()写成了一个尾递归的函数,这个尾递归是函数式的精髓了,只可惜在js里面不支持它的优化。WUWU~~ 回头我查查资源,看看新的chrome v8是不是支持了。v8同学,尚V5否?:)
5、问题的泛化与求解:Guy进阶级别
从上一个小节中,我们看到了Guy解决问题的思路。但是在这个级别上,第一步的抽象通常是最关键的。简单地说,V3里认为:
[javascript] view plaincopy
// v3,采用纯函数式的、递归方案的框架 function e_xor(a, b) { ... }
这个框架抽象本身可能是有问题。正确的理解不是“a,b求异或”,而是“a跟其它元素求异或”。由此,v4的框架抽象是:
[javascript] view plaincopy
// v4,更优的函数式框架抽象,对接口的思考 function e_xor(a) { ... }
在v3中,由于每次要向后续部分传入b值,因此我们需要在tail()中做数组拼接concat()。但是,当我们使用v4的框架时,b值本身就隐含在后续部分中,因此无需拼接。这样一来,tail()就有了新的写法??事实上,这更符合tail()的原意,如果真的存在拼接过程,那它更应由foo()来处理,而不是由tail()来处理。
[javascript] view plaincopy
// 更符合原始抽象含义的tail方法 Function.prototype.tail = function() { return this.apply(this, [].slice.call(this.arguments, this.length)); }
在v4这个版本中的代码写法,会变得更为简单:
[javascript] view plaincopy
// v4.1,相较于v3更为简单的实现 function e_xor(a) { if (arguments.length return (!a == !arguments[1] ? arguments.callee.tail() : true); } // v4.1.1,一个不使用三元表达式的简洁版本 function e_xor(a) { if (arguments.length if (!arguments[1] != !a) return true; return arguments.callee.tail(); }
6、问题的泛化与求解:Guy无阶级别
所谓无阶级别,就是你知道他是Guy,但不知道可以Guy到什么程度。例如,我们可以在v4.1版本的e_xor()中发现一个模式,即:
- 真正的处理逻辑只有第二行。
由于其它都是框架部分,所以我们可以考虑一种编程范式,它是对tail的扩展,目的是对在tail调用e_xor??就好象对数组调用sort()方法一样。tail的含义是取数据,而新扩展的含义是数组与逻辑都作为整体。例如:
[javascript] view plaincopy
// 在函数原型上扩展的tailed方法,用于作参数的尾部化处理 Function.prototype.tailed = function() { return function(f) { // 将函数this通过参数f保留在闭包上 return function() { // tailed()之后的、可调用的e_xor()函数 if (arguments.length if (f.apply(this, arguments)) return true; // 调用tailed()之前的函数f return arguments.callee.apply(this, [].slice.call(arguments, f.length)); } }(this) }
tailed()的用法很简单:
[javascript] view plaincopy
e_xor = function(a){ if (!arguments[1] != !a) return true; }.tailed();
简单的来看,我们可以将xor函数作为tailed()的运算元,这样一样,我们可以公开一个名为tailed的公共库,它的核心就是暴露一组类似于xor的函数,开发者可以使用下面的编程范式来实现运算。例如:
[javascript] view plaincopy
/* tiny tailed library, v0.0.0.1 alpha. by aimingoo. */ Function.prototype.tailed = ....; // 对参数a及其后的所有参数求异或 function xor(a) { if (!arguments[1] != !a) return true; } // ...更多类似的库函数
那么,这个所谓的tailed库该如何用呢?很简单,一行代码:
[javascript] view plaincopy
// 求任意多个参数的xor值 xor.tailed()(a,b,c,d,e,f,g);
现在我们得到了一个半成熟的、名为tailed的开放库。所谓半成熟,是因为我们的tailed()还有一个小小缺陷,下面这行代码:
[javascript] view plaincopy
if (arguments.length
中间的f.length+1的这个“1”,是一个有条件的参数,它与xor处理数据的方式有关。简单的说,正是因为要比较a与arguments[1],所这里要+1,如果某种算法要比较 多个运算元,则tailed()就不通用了。所以正确的、完善的tailed应该允许调用者指定终止条件。例如:
[javascript] view plaincopy
// less_one()作为tailed库函数中的全局常量,以及缺省的closed条件 // 当less_one返回true时,表明递归应该终止 function less_one(args, f) { if (args.length } // 在函数原型上扩展的tailed方法,用于作参数的尾部化处理 Function.prototype.tailed = function(closed) { return function(f) { // 将函数this通过参数f保留在闭包上 return function() { // tailed()之后的、可调用的e_xor()函数 if ((closed||less_one).apply(this, [arguments,f])) return false; if (f.apply(this, arguments)) return true; // 调用tailed()之前的函数f return arguments.callee.apply(this, [].slice.call(arguments, f.length)); } }(this) }
使用的方法仍然是:
[javascript] view plaincopy
xor.tailed()(a,b,c,d,e,f,g); // 或者 xor.tailed(less_one)(a,b,c,d,e,f,g);
在不同的运算中,less_one()可以是其它的终止条件。
现在,在这个方案??我的意思是tailed library这个库够Guy了吗?不。所谓意淫无止尽,淫人们自有不同的淫法。比如,在上面的代码中我们可以看到一个问题,就是tailed()中有很多层次的函数闭包,这意味着调用时效率与存储空间都存在着无谓的消耗。那么,有什么办法呢?比如说?哈哈,我们可以搞搞范型编程,弄个模板出来:
[javascript] view plaincopy
/* tiny tailed library with templet framework, v0.0.0.1 beta. by aimingoo. */ Function.prototype.templeted = function(args) { var buff = ['[', ,'][0]']; buff[1] = this.toString().replace(/_([^_]*)_/g, function($0,$1) { return args[$1]||'_'}); return eval(buff.join('')); } function tailed() { var f = _execute_; if (_closed_(arguments, f)) return false; if (f.apply(this, arguments)) return true; return arguments.callee.apply(this, [].slice.call(arguments, f.length)); } function less_one(args, f) { if (args.length } function xor(a) { if (!arguments[1] != !a) return true; } e_xor = tailed.templeted({ closed: less_one, execute: xor })
当然,我们仍然可以做得更多。例如这个templet引擎相当的粗糙,使用eval()的方法也不如new Function来得理想等等。关于这个部分,可以再参考QoBean对元语言的处理方式,因为事实上,这后面的部分已经在逼近meta language编程了。
7、Guy?
我们在做什么?我们已经离真相越来越远了。或者说,我故意地带大家兜着一个又一个看似有趣,却又渐渐远离真相的圈子。
我们不是要找一段“不那么烂的代码”吗?如果是这样,那么对于a,b,c三个运算条件的判断,最好的方法大概是:
[javascript] view plaincopy
(a!=b || a!=c)
或者,如果考虑到a,b,c的类型问题:
[javascript] view plaincopy
(!a!=!b || !a!=!c)
如果考虑对一组运算元进行判断的情况,那么就把它当成数组,写成:
[javascript] view plaincopy
function e_xor(a) { for (var na=!a,i=1; i if (!arguments[i] != na) return true } return false; }
对于这段代码,我们使用JS默认对arguments的存取规则,有优化就优化,没有就算了,因为我们的应用环境并没有提出“这里的arguments有成千上万个”或“e_xor()调用极为频繁”这样的需求。如果没有需求,我们在这方面所做的优化,就是白费功能??除了技术上的完美之外,对应用环境毫无意义。
够用了。我们的所学,在应用环境中已经足够,不要让技巧在你的代码中泛滥。所谓技术,是控制代码复杂性、让代码变得优美的一种能力,而不是让技术本身变得强大或完美。
所以,我此前在“读烂代码”系统中讨论时,强调的其实是三个过程:
- 先把业务的需求想清楚,
- 设计好清晰明确的调用接口,
- 用最简单的、最短距离的代码实现。
其它神马滴,都系浮云。
=====
注:本文从第2小节,至第6小节,仅供对架构、框架、库等方面有兴趣的同学学习研究,有志于在语言设计、架构抽象等,或基础项目中使用相关技术的,欢迎探讨,切勿滥用于一般应用项目。
查看更多关于前端要给力之:代码可以有多烂?_html/css_WEB-ITnose的详细内容...