2009年8月24日星期一

RCP 编程记录

RCP Perspective中View的布局
Perspective用于把一组任务相关的视图组合在一起提供给用户,其中有个区域叫做EditorArea,这个区域用于放置编辑器。
如果你的perspective无需编辑器,则可以通过pageLayout.setEditorAreaVisible(false);把编辑区域隐藏起来。这样,你的workspace就不会有一块灰色的区域了。

如果想让两个view占据一个位置(如IPageLayout.RIGHT),在Perspective类中的createInitialLayout用如下方法:
IFolderLayout folder = layout.createFolder("messages", IPageLayout.RIGHT,
0.75f, IPageLayout.ID_EDITOR_AREA);
folder.addPlaceholder(View1.ID + ":*");
folder.addView(View1.ID);
folder.addPlaceholder(View2.ID + ":*");
folder.addView(View2.ID);

要使得一开始view不可见,只当用户点击触发showView的时候才显示,可以如下:folder.addPlaceholder(View1.ID + ":*");
不要加上folder.addView(View1.ID)就可以了。
如果要使得Editor消失不可见,可以在Perspective类中的createInitialLayout加上一句,如下:
layout.setEditorAreaVisible(false);
如果要使得某个view不能关闭,可以在Perspective类中的createInitialLayout加上一句,如下:
layout.getViewLayout(View.ID).setCloseable(false);


TableListViewer的可编辑cell
按照一般的套路:设置tableviewer的columnProperties;设置CellEditors;设置CellModitifier;
其中实现ICellModitifier接口比较繁琐点。
最后值得注意的是:需要把ListTableView设置为SWT.FULL_SELECTION。否则,就必须单击两次,第一次表示选中,第二次表示编辑。

2009年8月6日星期四

用spring构造bean的3种方法。

1)直接构造
2)用静态工厂方法
3)非静态工厂方法。

这是一段静态工厂方法的例子,向工厂方法传递参数使用constructor-arg。

类功能的分配

开始写了个TclExecutor类。这个类可以执行tcl语句、tcl 文件。因为tcl interpreter是在这个类中创建的,interpreter是用一个线程在后台驱动的,所以我把TclExecutor当作资源类。是资源的东西,当然是要被管理的,否则就有资源泄漏的危险。所以,我就加入了结束tcl executor生命的方法disposeI(), disposeAll()。
因为要管理这些资源,又让TclExecutor的构造函数接受一个字符串当作executor的名字。以后就可以用名字来定位需要的executor。
为了定位,就需要一个管理类来管理这些executors。可以把这些管理executor的方法通通写到TclExecutor 中,也可以单独写一个manager类。

当代码紧凑不是优点而是缺点时,就已经没有理由不把这些功能分别安排到不同的类中了。

2009年8月1日星期六

setup svnserver for windows

1)从http://subversion.tigris.org/ 下载svnserver.

2)创建一个reaml svnadmin create E:\svn_repository\dahui

3)在新的reaml下的conf目录中,配置密码及其它。
passwd文件:
[users]
admin = mypassword

svnserver.conf文件:
[general]
anon-access = read
auth-access = write
password-db = passwd
realm = goreader

2009年7月21日星期二

clone 和copy/paste

今天需要写一个copy/paste的功能。copy的动作执行以后得到都是data的reference。需要一个clone的动作。而以前的数据对象根本没有定义clone()。
可以总结一条了:当一个数据对象需要被copy/paste时,请准备好clone()方法。

对于糟糕的遗留系统的补丁,我真的觉得是浪费生命。平时觉得稀松平常的原理,在这种系统面前凸显价值,但是,这种价值此时也只能成为教训了。

今天还给诺西的同事show了一下如何用itcl写spirent testcenter的tcl。 如果当初他们写tcl之前好好建模,好好学习itcl,现在对新需求的应付应该是很轻松,很轻松了。

一个项目的成功也许是因为技术的失败而失败。但是,归根结底是因为管理的失败而失败,决策的失败而失败,用人的失败而失败。
技术上的保障只是项目成功保障链条上的最后一环,前面的环节更具决定性。

面向对象的方法是一种思考问题、解决问题的方法,不单纯的是一种语言。

2009年7月16日星期四

技术平台

断断续续地琢磨CLTF也好久了。今天再看Jelly,却发现有些需要的特性是Spring提供的,而在Jelly中不能获得。再一想,Jelly提供的最基本的功能,其实自己也可以比较容易的模拟出来。看来需要考虑使用一下Spring了。

Groovy其实也应该是很好的。毕竟是解释型的,改动一起来应该很容易。

最基本的技术选择,看上去简单,确实最具影响力的。

2009年7月15日星期三

Object System for TCL

下面这篇文章很好地讲述了用TCL语法模拟一个简单的Object system。http://users.telenet.be/koen.vandamme1/papers/tcl_objects/tcl_objects.html
对文章中的“Object orientation is really just a way of thinking; it has more to do with design than with implementation.” 深有体会。

http://www.tcl.tk/about/oo.html
这是一篇关于object tcl情况的简单介绍。到tcl 8.6,TCL将内置一个object system了。

目前存在的用于tcl的object system有:incr tcl, Snit, XoTcl ...

incr TCL的HomePage: http://incrtcl.sourceforge.net/itcl/


2009年6月25日星期四

Spirent testcenter 的基本模型。

chassis就理解成一个基础设备,对应一个IP。
slot是设备上的一个插卡,其中有若干ports。

每个port上有一个generator,用这个Generator可以发送多个StreamBlock.

Something about tcl programming for Spirent TestCenter

前两天为了解决遗留程序的一个bug,动手写了点TCL程序。

TCL作为解释型的语言,并不在你第一次定义变量时给你预设一个值,哪怕是随机的值。所以,刚用起来有点不顺手。
TCL的数组其实很类似hashtable,tcl数组的index可以是任意值。所以,虽然tcl不支持多维数组,但是依旧有些小技巧模拟多维数组。这很重要,因为tcl同时没有structure类型。复杂的需要分层的数据结构只能依赖所模拟出来的多维数组了。

如下的一段代码,就是用来模拟多维数组的:

set trafficConfig($trafficHandle,Transmit_mode) $transmit_mode;
set trafficConfig($trafficHandle,LoadUnit) $loadUnit;
set trafficConfig($trafficHandle,FixedLoad) $loads;

需要注意的是,必须跟后面的字符串紧挨着。

有了“多维”数组,再利用tcl的namespace eval 语句,就可以完成基本的数据结构的定义了。再写点函数,就可以模拟一个类了。

package provide DataCenter 0.1

set trafficConfig(NULL) NULL;

namespace eval StcTools {

namespace eval Traffic {

proc construct { trafficHandle transmit_mode loadUnit loads} {
global trafficConfig;

puts "Traffic construct() for $trafficHandle "

set trafficConfig($trafficHandle,Name) $trafficHandle;
set trafficConfig($trafficHandle,Transmit_mode) $transmit_mode;
set trafficConfig($trafficHandle,LoadUnit) $loadUnit;
set trafficConfig($trafficHandle,FixedLoad) $loads;
}

proc setting { trafficHandle name value } {
global trafficConfig;
set trafficConfig($trafficHandle,$name) $value;
}

proc printTrafficSettingInfo { } {
global trafficConfig;
puts "trafficSettingInfo:"
foreach {key value} [array get trafficConfig] {
puts " trafficConfig($key)= $value";
}
}


StcTools ::Traffic就可以理解为一个类。
construct()就是构造方法。
setting(handle, name, value)就可以理解为setter方法, 业务逻辑的方法就可以自己随便起名、定义了。

用这种思路写出程序比遗留代码强多了---清晰。清晰了,当然就易维护。这种思路是从TCPMP项目中学来的。TCPMP利用C语言开发,但是它利用c的结构体,以及其它技术,也构造了自己的简单的面向对象架构。我都怀疑面向对象的初始想法可能就是基于这种简单的程序架构发展而来的了。

程序开发中数据与逻辑的分离,数据层次的划分、组织,是很基本的事情。
数据的组织,又是直接和业务模型的抽象、建立相关的。

2009年5月26日星期二

TestCenter TCL env

Spirent's tcl env

pkgIndex.tcl 是包含在TestCenter的安装目录的:C:\Program Files\Spirent Communications\Spirent TestCenter 2.30\Spirent TestCenter Application

在pkgIndex.tcl文件中包含CTS_INSTALL_DIR,而这个变量并没有定义。
所以,如果想写tcl脚本使用TestCenter提供的API,就要设置环境变量CTS_INSTALL_DIR,并把CTS_INSTALL_DIR改为env(CTS_INSTALL_DIR).
比如:package ifneeded SpirentTestCenter 2.30 [list source [file join env(CTS_INSTALL_DIR)  SpirentTestCenter.tcl]]
另一个,更好点的办法是在pkgIndex.tcl开始部分添加下面一行,:
set STC_INSTALL_DIR [pwd]


然后把TestCenter的安装目录添加到autopath:
lappend auto_path $env(CTS_INSTALL_DIR)
puts $auto_path


这样,按照Spirent_TestCenter_Automation_Conf_Prog_Guide.pdf的描述,就可以运行下面的代码了:
lappend auto_path $env(CTS_INSTALL_DIR)
puts $auto_path

package require SpirentTestCenterConformance

set chassisAddress 172.18.101.31
set slot1 10
set slot2 9
set port1 11
set port2 12
puts "==== begin to create project"

set project [stc::create project]
puts "[ stc::get $project -children]\n"
set physicalChassisManager [stc::get system1 -children-physicalChassisManager]
puts "Connect to chassis...\n"
stc::connect $chassisAddress
puts "==== connect ok."

2009年5月23日星期六

reference about tcl debug


总的一个reference.   http://wiki.tcl.tk/8637

activestate关于开发工具的资源:  http://www.activestate.com/tcl_dev_kit/

一个免费的tcl debugger tool: http://gid.cimne.upc.es/RamDebugger/Ramdebugger_toc.html

与eclpse的集成开发,不仅仅是TCL了,还有python, ruby,...: http://download.eclipse.org/technology/dltk/downloads/drops/R1.0/S-1.0RC1b-200905210941/

2009年5月14日星期四

Eclipse's CheckboxCellEditor

项目中需要把表格中的某列用checkbox表示出来,按照《eclipse从入门到精通》中的步骤没有弄出来。
这是eclipse celleditor的一些samples: http://wiki.eclipse.org/index.php/JFaceSnippets#Snippet009CellEditors

TableViewerColumn TableColumn的区别?
TableViewerColumn .getColumn() 返回的是TableColumn对象。什么时候该用哪个呢?

尝试,ComboBoxCellEditor, 发现EditingSupport子类的getValue()需要返回Integer类型。CheckboxCellEditor则需要返回Boolean类型。
跟踪到CheckboxCellEditor的
protected Control createControl(Composite parent) {
return null;
},发现它根本没有实现。所以,CheckboxCellEditor是根本不能使用的。需要你继承它来完成这个方法。真是奇怪eclipse为什么不提供个实现呢?

最后发现了一个类似于CheckboxCellEditor的实现BooleanCellEditor: http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.jface.snippets/Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/BooleanCellEditor.java?view=markup 但是,BooleanCellEditor需要运行在eclipse3.4。

2009年5月12日星期二

和Martin交流

Martin告诉我:
解除依赖环在大型项目开发中尤其重要。否则, 你做好了,我改了一点东西后,就又坏了.
你改好之后, 我又不能编译了,故在大项目中不能存在依赖环,只能存在树状依赖. 中间没有环.


dahui said:
我以前一直觉得业务不是很重要,现在我觉得业务是基础,是你能够建立正确model的基础。

福祥 says:
有两个领域,一个是软件,一个domain.

dahui says:
我还在看domain drived development. 很多观点我都很认同。
其中也提到了敏捷开发的缺陷。 我也深有感触。
你想过没有为什么开始流行敏捷?

福祥 says:
CMM太繁琐,
并且中间件出现,
可以总结出一些模式.

dahui says:
我觉得是因为程序员的水平普遍提高了,程序员都有了一定的(甚至不错的)设计能力。有些分工就可以不那么明确了。 以前是系统分析员、设计员做的工作,分配到了程序员手里。
其实,这不一定适合所有项目、不适合所有项目组。

福祥 says:
?

dahui says:
一定要根据具体情况来开发。
说到底,还是跟人有关。 如果一个项目组没有“高人”,或是没把“高人”放在合适的位置上,不管你项目用CMMI还是agile,都不会成功的。我现在还是看重人的作用。

利用inferface切断依赖

背景:
存在模块A、模块B,其中A是common的,发现有些B中的class BClass应该移到A中。
当把BClass 移到模块A中后发现,BClass的有些方法依赖于模块B中的一个类BbClass。而BbClass又跟模块B中许多其它类强烈耦合。

问题:
总不能把所有依赖的东西都move到模块A中呀!

解决:
1)根据BbClass,抽象出一个接口(或抽象类)--比如叫做IBClass,这个接口要定义在模块A中。
2)让BbClass实现IBClass。
3)修改模块A中对BbClass的调用接口为对IBClass的调用。

ORM Relationships

DataNucleus的文档不错,写得很精要,可以读一下:Relationships

在ORM的表述中经常用到 Entity这个词,entity对应于OO的class,对应于DB的Table。

relationship: 1-1, 1-N, N-1, N-M,N-M是bidirectional的,其它的都分为unidirectinal 或者bidirectional。

各种关系中的life dependence是不同的,用GAE的JPA文档中的话来说就是owned和unowned关系。用UML的术语就是: aggregation和composition关系。聚集和合成是很强的耦合关系,在GAE中就是owned关系。在hibernate 中,这种关系就可以用来表达。如果用来表达,那么CascadeType就要进行合适的设置来表达owned或是unowned。CascadeType参数是跟对象生命期有关的。

one-to-one:
1)两个Entity共享主键。
2) Foreign Key。

Assigning Relationships:
With a bidirectional relation you must set both sides of the relation 。

Persisting Relationships - Reachability:
persistence-by-reachability。

Managed Relationships:

Casecading:
casecading要解决的问题是,你对一个object进行持久化操作时,它的域对象是否也执行相应的持久化操作。DataNucleus的casecading例子中描述的情景不错。一个“驾驶员”有一个“驾驶执照”,同时还拥有多辆“汽车”。当驾驶员不存在了,执照也跟随被删除,但是汽车不该被删除。如果汽车被删除,那么驾驶员应该还存在。这是符合现实常理的。用casecading的annotation解决此问题:
@OneToOne(cascade=CascadeType.ALL)
@OneToMany(mappedBy="owner", cascade={CascadeType.PERSIST, CascadeType.MERGE})
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})

想建立coder到project的many-to-many映射,但是GAE server却总是报错,才想起GAE对JPA中的
“多对多”是不支持的。需要在各个类中添加Key的集合,来模拟多对多。

2009年5月11日星期一

关于DRM的一点东西。

一些开源的。
Media-S http://www.sidespace.com/products/medias/
OpenIPMP
http://www.linuxdevices.com/news/NS7299359757.html

虎告诉我,wdrm收费。有被破解的wmdrm8,9可用。

国内的一些解决方案:
http://www.china-drm.com/ 价位在:7800元。 不过虎.无名说,这个是基于老的wdrm系统,存在缺陷,可以被破解、复制。
http://www.onlinecn.net/product/free.htm


wmdrm8,9,10的破解: http://forum.doom9.org/showthread.php?t=114916

2009年5月8日星期五

熟悉业务

这个项目搞可一个季度了,抛开纯技术的实现上的问题不说,越来越发觉自己距离真实需求很远。
感觉这个项目虽然不缺乏与用户的沟通,但是缺乏有效的沟通。软件设计之时并没有对dslam测试领域深入了解。所以,感觉这个项目并没有深入主题,或者说不能有效解决问题。

想申请去做测试了,以便熟悉这个domain。

对程序员来说,熟悉业务就是建立“正确”的domain。在正确上打引号是因为,domain没有觉得的正确,只有相对的“适合”。毕竟domain是抽象、提炼出来的东西。

2009年4月30日星期四

当卸载symantec客户端需要密码时

忘记symantec病毒防护中心密码的情况下卸载symantec客户端的方法:
1.) Norton Symantec AntiVirus 8.0卸载的通用密码:symantec
2.) Norton Symantec AntiVirus 9.0无通用密码,不过可以采取以下方式来去除卸载密码,修改注册表
HKEY_LOCAL_MACHINE\SOFTWARE\INTEL\LANDesk\VirusProtect6\CurrentVersion\AdministratorOnly\Security
修改UseVPUninstallPassword键值为0,即可无需密码卸载
3.) Norton Symantec AntiVirus 10.0可同样使用方法2。

2009年4月27日星期一

面向接口编程

在类A中发现如下代码:

public TestStepExecuteResult deviceCommand(final String command,
final Vector inputParams, final Vector outputParams) {
... ...
AbcCommandExecutor executor = AbcExecutorSessions.getInstance()
.getExecutor(AbcExecutorType.CASE_EXECUTOR);
if (command.equals("TspStatusCheck")) {
testStep = new TspSysStatusCheck();
return (testStep.testStepRun(inputParams, outputParams, executor));
} else if (command.equals("TspCreateVlan")) {
testStep = new TspVlanCreateVLAN();
return (testStep.testStepRun(inputParams, outputParams, executor));
} else if (command.equals("TspCreateDhcpProvider")) {
testStep = new TspDhcpCreateDhcpProvider();
return (testStep.testStepRun(inputParams, outputParams, executor));
} else if .....
.....// 省略n多行。
} else {... ...
}
....
}

显然,可以重构,很简单。
  1. 找到testStep对应的父类或接口,假如是ITestStep。
  2. 修改deviceCommand()签名,
    public TestStepExecuteResult deviceCommand(ITestStep testStep,
    final Vector inputParams, final Vector outputParams) 。
  3. 把诸如 new TspSysStatusCheck() 的代码,移到方法调用前的某个合适位置(也许需要利用反射)。
  4. 修改臭味部分,把n多行的if-else if -else if替换为一行:
    return testStep.testStepRun(inputParams, outputParams, executor);

实际上,inputParams, outputParams也是可以/应该放到ITestStep中的。因为,本来这些数据的属主也是ITestStep。

2009年4月23日星期四

用tclBlend 执行tcl脚本

void eval(String str)
void evalFile(String fileName)

eval("source C:/tclScript/test.tcl");
evalFile("C:/tclScript/test.tcl");
上面这两种调用的效果是一样的。值得注意的是目录分割符是"/",而不是"\"。TclBlend会把"\"当作转义符。

2009年4月21日星期二

安装Perl模块

类似于Net-Server-0.97.tar.gz这样的模块,只要解压后按照:
perl Makefile.PL
[configurate]
make
make test
make install
这样的步骤就可以把模块安装到perl的运行环境中了。

另:
POE: Perl Object Environment
POE is a framework for creating event-driven cooperative multitasking programs in Perl.

2009年4月16日星期四

在Blogger中让代码着色

Google code prettify用于web页面code着色显示。
http://google-code-prettify.googlecode.com/svn/trunk/README.html

按照这篇文章(http://sls.weco.net/blog/rjrsjajawhu/26-dec-2008/11929)的讲述,
三步就可以让blogger中发布的code获得语法着色功能。
package com.dh.tcl;

public class TclTest {

private Thread t;
private Thread t2;

private EventProcessingThread ep;
private EventProcessingThread ep2;

public TclTest() {
startEventProcessThread();
}


private void startEventProcessThread() {
ep = new EventProcessingThread();
t = new Thread(ep);
t.setDaemon(true);
t.start();

ep2 = new EventProcessingThread();
t2 = new Thread(ep2);
t2.setDaemon(true);
t2.start();

// Wait for other Thread to get ready
while (ep.interp == null || ep.interp2 == null
|| ep2.interp == null || ep2.interp2 == null) {
try {Thread.sleep(10);} catch (InterruptedException e) {}
}

}

public void printInfo() {
System.out.println(ep.interp.getNotifier());
System.out.println(ep.interp2.getNotifier());

System.out.println(ep2.interp.getNotifier());
System.out.println(ep2.interp2.getNotifier());
}


public static void main(String[] args) {
TclTest test = new TclTest();
test.printInfo();
}

}
程序输出:
tcl.lang.Notifier@dd5b
tcl.lang.Notifier@dd5b
tcl.lang.Notifier@c4bcdc
tcl.lang.Notifier@c4bcdc
可见,在不同线程中创建的Interp对象所对应的Notifier对象是不同的。
但是同一线程中Interp对象的notifier对象是相同的。

2009年4月15日星期三

Tcl Blend中的线程和event处理

Jacle和Tcl Blend中的tcl.lang.Notifier实现了事件循环处理功能。 一个线程可以包含N个Tcl解释器,在同一个线程中创建的解释器Interp共享同一个Notifer。也就是Notifier是和绑定创建Interp时的线程对应在一起的。最简单的情况,一个线程包含一个Tcl解释器和一个Notifier对象。
看看Interp的构造函数的定义就清楚了:

cThread = Thread.currentThread();
notifier = Notifier.getNotifierForThread(cThread);
notifier.preserve();

Notifier.getNotifierForThread()定义是:

public static synchronized Notifier getNotifierForThread( Thread thread)
{
Notifier notifier = (Notifier) notifierTable.get(thread);
if (notifier == null) {
notifier = new Notifier(thread);
notifierTable.put(thread, notifier);
}

return notifier;
}



通常,事件是在一个循环中通过不断的调用doOneEvent()方法得到处理。
而TclBlend的Event包含了你要完成的逻辑。如果事件队列中没有event,则doOneEvent()会被阻塞,直到获得新的事件。所以,一般创建一个独立的线程来运行Notifier.doOneEvent()。而tcl event都是在其它的线程中创建并通过interp.getNotifier().queueEvent(event, TCL.QUEUE_TAIL) 放到event query中。

调用event.sync(); 会阻塞,直到这个event被处理完毕。

从不是运行doOneEvent()的线程中调用interp.eval()是不合法的,通常导致crash和随机的错误。

If you want to use the tclblend in your java project, you need not only the tclBlend libraries but also the jacl libraries. You should copy the tclBlend and jacl jar files into the same directory otherwise tclBlend it can not work rightly.

2009年4月14日星期二

对单例的另一点体会。

单例的核心是保证类的实例只有一个。

你可以在编程中为你的应用很小心地只为某个类创建一个实例,并小心翼翼的保证只有一个实例。
但这太累了,需要把这用保证集中起来,最好的地方当然就是这个类自己了。

单例把这种保证是放在了该类自己的代码中。所以,单例虽然很简单,却很漂亮。

eclipse plugin的误用

现在的项目,根据设备的不同而构建了不同的plugin。而每次程序发布时都需要重新编译所有代码,一起同步发布。
个人感觉eclipse plugin应该用于更宽松的耦合。就是,需要可以在不知道你的app的情况下,直接把plugin拷贝到规定目录,就能run起来,提供服务。
而现在的项目,plugin甚至是依赖于base app某些包的。所以个人感觉完全没必要把device实现为plugin。

构建应用:业务知识、编程知识、需求分析能力、设计能力

工作的时候应该不断积累“业务知识、编程知识、需求分析能力、设计能力”。

业务知识,编程知识都是最低级的。它不需要你太多的拓展,你只要理解、记住这些知识本身就足够了。比如,dslam的命令行使用(及其含义)、协议规范(如SIP的呼叫流程)、java多线程编程的注意事项及惯用法、socket编程... ... 这些知识有的很简单,有的相当复杂,但总的来说,是比较易懂易学的。
难的是,根据业务知识结合编程知识,进行需求分析,进行系统设计。分析和设计需要更高一点的抽象处理,是需要全盘考虑的,甚至是要进行平衡取舍处理。

哪种知识都很重要,哪种能力都很关键。在工作中多思考、多实践,应该就会不断进步吧。

资源的生命期

在C++ 语言中,特别注意对资源的管理。写构造函数、析构函数时都要仔细考虑类中各种资源的初始化和释放的问题。
什么是资源?任何一个对象的实例也可以理解为资源,因为它至少占用了内存。我想说的资源是诸如:数据库连接、socket连接 这样的对象、线程对象、文件句柄、IO端口... ...这种资源不仅占据本程序的资源(内存、CPU运算)而且还占用外部系统的能力,而这些外部系统的能力了往往并不是无限的。是资源的东西,同时也意味着被动的东西。资源天生就是被用来管理、使用的。

在Java的项目中,因为java的垃圾回收机制,在类的级别我们确实不用操心太多。但是,就程序的结构上,我们必须要确切的知道我的程序中到底有哪些资源。一般情况下我们必然会有对某种资源的管理类。这个类负责资源的生命期。有些资源根据某些策略也许需要lazy-initialization,有些需要提前的hungry-initialization。但是如论如何,在程序的任何运行阶段我们要对这种资源的使用心里有数。我们可以构建ResourceManager来管理程序中的所有资源。
这样,当我们按下“暂停”“停止”“restart”这样的按钮时就可以很容易的通过resource manager来对资源进行管理。
当然,如果你的程序不是关键应用,可以不断重启,那么不必太关心resource。但是,这样的程序会给以后的维护和修改设置障碍。

这个看似简单的论述,如果不能再设计阶段进行考虑的话,也会带来混乱。相反,程序就很好维护、易懂。

2009年4月2日星期四

telnet中的颜色控制字符

十六进制
1b 5b 30 30 6d 1b 5b 30 30 6d ###### 1b 5b 30 30 6d 20
. [ 0 0 m . [ 0 0 m ###### . [ 0 0 m
其中###表示字符串。

.[00m.[00m 第一个.[00m表示颜色开始,第二个.[00m表示白色。
.[00m 表示颜色设置结束。
.[00m.[34m 第一个.[00m表示颜色开始,第二个.[34m表示蓝色。
.[00m 表示颜色设置结束。

.[00m.[00manaconda-ks.cfg.[00m .[01;34mDesktop.[00m .[00minstall.log.[00m .[01;34mpackages.[00m
manaconda-ks.cfg和 install.log是白色,Desktop和packages是蓝色。


下面的定义摘自:
http://www.linuxselfhelp.com/howtos/Bash-Prompt/Bash-Prompt-HOWTO-12.html,可见telnet与unix的渊源很深。
local BLUE="\[\033[0;34m\]"
local LIGHT_GRAY="\[\033[0;37m\]"
local LIGHT_GREEN="\[\033[1;32m\]"
local LIGHT_BLUE="\[\033[1;34m\]"
local LIGHT_CYAN="\[\033[1;36m\]"
local YELLOW="\[\033[1;33m\]"
local WHITE="\[\033[1;37m\]"
local RED="\[\033[0;31m\]"
local NO_COLOUR="\[\033[0m\]"

建模,也是一种分类。

写telnet connection的时候用到一些常量。比如login的提示符“login: ”,支持的命令“dir”、“list”。这些刚开始都是写到telnetConnection的类里作为常量。但是,当试图支持Linux和XP两种OS上的telnet server时发现这是错误的。
新的想法是:创建一个Device类,这个类包含IP, Name之类, Device类中再包含一个TelnetServer类。TelnetServer有两个子类分别对应Linux, XP或者其它server软件(比如dslam)。把“login: ”这样的东西放到TelnetServer中是合适的。
而dir, list这样的命令放在OsCommandSet类中也许比较合适。
这样就成了:

Device
|
|------>TelnetServer
|------->OsCommandSet


而 TelnetConnection<---Device

分类就是把数据、方法放到合适的类里。分类正确了,代码逻辑就清楚,同时维护就容易。

Windows Telnet server设置

通过CLTF连接windowsXP的telnet server时,输入用户名、密码之后,会出现诸如:“
[5;1H [K [6;1H [K [7;1H [K [8;1H [K [9;1H [K [10;1H [K [11;1H [K [12;1H [K [13;1H [
”的字符。实际上这些都是光标定位用的信息。通过执行tlntadmn localhost config mode=stream,把server设置为stream模式,就可以去除这些“乱码”了。
[参考:http://mail-archives.apache.org/mod_mbox/commons-user/200806.mbox/%3C23ec93be0806070251y39b0b175m89bbdb6c9f16ab0a@mail.gmail.com%3E]

tlntadmn的详细用法:
C:\Documents and Settings\Dahui>tlntadmn -?
用法: tlntadmn [computer name] [common_options] start | stop | pause | continue
| -s | -k | -m | config config_options
所有会话用 'all'。
-s sessionid 列出会话的信息。
-k sessionid 终止会话。
-m sessionid 发送消息到会话。

config 配置 telnet 服务器参数。

common_options 为:
-u user 指定要使用其凭据的用户
-p password 用户密码

config_options 为:
dom = domain 设定用户的默认域
ctrlakeymap = yes|no 设定 ALT 键的映射
timeout = hh:mm:ss 设定空闲会话超时值
timeoutactive = yes|no 启用空闲会话。
maxfail = attempts 设定断开前失败的登录企图数。
maxconn = connections 设定最大连接数。
port = number 设定 telnet 端口。
sec = [+/-]NTLM [+/-]passwd
设定身份验证机构
mode = console|stream 指定操作模式。


1)还发现了windows telnet server和Linux telnet server的一个不同:当提示口令时,linux是“password: ” 而windows是“Password: ”。

2)Windows是中文操作系统时,通过inputStream得到的byte[]转为String是需要GBK编码。

2009年4月1日星期三

配置管理的基础环境

项目越大越复杂,对CM(配置管理)的要求就越高。源码管理、版本管理、build管理,发布管理,项目branch, patch的管理、甚至用户的管理。

CM在项目中的发展也是渐进的、迭代的,是根据开发中的需求变化的。当然前期的高瞻可以解决不少问题。前期的错误决策也会给CM的管理员(甚至开发人员、测试人员)带来痛苦。

我遇到的最“愚蠢的”,似乎也是最常见的错误是:“目标环境是windows(或是其它比如mobile), 开发环境是windows,而version control和CI(Continuous Integration)的环境却是linux”。要知道,有时候你的脚本会因为处理OS的差异而变得丑陋,付出很多而得不偿失。
我真的不明白为什么要把环境人为的差异化。用linux稳定?还是显得水平高?

2009年3月20日星期五

CLTF Domain Analysis

一直没有踏下心来先对domain建模,就先凭感觉写了connection的代码。要对domain建模就需要了解需求,在工作中也日益发现 需求的复杂性。业务的复杂需求如何让domain model清晰的表现出来是个问题。entity之间的关系能够搞对看似简单,其实已不是件容易的事情了,再加上用户对业务规则貌似‘挑剔’的需求更使得 建模不容易。
就CLTF来说,我们希望这个软件能够:1,通过telnet协议telnet到某个设备,2,来执行设备上允许的各种命令,3,并对命令的结果正确与否作出判断。
下面展开来说:
  1. 希望这个软件能够同时telnet到任意多个设备。
  2. telnet到设备后能够提供一个UI来模拟终端界面,通过这个界面能够发出设备可识别的命令并显示执行结果。
  3. 能够把一系列的命令放在一个文本文件中,在CLTF中将这些命令发送到指定的设备上去运行。
  4. 使用正则表达式对命令结果的正确与否进行判断。
  5. 能够通过UI显示执行进度。
  6. 能够通过UI看到命令执行时信息。
  7. 命令执行时可以选在是否记录命令执行信息到日志文件。
  8. 能够清晰地标识出哪个命令失败了。

这8个需求比起前3个已经具体一些了,但还是远远不够。在这些简短语言的描述下,还掩藏着很多的变数,还有很多需求将随着对这些大粒度的需求的澄清而提出来。正是随着需求的逐渐细化,我们的domain才变得“复杂”起来。
希望这个软件能够同时telnet到任意多个设备。
  • 提供一个device视图,以图形化的方式显示用户定义了的设备。
  • 在这个视图里能够添加设备、删除设备、修改设备信息。
  • 用户点右击某个设备时弹出菜单中有一个“telnet连接”按钮,用户点击此按钮,在建立到该设备的一个telnet连接。

至此关于连接设备的需求清楚了么?没有!
‘图形化的方式显示’是怎样的显示?各个设备都是相同的小图片代表?设备信息包括哪些?显示给用户哪些?
允许用户在视图里添加设备,那么用户是在视图里右击出菜单添加,还是通过视图的action item添加?
根据以上问题,细化出新需求:
  • 设备信息包括:设备名称、IP、描述、以及一个与CLTF发生的连接数。
  • 系统提供若干代表不同设备的小图片,当创建某类设备时就使用某个图片形象化地表示该设备。
  • 以表格的方式显示设备,一行一个设备。
  • 点击表格的各个列头可以对设备进行排序。
  • 每行列表提供一个delete按钮来执行删除设备的动作。
  • 每行提供modiy按钮来执行修改设备信息的动作,修改后需要通过update按钮提交修改。

TelnetEngine重构 (2)

Dahui's Code Life: TelnetEngine重构,Connection, Protocol 以及 Command各司其职 (1)

Refact:
  • IConnection 的connect()方法重命名为open()、disconnect()改为close()、receiveText()改为receive()。
  • IResponseListener 改名为IReceiverListener,这样就可以与IConnection的receive()方法对应了。一个IConnection既是 sender也是receiver,有双重功能、角色。如果另外建立ISender, IReceiver,再让IConnection实现这个两个接口也是很有道理的。暂且不这么做吧,毕竟现在只有IConnection隐含有这样的角 色。
  • DslamTelnetProtocol重命名为DslamTelnetStreamParser,并且imeplements IReceiverListener。
  • Create another class WindowsTelnetStreamParser which implements the IReceiverListener。这样就可以对设计进行脱离设备的调试。先复制DslamTelnetStreamParser的代码过来,这样以后可以再考虑哪些可以提取到父类中。
  • Create a data class WindowsTelnetConstantData。这个类包含处理window Telnet客户端需要的一些常量,比如欢迎字符串、login提示符、密码提示字串、More page提示等。
  • 问题:如何把输入反馈给外面的一层?

Refact:
  • ResponseReceiverThread 设计成为TelnetConnection的内部类。毕竟这个thread只供TelnetConnection使用。

2009年3月16日星期一

GAE and Pydev

参考此文http://daily.profeth.de/2008/04/google-app-engine-eclipse-pydev.html,就可以通过eclipse+python开发GAE应用了。我用eclipse3.4, python2.5, GAE 1.1.7,按照文章中的步骤去做遇到一些问题,又下载了jypthon2.5并配置pydev中与jpython相关的条目。python项目有两种类型python和jypthon。这两种类型的项目都可以跑起来。

但有时候启动时提示“Variable references empty selection: ${project_loc}”。改用绝对路径就没有问题,使用${workspace_loc:YourPrjName/appengine/dev_appserver.py}也没有问题。这也许是pydev 的bug.

eclipse中通过"${project_loc}\src" 表示项目的src目录。${workshop_loc}是workshop目录。

今天按照"Getting Started: Python"简单的学了一下GAE。感觉很多思想跟用java开发web app雷同。Django的template跟velocity、freemaker的使用模式差不多。GHL跟HQL差不多,我都怀疑google的datastore是不是已经直接就是OODB了。
不知道google的datastore API是不是像Hiberante一样强大,希望是!

2009年3月10日星期二

需求之后,先澄清关系。

设计模式、分析模式、实现模式,各种各样的模式。思考的模式、挖掘需求的模式也很重要。所有这些模式都和围棋的"定式"差不多,都是是些经验:解决这样的问题,基本就这些套路。最低一级的模式就是类似于hibernate SessionFactory使用这样的惯用法。下面的套路是High level一层:

现在工作中修改代码时总是发现问题,其中一些问题是因为当时给软件建模时没有把各个实体的关系搞对。一对一,多对一,一对多?这些经常在create table时要思考的问题,在紧随需求阶段之后的设计阶段搞清楚'对象'的关系很重要。根据需求,抽取名词之后,需要给这些名词连线的时候,请思考一下"一对一,多对一,一对多"?还是包含关系?还是没关系?只有根据需求,给各个"名词"建立了适当的关系,才能保证你设计的基础是稳固的,因为这最近接你要用软件模拟的现实世界。当你发现软件改动起来分费劲,基本上是因为你的软件模型与现实中的实体不太符合――缺少了某种属性、行为的表达,或者它们表达在了错误的地方(Class)。

2009年3月6日星期五

功能的作用域

现状:
在测试脚本执行过程中需要监控CPU, Memory的使用情况。目前的情况是自定义了一组脚本命令:开始监控、等待、停止监控。
然后让创建脚本的人去把这些指令加入到测试脚本中。

问题:

最近用户又提出新需求:需要监控某些process的RSS, VSZ的情况。目前的办法是把process监控的功能发到了以前的脚本命令中。这样,如果监控,就监控了所有的东西。用户并不能容易的定制(需要程序员开发出GUI界面)。我不是脚本的使用者,但是,还有一个功能我觉得更重要:可以随时确定是否对某个测试(若干设备命令组成)进行监控。

解决:
像性能监控这种问题是不应该放在脚本层次来做的。应该作为一个通用功能提取到更高一个逻辑层次上。用户可以在运行测试用例时决定是否进行性能监控、以及监控什么(CPU, memory, process)。通过GUI用户可以在启动项目前进行设置,这是粗粒度的使用。应该还提供定义良好的API进行供程序员对监控功能进行细粒度的使用、暴露。

变量有作用域,其实功能也是有的,就如上面说的。

2009年2月26日星期四

Hibernate 的Configuration和它的ClassLoader.

还是在整理遗留系统的hibernate问题。
现状:
1)应用是eclipse RCP应用。多个自定义的plugin。其中一个是用于共享通用代码目的的base plugin。
2)Hibernte2.0版。在base plugin项目中包含了一个hibernte.cfg.xml,在其lib目录下有hibernate2.jar。其它plugin的lib目录也有hibnerate2.jar,也在使用自己的hibernate.cfg.xml.

问题:
1)开始试图让整个应用只有一个sessionFactory实例。发现这就有个项目依赖问题。如果把hibernate sessionFactory的管理放在base-plugin项目里。那么其它项目如果需要自己的hibernate dao 访问怎么办?虽然可以通过在base-plugin模块里定义一些配置文件读取点的方式加载额外的hibernate mapping文件,但是,如何把子项目的class加到base-plugin的classpath中呢?(也许可以写自己的classloader)。通过配置文件是解决依赖的一个好办法。但是,没有写classloader的经验,也没有时间去做这种尝试。这种逻辑放在框架里比较合适,而不是放在仅仅作为工具库的base project里。

2)接下来就考虑把Hibernate的jar文件只放在base-plugin的lib中。如果那个项目需要书库访问功能,就在自己的(子)项目中写new Configuration().configure().buildSessionFactory()。觉得这还算个差不多干净的办法。当进行代码实现时,技术性问题又冒出来了。以下蓝色文字仅代表自己对所遇问题的一些理解和猜测。plugin运行时有自己的classloader。而hibernte的Congfiguration().configure()在搜索hibernate.cfg.xml时是从加载Configuration这个类的classloader的classpath路径中寻找。这在hibernate2.jar仅存在于base项目的情况下是找不到你子项目的hibernate配置的,它又重复加载了base项目中的hibernate配置。查看Hibernat API文档,知道可以给configure()传入resource参数指定hibernate.cfg.xml的位置。通过AbcClass.class.getClassLoader().getResource("/abcHibernate.cfg.xml");的方式指定了abc项目用的.cfg.xml文件。Ok. Hibernate 被成功初始化了。但是,当使用hibernate query后,访问list的元素时发生了ClassCaseException,跟踪的SDK的source, List还是跟classloader有关系的。

3)接下来的想法就是把hibernate的jar文件放在各自project的lib里。应用程序在启动时,启动一个线程初始化sesssionFactory(),问题又来了。当第二个sessionFactory在同一个线程中初始化时,得到了另一个运行时 exception: Exception in thread "Thread-2" java.lang.LinkageError: Class a/b/c/d/Abc.class violates loader constraints. ... ... google到这段。没办法了。就先初始化一个sessionFactory吧。只是不知道Hibernte3.0是否也是同样的机制。
Hibernate always uses the classloader of the current running thread, which guarantees predictable behavior in all environments. In some environments with weak classloader/deployment configuration options (SAP, JONAS, etc.), it is necessary to tell Hibernate to use a different classloader, not the loader that loaded hibernate2.jar. Usually, this should be transparent to Hibernate and the application server would know what classloaders to use, depending on the packaging. Also note that this is not an issue if the "one application - one Hibernate version" strategy is used, which is the case in most production environments (you have dedicated libraries per application).

We currently allow a customizable classloader for addResource(), but this only loads the mappings with the given classloader. We would need an option to specify a classloader for the persistent classes as well, basically, for all Hibernate operations.
http://opensource.atlassian.com/projects/hibernate/browse/HB-1310
经验:
是不是common的功能,有时候不能仅仅从功能上划分,技术细节也是重要的。

====下面是程序运行时打印的log, 可以看出currentThread的classloader都是null(
Some implementations may use null to represent the bootstrap class loader.)。而不同plugin的classloader是不同的。
Current Thread's classLoader: null
BtafHibernateUtil's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1d520c4
Configuration's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1d520c4
cf's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1d520c4


Current Thread's classLoader: null
SessionFactory's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1c6f579
Configuration's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1c6f579
cf's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1c6f579

2009年2月25日星期三

网络中的Connection和业务中的Session

网络编程中总会建立Connection对象代表到某个endpoint的连接,或长或短,代表了一个逻辑上的并且真实的数据传输通道。
Session并不像connnection那样对应某个真实网络元素。它是应用程序中的一个逻辑单元,是某种业务(需求)的抽象。

举例说明(借机总结一下以前项目中的设计):
1)Neustar IM项目中,Connection类就是代表了一个网络连接。在客户端应用程序中有一个连接是到服务器的长连接,用于侦听服务器发送来的命令、通知。客户端还有一个连接是短的,用户每次发送消息时使用。而session,在这个应用中代表了用户与其它用户的一个(相对某段时间内连续的)会话。这个会话保存了聊天记录。这个会话可能是某种特别的session,比如群组聊天。(当时的项目没有把群聊的功能利用session作为接口。现在再说是马后炮了。)总之,session是你业务逻辑方面的概念。
2)现在的项目又遇到了类似的问题。到dslam设备需要真正的telnel连接。每套测试用例都要通过connection被发送到设备执行。如果需要并行执行一些测试,就需要同时存在若干到设备的连接。这里,我们就可以把执行一次测试,这样逻辑上的概念,当作一个session。

connection和session的区别应该是这样的了。它们之间的联系呢?
应该是session必然对应一个connection吧,并且至少一个吧,多数情况下恐怕也是一个。

ps 中VSZ、RSS的意义

工作中要通过ps监控unix设备中某些进程的内存使用情况。需求是统计进程使用内存的变化。

VSZ:虚拟内存大小。 RSS:常驻集大小。
VSZ表示如果一个程序完全驻留在内存的话需要占用多少内存空间,而RSS指明了当前实际占用了多少内存。
另外的输出STAT:S表示程序处于休眠状态,R表示处于运行状态(指在运行队列中)

我不知道的是VSZ是否会根据程序的运行不断的增长或变小,比如程序不断的分配内存。要写个程序跑到unix上试试了。呵呵。

今天先不管那么多,先抓取数据吧。

关于linux内存http://q.mq35.com/html/6/2/200810/06-655.html
IMB DW 系统管理员工具包-进程管理技巧: http://www.ibm.com/developerworks/cn/aix/library/es-unix-sysadmin1.html

可以学习、理解一下这段:
$ ps -A -o rss,vsz,command | grep bash | awk '{rss += $1; vsz += $2 } END { print "Real: ",rss, "Virtual: ",vsz }'
Real:  4004 Virtual:  305624

2009年2月24日星期二

SonarJ印象

hello2morrow is an independent software vendor specialized in tools for the management and monitoring of architecture and technical quality of software written in Java, C/C++ orC#.
SonarJ. 轻盈的色彩,简单的逻辑。读读tutoria就l很容易上手使用。
感觉就是一个依赖管理工具,要想依赖的漂亮,当然要先分层。它也就是一个分层管理工具。说管理也许不准确。它主要还是监控功能。
1)你先用横条竖棍把一个系统划分成architectural artifacts。
2)然后把程序中用package组织的class添加到格子里。(一个class只能在一个格子里。)
3)建立、设立各种允许、禁止的依赖关系。
4)在Exploration视图中验证你的code。

应该说SonarJ做的事情很基本很有限。它只是对你预先建立好的layer进行了monitor。而对你如何划分这些层次并没有给出任何回馈。虽然简单,个人感觉还是非常值得使用。就如今天发现的竟然在数据类中看到了import ui的类。这种超低级的错误足以让你的项目在维护期遭受无情的骂名。(今天看到一个同事的签名档是:前人种树,后人捉虫。哈)

适合于architect已经搭建完了框架后,在程序员完成项目过程中进行监管。如果SonarJ在提供脚本API进行报表生成,那么再利用持续集成工具进行驱动,就可以实现对违反架构的编码自动化报警了。

SonarJ可以以eclipse插件的形式与eclipse集成。正如其文档所说,在eclipse中仅仅适用于verify的功能。各种约束的设置还要在SonarJ App中完成。

关于SonarJ的论述及其它类似工具比较:http://www.infoq.com/news/2009/02/sonarj-community-edition
SonarJ主页:http://www.hello2morrow.com/

纠错对hibernate sessionFactory的使用

现状:
1 程序中使用username/password。硬编码。
2 待连接的数据库IP可能变化,并且不是从默认的hibernate.cfg.xml读取,而是从eclipse的preference本地存储读取。
3 获取hibernate sessionFactory的代码放在common包里。而数据库的IP配置是在btaf包里。

问题:

因为database IP可以动态改变,所以,HibernateUtil传统的静态初始化已经不能使用。同时,对preference中IP的依赖要传递到HibernateUtil中。
DBConnInfo在构造时保存从preference中获得的IP等信息,并提供方法返回DB URL。但是,当用户更改了IP后不能够构造新的sessionFactory.

解决:

1)在common包中创建HibernateUtil类。提高静态方法 public synchronized static SessionFactory getSessionFactory(String url);
这个方法根据url构建hibernate SessionFactory实例。如果传入的url不同于上个,则需要首先关闭以前创建的sessionFactory.
2) 所有获得sessionFactory的地方,都通过HibernateUtil.getSessionFactory(url)获得。不能把sessionFactory作为类变量保存。这样就保证了sessionFactory的单例。
3)在UI上更改DB IP后,要更新DBConnInfo实例。因为DBConnInfo是单例,所以获得url可以保证最新。

经验:
  • Hibernae的SessionFactory代表了对数据库的连接资源。
  • 如果你的应用中只需要用一个用户名密码对一个数据库连接,那么,一定要保证对SessionFactory实例是单例的。
  • 对sessionFactory 对象的引用生命期越短越好,最好不要出现在域变量中。
  • 如果你需要重新建立一个sessionFacotry,你需要保证对被废弃的sessionfactory object进行必要的清理工作。

**
DbConnInfo 这个类本来的目的就是保存数据库连接信息。但是,里面竟然出现了import ##.ui.preferences.PreferenceConstants;

2009年2月19日星期四

建模 --- 对称性及传递性

场景:
当用户点击启动后,一个线程T1开始运行某个Job队列中的各个job对象的代码。当用户点击stop按钮时,就需要让线程停止驱动job对象的代码。
T1就叫做JobExecutor, 被执行的工作队列叫做JobSuilt, JobSuilt中的对象就是Job。
现有的系统中,JobExecutor有start(), stop()方法。系统目前不能很好的控制job的停止。

问题:
当时肯定还没有把很多事情想清楚就开始编码了。根本没有很好的建模。当按下停止按钮时。目前的代码只是在想简单的让JobExecutor线程停止下来。而如何通知正在运行的job没有很好的设计。

解决:
start(), stop()是对称性的两个方法。一个很好的思路是:被启动(started)的对象也要有对应的start()方法来响应executor的start操作。被停止的对象也要有stop()方法。
这样这种操作就具有了一致的传递性。

扩展:
当Job遇到问题(Exception, Error之类)需要通知exector时,需要通知executor进行对应处理时,可以把executor传递给Job。但是这样executor和Job就有了较强的耦合。
一个办法就是声明一个接口比如叫JobListener,这个接口不妨定义void processException(Exception e), void processError(), void finished()...,而executor可以实现JobListener接口。
这样就有了从executor到JobListner的双向通讯机制。


《实现模式》这本书中提到了对称性,并把其发在了“原则”性的地位。在建模过程中,你越是能够抽象出真正的Mode(拥有确实是属于自己的属性、方法),发现mode之间真正的关系(Mode之间的关联),你未来的麻烦就越少。

2009年2月17日星期二

Eclipse RCP plugin之间的jar依赖及共享。

问题:
Eclipse的RCP编程中需要开发若干plugin,如果一个jar已经包含在某个plugin中,那么如何实现这个jar文件的共享?

Resolve:
1) 不妨定义一个generic的plugin,比如叫做common-plugin,这个plugin就是为了共享资源建立的。当然你也可以根据你的系统设计找到某个“最根本”的plugin,把必要的jar文件放到这个plugin里。
2)通过eclipse project properties设置页,把共享的lib都export出来。
3)编辑common-plugin,的plugin.xml。在RunTime页中把需要共享给其它plugin的包,加入到exported packages列表里。
4)打开需要common-plugin库的其它plugin的plugin.xml文件,在Dependencies tab中添加上common-plugin为被依赖的包。

2009年2月16日星期一

Executor为谁而生?---- 执行策略

Executor与Runnable的定义极为相似。为什么要加上个Executor?
public interface Executor {
    void execute(Runnable command);
}

public interface Runnable {
    void run()
}

Runnable规范了任务的入口。Thread提供了驱动任务的线程接口。
有了这两个interface,你就可以完成最简单的多线程程序。但是如果你实现一些执行策略,你将面临非常有挑战性的编程。什么是执行策略?执行策略定义了用线程执行任务的一些方式。如,可以有多少个任务可以并行?何种顺序(FIFO, FIFO,自定义的优先级算法)?最多有多少任务可以等待?如果超过了可等待的任务数,系统如何反应? 任务执行前做什么?任务执行后做什么?诸如此类。
Runnable和Thread没有从API上提供解决"执行策略"的任何线索。要实现你特定的执行策略就需要你利用Thread的sleep(),wait(),notify(), interrunppt(),isInterrupt()进行高水平的设计。
如果你的系统不同模块需要使用不同的执行策略,如果你的系统需要动态的调整执行策略,这些功能是不是更具挑战性?

Executor就是Thread和Runnable之间的一层隔离、解耦。这层隔离为Thread和Runnable之间实现多种交互提供了可能与方便。Executor的各种子类实际上就实现了各种不同的执行策略。

Exector的是java.util.concurrent的基石。

Link:Executor API
All Known Subinterfaces:
ExecutorService, ScheduledExecutorService
All Known Implementing Classes:
AbstractExecutorService, ScheduledThreadPoolExecutor, ThreadPoolExecutor

2009年2月15日星期日

Google App Engine很吸引人。

免费享受Google的云计算,只要你掌握GoogleAppEngine API。太诱人了。真不知道将如何影响搞虚拟主机业务的小公司了,也许取决于 python的流行。

开始做GAE的用户了。呵呵。

A webapp application has three parts:
  • one or more RequestHandler classes that process requests and build responses.
    相当于Servlet;
  • a WSGIApplication instance that routes incoming requests to handlers based on the URL.
    差不多是ServletFilter的功能。
  • a main routine that runs the WSGIApplication using a CGI adaptor.
    就是完成servlet engine里分发功能吧。
... ...

2009年2月13日星期五

TelnetEngine重构,Connection, Protocol 以及 Command各司其职

从这个class diagram中可以看到设计问题。
IConnection代表一个对设备的连接的抽象。具体有可能是Telnet, 或者HTTP之类。

TelnetConnection是基于Telnet协议的连接。

ResponseReceiverThread 是TelnetConnection被open()之后启动的接受connection output的线程。

IResponseListener抽象了对output输出进行监听的回调方法。

DslamTelnetProtocol实现了IResponseListener,希望这个类能够实时处理输出。

从类的名称到类提供的方法,都有不少问题。下面依次分析:
  1. TelnetConnection 的其中一个构造函数使用了用户名、密码。Login()功能应该从TelnetConnection中移走。

  2. 在TelnetConnection 中的connect()方法中编码实现了DslamTelnetProtocol到ResponseReceiverThread的注入、ResponseReceiverThread的启动。这样,依赖关系不明显。应当显式的把responseListener注入到connection。ResponseReceiverThread可直隐式的管理比较好。

  3. DslamTelnetProtocol是个名词。实际它完成对Dslam命令结果进行解析的工作。这个类应该分拆成两个类。并应该重命名。
    DslamDeviceConstantData: DslamTelnetProtocol中定义的与DSLAM相关的常数应该分离到这个类中。而且若干参数是用户设定不变的、一些是连接到设备后从中读取的。
    DslamCommandResultParser: 这个名称更能体现类的功能。
    【是否给DslamCommandResultParser创建一个接口ICommandResultParser?目前还感觉不到有什么意义,因为目前还只是处理一个设备,所以也就不知道到底该抽象出哪些东西。其实上面的IConnection, TelnetConnection 也有同样的问题。】

  4. 还有一些方法实现的细节需要改正。比如, wdy建议读取到一个命令结果后就自动把输出缓存清空。是啊,我为什么要保留2M的输出缓存呢?目前其没有对结果缓存的需求,也使得结果处理复杂了。

2009年2月12日星期四

控制线程 ---- 停止

写多线程的程序需要对并发带来的危险(竞争、死锁、脏读...)进行预防。同时还有一个重要的方面是对那些并行执行的代码进行控制,比如:停止、等待、继续。

让线程驱动的代码要么是被while括起来的需要长时间运行;要么是个临时的任务,运行到最后让线程自动终结。

为什么停止线程是个问题?因为线程的阻塞状态。
线程的四个状态:New, Runnable, Blocked, Dead
导致thread blocked的四个原因: sleep(sometime)、 wait()、I/O 操作、 synchronized导致的锁竞争。

需要停止线程操作的通常都是要终止长时间运行的线程。让线程停下来,大致有以下几种方法:
1)利用代码中while的条件判断语句,检测一(多)个是否需要停止的标志。
这个标志的访问需要同步控制。同时可以被其它线程代码改变。
2)使用interrupt()方法,让线程正在驱动的的代码产生InterruptedException
你可以在代码中捕获这个exception,进行适当的判断,然后让线程优雅的停止。
3)但是第二种方法对于因为以下两种原因造成的阻塞不起作用:IO和等待进入synchronized块。
针对IO造成的阻塞,可以关闭IO资源,这样迫使IO操作产生exception而让线程从阻塞状态返回,从而获得停止线程的机会。
4)因为等待锁而阻塞的线程利用传统的thread的api似乎没有什么好办法。一个建议是不要长时间的占用那些很多代码都想获得的锁
5)在提倡使用concurrent包的情况下。可以利用Executor.shutdownNow()停止所有其管理的线程。利用Executor.submit()获得的Future cancel(true)来对单独的线程进行停止操作。

**
现象:
启动一个线程执行一串代码,其中某步抛出了Error,注意是error不是Exception。这意味着你的线程嘎然而止。这种退出方式实在是太暴力了。:)
实践:
1)需要在你的线程执行的代码中捕获任何异常、错误。
Throwable 是Error、Exception的父类。而Exception又分为Runtime的exception和不是runtime的。我的实践是在run()的代码中catch (Throwable t),这样就可以保证你不遗漏任何异常了。
2)对产生错误的原因进行分析。具体问题具体分析。可以或应该继续执行的,请记录异常信息,继续代码的执行。如果需要退出线程的,需要注意释放在线程执行中申请的资源以及修改程序状态标志(某些应用数据)。

2009年2月11日星期三

Fat Client中使用Hibernate C3P0

BTAF项目使用hibernate。作为胖客户端使用hibernate应该注意对连接池的配置。因为你不能控制客户端的在线时间,所以基本的原则是:尽少占用数据库连接、尽快释放连接。
所以
1)连接池最少连接数应该为零或者1;
2)最多缓存的连接数应该根据具体应用不同而不同;
3)从连接池中的撤销的周期应该短;
4) 检查过期连接的频率也应该频繁些。
下面是个示例:
<property name="c3p0.min_size">0</property>
<property name="c3p0.max_size">5</property>
<property name="c3p0.timeout">180</property>
<property name="c3p0.idleTestPeriod">60</property>
从hibernate的一些输出:
BasicResourcePool:1935 - Checking for expired resources - Thu Feb 12 11:04:32 CST 2009 [com.mchange.v2.resourcepool.BasicResourcePool@9ddde9]
BasicResourcePool:1447 - BEGIN check for expired resources. [com.mchange.v2.resourcepool.BasicResourcePool@9ddde9]
BasicResourcePool:1468 - FINISHED check for expired resources. [com.mchange.v2.resourcepool.BasicResourcePool@9ddde9]


关于如何在hibernate中配置C3P0:http://www.hibernate.org/214.html
关于c3p0更详细的介绍:http://www.mchange.com/projects/c3p0/index.html#configuration_properties

注意, hibernate文档中介绍说有些参数Must be set in hibernate.cfg.xml(or hibernate.properties),而有些Must be set in c3p0.properties。为什这样?因为必须在hibernate中设置的参数肯定经过了hibernate的封装,也许是因为这些参数太基本(重要)了。

使用google应用

1)创建google账户
https://www.google.com/accounts/NewAccount?continue=http%3A%2F%2Fwww.google.cn%2F&hl=zh-CN

2)下载用于浏览器的google工具栏
http://www.google.com/tools/firefox/toolbar/FT5/intl/zh-CN/index.html

3)登录google Docs在线文档应用程序。
建立、维护自己的的文章。(word, PPT, PDF ...) 这些文章可以发布成:网页、发布到博客、发布到自己建立的google site。尽量在docs.google.com里维护自己的文档。
http://docs.google.com/


4)用第一步创建的google帐号创建博客.
http://www.blogger.com/

5) 利用google的服务创建自己的site.
http://sites.google.com/
如果自己审美能力强,可以做出比较漂亮的站点:http://www.google.com/sites/help/intl/zh-CN/overview.html

6)我还是用了google code, google reader, goolge analytics ... 都非常好。

2009年2月10日星期二

Jelly ---- 为XML赋予执行能力

Jelly的思想看上去很简单。预定义了一些tag,关键是你可以自定义tag,然后用自己的xml引擎回调自己的tag processor。恩。就是这样的原理吧。

Jelly的这种能力正好与我现在的问题域相匹配:
1)我需要自动的通过telnet去执行很多的DSLAM命令;
2)对命令的结果进行处理,最终获得命令成果或是失败的结果;
3)结果的true/false会控制后续的命令是否执行
4)需要能够让用户自定义变量给DSLAM命令使用;
5)用户执行dslam命令时需要while和if 的能力。
浏览了一下Jelly的预定义tag,已经包含了很丰富的用于逻辑判断的tag。太棒了!只要很好总结、抽象出对命令的处理,我一定是可以写出功能强大的CLTF的。:)

感谢zero.liu的一篇摘读,让我看到了Jelly。曾经的一个想法是去使用groovy这样的脚本语言的。

多态与适配器模式

背景:N2X和TestCenter的很多功能都很类似。以前同样的功能都是针对不同的设备写的独立代码。
提出一个需求:测试人员希望一个测试step的代码对于设备是透明的,既能在n2x上跑,也能在TC上跑。
问题:虽然这两个设备的多数功能都有重叠,但是每个功能API的接口参数、格式还是有差别的,而且具体的功能能力也是有些差别的。

解决:
抽象出一个参数类。
抽象出一个设备接口,定义出设备能力。
直接用OO的多态特性对调用进行设备匹配。
有的同事说用适配器模式。适配器模式是针对接口不一致时进行中间层的适配。这里其实体现的是一种动态地对某种实现的选择。似乎没有必要立刻就考虑使用哪种模式。
【UI层通过使用tab等组件根据设备的不同接受参数输入。】

困难:
功能的差异还是很难解决。

2009年2月9日星期一

对敏捷的一点思考 ---- 敏捷是强调结果的

现在软件工程项目管理流行使用“敏捷”。经历了一些敏捷项目,有了些感想。
现在的项目组强制执行结对编程。我喜欢敏捷因为敏捷是山寨版的CMMI。而山寨代表着先进生产力、代表着具体问题具体分析的思想与实践。

先看看Agile 宣言与原则。你就发现敏捷其实是强调结果的。它用结果督促、指导项目的进行。
但是我觉得敏捷开发忽略了对总体架构或者系统设计的要求与指导。

在最近的几个项目中,都号称用敏捷的模式进行项目管理:每天早上的15分钟会议、结对编程、与用户的直接沟通。但这些手段都不能很好的解决在软件框架的设计问题。因为大多数程序员的经验与水平还不能够为项目建立框架(spring, struts这些现有框架确实解决了很多问题。但是一但项目需要定制自己的框架时,问题就凸显出来。)
如何根据具体的项目特点搭建适合自己业务需求的框架能够敏捷出来么?我个人觉得是不大可能的。因为这要求开发人员在对业务、技术比较了解的情况下进行更深一个层次的抽象。如何抽象?哪些可以抽象?抽象后如何向外提供服务(提供调用接口或IoC)?都是需要有比较专业的思考,同时很多也是经验。
想说的是,敏捷并不能替代全部的传统的软件工程流程,尤其是系统设计这一块
敏捷是方法论,并不是保证。就如,设计也需要敏捷一样。

虽然我自己对敏捷有了以上负面的感觉,但我依旧喜欢敏捷,因为它思想包括:个体和交互、客户合作。

Manifesto for Agile Software Development
个体和交互 胜过 工具和过程
可以运行的软件 胜过 面面俱到的文档
客户合作 胜过 合同谈判
响应变化 胜过 遵循计划


敏捷原则

1. 优先级最高的是,通过早期和持续交付有价值的软件来满足客户。
2. 欢迎变更需求,即使在开发的后期提出。敏捷过程为客户的竞争优势而控制变更。
3. 以两周到两月为周期,频繁地交付可运行的软件,首推较短的时间定量。
4. 在整个项目过程中,每一天开发人员都要和业务人员合作。
5. 由个体推动项目的建设,为个体提供所需的环境,支持和信任。
6. 在开发团队中或开发团队间传递信息的最为有效和高效的方法是面对面的交谈。
7. 衡量进展的重要尺度是可运行的软件。
8. 敏捷过程提倡可持续的开发。
9. 发起人,开发者和用户应该步调一致。
10.不断地关注技术上优越的设计会提高敏捷性。
11.简洁是最重要的,简洁就是尽量减少工作量的艺术。
12.最佳的架构,需求和设计来自于自组织的团队。
13.团队要定期反省如何使工作更有效,然后相应地调整行为。

2009年2月6日星期五

Telnet命令执行器--BlockingQueue

.
昨天完成了DslamTelnetProtocol类,这个类实现了IResponseListener接口,用于对接收到的输入流进行实时的解析、处理。DslamTelnetProtocol这个类的名称也许改成...ProtocolFilter比较合适。
目前启动了一个线程处理telnet的输出。对telnet的输入还是在主线程中调用telnetConnection.send()实现的。下一步应该把输入也用另一个线程来处理。

TODO: 对设备发送的命令放到一个BlockingQueue commandQueue中。DslamTelnetProtocol解析获得的命令结果放BlockingQueue resultQueue中。BlockingQueue确实为生产者-消费者模式简化了很多代码。否则你自己必须封装一个合适的queue,对这个queue进行适当的同步保证并发性。而且需要很好的设计wait() notify()来进行线程之间的协调。

2009年2月4日星期三

Telnet命令执行器----对the end of a stream的理解

现在工作中的软件框架中有一个比较严重的问题是网络命令的执行request-reponse之间没有进行很好的协调而是使用sleep, check这种糟糕的方式。
很自然联想起给Neustar做的IM软件。但telnet协议又不同于IM系统所使用的协议。前者是基于长连接,而后者是短连接。对于短链接,请求与回应很容易进行匹对。而长连接,则需要依靠同一个网络连接顺序地、依次地进行请求-回应的处理。
对于利用像telnet协议基于长连接来执行的命令,同步是其天然特性。所以对于命令的执行是没有必要进行异步处理的。(当然也可以进行异步处理,但是意义基本不大,这里异步/同步的选择应该是基于应用的不同而变化。)

先不考虑接口、抽象。直接用两个类封装逻辑。TelnetConnection利用common-net项目中的TelnetClient建立连接,获得input, output。TelnetCommond封装通过telnet协议发出的命令。
public String receiveText() {
StringBuilder sb = null;
try {
sb = new StringBuilder();
byte ch = (byte) in.read();
while (ch > 0) {
sb.append((char) ch);
ch = (byte) in.read(); // 当读到-1之后再用out发送指令之后再也不能读出response了。
// 而且读取这个-1的值时程序被阻塞!

}
} catch (IOException e) {
Log.warn(e);
}
Log.debug(sb.toString());

return sb.toString();
}


改为使用结束字符串做检查标志后就正常了。
byte ch = (byte) in.read();
while (ch > 0) {
sb.append((char) ch);
if (sb.indexOf(endFlagStr) >=0) {
break;
}
ch = (byte) in.read();
}


很奇怪,难道是apache common-net里的类TelnetInputStream实现得有问题?当inputstream读到末尾后,有新的数据进入后没有进行适当的处理?
呵呵,是我的错误!inputStream.read()返回-1说明已经读到了the end of the stream。对于有限容量的stream(比如来自文件的流)来说就是读尽了,对于未知容量的stream比如网络流,如果 read()返回-1,说明这个流可能已经被对端关闭了。总之就应该对这种情况进行处理。再读下去,也是-1。这篇文章对此有些所描述:http://publib.boulder.ibm.com/infocenter/zos/v1r9/index.jsp?topic=/com.ibm.zos.r9.rexa100/h1981605209.htm


Telnet概述及RFC: http://en.wikipedia.org/wiki/Telnet
common-net 的使用:http://www.informit.com/guides/content.aspx?g=java&seqNum=40

2009年2月3日星期二

对synchronized 的又一点理解

java里的synchronized给我的一个强烈印象是“同步资源”,或者说是利用对象的monitor进行序列化访问的手段。
当使用object.wait()而没有事先synchronized object时,问题出现了:IllegalMonitorStateException。

wait() 之前必须先过的这个对象上的锁。这就要依靠synchronized。因为
synchronized的基本作用是“获取对象上的monitor”。所谓的同步、序列化访问都是建立在monitor的作用之上的。也许把synchronized理解成获得锁的语句可能更接近事实。


sleep() yield()并没有释放锁,这是和wait()的巨大区别。