论坛首页 Java企业应用论坛

奇技淫巧?

浏览 46481 次
锁定老帖子 主题:奇技淫巧?
该帖已经被评为良好帖
作者 正文
   发表时间:2006-12-26  
yelongyelong 写道
不是每个人都能卖弄滴吧



卖弄是最简单地,翻翻google就能搞定,倒是把程序写得平实大路,重剑无锋是最难的。

前者叫新潮,后者叫功底。
0 请登录后投票
   发表时间:2006-12-27  
ray_linn 写道
说实话 Dy那个什么proxy十分的丑陋,是我见过的最丑的东西之一. 而且proxy这个词又十分含糊,鬼知道这里突然出现这个proxy,是什么原因什么由来,是不是天外又飞仙了?

这种需求,反射是我马上能想到的东西,

个人觉得EastSun的代码,兼顾的简洁和易读二者的平衡.LZ那个代码换个人,估计就得浪费一个早上去看堆资料,而且行数比反射只多不少.

那种感觉,真的就象柳永的词,卖弄的成分多,实用的成分少.


lz又没说哪里这样用了,这样秀出来大家讨论讨论不是很好嘛,不懂的人也有机会学学懂,永远接触简单的东西,哪来深入理解呢?
而且这里面的知识点不都是很有用嘛,我总结几点:
1.java的反射
2.代理模式
3.apache的beanutils工具包
4.javabean数据的另外几种存储方式(二纬数组(容器也可以),map),
5.用以上知识点思考,
总结得不一定正确,个人观点,我感觉对4能深入理解得人不见得多吧,很多web框架这些知识点都是基础,不理解的人用得好也是比较困难的吧(至少学习的曲线会复杂点,到最后会用了也许都没理解到呢)。

0 请登录后投票
   发表时间:2006-12-28  
jianfeng008 写道
又没说哪里这样用了,这样秀出来大家讨论讨论不是很好嘛,不懂的人也有机会学学懂,永远接触简单的东西,哪来深入理解呢?
而且这里面的知识点不都是很有用嘛,我总结几点:
1.java的反射
2.代理模式
3.apache的beanutils工具包
4.javabean数据的另外几种存储方式(二纬数组(容器也可以),map),
5.用以上知识点思考,
总结得不一定正确,个人观点,我感觉对4能深入理解得人不见得多吧,很多web框架这些知识点都是基础,不理解的人用得好也是比较困难的吧(至少学习的曲线会复杂点,到最后会用了也许都没理解到呢)。



举这样的例子根本就是教坏小孩...而且搞得复杂西西的, 还突然出现了ruby, my god.
0 请登录后投票
   发表时间:2006-12-28  
ajoo喜欢研究问题,是深入的研究,很是佩服。只是提醒看的人,还是多花时间研究些基础问题,这种问题有空学习学习思想就行了。
0 请登录后投票
   发表时间:2006-12-29  
我觉得这里还有一个隐藏的问题, 就是并发同步, 当然如果程序保证是单线程执行没有这个顾虑, 但是用在并发应用上, 问题可能很严重. 特别是以后内存越来越大了, CPU核心越来越多了, 应用架构也会更多偏向并发模式的.

动态语言可以容易实现很漂亮的语法, 但是却埋藏了并发互斥的问题和简单解决办法. 如果回过头来再看看最开始的 for 循环, 一旦这段程序要从单线程改为并发执行, 只需找合适的地方加个互斥资源对象, 和一些 synchronized(){} 块就可以了, 虽然解决起来比较笨拙, 但是容易发现和想到.

其实ajoo碰到的这个情况换作我的话也会很郁闷, 重复性的东西很容易让人乏味, 是最适合让机器去做的事情. 这样的问题还是在大前提, 整体上下文下, 从自然世界的本源问题出发, 逐环解决比较好. 本可以避免很多因为编程工具而引发的问题, 但从中间环节再开始想办法的话, 之前的很多问题就在所难免, 也就被无缘无故的拖累. 不过商业环境没有等待完美的耐性, 有时候郁闷也得去做, 没有办法.
0 请登录后投票
   发表时间:2006-12-30  
继续我们的奇技淫巧之旅。


上面我们说了几种做sum的方法(怎么感觉象是回字的几种写法?)

1。质朴无华的"for(...){sum+=arr[i];}"。这个方法直观地象白开水,直来直去,没有一点花巧。这让我们这些很聪明的(至少我们喜欢这样认为)程序员很不爽。你能想象高傲地象凤凰一样的慕容复一遍一遍地黑虎掏心么?而且,就算我们要面对讨厌的极端实用主义者的诘问,至少我们可以把几百分贝的“DO NOT REPEAT YOURSELF”和几十克拉的口水同时摔在他脸上。要求理论上应该很傻的业务逻辑程序员去写无数个四行的for loop,似乎也不太符合实用主义者的理想的。(如果“理想”这个词还能用在这些把灵魂出卖给了魔鬼的人身上的话)



2。用Getter接口来抽象。这是最中规中矩的面向对象方法论:遇到重复代码怎么办?把重复的地方提取出来,不同的地方用接口来抽象。如果说直接的"for loop"是黑虎掏心,这个方法就是少林七十二绝技。绝对的静态类型安全,绝对地优雅。整整十年,我们用这个方法几乎可以说打遍大江南北。所有的什么23个模式,说穿了都不过是这个思想方法在面对对手的不同招数时的各种衍化罢了。
唯一让人尴尬的,是我们这次面对的不是艰险狡诈的魔头,也不是助纣为虐的朝廷鹰爪。她,她就是对面那条街的面馆老板娘,今天慕容复碰巧吃面忘了带钱,这泼辣的女人头发长见识短,居然上来就挠。怎么办?来一招拈花指?面露微笑一下?对方看不懂,说俺是淫贼怎么办?(我哩!到底谁非礼谁呀?),运气不好再招来城管,被押去翻沙子?—— 跑题了 —— 抽象了半天,抽取了共性,我们每一步动作都绝对专业,结果最后发现“代码行数不减反多”。用黑皮的话说“你费那事干吗?”。



3。用commons beanutils。具体实现代码不写了,熟悉beanutils的都知道,不熟悉的,看看javadoc也就明白了(顺便说一下,不要什么东西都动不动就用“学习”两个字,beanutils也要学?pico也要学?是不是街角新开的洗脚房也需要“学习”一下?很让我有一种一个带着眼睛的三十岁左右男子,在和小姐commit transaction之前,在屋里到处细细索索地翻找东西,小姐问说你找什么?回答说要找到manual或者tutorial或者21天精通之类的书先“学习”一下的联想。)
最终的语法会是这样:
double totalBalance = sumOf(accountArray, "balance");
double totalRate = sumOf(accountArray, "rate");
double totalReturnOfInvestment = sumOf(accountArray, "returnOfInvestment");

类似地,也可以自己直接用reflection,最终代码大同小异:
double totalRoi = sumOf(accountArray, "getReturnOfInvestment");

同样地,具体实现我就不写了,要是想象不出怎么做的话,你的定位看来让你不适合看这个“奇迹淫巧”,还是回去做山贼这份有前途的职业吧。

这个方案看起来已经相当好了。一行代码搞定,逻辑上几乎没有废话。拿ruby写,也就到这样了吧?不过就是
total_balance = sum_of(accountArray, :balance)

的区别而已。

用反射的方法呢,自己要做的工作稍微多一些,用起来也要多写个"get"。好处是灵活性稍有增加,对一些没有按照javabean规范命名的数据点也可以求和了(比如,求一串List的size()合)。不过,总的来说,半斤八两。两个方法都无法处理数据点存在参数的情况,比如:
for(int i=0; i<arr.length; i++){
  sum += arr[i].getReturnOfInvestment(true);//你别管这个true代表什么
}

相比之下,黑虎掏心和少林拳都可以轻松处理这个情况。

呵呵,其实呢,我也就是鸡蛋里挑骨头。目前还没有发现有需要参数的情况。何况,就算需要参数,你直接自己单独写一个for loop能死啊?

更致命的问题来自java和ruby这类动态语言的根本哲学差异:java是静态强类型的。这么多年了,我们早就习惯于依赖编译器告诉我们“不是getBalace(),你写错啦,是getBalance(),笨蛋!”。一旦某一天这个拐棍被撤去了,我们就像考试的时候本来约好的大牛同学被别人强行占有了一样地无助,忧郁和悲愤了。
我知道,gigix肯定会说:“你不写单元测试的么?” 好吧,就算我可以接受用考试后的红色的零蛋来鞭策自己的结果(毕竟,我们上大学的最终目的不是某次测验,而是找到好工作,对么?),那么重构呢?我还敢在eclipse里面随便rename method么?eclipse当然是有这个“同时替换字符串”的选项,不过这种东西就不是那么保险了。至少的,你不敢不经过review就直接强行替换吧?还有,上下文提示呢?自动完成功能呢?returnOfInvestment这个词可是有点臭长啊。

经过哲学的思辨,缜密的思考,严谨的求证,精密的实现,我们终于骄傲地宣布了这个有史以来最实用的summer库(夏天?)了。我们可以和蔼可亲平易近人亲切地告诉围绕在我们周围的感动的热泪盈眶的业务程序员:从今天开始,你们不用再自己写可怕的for loop了,(掌声)。漫漫长夜去过去了,今天我们终于迎来了第一缕曙光。你们唯一需要做的,就是如此如此,这般这般(掌声,经久不绝的掌声)。—— 当然,你们不能用上下文提示和自动完成了 —— 自然,这无足轻重,一个真正的程序员是不应该留恋这些无聊的东西的。——— 对了,顺便说一句,重构的时候稍微小心一些也就是了。

当然,我这是理想情况。现实是,不论演讲的是领导,还是随便哪个写这个库的精英专家程序员架构师,都不会这么傻实诚地自爆弱点的。演讲到了“经久不绝的掌声”,就已经结束了。后面的得靠使用者自己用血一般的教训来补充了。


4。于是,就出现了我们慕容家的“斗转星移”神功了。就像做梦一样,你可以这样写了:
double totalBalance = sumOf(accountArray).getBalance();
double totalRoi = sumOf(accountArray).getReturnOfInvestment();

作为一个客户,我几乎无法挑出什么毛病了,所有的Eclipse提供的拐棍都还在,语法也是简练自然得象自然语言。

当然,不管什么东西,都是有代价的,而我们当然从来是只报喜不报忧的。
这个方法最大的问题就在于:它太过魔术化了一点。用起来虽然很爽,可是一旦某个业务程序员自己觉得自己已经是个很优秀的程序员了,不安心本职工作,居然想刨根究底,就会发现这东西在光鲜的外表下隐藏着太多的机关。等你通过“学习”java 1.3的dynamic proxy机制,终于弄懂了sumOf()到底干了些什么,十有八九会破口大骂“这都是什么乱七八糟的?,就为了一个求和么?真想杀了那个写代码的自以为聪明的傻瓜。他难道没有听说过KISS原则么?”
而可悲的是,这个指责合情合理。为了一点点语法上的“自慰”般的快感,真的值得在内部搞这么多玄虚么?要知道,软件是一个需要维护的产品,只为了做一个求和这种简单地不能再简单的操作,就要付出团队内大部分人都看不懂代码的代价?(我承认,我认罪,我虚荣,我炫耀技巧——如果这也可以炫耀的话,不过要说“玄虚”,要说为了语法的快感而无恶不作,真希望指责的人研究研究modern c++ design,看看人家Alexandrei小侠那叫怎么个玩弄技巧,怎么个“玄虚”,跟人家相比,我真不好意思自己这点过家家的东西居然也敢称为“玄虚”,在这网络时代,难道是个人都敢玩玄虚了么?)

到底应该keep it simple and stupid还是do not repeat yourself?几行重复的for loop到底算不算一个需要克服的“重复”?一个dynamic proxy到底算不算“复杂”?是善还是恶?是生存还是死亡?你觉得你正在读的是屈原还是哈姆雷特?是梵高还是孔乙己?还是啥也不是,就一装腔作势,无病呻吟的死猪?





(待续)








0 请登录后投票
   发表时间:2006-12-30  
淫一手好湿不难,难的是淫一被子好湿。继续。

怀着内疚的心情,虽然我自己也对这个sumOf()感觉很矛盾,虽然很多朋友对这个东西恨得牙痒痒,世上不如意的事情常八九,这个东西,居然就进入了production code里了。毕竟,如果极端实用主义的话,从客户代码角度看,它用起来是最爽的。

就在我快要把自己做了这么一件亏心事的事实忘掉的时候,我居然发现有一种忍不住再次犯罪的倾向。

事情的发生是这样的。

这个遗留系统是一个金融软件。不知道是不是世界上搞金融的都是头脑简单的西装男的原因,这个系统里面居然几乎到处都有对一个数组求和的操作。前面我发现的对一个object model重复七次的求和居然仅仅是冰山的一角。在陆续地对系统熟悉的过程中,发现"for(...){sum+=arr[i]}"这种操作真是到处都是。辛勤的程序员们从来都是遇到一个求和就写一遍for loop,从来没有听他们抱怨过,多好的同志啊!

于是,我就想,恩,根据所谓窃钩者诛,窃国者王的普遍真理,既然我已经搞了奇迹淫巧了,一不做二不休,干脆就干到底了。这个dynamic proxy从实现看并不是只能局限于Account这一个object model的。为什么不能把所有的四行的求和操作都换成一行的:
double totalWhatever = sumOf(WhateverInterface.class, whateverArray).getWhateverValue();


然后这个dynamic proxy就可以从一个尴尬的偷娶回来的二奶的地位一跃成为圣上钦赐的二太太甚至大妇也说不定阿。然后它就变成了一个需要学习的"pattern",而不是一个需要审视的奇迹淫巧了。

恩。就这么办。

改动dyn proxy不难,(其实,几乎没什么需要改的)。就是有一个毛病啊一个毛病:它要求被求和的object model必须实现一个接口,不管什么接口,但是必须有个接口(否则dynamic proxy不好使阿)。


幸运的是,这个遗留系统一定最初是被一个刚刚接触“program against interface”理论而把这个理论奉如神明的好同志设计的,几乎所有的东西都有接口。这么说吧,就算你要的仅仅是一个Pair,一个只有getFirst()和getSecond()的java bean,它也会有一个IPair的接口?为啥?不为啥,就为了“什么东西都应该有个接口”的金科玉律。

当初我没少破口大骂这个棒槌,真是成事不足败事有余。该用接口的地方偏偏用的是继承,而明明多此一举的地方就非要碍眼地站着一个ISomething。接口本来应该是用来驳接系统的各个模块,隔离实现细节用的,他却是内部该耦合的还是耦合,仅仅在每个东西的外面包上一层金光闪闪的接口来证明“今天我接口了”。靠!

现在,第一次发现这家伙原来也有可爱的一面。(声明,我仍然反对滥用接口,某个人随便乱扔瓜果皮核结果凑巧让一个追杀我的坏蛋摔了一交,固然让人高兴,但是不代表他不应当被罚款)



不过,虽然说大部分的情况如此,还是有少部分东西没有接口的。咋办?自己写的类,还可以死契白列地加上这个接口。那要是一个jar里面的类呢?要是axis自动生成的呢?

说到这,想起了几天前讨论的这个框架侵入性问题了:http://www.iteye.com/topics/next?topic=39330&forum=39

要说我要求每个类都实现一个接口,这不是侵入么?而且是赤果果的侵入把?

怎么办尼?哈,继续炫耀,使用酒窝:http://ajoo.iteye.com/blog/38299,把dynamic proxy的代码变成如下模样:
public class All implements InvocationHandler, Serializable {
  private static final long serialVersionUID = 3992411460899603473L;
  private final List participants;
  private final Class targetType;
  private final Implementor implementor;
  public static <T> T sumOf(Class<T> targetType, Object[] arr) {
    return sumOf(targetType, Arrays.asList(arr), arr.getClass().getComponentType());
  }
  @SuppressWarnings("unchecked")
  public static <T> T sumOf(Class<T> targetType, List<?> participants, Class<?> componentType) {
    return (T)Proxy.newProxyInstance(targetType.getClassLoader(), 
        new Class<?>[]{targetType}, new All(participants, targetType, 
            determineImplementor(targetType, componentType))
    );
  }
  private static Implementor<?> determineImplementor(
      Class<?> targetType, Class<?> componentType) {
    return componentType==null||targetType.isAssignableFrom(componentType)?
        null:Implementor.instance(componentType);
  }
  public static <T> T sumOf(Class<T> targetType, List<?> participants) {
    return sumOf(targetType, participants, null);
  }
  private Implementor getImplementorFor(Object obj) {
    if(implementor==null){
      return Implementor.instance(obj.getClass());
    }
    else return implementor;
  }
  @SuppressWarnings("unchecked")
  private Object convert(Object from){
    return getImplementorFor(from).implement(targetType, from);
  }
  private All(List participants, Class targetType, Implementor implementor) {
    this.participants = participants;
    this.targetType = targetType;
    this.implementor = implementor;
  }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if(Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      }
      else {
        double result = 0;
        for(Iterator<?> it = participants.iterator(); it.hasNext();){
          Object participant = it.next();
          if(participant==null) continue;
          if(!targetType.isInstance(participant)) {
            participant = convert(participant);
          }
          Number val = (Number)method.invoke(participant, args);
          if(val != null)
            result += val.doubleValue();
        }
        return new Double(result);
      }
    }
    catch(InvocationTargetException e) {
      throw e.getTargetException();
    }
  }
  /**
   * if the object compared to is a dynamic proxy, unwrap it and get the InvocationHandler to compare with.
   */
  @Override
  public boolean equals(Object obj) {
    if(obj!=null && Proxy.isProxyClass(obj.getClass())){
      return super.equals(Proxy.getInvocationHandler(obj));
    }
    else return super.equals(obj);
  }
}

这个实现比最初的实现考虑的多了一些,因为要做大妇嘛,当然要考虑全面一些,于是,增加了对equals的支持,对generics的支持,支持了Serializable,同时支持了对数组和list的sum动作。
更重要的是,它用dimple来支持当被sum的类不实现目标接口的时候自动进行委托(所谓duck typing咯)

因此,当调用:
sumOf(TargetInterface.class, array).getBalance()

而array里面的元素没有实现TargetInterface的时候,代码仍然工作(只要你有getBalance()函数定义就成)


这样,当客户代码要对某一个没有接口的类求和的时候,可以额外声明一个interface,把要求和的方法写进去,不需要写任何实现,直接用就是了。

比如说:
public class MyAccount {//第三方类,没有实现任何接口
  public double getBalance(){...}
}
private interface IAccount {//我们自己的接口
  public double getBalance();
}
MyAccount[] accts = ...;
double totalBalance = All.sumOf(IAccount.class, accts).getBalance();



到这里,我又想到了在酒窝的那个帖子里和taowen的讨论。taowen希望酒窝能自动检查用来动态实现接口的类里面的每个方法,如果在接口里面没有匹配,就证明可能是笔误,直接报错。我当时就很犹豫,因为这似乎有些影响灵活性(虽然对stub test的目的来说这个灵活性似乎没什么用)。所以,我只给出了两个额外的类型检查函数和一个额外的annotation来让用户有选择地检查。

现在从这个例子里面可以看出这个灵活性的作用了。如果我做这个检查,那么就要求MyAccount里面所有的public方法都要在IAccount里面,明显不实用了。

好了,太多的炫耀了。一个简单的求和操作已经被我差不多引申到世界和平了。呵呵,原来我还遮遮掩掩地淫,这不,既然被撞破了,那就只好赤果果地淫了。

对了,无可争议地,本系列属于少儿不宜。需要不通过“由拙而巧,再返璞归真”的普通人方法,而是直接跳级学习“我拙,我拙,我大巧若拙”的无上大道的朋友们,不要被本系列误导了,你走错门了,这是男厕所,女厕所在隔壁。
0 请登录后投票
   发表时间:2006-12-30  
貌似歇斯底里了,哈哈。

所谓的大牛的通病是容不得别人戳指头,于是愤怒,郁闷,极度愤怒,极度郁闷。
0 请登录后投票
   发表时间:2006-12-31  
看了半天还是觉得这句话最为中肯:

淫一手好湿不难,难的是淫一被子好湿
0 请登录后投票
   发表时间:2007-01-01  
ray_linn 写道
貌似歇斯底里了,哈哈。

所谓的大牛的通病是容不得别人戳指头,于是愤怒,郁闷,极度愤怒,极度郁闷。


如果真是这样, 那也应该算大牛可以作出超出普通人成绩的一个本质原因了
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics