`
ajoo
  • 浏览: 453052 次
社区版块
存档分类
最新评论

论面向组合子程序设计方法 之 新约

阅读更多
每个小孩刚开始走路的时候都是跌跌撞撞的。
我们不自量力,妄图踩着上帝的步伐前进。结果就是这么几个简单的象白开水似的类。失望吗?是不是造物试图模仿造物主本身就是一种可笑的狂妄呢?

别急,让我们失声痛哭之前先看看我们这几步走的是不是一钱不值。
[list]
1。logger可以把信息打印到log文件中。

容易,直接创建一个WriterLogger就好了。

2。不同的重要程度的信息也许可以打印到不同的文件中?象websphere,有error.log, info.log等。
如果这样,那么什么重要程度的信息进入error.log,什么进入warning.log,什么进入info.log也需要决定。


不同的文件吗?好办啊。就是不同的PrintWriter对象了。
Logger err_log = writer(err_writer);;
Logger warning_log = writer(warning_writer);;
Logger info_log = writer(info_writer);;


各个文件记录不同重要程度的信息是么?
err_log = filter(ERROR, err_log, nop(););;
warning_log = filter(WARNING, warning_log, nop(););;
info_log = filter(INFO, info_log, nop(););;

最终把三个不同的logger串联起来就是了:
Logger logger = sequence(err_log, warning_log, info_log);;



3。也许可以象ant一样把所有的信息都打印到一个文件中。

这就更简单了,就一个是WriterLogger。


4。每条logging信息是否要同时打印当前的系统时间?也是一个需要抉择的问题。

拿不定主意是么?没关系,想好了再告诉我。
反正,如果你需要系统时间,我只需要
logger = timestamp(logger);;





5。不仅仅是log文件,我们还希望能够在标准错误输出上直接看见错误,普通的信息可以打印到log文件中,对错误信息,我们希望log文件和标准输出上都有。

可以创建针对标准输出的Logger,然后和打印log 文件的logger串联起来。
Logger std_logger = writer(new PrintWriter(System.err););;
std_logger = ignore(ERROR, std_logger);;
logger = sequence(std_logger, logger);;





6。标准输出上的东西只要通知我们出错了就行,大概不需要详细的stack trace,所以exception stack trace可以打印到文件中,而屏幕上有个简短的exception的message就够了。

这里需要对std_logger稍微改写一下:
PrintWriter out = new PrintWriter(System.err);;
std_logger = new ErrorMessageLogger(out, writer(out););;
std_logger = ignore(ERROR, std_logger, nop(););;

用ErrorMessageLogger来改写对异常的log逻辑。



7。warning似乎也应该输出到屏幕上。

好啊。就是把ignore函数里面的ERROR换成WARNING就好了
std_logger = ignore(WARNING, std_logger, nop(););;



8。不管文件里面是否要打印当前系统时间,屏幕上应该可以选择不要打印时间。

对std_logger不掉用timestamp就是了。

9。客户应该可以通过命令行来决定log文件的名字。


这条和logger组合子其实没什么关系。

10。客户可以通过命令行来决定log的细节程度,比如,我们只关心info一样级别的信息,至于debug, verbose的信息,对不起,不要打印。

生成那个最终使用的Logger对象的时候,再ignore一下就行了:

logger = ignore(some_level, logger, nop(););;



11。neptune生成的是一些Command对象,这些对象运行的时候如果出现exception,这些exception会带有execution trace,这个execution trace可以告诉我们每个调用栈上的Command对象在原始的neptune文件中的位置(行号)。
这种exception叫做NeptuneException,它有一个printExecutionTrace(PrintWriter)的方法来打印execution trace。
所以,对应NeptuneException,我们就不仅仅是printStackTrace()了,而是要在printStackTrace()之前调用printExecutionTrace()。


NeptuneExceptionLogger就是给这个准备的呀。

12。neptune使用的是jaskell语言,如果jaskell脚本运行失败,一个EvaluationException会被抛出,这个类有一个printEvaluationTrace(PrintWriter)的方法来打印evaluation trace,这个trace用来告诉我们每个jaskell的表达式在脚本文件中的位置。
所以,对应EvaluationException,我们要在printStackTrace()之前,调用printEvaluationTrace()。


JaskellExceptionLogger


13。execution trace和evaluation trace应该被打印到屏幕上和log文件两个地方。


这就是说,上面两个Logger应该被应用到std_logger和logger两个对象中。


14。因为printExecutionTrace()和printEvaluationTrace()本身已经打印了这个异常的getMessage(),所以,对这两种异常,我们就不再象对待其它种类的异常那样在屏幕上打印getMessage()了,以免重复。


就是说,一旦一个exception被发现是NeptuneException,那么ErrorMessageLogger就要被跳过了。
    final Logger err_logger = new ErrorMessageLogger(writer);;
    final Logger jaskell_logger = new JaskellExceptionLogger(writer, err_logger);;
    final Logger neptune_logger = new NeptuneExceptionLogger(writer, jaskell_logger);;
    return neptune_logger;

这个neptune_logger先判断异常是不是NeptuneException,如果是,直接处理,否则,传递给jaskell_logger。jaskell_logger继续判断,如果不是它感兴趣的,再传递给ErrorMessageLogger来做最后的缺省处理。


15。也许还有一些暂时没有想到的需求, 比如不是写入log文件,而是画个图之类的变态要求


放马过来吧。看我们的组合子能不能对付。
[/list:u]

很惊讶地发现,就这么几个小儿科似的积木,就似乎全部解决了曾让我们烦恼的这些需求?

为了给大家一个完整的印象,下面是我实际项目中使用这些组合子应对上面这些需求的代码:
public class StreamLogger {
  private final OutputStream out;
  
  /**
   * To create a StreamLogger object.
   * @param out the OutputStream object that the log message should go to.
   */
  public StreamLogger(OutputStream out); {
    this.out = out;
  }
  
  /**
   * To get the OutputStream that the log messages should go to.
   */
  public OutputStream getStream(); {
    return out;
  }
  private static Logger getBaseLogger(PrintWriter writer);{
    final Logger nop = new NopLogger();;
    final Logger base = Loggers.logger(writer);;
    final Logger neptune_logger = new NeptuneExceptionLogger(writer, nop);;
    final Logger jaskell_logger = new JaskellExceptionLogger(writer, nop);;
    return Loggers.sequence(
        new Logger[]{neptune_logger, jaskell_logger, base}
    );;
  }
  private static Logger getEchoLogger(PrintWriter writer);{
    return new ErrorMessageLogger(writer);;
  }
  private static Logger getErrorLogger(PrintWriter writer);{
    final Logger err_logger = new ErrorMessageLogger(writer);;
    final Logger jaskell_logger = new JaskellExceptionLogger(writer, err_logger);;
    final Logger neptune_logger = new NeptuneExceptionLogger(writer, jaskell_logger);;
    return neptune_logger;
  }
  /**
   * Get the Logger instance.
   * @param min_level the minimal critical level for a log message to show up in the log.
   * @return the Logger instance.
   */
  public Logger getDefaultLogger(int min_level);{
    final PrintWriter writer = new PrintWriter(out, true);;
    final PrintWriter err = new PrintWriter(System.err, true);;
    final PrintWriter warn = new PrintWriter(System.out, true);;
    final Logger all = Loggers.sequence(new Logger[]{
        Loggers.ignore(getErrorLogger(err);, Logger.ERROR);,
        Loggers.filter(getEchoLogger(warn);, Logger.WARNING);,
        getBaseLogger(writer);
      }
    );;
    return Loggers.ignore(all, min_level);;
  }
}


为了偷懒,我没有用配置文件,就是把这些策略硬编码进java了。好在上面的代码非常declarative,改起来也很容易。

没习惯读代码的朋友。这里奉劝还是读一读吧。很多时候,代码才是说明问题的最好手段。我相信,只有读了代码,你才能真正尝到CO的味道。


有朋友问,你这个东西和decorator pattern有什么区别呀?乍看上去,还真是长得差不多呢。不都是往现有的某个对象上面附加一些功能吗?
也许是把。我不知道象SequenceLogger这种接受一个数组的,是否也叫做对数组的decorator;也不知道IgnoreLogger接受了两个Logger对象,这个decorator究竟是修饰谁的呢?

其实,叫什么名字无所谓。我这里要讲的,是一种从基本粒子推演组合的思路。形式上它也许碰巧象decorator, 象ioc。但是正如workinghard所说(这句话深得我心),思路的切入点不同。

如果你仔细看上面的代码,也许你会有所感觉:对Logger的千奇百怪的组合本身已经有点象一个程序代码了。
如果用伪码表示:

  all_logger = ignore messages below ERROR for getErrorLogger(err);;
          filter messages except WARNING for getEchoLogger(warn);;
          baseBaseLogger(writer);;
  ignore messages below lvl for all_logger;

当组合子越来越多,需求越来越复杂,这个组合就会越来越象个程序。

这里,实际上,(至少我希望如此),我们的思维已经从打印什么什么东西上升为在Logger这个级别的组装了。

这也就是所谓higher order logic的意思。


所谓combinator-oriented,在这里,就体现为系统性地在高阶逻辑的层面上考虑问题,而不是如decorator那样的零敲碎打的几个功能模块。
大量的需求逻辑被以声明式的方式在高阶逻辑中实现,而基本的组合子只负责实现原字操作。


当然,缺点也是明显的,对此我不讳言:

[list]高阶逻辑不容易调试,当我们使用一个组合了十几层的复杂的Logger对象的时候(真正用了co这种情况不少见),一旦出现bug,跟踪的时候我们就会发现象是陷入了一个迷宫,从一个组合子跟踪进入另一个组合子,绕来绕去。

另外,异常的stack trace也无法反映组合层次关系,造成错误定位麻烦。[/list:u]


这也许不是co本身的问题,而是因为java这种oo语言对co没有提供语言上的支持。但是无论如何,这对在java中大规模使用co造成了障碍。

也许你还无法理解。平时我们在java中用那么几个decorator,本身非常简单,所以debug, trace都不是问题。可是,一旦oriented起来,情况就不同了。街上有两辆车和成千上万辆车,对交通造成的压力截然不同。


还有朋友,对co如何把握有疑问。难道co就是瞎猫碰死耗子么?

其实,无论co还是oo,对设计者都是有一定要求的。
oo要求你了解需求,并且经验丰富,也要有一点点运气。
co也要求经验,这个经验是设计组合子系统的经验。什么样的组合子是好的?怎么才算是足够简单?什么组合规则是合理的?等等,这些,也有规律可循,就像oo的各种模式一样。同时,也可以refactor。毕竟,怎么简单地想问题比怎么分解复杂问题可能还是要容易掌握一点。

不过,co对经验的要求稍微小一点,但是对数学和逻辑的基本功要求则多了一点。有了一些数学和逻辑方面的基本功,那么设计组合子就会轻松的多。

co也要有一点点运气。所以遇到你怎么想也想不明白的情况,就别死抗啦,也许这个问题就抽象不出组合子来,或者以我们普通人的智慧抽象不出来。



co是银弹吗?当然不是,至少以我的功力没有这种自信。
遇到复杂的问题我也是先分解需求,面向接口的。只有问题的规模被控制在了一定的范围,我才会试图用co来解决问题。靠着对co的一些经验和感觉,一旦发现了可以组合子化的概念,成果会非常显著。


而且,co和oo关注的层面也不同。co是针对一个单独的概念(这点倒有点象ao),一点一点演绎,构成一个可以任意复杂的系统,一个好的co也会大大减少需要引入的概念数。而oo是处理大量互相或者有联系,或者没有联系的概念,研究怎么样把一个看上去复杂的系统的复杂度控制住。所以两者并不是互相排斥的。自顶向下,自底向上,也许还是两手一起抓更好些。

这段时间应用co做了几个软件后,感觉co最擅长的领域是:
问题域有比较少的概念,概念简明清晰(比如logger, predicate, factory,command),但是对概念的实现要求非常灵活的场合。
这种时候,用oo就有无处下嘴之感。毕竟概念已经分解了,职责也清楚,就是怎么提供一个可以灵活适应变化的体系,而对这个,oo并没有提供一个方法论。基本上就是用po的方法吭哧吭哧硬做。而co此时就可以大展用武之地。弥补这个空隙。



看过圣经的,也许有感觉,旧约里面的上帝严厉,经典,就像是一个纯粹的fp语言,每个程序员都被迫按照函数式,组合子的方式思考问题,你感觉困难?那是你不够虔诚。你们人不合我的心意,我淹死你们!


而新约里面,明显添加了很多人情味儿。上帝通过自己的儿子和人们和解了。既然淹死一波,再来一波还是这样,那么是不是说大家应该各让一步呢?
co和oo,既然各自都不能宣称自己就是银弹,那么为什么不能拉起手来呢?我们不是神,不可能真正按照神的方式用基本粒子组合演化世界,所以就别象清教徒一样苦苦追求不可能的目标了。但是,上帝的组合之道毕竟相当巧妙,在有些时候荆棘里面出现火焰的时候,我们又何必固执地拒绝造物主的好意呢?礼貌地说一声:“谢了!老大”。不是皆大欢喜?


这一节我们继续讲解了这个logging的例子。实际上,logging是一个非常简单的组合子,下面如果大家对这个例子有疑问,我会尽力解答。

然后,我们会进军下一个例子。
分享到:
评论
1 楼 王者之剑 2009-04-17  
这种思想不错

相关推荐

    《C#面向对象程序设计》源代码(CS)

    本书以面向对象的软件工程思想为主线,细致深入地讲解了C#语言面向对象程序设计的方法和技巧,内容涵盖面向对象的基本概念、基于接口的设计、泛型程序设计方法、Windows和Web应用开发,以及数据库访问技术。...

    UML与面向对象程序设计

    面向对象方法论为软件系统的设计提供了概念框架,而UML则是实现这一框架的实用工具。熟练掌握它们,能够极大地提升软件开发的效率与质量,帮助开发者在复杂的软件项目中保持清晰的设计思路,使得最终交付的软件产品...

    《C++面向对象程序设计教程(第3版)》电子课件 (程细柱PPT).rar

    C++面向对象程序设计教程(第3版) 目录: 第1章 面向对象程序设计概述,第2章 C++概述,第3章 类和对象,第4章 派生类与继承,第5章 多态性,第6章 模板与异常处理,第7章 C++的流类库...第8章 面向对象程序设计方法与实例.

    Java与UML面向对象程序设计.pdf

    《Java与UML面向对象程序设计》强调理论和设计相结合,重视对软件开发方法学有指导作用的重要概念。《Java与UML面向对象程序设计》可作为高等学校计算机科学系及软件学院高年级学生和研究生的教科书,也可作为从事...

    C++程序设计 思想与方法 第2版 完整扫描版-翁惠玉2012

    简介:本书以C++语言为环境 重点讲授程序设计的思想和方法 包括过程化的程序设计和面向对象的程序设计 本书也非常强调程序设计的风格 使读者通过学习 并经过一定的训练和实践 能够掌握程序设计的方法和过程 并具备...

    Visual C++面向对象与可视化程序设计 黄维通 课后习题答案程序及debug

    《Visual C++面向对象与可视化程序设计》是黄维通教授编著的一本经典教材,主要介绍了使用Microsoft的Visual C++编程环境进行面向对象程序设计和可视化应用开发的基础知识。这本书深入浅出地讲解了C++语言的核心概念...

    Abaqus用户子程序二次开发官方PPT教程(全350页).pdf

    ### 综上所述,Abaqus用户子程序二次开发官方PPT教程中涵盖了从基本概念、编程技巧、特定子程序的接口使用,到实际案例分析的全方位知识。通过学习本教程,用户可以有效地进行ABAQUS的二次开发,以满足特定工程问题...

    C++ 面向对象程序设计(第七版) 周靖 译

    《C++ 面向对象程序设计》是周靖翻译的第七版教材,该书深入浅出地介绍了C++这门强大的编程语言,特别强调了面向对象编程的概念和实践。面向对象编程(Object-Oriented Programming,OOP)是C++的核心特性,它通过类...

    C++程序设计课件 ch3 程序设计初步

    这种程序设计方法将程序视为一系列函数或子程序的集合,每个函数完成特定的任务。 #### 1.2 数据结构与算法 - **数据结构**:是指在计算机中存储、组织数据的方式。有效的数据结构能够提高程序的效率和性能。 - **...

    IBM PC汇编语言程序设计

    第五、六章叙述循述循环、分支、子程序等基本程序结构以及程序设计的基本方法和技术;第七章为宏汇编技术;第八章说明以中断为主的输入/输出程序设计方法;第九章介绍BIOS和DOS系统功能调用的使用方法;第十~十二章...

    完整版C语言程序设计TXT电子书

    1.5 面向对象的程序设计语言 1.6 C和C++ 1.7 简单的C程序介绍 1.8 输入和输出函数 1.9 C源程序的结构特点 1.10 书写程序时应遵循的规则 1.11 C语言的字符集 1.12 C语言词汇 1.13 Turbo C ...

    基于微信小程序点餐系统的设计与实现(含word论文)

    《基于微信小程序点餐系统的设计与实现》 随着信息技术的快速发展,互联网已深入人们生活的各个领域,其中,餐饮行业的数字化转型尤为明显。微信小程序作为移动应用的一种轻量化形式,为餐饮业提供了便捷的线上点餐...

    C++程序设计(谭浩强PDF)

    1. **C++语言基础**:C++是一种静态类型的、编译式的、通用的、大小写敏感的、不仅支持过程化编程,也支持面向对象编程的程序设计语言。谭浩强的书中会首先介绍C++的基本语法,包括变量、常量、数据类型、运算符、...

    8086/8088汇编语言程序设计教程

    基础部分主要以8086/8088微处理器和DOS操作系统为学习平台,介绍汇编语言基础知识、寻址方式、指令系统和程序设计方法。提高部分则是针对80386微处理器及其保护模式下的编程技术和细节。最后,上机实验指导部分为...

    程序设计语言 实践之路 第3版

    《程序设计语言 实践之路 第3版》是一本深度探讨编程语言原理与实践的书籍,由Michael L. Scott撰写。本书旨在帮助读者理解各种编程语言的设计选择,以及这些选择如何影响程序的构造、效率和可维护性。通过阅读这...

    《Python语言程序设计》[刘卫国][习题解答]

    全书共13章,主要内容有Python语言基础、顺序结构、选择结构、循环结构、字符串与正则表达式、列表与元组、字典与集合、函数与模块、面向对象程序设计、文件操作、异常处理、图形绘制、图形用户界面设计。...

    基于微信小程序的点餐系统设计与实现 毕业论文.docx

    本文档是一篇关于基于微信小程序的点餐系统设计与实现的毕业论文,旨在利用微信小程序这一日益普及的技术,优化餐厅点餐流程,提供便捷的在线点餐服务。论文详细介绍了系统的开发背景、技术选型、需求分析、系统设计...

Global site tag (gtag.js) - Google Analytics