1、烂代码是怎么定义的?

!KissyUI是淘宝Kissy这个前端项目的一个群,龙藏同学在看完我在公司内网的“读烂代码系列”之后就在群里问呵:烂代码是怎么定义的?

是呵,到底什么才算烂代码呢?这让我想到一件事,是另一个网友在gtalk上问我的一个问题:他需要a,b,c三个条件全真时为假,全假时也为假,请问如何判断。

接下来KissyUI群里的同学给出了很多答案:

[javascript] view plain copy
  1. // 1. 圆心
  2. if( a&&b&&c || !a&&!b&&!c){
  3. return false
  4. }
  5. // 2. 龙藏
  6. (a ^ b) & c
  7. // 3. 愚公(我给gtalk上的提问者)的答案
  8. (a xor b) or (a xor c)
  9. // 4. 提问者自己的想法
  10. (a + b + c) % 3
  11. // 5. 云谦对答案4的改进版本
  12. (!!a+!!b+!!c)%n
  13. // 6. 拔赤
  14. a ? (b?c:b) : (b?!b:!c)
  15. // 7. 吴英杰
  16. (a != b || b != c)
  17. (!a != !b || !b != !c)
  18. // 8. 姬光
  19. var v = a&&b&&c;
  20. if(!v){
  21. return false;
  22. }else if(v){
  23. return false;
  24. }else{
  25. return true;
  26. }

en... 确实,我没有完全验证上面的全面答案的有效性。因为如同龙藏后来强调的:“貌似我们是要讨论什么是烂代码?”的确,我们怎么才能把代码写烂呢?上面出现了种种奇异代码,包括原来提问者的那个取巧的:

[javascript] view plain copy
  1. // 4. 提问者自己的想法
  2. (a + b + c) % 3

因为这个问题出现在js里面,存在弱类型的问题,即a、b、c可能是整数,或字符串等等,因此(a+b+c)%3这个路子就行不通了,所以才有了

[javascript] view plain copy
  1. // 5. 云谦对答案4的改进版本
  2. (!!a+!!b+!!c)%n

 

 
2、问题的泛化与求解:普通级别

如果把上面的问题改变一下:

- 如果不是a、b、c三个条件,而是两个以上条件呢?

- 如果强调a、b、c本身不一定是布尔值呢?

那么这个问题的基本抽象就是:

[c-sharp] view plain copy
  1. // v0,对任意多个运算元求xor
  2. function e_xor() { ... }
  3. 对于这个e_xor()来说,最直接的代码写法是:
  4. // v1,扫描所有参数,发现不同的即返回true,全部相同则返回false。
  5. function e_xor() {
  6. var args=arguments, argn=args.length;
  7. args[0] = !args[0];
  8. for (var i=1; i<argn; i++) {
  9. if (args[0] != !args[i]) return true;
  10. }
  11. return false;
  12. }

接下来,我们考虑一个问题,既然arguments就是一个数组,那么可否使用数组方式呢?事实上,据说在某些js环境中,直接存取arguments[x]的效率是较差的。因此,上面的v1版本可以有一个改版:

[javascript] view plain copy
  1. // v1.1,对v1的改版
  2. function e_xor() {
  3. var args=[].slice.call(arguments,0), argn=args.length;
  4. ...
  5. }

这段小小的代码涉及到splice/slice的使用问题。因为操作的是arguments,因此splice可能导致函数入口的“奇异”变化,在不同的引擎中的表现效果并不一致,而slice则又可能导致多出一倍的数据复制。在这里仍然选用slice()的原因是:这里毕竟只是函数参数,不会是“极大量的”数组,因此无需过度考虑存储问题。

3、问题的泛化与求解:专业级别

接下来,我们既然在args中得到的是一个数组,那么再用for循环就实在不那么摩登了。正确的、流行风格的、不被前端鄙视做法是:

[javascript] view plain copy
  1. // v2,使用js1.6+的数组方法的实现
  2. function e_xor(a) {
  3. return ([].slice.call(arguments,1)).some(function(b) { if (!b != !a) return true });
  4. }

为了向一些不太了解js1.6+新特性的同学解释v2这个版本,下面的代码分解了上述这个实现:

[javascript] view plain copy
  1. // v2.1,对v2的详细分解
  2. function e_xor(a) {
  3. var args = [].slice.call(arguments,1);
  4. var callback = function(b) {
  5. if (!b != !a) return true
  6. }
  7. return args.some(callback);
  8. }

some()这个方法会将数组args中的每一个元素作为参数b传给callback函数。some()有一项特性正是与我们的原始需求一致的:

- 当callback()返回true的时候,some()会中断args的列举然后返回true值;否则,

- 当列举完全部元素且callback()未返回true的情况下,some()返回false值。

现在再读v2版本的e_xor(),是不是就清晰了?

当然,仅仅出于减少!a运算的必要,v2版本也可以有如下的一个改版:

[javascript] view plain copy
  1. // v2.2,对v2的优化以减少!a运算次数
  2. function e_xor(a) {
  3. return (a=!a, [].slice.call(arguments,1)).some(function(b) { if (!b != a) return true });
  4. }

在这行代码里,使用了连续运算:

[javascript] view plain copy
  1. (a=!a, [].slice.call(arguments,1))

而连续运算返回最后一个子表达式的值,即slice()后的数组。这样的写法,主要是要将代码控制在“一个表达式”。

4、问题的泛化与求解:Guy入门级别

好了,现在我们开始v3版本的写法了。为什么呢?因为v2版本仍然不够酷,v2版本使用的是Array.some(),这个在js1.6中扩展的特既不是那么的“函数式”,还有些面向对象的痕迹。作为一个函数式语言的死忠,我认为,类似于“列举一个数组”这样的问题的最正常解法是:递归。

为什么呢?因为erlang这样的纯函数式语言就不会搞出个Array.some()的思路来——当然也是有这样的方法的,只是从“更纯正”的角度上讲,我们得自己写一个。呵呵。这种“纯正的递归”在js里面又怎么搞呢?大概的原型会是这样子:

[javascript] view plain copy
  1. // v3,采用纯函数式的、递归方案的框架
  2. function e_xor(a, b) {  ... }

在这个框架里,我们设e_xor()有无数个参数,但每次我们只处理a,b两个,如果a,b相等,则我们将其中之任一,与后续的n-2个参数递归比较。为了实现“递归处理后续n-2个参数”,我们需要借用函数式语言中的一个重要概念:连续/延续(continuous)。这个东东月影曾经出专题来讲过,在这里:

http://bbs.51js.com/viewthread.php?tid=85325

简单地说,延续就是对函数参数进行连续的回调。这个东东呢,在较新的函数式语言范式中都是支持的。为了本文中的这个例子,我单独地写个版本来分析之。我称之为tail()方法,意思是指定函数参数的尾部,它被设计为函数Function上的一个原型方法。

[javascript] view plain copy
  1. Function.prototype.tail = function() {
  2. return this.apply(this, [].slice.call(arguments,0).concat([].slice.call(this.arguments, this.length)));
  3. }

注意这个tail()方法的有趣之处:它用到了this.length。在javascript中的函数有两个length值,一个是foo.length,它表明foo函数在声明时的形式参数的个数;另一个是arguments.length,它表明在函数调用时,传入的实际参数的个数。也就是说,对于函数foo()来说:

[javascript] view plain copy
  1. function foo(a, b) {
  2. alert([arguments.length, arguments.callee.length]);
  3. }
  4. foo(x);
  5. foo(x,y,z);

第一次调用将显示[1,2],第二次则会显示[3,2]。无论如何,声明时的参数a,b总是两个,所以foo.length == arguments.callee.length == 2。

回到tail()方法。它的意思是说:

[javascript] view plain copy
  1. Function.prototype.tail = function() {
  2. return this.apply( // 重新调用函数自身
  3. this, // 以函数foo自身作为this Object
  4. [].slice.call(arguments,0) // 取调用tail时的全部参数,转换为数组
  5. .concat( // 数组连接
  6. [].slice.call(this.arguments, // 取本次函数foo调用时的参数,由于tail()总在foo()中调用,因此实际是取最近一次foo()的实际参数
  7. this.length)  // 按照foo()声明时的形式参数个数,截取foo()函数参数的尾部
  8. )
  9. );
  10. }

那么tail()在本例中如何使用呢?

[javascript] view plain copy
  1. // v3.1,使用tail()的版本
  2. function e_xor(a, b) {
  3. if (arguments.length == arguments.callee.length) return !a != !b;
  4. return (!a == !b ? arguments.callee.tail(b) : true);
  5. }

这里又用到了arguments.callee.length来判断形式参数个数。也就是说,递归的结束条件是:只剩下a,b两个参数,无需再扫描tail()部分。当然,return中三元表达式(?:)右半部分也会中止递归,这种情况下,是已经找到了一个不相同的条件。

在这个例子中,我们将e_xor()写成了一个尾递归的函数,这个尾递归是函数式的精髓了,只可惜在js里面不支持它的优化。WUWU~~ 回头我查查资源,看看新的chrome v8是不是支持了。v8同学,尚V5否?:)

5、问题的泛化与求解:Guy进阶级别

从上一个小节中,我们看到了Guy解决问题的思路。但是在这个级别上,第一步的抽象通常是最关键的。简单地说,V3里认为:

[javascript] view plain copy
  1. // v3,采用纯函数式的、递归方案的框架
  2. function e_xor(a, b) {  ... }

这个框架抽象本身可能是有问题。正确的理解不是“a,b求异或”,而是“a跟其它元素求异或”。由此,v4的框架抽象是:

[javascript] view plain copy
  1. // v4,更优的函数式框架抽象,对接口的思考
  2. function e_xor(a) {  ... }

在v3中,由于每次要向后续部分传入b值,因此我们需要在tail()中做数组拼接concat()。但是,当我们使用v4的框架时,b值本身就隐含在后续部分中,因此无需拼接。这样一来,tail()就有了新的写法——事实上,这更符合tail()的原意,如果真的存在拼接过程,那它更应由foo()来处理,而不是由tail()来处理。

[javascript] view plain copy
  1. // 更符合原始抽象含义的tail方法
  2. Function.prototype.tail = function() {
  3. return this.apply(this, [].slice.call(this.arguments, this.length));
  4. }

在v4这个版本中的代码写法,会变得更为简单:

[javascript] view plain copy
  1. // v4.1,相较于v3更为简单的实现
  2. function e_xor(a) {
  3. if (arguments.length < 2) return false;
  4. return (!a == !arguments[1] ? arguments.callee.tail() : true);
  5. }
  6. // v4.1.1,一个不使用三元表达式的简洁版本
  7. function e_xor(a) {
  8. if (arguments.length < 2) return false;
  9. if (!arguments[1] != !a) return true;
  10. return arguments.callee.tail();
  11. }

6、问题的泛化与求解:Guy无阶级别

所谓无阶级别,就是你知道他是Guy,但不知道可以Guy到什么程度。例如,我们可以在v4.1版本的e_xor()中发现一个模式,即:

- 真正的处理逻辑只有第二行。

由于其它都是框架部分,所以我们可以考虑一种编程范式,它是对tail的扩展,目的是对在tail调用e_xor——就好象对数组调用sort()方法一样。tail的含义是取数据,而新扩展的含义是数组与逻辑都作为整体。例如:

[javascript] view plain copy
  1. // 在函数原型上扩展的tailed方法,用于作参数的尾部化处理
  2. Function.prototype.tailed = function() {
  3. return function(f) {  // 将函数this通过参数f保留在闭包上
  4. return function() {  // tailed()之后的、可调用的e_xor()函数
  5. if (arguments.length < f.length+1) return false;
  6. if (f.apply(this, arguments)) return true;  // 调用tailed()之前的函数f
  7. return arguments.callee.apply(this, [].slice.call(arguments, f.length));
  8. }
  9. }(this)
  10. }

tailed()的用法很简单:

[javascript] view plain copy
  1. e_xor = function(a){
  2. if (!arguments[1] != !a) return true;
  3. }.tailed();

简单的来看,我们可以将xor函数作为tailed()的运算元,这样一样,我们可以公开一个名为tailed的公共库,它的核心就是暴露一组类似于xor的函数,开发者可以使用下面的编程范式来实现运算。例如:

[javascript] view plain copy
  1. /* tiny tailed library, v0.0.0.1 alpha. by aimingoo. */
  2. Function.prototype.tailed = ....;
  3. // 对参数a及其后的所有参数求异或
  4. function xor(a) {
  5. if (!arguments[1] != !a) return true;
  6. }
  7. // ...更多类似的库函数

那么,这个所谓的tailed库该如何用呢?很简单,一行代码:

[javascript] view plain copy
  1. // 求任意多个参数的xor值
  2. xor.tailed()(a,b,c,d,e,f,g);

现在我们得到了一个半成熟的、名为tailed的开放库。所谓半成熟,是因为我们的tailed()还有一个小小缺陷,下面这行代码:

[javascript] view plain copy
  1. if (arguments.length < f.length+1) return false;

中间的f.length+1的这个“1”,是一个有条件的参数,它与xor处理数据的方式有关。简单的说,正是因为要比较a与arguments[1],所这里要+1,如果某种算法要比较 多个运算元,则tailed()就不通用了。所以正确的、完善的tailed应该允许调用者指定终止条件。例如:

[javascript] view plain copy
  1. // less_one()作为tailed库函数中的全局常量,以及缺省的closed条件
  2. // 当less_one返回true时,表明递归应该终止
  3. function less_one(args, f)  {
  4. if (args.length < f.length+1) return true;
  5. }
  6. // 在函数原型上扩展的tailed方法,用于作参数的尾部化处理
  7. Function.prototype.tailed = function(closed) {
  8. return function(f) {  // 将函数this通过参数f保留在闭包上
  9. return function() {  // tailed()之后的、可调用的e_xor()函数
  10. if ((closed||less_one).apply(this, [arguments,f])) return false;
  11. if (f.apply(this, arguments)) return true;  // 调用tailed()之前的函数f
  12. return arguments.callee.apply(this, [].slice.call(arguments, f.length));
  13. }
  14. }(this)
  15. }

使用的方法仍然是:

[javascript] view plain copy
  1. xor.tailed()(a,b,c,d,e,f,g);
  2. // 或者
  3. xor.tailed(less_one)(a,b,c,d,e,f,g);

在不同的运算中,less_one()可以是其它的终止条件。

现在,在这个方案——我的意思是tailed library这个库够Guy了吗?不。所谓意淫无止尽,淫人们自有不同的淫法。比如,在上面的代码中我们可以看到一个问题,就是tailed()中有很多层次的函数闭包,这意味着调用时效率与存储空间都存在着无谓的消耗。那么,有什么办法呢?比如说?哈哈,我们可以搞搞范型编程,弄个模板出来:

[javascript] view plain copy
  1. /* tiny tailed library with templet framework, v0.0.0.1 beta. by aimingoo. */
  2. Function.prototype.templeted = function(args) {
  3. var buff = ['[', ,'][0]'];
  4. buff[1] = this.toString().replace(/_([^_]*)_/g, function($0,$1) { return args[$1]||'_'});
  5. return eval(buff.join(''));
  6. }
  7. function tailed() {
  8. var f = _execute_;
  9. if (_closed_(arguments, f)) return false;
  10. if (f.apply(this, arguments)) return true;
  11. return arguments.callee.apply(this, [].slice.call(arguments, f.length));
  12. }
  13. function less_one(args, f)  {
  14. if (args.length < f.length+1) return true;
  15. }
  16. function xor(a) {
  17. if (!arguments[1] != !a) return true;
  18. }
  19. e_xor = tailed.templeted({
  20. closed: less_one,
  21. execute: xor
  22. })

当然,我们仍然可以做得更多。例如这个templet引擎相当的粗糙,使用eval()的方法也不如new Function来得理想等等。关于这个部分,可以再参考QoBean对元语言的处理方式,因为事实上,这后面的部分已经在逼近meta language编程了。

7、Guy?

我们在做什么?我们已经离真相越来越远了。或者说,我故意地带大家兜着一个又一个看似有趣,却又渐渐远离真相的圈子。

我们不是要找一段“不那么烂的代码”吗?如果是这样,那么对于a,b,c三个运算条件的判断,最好的方法大概是:

[javascript] view plain copy
  1. (a!=b || a!=c)

或者,如果考虑到a,b,c的类型问题:

[javascript] view plain copy
  1. (!a!=!b || !a!=!c)

如果考虑对一组运算元进行判断的情况,那么就把它当成数组,写成:

[javascript] view plain copy
  1. function e_xor(a) {
  2. for (var na=!a,i=1; i<arguments.length; i++) {
  3. if (!arguments[i] != na) return true
  4. }
  5. return false;
  6. }

对于这段代码,我们使用JS默认对arguments的存取规则,有优化就优化,没有就算了,因为我们的应用环境并没有提出“这里的arguments有成千上万个”或“e_xor()调用极为频繁”这样的需求。如果没有需求,我们在这方面所做的优化,就是白费功能——除了技术上的完美之外,对应用环境毫无意义。

够用了。我们的所学,在应用环境中已经足够,不要让技巧在你的代码中泛滥。所谓技术,是控制代码复杂性、让代码变得优美的一种能力,而不是让技术本身变得强大或完美。

所以,我此前在“读烂代码”系统中讨论时,强调的其实是三个过程:

- 先把业务的需求想清楚,

- 设计好清晰明确的调用接口,

- 用最简单的、最短距离的代码实现。

其它神马滴,都系浮云。

=====

注:本文从第2小节,至第6小节,仅供对架构、框架、库等方面有兴趣的同学学习研究,有志于在语言设计、架构抽象等,或基础项目中使用相关技术的,欢迎探讨,切勿滥用于一般应用项目。

from: http://blog.csdn.net/aimingoo/article/details/6036574

前端要给力之:代码可以有多烂?相关推荐

  1. 前端要给力之:红绿灯大战中的火星生命-Promise

    目录 目录 传说的开始 看到winter的代码我的第一反应是全无promise的精髓 其实我了解Promise也是新近的事情 我与Promise后来发生的故事 红绿灯大战的亲历实录 Promise写出 ...

  2. 前端开发者必备的代码开源平台,记得收藏转发!

    作为一个前端开发者,写代码处理BUG是日常,我们可以通过去看一些大神的代码来学习大神的思路.今天小千就来给大家介绍几个国内可以访问的开源代码平台,记得收藏转发哦~ 1.GitHub 这个就不用多说了, ...

  3. 前端开发者必备的代码开源平台

    作为一个前端开发者,写代码处理BUG是日常,我们可以通过去看一些大神的代码来学习大神的思路,今天小千就来给大家介绍几个国内可以访问的开源代码平台,记得收藏转发哦~ 1.GitHub 这个就不用多说了, ...

  4. 张铁柱-前端实现《低代码可视化编辑器》(一)思路整理 React-dnd+Ts

    张铁柱-前端实现<低代码可视化编辑器>(一)思路整理 React-dnd+Ts 先上效果: 拖拽生成页面+调整顺序 最近,接到任务做一个低代码编辑器,于是着手整理一下思路,调研一下实现方式 ...

  5. 前端常用得CSS代码分享

    本文首发于公众号:执行上下文,同步更新个人博客:执行上下文,转载请署名.代码不断更新中!! 前提 2019年11月的最后一篇文章来拉,在日常开发中高频使用的CSS代码分享给大家,其中可能有很多大家经常 ...

  6. 前端性能优化常用代码

    前端性能优化常用代码 为什么要做性能优化?性能优化到底有多重要? 网站的性能优化对于用户的留存率.转化率有很大的影响,所以对于前端开发来说性能优化能力也是重要的考察点. 性能优化的点非常的多,有的小伙 ...

  7. 前端常用的CSS代码

    本文首发于公众号:小夭同学,同步更新个人博客:故事胶片,转载请署名.代码不断更新中!! Demo 代码Demo 补充 大家如果有常用的也可以分享出来鸭!!! 前提 日常开发中高频使用的CSS代码分享给 ...

  8. html前端元素—菜单导航代码实例

    今天分享下"html前端元素-菜单导航代码实例"这篇文章,文中根据实例编码详细介绍,或许对大家的编程之路有着一定的参考空间与使用价值,需要的朋友接下来跟着云南仟龙Mark一起学习一 ...

  9. 面对前端六年历史代码,如何接入并应用ES6解放开发效率

    作者/分享人:Lucas http://gitbook.cn/gitchat/author/583f821b87fdee392692ed22 写在最前 很荣幸有机会和大家分享自己在前端工作中的一些经验 ...

  10. 黑马前端h5团队开发代码规范

    黑马前端h5团队开发代码规范 1. 概述 欢迎使用品优购代码规范, 这个是我借鉴京东前端代码规范,组织的品优购内部规范.旨在增强团队开发协作.提高代码质量和打造开发基石的编码规范, 以下规范是团队基本 ...

最新文章

  1. 阿里云 flask uwsgi SSl 证书 http 转 https
  2. mysql 存储过程 数组参数_问个小问题,关于存储过程传递数组参数
  3. 【django】创建模型类
  4. 史上最全搞怪WC标志(组图)--设计者太有才了。
  5. Linux之RPM 软件管理程序
  6. 【转】产品经理如何进行BRD,MRD,PRD,DRD,FRD编写
  7. C语言 printf函数实现
  8. 开机后系统时间被恶意修改
  9. 嵌入式组态软件HMImaker(绿色版人机界面组态软件)
  10. e站app里站hosts_硬核干货区 | E站的国际站运营知识星球上线啦
  11. matlab给hfss建模,HFSS-MATLAB联合建模
  12. MVC学习一:MVC简单流程
  13. sqlite的Query方法操作和参数详解
  14. java毕业设计飞机订票管理系统Mybatis+系统+数据库+调试部署
  15. word默认文字环绕方式是什么_在Word 2010文档中设置图片文字环绕方式
  16. 信息学奥赛一本通:1084:幂的末尾
  17. 附合导线简易平差(4500PA)
  18. 三星为企业服务器开发高性能PCIe 5.0固态硬盘;阳狮集团升任张珲杰为明思力中国董事总经理 | 全球TMT...
  19. Excel2016 无法粘贴图片
  20. 微信公众号,微信开发平台,openid问题

热门文章

  1. 白话uni-app 【也是html、vue、小程序的区别】
  2. 资深算法工程师万宫玺:Java工程师转型AI的秘密法宝——深度学习框架Deeplearning4j | 分享总结
  3. 微软沈向洋:计算机视觉未来在语义层 “两大一精”是关键
  4. 王兴:互联网化是企业家最大的机会
  5. Pandas常用技巧总结
  6. linux安装python库报错pywin32,Linux windows安装paramiko模块
  7. JVM-07垃圾收集Garbage Collection【GC日志分析】
  8. Spring-注入参数详解-[集合类型属性]
  9. kdj指标主要看哪个值_什么是KDJ?KDJ指标如何使用
  10. 【新星计划】MATLAB plot绘制图像