Shao Fan

关于JAVA与软件工程
posts - 31, comments - 71, trackbacks - 0, articles - 4
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2006年6月12日

目前开发人员对系统开发的一个共识是使用三层架构,分为表示层,业务层,和持久层。而这三层之间的依赖关系如何?比较常见的一种看法是

表示层 --> 业务层 --> 持久层

这表明了层与层之间的调用关系,表示层通过调用业务层来完成任务,而业务层则调用持久层。从另一个角度来看,一种依赖关系是

表示层 --> 领域模型(Domain Model) <-- 持久层

表示层和持久层都应该理解(recognize)领域模型。而领域模型则是业务层的一部分。业务层正是系统的价值所在。虽说表示和持久也很重要,在某些系统中可以说是很关键,但是它们的最终目的都是为业务服务,所以业务层应该是系统的核心

基于以上的认识,在系统设计的时应首先分析需求得到领域模型,找出系统中的实体、对象(静态的一面),并明确大致的业务流程(动态的一面)。 而另两层应尽最大努力为业务层服务,且尽量减少业务层受另两层的限制。


各层的职责:

表示层:负责显示信息,及从系统外部得到输入。表示层的设计决定系统界面的可用性,及信息输入和展示的可靠性。表示层只知道如何展示信息,及收集用户输入,并不知道该如何对这些输入进行处理来完成业务。

业务层:完成业务逻辑。业务层设计决定客户价值是否能够得到实现。这是系统的关键。外在的表现是功能性。业务层设计和实现的失误表现在用户端即功能缺失,功能不可靠等。如果需要对业务层的业务规则进行解耦,则可以使用规则引擎如Drools,把业务规则分离出来。但分离后的业务规则仍属于业务层。业务层知道如何对用户输入进行处理,能够应用业务规则完成用户所需的业务,但它不知道数据如何读取和保存。

持久层:负责用户信息的持久化。持久层的失误表现在外即数据处理(储存,展示等)不可靠。持久层完全不知道业务,只专注于数据存储和读取。所谓持久化并不一定是指数据库,任何方式的持久化(通过文件,网络的持久化等)都应由持久层完成。

各层的设计都会直接影响系统性能。

三层的体积大小和复杂度在不同的系统中可能会有很大的不同。比如说GOOGLE的搜索引擎,它的界面很简单,可以想像表示层是比较容易实现的,而它的业务层,关系到处理关键字,分析搜索结果,决定排名等,而持久层则要负责处理超大量的数据。业务层和持久层则相当复杂。而有的系统持久层会很小,比如杀毒软件,媒体播放软件等。业务层小而另两层大的例子暂时还没有想到:)


posted @ 2007-09-08 19:45 shaofan 阅读(5114) | 评论 (2)编辑 收藏

help是一个内置函数,所谓内置函数,就是在Python中被自动加载的函数,任何时候都可以用。参数分两种:

  • 如果传一个字符串做参数的话,它会自动搜索以这个字符串命名的模块,方法,等。
  • 如果传入的是一个对象,就会显示这个对象的类型的帮助。

比如输入help(’print’),它就会寻找以’print’为名的模块,类,等,找不到就会看到提示信息。而print在python里是一个保留字,和pass,return同等,而非对象,所以help(print)也会出错((kkkkkkk))。

举个例子:

1 help(’sys’) #会列出sys模块的帮助
2 = [1,2,3]
3 help(a) #会显示list的帮助
4 help(a.append) #会显示list的append方法的帮助

python安装自带的library reference,2.1节是关于内置函数的。

Reference Manual的6.6节可以找到关于print的东东。

posted @ 2007-06-05 06:28 shaofan 阅读(2757) | 评论 (0)编辑 收藏

Struts2默认theme是xhtml,它用表格来对表单中的控件进行排版。它也提供一个客户端的js验证功能,但是它的js脚本却有些问题,在某些情况下,前次验证的提示信息无法被清除,提示信息会不断的累积显示在屏幕上。而按照设计,每次提交表单时应只显示每次验证的出错信息。

它的客户端验证的流程大概是这样,用户提交表单时,对各个控件的输入按预先设置的规则进行验证,如果有问题,则清除表单里原有的出错提示信息,并写入新的提示。其设计的功能是把出错信息写表格里出错控件的上方,以便用户看得更加清楚。问题就出在其用来清除原出错信息的函数,其代码是这样的(在struts.jar的template/xhtml目录下可以找到):

 1 function clearErrorMessages(form) {
 2 
 3     var table = form.childNodes[1];
 4     iftypeof table == "undefined" ) {
 5         table = form.childNodes[0];
 6     }
 7 
 8     // clear out any rows with an "errorFor" attribute
 9     var rows = table.rows;
10     var rowsToDelete = new Array();
11     if (rows == null){
12         return;
13     }
14 
15     for(var i = 0; i < rows.length; i++) {
16         var r = rows[i];
17         if (r.getAttribute("errorFor")) {
18             rowsToDelete.push(r);
19         }
20     }
21 
22     // now delete the rows
23     for (var i = 0; i < rowsToDelete.length; i++) {
24         var r = rowsToDelete[i];
25         table.deleteRow(r.rowIndex);
26         //table.removeChild(rowsToDelete[i]);
27     }
28 }


看这个函数的前三行,它试图取得form的第1个或第2个子节点,并把它作为table来处理(看接下来的几行)。要想清除表格里的错误信息,首先要取得表格本身,这没错,但是如果第1个或第2个子节点不是table的话,脚本就会出错,造成原出错信息无法清除,这样每次提交后的提示信息就会累积在屏幕上。

要解决这个问题有两个办法:
  • 写代码时要小心,保证form的第1或2个子节点是table,不要在生成table前加其他代码。
  • 或,修改xhtml的validation.js,使它总能获得正确的table元素,重新打包到struts.jar。
刚看了一下Struts的JIRA,已经有人报告了这个问题(id WW-1802),而且这个bug在2.1版本中已经解决了。

posted @ 2007-06-03 17:56 shaofan 阅读(2529) | 评论 (3)编辑 收藏

假设:用两者写一个最小的WEB程序。
过程可以参照:
1.struts的就太多了,随便哪个都可以
2.python/django可以看limodou写的Django step by step

 

Java/Struts/JSP  Python/Django
开发步骤 1.在web.xml里配置struts的servlet
2.在struts-config.xml里配置URL和action的映射
3.写action
4.写JSP
1.在urls.py里配置URL到方法的映射
2.写相应的方法
3.写HTML模板
调用过程 1.根据web.xml的映射调用struts的servlet controller
2.servlet controller根据struts-config.xml的映射调用相应的action
3.action处理请求
4.JSP渲染显示
1.根据urls.py的映射调用相应的方法
2.方法处理请求
3.HTML渲染显示


相比之下前者用了两层才把一个HTTP请求映射到实际处理的方法:第一次是servlet的映射,第二次是struts action的映射。
而django则一次就从URL映射到相应的方法了。

另外一个比较显著的区别,也是基于java和python的语言上的区别吧,java的所有方法必需包含在一个类中,因此action mapping配置时是映射到类,而action在实现类则应实现事先约定的方法(通过继承或实现接口)。而django则直接得多,可以直接在配置里写明处理请求的方法名。


posted @ 2007-04-06 19:11 shaofan 阅读(4966) | 评论 (0)编辑 收藏

DOM (Document Object Model)是一套语言无关的XML解析的接口定义。它定义了在XML解析中需要的类型,方法,以及属性,比如如何获得一个XML标签,如何改变标签的内容,如何改变它的属性,等等。

DOM只是一个定义,并不是具体的实现,它的目的就是为了让大家在各个平台上都能用相同的方式来处理XML,这样一来,我只要了解DOM,基本上在各个平台上都可以方便的处理XML,而不用重新学习了。比如说,Java, JavaScript, Python都有DOM的实现,用它们来处理XML,方式基本上都是一样的(当然也有非DOM的XML解析方式)。在Java下,实现DOM的类库就有很多,比如JDom,Xerces, 用GOOGLE一搜就一大把。现在Java 5.0内置的就是Xerces。而JavaScript本身就内置了DOM的实现。Python也默认安装了DOM的库。

正因为DOM致力于实现各个平台上对XML一致的处理方式,它定义了一堆自己的接口。因此在用DOM的时候,会有很多非NATIVE的东东。比如说,返回节点的子节点的方法,childNodes,返回的类型是NodeList。我第一次在Java上用,就以为是返回一个List,然后用get(n)方法来取得某元素。而实际上NodeList是用item(n)的方法来取得某元素的。这就让我觉得很怪。而DOM正是用这种方式来获得“语言无关”的能力的。

DOM是用IDL(Interface Definition Language)来定义的。完整的定义可以在这里找到 http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html。IDL也很容易看懂。定义的1.1节列出了所有的接口。

这些接口里,最重要而且常用的是Node,NodeList,Document,Element,Text,Attr这几个。DOM把XML文档看作一棵树,树上的每个元素都是Node。每个Node都属于某个类型,比如Element,attribute,text等。这些类型就表明这个节点在XML文档里的类型了。

比如Node里有个属性:

  readonly attribute unsigned  short   nodeType;

根据这个定义,对于取得的节点,我们就可以通过读取nodeType这个属性来判断这个节点的类型。在Java里,所有的属性都是用getter来取得的,因此对某节点n,就可以用n.getNodeType()取得它的类型。Node接口里也定义了类型常量:

  const unsigned short      ELEMENT_NODE                   = 1;
  const unsigned short      ATTRIBUTE_NODE                 = 2;
  const unsigned short      TEXT_NODE                      = 3;
  const unsigned short      CDATA_SECTION_NODE             = 4;
  const unsigned short      ENTITY_REFERENCE_NODE          = 5;
  const unsigned short      ENTITY_NODE                    = 6;
  const unsigned short      PROCESSING_INSTRUCTION_NODE    = 7;
  const unsigned short      COMMENT_NODE                   = 8;
  const unsigned short      DOCUMENT_NODE                  = 9;
  const unsigned short      DOCUMENT_TYPE_NODE             = 10;
  const unsigned short      DOCUMENT_FRAGMENT_NODE         = 11;
  const unsigned short      NOTATION_NODE                  = 12;

用这些常量和和n.getNodeType()的结果比较,就可以知道它是不是某种类型。

Node接口中也定义了一些方法,比如:

 Node     appendChild(in Node newChild)    raises(DOMException);

表明appendChild方法需要一个Node类型的参数,返回一个Node。 具体的说明可以点文档上的链接进去,也很容易看懂。

Node接口里定义了操纵节点的方法,比如增加子节点,返回父节点,插入新节点,返回节点类型,等等。Document,Element等接口都继承Node接口,因此在它们上面都可以使用操纵节点的方法。

Document:代表整个XML文档。所有DOM元素都不能用类似Java里new的方式来生成,而是要通过调用Document里的相应方法来生成。因此它提供了生成诸如Element, Attr, Text的方法。比如createElement, createTextNode, createComment等。它也提供了名为getElementsByTagName的方法,用来通过标签名称来取得其对象。比如getElementByTagName("ul")就可以获得所有ul标签。它也提供一些文档的属性,比如xmlEncoding,inputEncoding等。它的一个属性,documentElement代表文档的根节点。所有对XML元素的操作,基本上都是从Document开始的。

Element:代表一个XML标签。它可以有属性,子标签,等。比如<ul id="booklist"><li>hello</li></ul>。标签ul是一个Element,它有一个属性叫id,属性的值是booklist。它有一个子结点li。li也是一个标签,它也有个子节点hello,是一个Text类型的节点。这个接口提供操纵其标签属性的方法,比如getAttribute,setAttribute,removeAttribute等。它也提供了和Document中一样的getElementsByTagName的方法,用来获得在这个节点下的元素。

Attr:代表标签中的属性。比如上面的id。它也是一个Node。它有名字,值,也可以获得它的所属标签。

Text:代表一段文字,比如上面的hello,它也一个Node,但比较特殊,它不是直接继承Node,而是继承CharacterData接口,后者继承了Node。但是它不能有子节点。

用JavaScript给一个例子。假设有一个HTML文档:

< html >< head >< title > Try DOM </ title ></ head >< body >
< ul >
< li > hello </ li >
< li > world </ li >
</ ul >
</ body ></ html >

下面是增加一个li的JavaScript方法:

ulList  =  document.getElementsByTagName( " ul " );
ul     
=  ulList.item( 0 );
txt    
=  document.createTextNode( " I am new li " );
li     
=  document.createElement( " li " );
li.appendChild(txt);
ul.appendChild(li);

用Java来写,是这样:

NodeList ulList  =  document.getElementsByTagName( " ul " );
Node        ul      
=  ulList.item( 0 );
Text          txt      
=  document.createTextNode( " I am new li " );
Element    li        
=  document.createElement( " li " );
li.appendChild(txt);
ul.appendChild(li);

可以看到处理方式和数据类型都是一样的。如果要了解更多,可以看看DOM的定义,都是IDL。

posted @ 2007-04-01 18:45 shaofan 阅读(844) | 评论 (0)编辑 收藏

我的博客已搬家,请移步到 http://shao-fan.com/blog 阅读最新内容!


Ubuntu/Debian中的update-alternative用来对系统中不同版本的同个软件进行管理。
比如,系统中可能装有GNU的Java编译器,和SUN的Java编译器。可以用update-alternatives来设置当前使用它们中的哪一个。

它的原理是在/usr/bin中建立一个link,指向/etc/alternatives中的一个文件,而些文件又是一个link,指向当前使用的命令。比如java命令,查看如下:

 $ which java
/usr/bin/java
$ ls -l /usr/bin/java
lrwxrwxrwx /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
lrwxrwxrwx /etc/alternatives/java -> /usr/lib/j2re1.5-sun/bin/java
$ ls -l /usr/lib/j2re1.5-sun/bin/java
-rwxr-xr-x /usr/lib/j2re1.5-sun/bin/java

参数--display可以某个软件的当前配置,如:

 $ /usr/sbin/update-alternatives --display java
java - status is auto.
link currently points to /usr/lib/j2re1.5-sun/bin/java
/usr/lib/kaffe/bin/java - priority 300
slave java.1.gz: /usr/share/man/man1/java.kaffe.1.gz
/usr/lib/j2re1.5-sun/bin/java - priority 315
slave java.1.gz: /usr/lib/j2re1.5-sun/man/man1/java.1.gz
Current `best' version is /usr/lib/j2re1.5-sun/bin/java.

status有auto和manual两种。一旦用户更改了系统的默认设置,它就变为manual。在auto的状态下,系统会根据几套配置的priority来判断当前应该使用哪套配置。

每套配置可以设定多个link,它们被称为slave。上面的例子中,有两套java的配置。一套是/usr/lib/kafe/bin/java,另一套是/usr/lib/j2re1.5-sun/bin/java,它们各有一个slave。在些例中这些slave设置的是java命令的 manual。当更改了配置时,用man命令查看的帮助也会相应更改。

参数--config可以用来更改当前的配置。

 $ sudo /usr/sbin/update-alternatives --config java 
There are 2 alternatives which provide `java'.
Selection Alternative
-----------------------------------------------
1 /usr/lib/kaffe/bin/java
*+ 2 /usr/lib/j2re1.5-sun/bin/java
Press enter to keep the default*, or type selection number: 1
Using `/usr/lib/kaffe/bin/java' to provide `java'.

参数--install用来设置一套新的配置。具体参见 http://blog.stevenkroon.com/2006/08/29/debian-update-alternatives/

posted @ 2006-12-25 01:43 shaofan 阅读(6305) | 评论 (1)编辑 收藏

Web开发真是越来越有意思了。现在居然可以在JavaScript里直接调用Java写的方法。大水牛Buffalo的最新版1.2.3发行离现在已有半年时间了,现在才注意到。在客户端的代码相当简单:

buffalo.remoteCall("userService.listAll",[],function(reply){
 // 不用担心,reply.getResult会从聪明的判断服务器端远程调用的结果类型。
 var userList = reply.getResult(); 
 var firstUserFamilyName = userList[0].name.familyName;
});

给Web程序加上Buffalo也相当容易。只要下载几个jar文件放到lib目录下,外加几个JS文件,然后在web.xml里加一个Servlet即可。用的时候是需要有个properties文件来定义哪些JAVA方法可以被JS调用。而服务器端的JAVA代码不需要做任何改动。

暂时手头上还想不起来哪些地方会用,但是这个东东可是我很久以前曾经想过的,现在有人实现了,还是很激动人心的啊。Buffalo的主页是 http://www.amowa.net/buffalo/zh/index.html 。


posted @ 2006-09-08 03:59 shaofan 阅读(8908) | 评论 (6)编辑 收藏

最近因为Globus的原因,不得不用Linux了。以前几次想学,都因为难得要领放弃了。这次装了Ubuntu,又碰巧在它的论坛上看到一篇很不错的教程,终于有些“入门”的感觉了。看来找到合适的教程真的是很重要啊。这几天用下来,感觉还真的很不错。用Python写了个显示目录树的小程序。也不知道有没有现成的,反正很简单,刚好就练练手,功能也够我自己用了:)

下载Python脚本


posted @ 2006-08-01 08:57 shaofan 阅读(1493) | 评论 (0)编辑 收藏

最近Firefox出了点问题。我这里用http proxy上网,连接设置里要设置相应的proxy。但发现每次重启ff,连接设置都被重置为“直接连接”。即使改回“通过proxy连接”,下次重启又被重置了。

马上google一把,发现类似的问题大致有两种解决办法:
1.进入safe mode,会出现一个对话框,把三个选项都勾上,重启。
2.在地址栏输入about:config,会出现所有的配置,把要改的改了即可。
3.直接修改prefs.js文件。

但这三个方法对偶的FF都不管用。

最后的解决方法是,删除
C:\Documents and Settings\<user name>\Application Data\Mozilla\Firefox\Profiles\<xxxxxxxx.default>\
下的user.js,然后再修改prefs.js。然后一切都正常了。

原来,ff最基本的配置文件是prefs.js。而user.js的优先级应该是比prefs.js高,因此每次重启它都从user.js读取配置。而通过正常途径,如在选项菜单里改设置,无法在user.js里保存(不知道为什么)。所以就造成了每次重启配置被重置的情况,其实就是设置无法被保存。把user.js删掉以后,ff就从prefs.js里读取配置了,而且新的设置也可以正常保存。

不管是从选项菜单里修改配置,还是以上的第二种方法,最终的结果都是配置被保存在prefs.js文件里,效果都是一样的。

目前已知的会私自修改FF配置的软件只有Hide IP Plantium,而以上情况正是在安装Hide IP Plantium以后出现的。用的时候要注意。

相关的讨论在mozillazine里有很多。不得不承认,这类的英文资源要比中文的多得多。看这里的详细讨论:
http://forums.mozillazine.org/viewtopic.php?t=413875&postdays=0&postorder=asc&postsperpage=15&start=15

posted @ 2006-07-24 18:21 shaofan 阅读(4586) | 评论 (2)编辑 收藏

1.理解页面流(flow),理解几种不同的定位方式:static, relative, absolute等
Positioning and other definitions:http://css.maxdesign.com.au/floatutorial/definitions.htm  

2.HTML页面表达内容逻辑,把样式控制写到CSS文件中

3.浮动(float)的DIV要配合width属性来用

4.怎样控制层的位置:用float,padding,width,height这些属性来控制
Float Tutorial: http://css.maxdesign.com.au/floatutorial/index.htm

5.设置可见的border或background-color来帮助排版

6.出现内层的DIV跑到外层之外的情况时,可以在外层DIV的</DIV>前加上一个clear层来解决问题。就是这样:
   <div class="container">
    ...
    ...
    <div class="clear"></div>
  </div>

  css:
  .clear{
    clear:both;
  }

7.样式表的选择符(Selector):
   Type/Class/ID等
  Selector Tutorial:http://css.maxdesign.com.au/selectutorial/index.htm

8.关于表单的样式设计:
让我们更好的设计表单:浅议Web的表单设计 http://tech.163.com/06/0529/04/2I8UPHTB0009158Q.html
用CSS制作具有亲和力的表单 作者:greengnn
http://www.jluvip.com/blog/article.asp?id=192

9.各种浏览器对各种标签用不同的方式渲染,如p, h1, ul等,它们会有不同的padding和margin,为了在各种浏览器下都正常的显示,可以把它们清零,如:
*{
    padding:0;
    margin:0;
}

10.使用list来显示数据或菜单
List Tutorial:
http://css.maxdesign.com.au/listamatic/
http://css.maxdesign.com.au/listamatic2/
http://css.maxdesign.com.au/listutorial/

11.关于各种标准,布局,技巧等的网站:
网页设计师:http://www.w3cn.org/
CSS教程:http://www.netvtm.com/w3s/css/
网页制作指导 - 致力于网页制作知识的普及:http://www.jianzhan8.cn/
当然还有必不可少的经典论坛blueidea:http://www.blueidea.com/bbs/list.asp?GroupName=%CD%F8%D2%B3%B1%EA%D7%BC%BB%AF%D7%A8%C0%B8

posted @ 2006-06-21 06:22 shaofan 阅读(928) | 评论 (0)编辑 收藏

如果你不幸装了NetBeans并且启用了外置的服务器,那么它会自动在你的web server的web.xml里添加一个filter,名叫HttpMonitorFilter。你用NetBeans时,它会过滤所有的http请求,这样NetBeans就可以显示所有的相关信息。但是如果不用NetBeans,比如我,用eclipse + Tomcat ,那就会得到这个异常:

NotifyUtil::java.net.ConnectException: Connection refused: connect
  at java.net.PlainSocketImpl.socketConnect(Native Method)
  at java.net.PlainSocketImpl.doConnect(Unknown Source)
  at java.net.PlainSocketImpl.connectToAddress(Unknown Source)
  at java.net.PlainSocketImpl.connect(Unknown Source)
  at java.net.Socket.connect(Unknown Source)
  at java.net.Socket.connect(Unknown Source)
  at sun.net.NetworkClient.doConnect(Unknown Source)
  at sun.net.www.http.HttpClient.openServer(Unknown Source)
  at sun.net.www.http.HttpClient.openServer(Unknown Source)
  at sun.net.www.http.HttpClient.(Unknown Source)
  at sun.net.www.http.HttpClient.(Unknown Source)
  at sun.net.www.http.HttpClient.New(Unknown Source)
  at sun.net.www.http.HttpClient.New(Unknown Source)
  at sun.net.www.http.HttpClient.New(Unknown Source)
  at sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source)
  at sun.net.www.protocol.http.HttpURLConnection.connect(Unknown Source)
  at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(Unknown Source)
  at org.netbeans.modules.web.monitor.server.NotifyUtil$RecordSender.run(NotifyUtil.java:237)



解决方法是

第一步. 删除 Tomcat目录中common\lib下的两个和netbeans有关的jar文件(以org-netbeans开头的)

第二步. 修改tomcat目录下的conf\web.xml,删除相关Filter的定义,就是这一段:

    <filter>
        
<filter-name>HTTPMonitorFilter</filter-name>
        
<filter-class>
            org.netbeans.modules.web.monitor.server.MonitorFilter
        
</filter-class>
        
<init-param>
            
<param-name>netbeans.monitor.ide</param-name>
            
<param-value>127.0.0.1:8082</param-value>
        
</init-param>
    
</filter>
    
<filter-mapping>
        
<filter-name>HTTPMonitorFilter</filter-name>
        
<url-pattern>/*</url-pattern>
        
<dispatcher>REQUEST</dispatcher>
        
<dispatcher>FORWARD</dispatcher>
        
<dispatcher>INCLUDE</dispatcher>
        
<dispatcher>ERROR</dispatcher>
    
</filter-mapping>

posted @ 2006-06-13 20:37 shaofan 阅读(1038) | 评论 (0)编辑 收藏

据英国的职位搜索引擎Workcircle的调查显示,93%的IT经理人和几乎所有的CEO和CTO会在英格兰队比赛的那天请病假。相比之下,程序员的请假的比例要低得多,JAVA程序员为86%,而最可怜的是SQL程序员,只有14%。更搞笑的是WikiHow上还有一个“怎样请病假”的教程。

原文如下:

Managers most likely to take a sickie to watch England play

9 June 2006, Cambridge UK – UK job site, Workcircle ran a survey this week asking how likely jobseekers are to take a sick day to watch an England World Cup match.

World cup surveyThe results show the deep divide between workers and management, just 14% of Engineers surveyed will take a sickie to watch a match, but a shocking 93% of managers will.

And it gets worse the higher you get – all CEOs and CTOs surveyed will take a day off.

As far as the developers go, Java programmers are the most patriotic – 86% would call in sick to watch a match, compared to 14% of hard-working SQL developers.

So when the boss is in an unexplained long meeting during the World Cup, check if there’s an England match on!

posted @ 2006-06-12 06:45 shaofan 阅读(681) | 评论 (0)编辑 收藏