转载自,原文作者 Aimingoo,感谢您的好文章。
1、烂代码是怎么定义的?
!KissyUI是淘宝Kissy这个前端项目的一个群,龙藏同学在看完我在公司内网的“读烂代码系列”之后就在群里问呵:烂代码是怎么定义的?
是呵,到底什么才算烂代码呢?这让我想到一件事,是另一个网友在gtalk上问我的一个问题:他需要a,b,c三个条件全真时为假,全假时也为假,请问如何判断。接下来KissyUI群里的同学给出了很多答案:
// 1. 圆心 if ( a && b && c || ! a &&! b &&! c){ return false } // 2. 龙藏 (a ^ b) & c // 3. 愚公(我给gtalk上的提问者)的答案 (a xor b) or (a xor c) // 4. 提问者自己的想法 (a + b + c) % 3 // 5. 云谦对答案4的改进版本 ( !! a +!! b +!! c) % n // 6. 拔赤 a ? (b ? c:b) : (b ?! b: ! c) // 7. 吴英杰 (a != b || b != c)或( ! a != ! b || ! b != ! c) // 8. 姬光 var v = a && b && c; if ( ! v){ return false ;} else if (v){ return false ;} else { return true ;}
// 4. 提问者自己的想法 (a + b + c) % 3
// 5. 云谦对答案4的改进版本 ( !! a +!! b +!! c) % n
如果把上面的问题改变一下:- 如果不是a、b、c三个条件,而是两个以上条件呢?- 如果强调a、b、c本身不一定是布尔值呢?那么这个问题的基本抽象就是:
// v0,对任意多个运算元求xorfunction e_xor() { ... }
// v1,扫描所有参数,发现不同的即返回true,全部相同则返回false。 function e_xor() { var args = arguments, argn = args.length; args[ 0 ] = ! args[ 0 ]; for ( var i = 1 ; i < argn; i ++ ) { if (args[ 0 ] != ! args) return true ; } return false ;}
// v1.1,对v1的改版 function e_xor() { var args = [].slice.call(arguments, 0 ), argn = args.length; ...}
接下来,我们既然在args中得到的是一个数组,那么再用for循环就实在不那么摩登了。正确的、流行风格的、不被前端鄙视做法是:
// v2,使用js1.6+的数组方法的实现 function e_xor(a) { return ([].slice.call(arguments, 1 )).some( function (b) { if ( ! b != ! a) return true });}
// v2.1,对v2的详细分解 function e_xor(a) { var args = [].slice.call(arguments, 1 ); var callback = function (b) { if ( ! b != ! a) return true } return args.some(callback);}
// v2.2,对v2的优化以减少!a运算次数 function e_xor(a) { return (a =! a, [].slice.call(arguments, 1 )).some( function (b) { if ( ! b != a) return true });}
(a =! a, [].slice.call(arguments, 1 ))
好了,现在我们开始v3版本的写法了。为什么呢?因为v2版本仍然不够酷,v2版本使用的是Array.some(),这个在js1.6中扩展的特既不是那么的“函数式”,还有些面向对象的痕迹。作为一个函数式语言的死忠,我认为,类似于“列举一个数组”这样的问题的最正常解法是:递归。为什么呢?因为erlang这样的纯函数式语言就不会搞出个Array.some()的思路来——当然也是有这样的方法的,只是从“更纯正”的角度上讲,我们得自己写一个。呵呵。这种“纯正的递归”在js里面又怎么搞呢?大概的原型会是这样子:
// v3,采用纯函数式的、递归方案的框架 function e_xor(a, b) { ... }
在这个框架里,我们设e_xor()有无数个参数,但每次我们只处理a,b两个,如果a,b相等,则我们将其中之任一,与后续的n-2个参数递归比较。为了实现“递归处理后续n-2个参数”,我们需要借用函数式语言中的一个重要概念:连续/延续(continuous)。这个东东月影曾经出专题来讲过,在这里:
[url=viewthread.php?tid=85325]http://bbs.51js.com/viewthread.php?tid=85325[/url]简单地说,延续就是对函数参数进行连续的回调。这个东东呢,在较新的函数式语言范式中都是支持的。为了本文中的这个例子,我单独地写个版本来分析之。我称之为tail()方法,意思是指定函数参数的尾部,它被设计为函数Function上的一个原型方法。 Function.prototype.tail = function () { return this .apply( this , [].slice.call(arguments, 0 ).concat([].slice.call( this .arguments, this .length)));}
function foo(a, b) { alert([arguments.length, arguments.callee.length]);}foo(x);foo(x,y,z);
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()函数参数的尾部 ) );}
// 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 );}
从上一个小节中,我们看到了Guy解决问题的思路。但是在这个级别上,第一步的抽象通常是最关键的。简单地说,V3里认为:
// v3,采用纯函数式的、递归方案的框架 function e_xor(a, b) { ... }
// v4,更优的函数式框架抽象,对接口的思考 function e_xor(a) { ... }
// 更符合原始抽象含义的tail方法 Function.prototype.tail = function () { return this .apply( this , [].slice.call( this .arguments, this .length));}
// v4.1,相较于v3更为简单的实现 function e_xor(a) { if (arguments.length < 2 ) return false ; return ( ! a == ! arguments[ 1 ] ? arguments.callee.tail() : true );} // v4.1.1,一个不使用三元表达式的简洁版本 function e_xor(a) { if (arguments.length < 2 ) return false ; if ( ! arguments[ 1 ] != ! a) return true ; return arguments.callee.tail();}
所谓无阶级别,就是你知道他是Guy,但不知道可以Guy到什么程度。例如,我们可以在v4.1版本的e_xor()中发现一个模式,即: - 真正的处理逻辑只有第二行。由于其它都是框架部分,所以我们可以考虑一种编程范式,它是对tail的扩展,目的是对在tail调用e_xor——就好象对数组调用sort()方法一样。tail的含义是取数据,而新扩展的含义是数组与逻辑都作为整体。例如:
// 在函数原型上扩展的tailed方法,用于作参数的尾部化处理 Function.prototype.tailed = function () { return function (f) { // 将函数this通过参数f保留在闭包上 return function () { // tailed()之后的、可调用的e_xor()函数 if (arguments.length < f.length + 1 ) return false ; if (f.apply( this , arguments)) return true ; // 调用tailed()之前的函数f return arguments.callee.apply( this , [].slice.call(arguments, f.length)); } }( this )}
e_xor = function (a){ if ( ! arguments[ 1 ] != ! a) return true ;}.tailed();
/* tiny tailed library, v0.0.0.1 alpha. by aimingoo. */ Function.prototype.tailed = ....; // 对参数a及其后的所有参数求异或 function xor(a) { if ( ! arguments[ 1 ] != ! a) return true ;} // ...更多类似的库函数
// 求任意多个参数的xor值 xor.tailed()(a,b,c,d,e,f,g);
// less_one()作为tailed库函数中的全局常量,以及缺省的closed条件 // 当less_one返回true时,表明递归应该终止 function less_one(args, f) { if (args.length < f.length + 1 ) return true ;} // 在函数原型上扩展的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 )}
xor.tailed()(a,b,c,d,e,f,g); // 或者 xor.tailed(less_one)(a,b,c,d,e,f,g);
/* 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 < f.length + 1 ) return true ;} function xor(a) { if ( ! arguments[ 1 ] != ! a) return true ;}e_xor = tailed.templeted({ closed: less_one, execute: xor})
我们在做什么?我们已经离真相越来越远了。或者说,我故意地带大家兜着一个又一个看似有趣,却又渐渐远离真相的圈子。我们不是要找一段“不那么烂的代码”吗?如果是这样,那么对于a,b,c三个运算条件的判断,最好的方法大概是:
(a != b || a != c)
( ! a !=! b || ! a !=! c)
function e_xor(a) { for ( var na =! a,i = 1 ; i < arguments.length; i ++ ) { if ( ! arguments != na) return true } return false ;}
对于这段代码,我们使用JS默认对arguments的存取规则,有优化就优化,没有就算了,因为我们的应用环境并没有提出“这里的arguments有成千上万个”或“e_xor()调用极为频繁”这样的需求。如果没有需求,我们在这方面所做的优化,就是白费功能——除了技术上的完美之外,对应用环境毫无意义。
够用了。我们的所学,在应用环境中已经足够,不要让技巧在你的代码中泛滥。所谓技术,是控制代码复杂性、让代码变得优美的一种能力,而不是让技术本身变得强大或完美。 所以,我此前在讨论“愚公读烂代码”时,强调的其实是三个过程:- 先把业务的需求想清楚,- 设计好清晰明确的调用接口,- 用最简单的、最短距离的代码实现。 其它神马滴,都系浮云。