treenode

在路上。

BlogJava 首页 新随笔 联系 聚合 管理
  5 Posts :: 1 Stories :: 53 Comments :: 0 Trackbacks

2006年6月3日 #


软件业总是充满了形形色色的隐喻。比如说,把程序中的问题称为bug;把互联网上传播的病毒叫做蠕虫;
把软件开发的过程比作造房子......这些都是我们这个行业中流行的隐喻,以至于它们已经成为软件开
发者文化中一个特有的组成部分。

在这里,我想要说的是一个特别具有“中国特色”的隐喻:我称之为武侠隐喻。

毋庸我多做解释,可能很多程序员看到这个词就足以勾起丰富的想象。我们中的很多人喜欢把自己所崇敬
和佩服的、软件界有影响力的人称之为大侠,并幻想自己有朝一日能够达到他们的境界。(跑题一下,
这个称谓现在似乎有了一个比较草根的、或者说比较Web2.0的版本——叫做牛人)上个世纪那个个人
英雄主义的年代,曾经涌现出一大批这样的人物,现在很多人仍然习惯称他们为“大侠”——这其
中包括求伯君、王志东、鲍岳桥、朱崇君...

除此之外,我们还喜欢将重量级的出版物称之为武林秘籍;把软件开发的组织团体比作江湖帮派;
要形容软件开发的理想境界,也常会搬出“飞花摘叶俱可伤人”或“无剑胜有剑”这样的句子。所有这些
都或多或少的表明:武侠深深影响了大量的程序员,他们非常喜欢用武侠中的理念来比喻软件开发过程中
的现象。或许,也是因为程序员0和1的生活太过枯燥,需要文化来加一点味道,而他们自觉不自觉的
选择了武侠。这就是所谓的中国特色吧。

我必须老实的承认:我自己就曾经深受武侠的影响,过去也一直没有感到有什么不妥。但是,
在前几天看过网上的某些回帖中一些充满武侠隐喻味道的文字,突然觉得有些不是滋味。
我开始思考:对于软件开发来说,武侠是不是一个好的隐喻?结论:不是不好,而是非常的
糟。武侠和软件开发根本没有什么共同点,甚至可以说是水火不容的。

为什么说武侠和软件开发没有共同点?武侠讲的是破坏的艺术。太史公说“侠以武犯禁”。
武侠的意义,在最好的情况下,也仅仅是杀富济贫、除暴安良,是对旧有秩序的破坏。
问题在于:破坏是痛快惬意的,但破而不立就是纯粹的破坏,没有任何积极意义。
破坏以后新的秩序如何建立呢?没有哪一个武侠故事为此做出答案,
也没有一个侠客操心这种事。他们在乎的是“十步杀一人,千里不留形”的高手形象,
至于走了以后烂摊子谁来收拾?那本大侠可就管不着了。

软件开发是建设,而不是
破坏。即使旧的系统非常糟糕,我们也没有理由将其付之一炬——这就是为什么现代的敏捷
开发者非常强调重构的原因。构造新的代码固然是极具创造快感的工作,但是软件开发过
程中还有成打的“肮脏”工作:需求分析,设计,文档,调试,维护......这些工作繁冗
而琐碎,但却是整个开发过程中必不可少的组成部分。想潇洒一下就拍拍屁股走人的
程序员没有什么职业素质可言。

武侠中的高手是什么形象?天马行空,独往独来,神出鬼没。这样的人看起来很有性格,
但在现代企业中恰恰是最忌讳的。而正正经经提倡Team Work的团队反倒在武侠中常常成为
讥刺的对象——你不妨看看少林或全真这样的大型团队在金庸小说中被丑化成了什么地步。

武侠所描绘的是农业社会的典型情况。一位高手通常只会把自己的技艺传授给至亲和少数几个
信得过的弟子;弟子亦然。这种结构非常脆弱:一旦出现任何问题,这门技艺很容易就失传了。

武侠中的秘籍是这样一种东西:你得到它以后,最好藏之名山,偷偷修炼。一旦泄漏,只会给你
带来杀身之祸。和师徒授受的问题相同,这样只会让最好的技艺在历史长河中渐渐湮灭。现代社会和开放
源代码运动则显示了相反的情况:知识可以由任何人获取与学习,而不分门派贵贱。与别人分享
知识也不会给你带来任何坏处。

武侠成为隐喻带来的恶果就是,程序员以成为“高手”为荣,以炫耀技巧为乐;无视风险
大量采用一些看上去比较炫的新技术;愤世嫉俗以为天下只有自己怀才未遇;不会与人
沟通,罔顾客户需求,把不懂技术的用户当白痴;不会开诚布公,总是自己偷偷留一手;
凡此种种,不能说都是因为武侠流毒。但是在程序员中造成了不好的风气,武侠的影响
是不可忽视的。

武侠是成年人的童话,但软件开发不是童话。软件开发要的是脚踏实地,而不是快意恩仇。
还在做侠客梦的程序员,愿你们早点醒来。

 

posted @ 2006-12-01 15:33 TreeNode 阅读(1185) | 评论 (8)编辑 收藏

可能是为了保持平台独立性,SWT没有开放许多控件的自定义接口。例如,Win32中的Button、Label、List和ComboBox都是可以自绘(Owner Draw)的,但是SWT并没有把这些绘制方法开放出来。在最新的3.2版本中添加的一个新特性是Table和Tree现在支持Custom Draw了(但是并未整合到Viewer体系中),不过对于上述控件的支持仍付阙如。

上一次,我实现了一个自绘的按钮。现在,看到有人询问是否可以在Combo的列表中加入图像。其实这相当容易,只要重载Combo Widget并把自绘接口暴露出来即可。以下是简单的代码示例:

package org.eclipse.swt.widgets;

import java.io.*;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.win32.*;

public class CustomCombo extends Combo
{
    
public CustomCombo( Composite parent, int style )
    {
        
super( parent, style );

        
try
        {
            InputStream is 
= getClass().getResourceAsStream( "bullet.gif" );
            image 
= new Image( getDisplay(), is );
            is.close();
        }
        
catch ( IOException e )
        {
            e.printStackTrace();
        }
        
final int CB_SETITEMHEIGHT = 0x0153;

        OS.SendMessage( handle, CB_SETITEMHEIGHT, 
024 );
        OS.SendMessage( handle, CB_SETITEMHEIGHT, 
-124 );
    }

    @Override
    
int widgetStyle()
    {
        
final int CBS_OWNERDRAWFIXED = 0x0010;
        
final int CBS_HASSTRINGS = 0x0200;
        
// final int CBS_OWNERDRAWVARIABLE = 0x0020;
        return super.widgetStyle() | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS;
    }

    @Override
    
protected void checkSubclass()
    {
    }

    @Override
    
public void dispose()
    {
        image.dispose();
        
super.dispose();
    }

    
/* @Override
    LRESULT wmMeasureChild( int wParam, int lParam )
    {
        MEASUREITEMSTRUCT mis = new MEASUREITEMSTRUCT();
        OS.MoveMemory( mis, lParam, MEASUREITEMSTRUCT.sizeof );
        mis.itemHeight = 40;
        OS.MoveMemory( lParam, mis, MEASUREITEMSTRUCT.sizeof );
        return null; // super.wmMeasureChild( wParam, lParam );
    } 
*/

    @Override
    LRESULT wmDrawChild( 
int wParam, int lParam )
    {
        DRAWITEMSTRUCT dis 
= new DRAWITEMSTRUCT();
        OS.MoveMemory( dis, lParam, DRAWITEMSTRUCT.sizeof );

        GC gc 
= new GC( new DCWrapper( dis.hDC ) );
        Rectangle rc 
= new Rectangle( dis.left, dis.top, dis.right - dis.left,
                dis.bottom 
- dis.top );
        Display display 
= getDisplay();
        
if ( (dis.itemState & OS.ODS_SELECTED) != 0 )
        {
            gc
                    .setBackground( display
                            .getSystemColor( SWT.COLOR_LIST_SELECTION ) );
            gc.setForeground( display
                    .getSystemColor( SWT.COLOR_LIST_SELECTION_TEXT ) );
            gc.fillRectangle( rc );
        }
        
else
        {
            gc.setBackground( display
                    .getSystemColor( SWT.COLOR_LIST_BACKGROUND ) );
            gc.setForeground( display
                    .getSystemColor( SWT.COLOR_LIST_FOREGROUND ) );
            gc.fillRectangle( rc );
        }
        String text 
= getItem( dis.itemID );
        gc.drawImage( image, dis.left 
+ 1, dis.top + 1 );
        gc.drawText( text, dis.left 
+ 20, dis.top );

        gc.dispose();

        
return null;
    }

    
private static class DCWrapper implements Drawable
    {
        
private int    hdc;

        DCWrapper( 
int hdc )
        {
            
this.hdc = hdc;
        }

        
public int internal_new_GC( GCData data )
        {
            
return hdc;
        }

        
public void internal_dispose_GC( int handle, GCData data )
        {
        }
    }

    
private Image    image;
}


值得说明的是,如果设置Combo为OwnerDraw Variable风格,则必须重载wmMeasureChild方法来指定每一项的高度。如果使用OwnerDraw Fixed风格,则只需要在构造的时候发送一条CB_SETITEMHEIGHT消息就行了。

 另外一种值得考虑的选择是将Win32的ComboBoxEx控件包装成SWT Widget。不过,这需要转换若干结构并提供接口,Win32的ImageList管理机制和SWT的Image包装方法差别比较大,使得这种方法实现起来麻烦的多。

posted @ 2006-07-06 10:08 TreeNode 阅读(2336) | 评论 (4)编辑 收藏

在上回的blog中,我抱怨过用Java用内部类来实现事件回调的机制是多么难看和麻烦。在这段时间里,我一直在考虑是否有什么方法可以不用内部类而实现同样的效果。因为Java语言本身的限制,所以常规方法是行不通的。有人建议用反射——的确通过反射可以调用任意的方法,但是反射效率不佳,对频繁发生的事件或许不太合适。动态代理也不能解决方法映射的难题。我似乎走进了死胡同。

既然此路不通,那么C#是如何实现delegate的呢?过去也曾听闻过一些内幕,不过这次被逼才真的下决心认真去看这方面的东西。原来M$使用的是代码生成的技术:对于每个delegate,C#都会为它生成一个派生于MulticaseDelegate的对象,其中实现了一个和delegate签名相同的方法。同时,对delegate的操作符+=和-=也会被编译器处理成对MulticaseDelegate方法的调用。

知道了这一点,接下来就需要看看Java中有没有类似的代码生成技术了。有意思的是,查找的时候发现有消息说,Java 6.0(Mutang)中将会提供动态代码生成的功能。这的确很吸引人,不过Java6还在Beta阶段,眼下还指望不上。其他比较出名的方法就是Apache becl和Objectweb ASM了。这两个库都比较底层,不过还有一个开源的项目——cglib——它在内部使用了asm,不过提供了较多的实用功能。据说Hibernate和Spring都用到了这个东西。研究这个库的时候,我一眼看到了MethodDelegate类——很明显这就是我要找的东西了。

MethodDelegate的设计思想很类似于C#的delegate——将接口调用转发给类的一个成员函数。不过阅读文档的时候我发现一个问题。MethodDelegate要求其所实现的接口必须只有一个公共方法,但是SWT中的许多事件接口都有不止一个方法;比如,SelectionListener就有widgetSelected和idgetDefaultSelected两个方法。因此要在SWT中使用MethodDelegate,还

必须再多实现另外一层转发。

了解手段,接下来的事情就不难了。总结起来,需要的步骤大致如下:
1、为每种需要实现的事件声明一个接口。这是MethodDelegate的要求。
2、用一个类实现SWT的事件接口,并将特定的接口调用转发到第一步所实现

的接口。
3、用MethodDelegate提供的方法,声明事件处理对象(Event Handler

Target,通常为主窗体或主部件)要实现上述的事件接口。下面就来实现一下。为了简单起见,将需要实现的接口声明为事件转发类的内部接口,以避免维护太多接口文件(因为该接口只需要声明一个方法,所以不会把外部类搞得太过复杂。)例如,处理部件选择事件(widgetSelected)的类可以如下实现:

package org.yuhao.swt.events;

import net.sf.cglib.reflect.MethodDelegate;

import org.eclipse.swt.events.*;

public class WidgetSelectedHandler implements SelectionListener
{
 public WidgetSelectedHandler( Object target, String

methodName )
 {
  delegate = (IWidgetSelectedDelegate)

MethodDelegate.create( target,
    methodName,

IWidgetSelectedDelegate.class );
 }

 public void widgetDefaultSelected( SelectionEvent e )
 {
 }

 public void widgetSelected( SelectionEvent e )
 {
  delegate.invoke( e );
 }

 public void invoke( SelectionEvent e )
 {
 }

 public interface IWidgetSelectedDelegate
 {
  void invoke( SelectionEvent e );
 }

 private IWidgetSelectedDelegate delegate;
}

这个接口虽然只有外部类用到,但是必须声明为public的,否则运行会出错(我想大概是因为代码生成以后还是外部类,需要公开访问权限)。为了简化调用,再声明一个处理事件的辅助类EventHandler,专门管理将各种事件转发到相应的Handler的工作:

public class EventHandler
{
 public EventHandler( Object target )
 {
  this.target = target;
 }

 public void handleSelected( Button btn, String methodName )
 {
  btn.addSelectionListener( new

WidgetSelectedHandler( target, methodName ) );
 }
 private Object target;
}

这样,在窗口中就可以简单的如下处理事件:
public MainShell extends Shell()
{
  public MainShell( Display display )
 {
  ......
  handler = new EventHandler( this );
  handler.handleSelected( btn, "btn_clicked" );
 }

 public void btn_clicked( SelectionEvent e )
 {
  ...
 }
}

这里还需要注意:1、任何事件处理方法必须声明为public的。这样似乎有违面向对象的封装原则,不过实际上并不会造成什么大问题。2、事件方法的签名必须和对应的事件方法相同。例如,widgetSelected方法有一个SelectionEvent参数,那么处理该事件的btn_clicked方法也必须有且只有这一个参数。如果写错了,那么运行的时候会抛出异常,说找不到指定的方法。这还是需要程序员的细心来保证。还算幸运的是出错的提示非常明显,不必担心使用了过度复杂的技术而找不到真正的出错点。

posted @ 2006-06-27 11:29 TreeNode 阅读(1202) | 评论 (0)编辑 收藏

请比较一下这两段功能大致相同的代码。抛开语法的差别不论,你觉得哪种风格好?你愿意维护哪一段代码?

        b.addSelectionListener( new SelectionAdapter()
        {
            
public void widgetSelected( SelectionEvent e )
            {
                Runnable longJob 
= new Runnable()
                {
                    
boolean    done    = false;
                    
int        id;

                    
public void run()
                    {
                        Thread thread 
= new Thread( new Runnable()
                        {
                            
public void run()
                            {
                                id 
= nextId[0]++;
                                display.syncExec( 
new Runnable()
                                {
                                    
public void run()
                                    {
                                        
if ( text.isDisposed() )
                                            
return;
                                        text
                                                .append( 
"\nStart long running task "
                                                        
+ id );
                                    }
                                } );
                                
for ( int i = 0; i < 100000; i++ )
                                {
                                    
if ( display.isDisposed() )
                                        
return;
                                    System.out
                                            .println( 
"do task that takes a long time in a separate thread "
                                                    
+ id );
                                }
                                
if ( display.isDisposed() )
                                    
return;
                                display.syncExec( 
new Runnable()
                                {
                                    
public void run()
                                    {
                                        
if ( text.isDisposed() )
                                            
return;
                                        text
                                                .append( 
"\nCompleted long running task "
                                                        
+ id );
                                    }
                                } );
                                done 
= true;
                                display.wake();
                            }
                        } );
                        thread.start();
                        
while ( !done && !shell.isDisposed() )
                        {
                            
if ( !display.readAndDispatch() )
                                display.sleep();
                        }
                    }
                };
                BusyIndicator.showWhile( display, longJob );
            }
        } );
另外一种:
        delegate void NotifyStartDelegate( int threadId );
        
delegate void NotifyFinishDelegate( int threadId );
        
        btnInvoke.Click 
+= BtnInvokeClick;
        
        
void BtnInvokeClick(object sender, System.EventArgs e)
        {
            text.Text 
= "invoke long running job";
            
            Cursor 
= Cursors.WaitCursor;
            Thread thread 
= new Thread( new ThreadStart(ThreadProc) );
            thread.Start();
        }
        
        
private void ThreadProc()
        {
            
int threadId = nextId ++;
            
bool done = false;
            
            
if ( IsDisposed )
                
return;
            
            Invoke( 
new NotifyStartDelegate(notifyThreadStart), new object[] { threadId } );
            
for ( int i=0; i<100000; i++ )
            {
                
if ( IsDisposed )
                    
return;
                Console.WriteLine( 
"do task that takes a long time in a separate thread " + threadId );
            }
        
            
if ( IsDisposed )
                
return;
            Invoke( 
new NotifyFinishDelegate(notifyThreadFinish), new object[] { threadId } );
            done 
= true;
        }
        
        
private void notifyThreadStart( int threadId )
        {
            text.Text 
+= "\r\nStart long task " + threadId;
            threadCount 
++;
        }
        
        
private void notifyThreadFinish( int threadId )
        {
            text.Text 
+= "\r\nCompleted long running task " + threadId;    
            threadCount 
--;
            
if ( threadCount == 0 )
                Cursor 
= Cursors.Default;
        }
        
        
private int nextId = 0;
        
private int threadCount = 0;

我在另一个地方也抱怨过:在所有我了解的语言特性里面,没有一种像Java内部类一样让我觉得反感——甚至到了恶心的地步。大多作B/S系统的Java程 序员可能不会有这样的感觉,因为那个领域基本上很少会用到这个概念。可是在C/S,不管用Swing还是SWT,内部类都是绕不过去的一座大山。在阅读Eclipse站点上许多代码示例以后,我终于有了痛苦到——一点也不夸张——想要作呕的地步。上面第一段代码就是让我感到窝心的代码之一(仅仅是其中之一,还不是最丑陋的)。我想,Java 语言的发明者大概从来就没写过桌面程序;他根本也不打算为这个领域的程序员提供一种比较好的事件回调机制。

内部类有什么问题呢?首先,你愿意在去看代码逻辑之前,先花上好几分钟去搞清楚“这个括号到底是和哪个配对”这种蠢问题吗?你不妨回头看看第一段的代码,想想这段其实相当简单的程序,是不是真的值得用这么多括号去考验你的智力。

内部类是对封装的严重破坏。它对外部类的任何私有变量都有完全的访问权限——如果你突然发现某个变量的内容不对劲了,你不能仅仅在setXXX里面加个断 点就指望能捕获到错误;真正的元凶可能是内部类里面的哪一句呢。如果内部类都非常简单,那倒也没什么。可是当内部类用来实现事件的时候,你就没法指望它一 直那么简单了。

内部类是测试的盲区。TDD总是说,要测试,测试,所有包含逻辑的类都应当通过充分的测试。可是内部类怎么测试?只要想想就能知道,大多数内部类是根本没法测试的,它和外部类实在是耦合的太紧密了。匿名内部类的问题更严重——它是绝对无法测试的。你怎么测试一个连名字都没有的方法?

不管有意无意,内部类在(至少我看到的)实践中事实上鼓励了不好的编程风格。就是说,它违背了DRY(Don't Repeat Yourself)的原则。比如,button.addSelectionListener后面几乎总是跟着SelectionAdapter+括号+ widgetSelected原型再+一堆括号;Display.asyncExec后面总是要写上new Runnble(),void run(),括号,等等。千篇一律的东西,可是又不得不写。而且,几乎没有什么好的办法可以改进!因为语法的规则要求你必须这样做。每天写这些无聊的东 西,你的话会不会烦?哦,工具是有的。可是工具只负责生成代码,以后的维护还是要你来做——不是么?
posted @ 2006-06-15 22:37 TreeNode 阅读(3550) | 评论 (31)编辑 收藏

     摘要: 本文试图透过SWT面向对象的封装,深入研究界面框架的构造中最复杂也可以说是最有趣的问题之一——即界面框架如何以面向对象的方式实现操作系统底层的消息机制。  阅读全文
posted @ 2006-06-03 12:46 TreeNode 阅读(3461) | 评论 (10)编辑 收藏