Morning@Weblog

4/19/2005

a way of known-to-unknown

Filed under: — site admin @ 6:12 pm

我们通常所说的软件设计与开发过程,无论是top-down(自顶向下)还是bottom-up(自底向上),都是一种过于简单化的认识。由此产生的对TDD的错误认识也就变得可以理解了。关于这一点,Kent Beck在他的TDD一书中讲的非常透彻:

A program grown from tests can appear to be written top-down, because you can begin with a test that represents a simple case of the entire computation. A program grown from tests can also appear to be written bottom-up, because you start with small pieces and aggregate them larger and larger.

Neither top-down nor bottom-up really describes the process helpfully. First, a vertical metaphor is a simplistic visualization of how programs change over time. “Growth” implies a kind of self-similar feedback loop where the environment affects the program and the program affects the environment. Second, if we have to have a direction in our metaphor, “known-to-unknown” is a helpful description. Known-to-unknown implies that we have some knowledge and experience on which to draw, and that we expect to learn in the course of development. Put these two together and we have programs growing from known to unknown.

4/10/2005

Rod Johnson对Java领域未来关注的技术的看法

Filed under: — site admin @ 5:14 pm

Rod Johnson对Java领域未来关注的技术的看法:
- Inversion of Control and dependency injection design patterns;
- unit testing and TDD (test-driven development);
- O/R (object/relational) mapping;
- post struts 1.x Web technologies such as JavaServer Faces, Spring Model-View-Controller and Tapestry, and value-add Web technologies such as Apache Beehive;
- the rich client space;

http://www.eweek.com/article2/0,1759,1773154,00.asp

4/9/2005

XPlanner impression

Filed under: — site admin @ 10:33 pm

XPlanner,从其字面意思即可大致了解其用途:作为XP实践的专用的计划订制与任务跟踪工具。XPlanner没有覆盖到所有的XP实践,主要集中于Planning Game方面,包括:User Story和Iteration Planning,同时还部分的融入了Pair Programming。XPlanner的定位,也就决定了它的局限性,不能作为一个XP过程实践的完整解决方案。

初次使用XPlanner的第一感觉是——清爽干净。不像某些软件,一开始就在页面上罗列很多功能,让人摸不着头脑。简洁的界面布局,风格一致的功能排布(这一点有些像AnthillPro),除了那些个图表最初让人有点费解之外,剩下的功能对于一个了解XP背景的人而言,完全可以在没有说明文档的前提下很快的掌握其大致功能(BTW:XPlanner的文档也确实有些简陋)。

基本功能包括:
- 简单的用户信息及帐号管理;
- 简单的项目管理,支持多个项目并行管理;
- 支持为每个项目定义多个迭代周期;
- 支持为每个迭代周期指定多个User Story,User Story的摘要信息包括:提交人(即XP中的customer),估计时间、实际执行时间、剩余时间、优先级(由customer定义),以及该Story的跟踪者;
- 支持为每个User Story定义多个Task,Task的种类有:Feature、Deb(a)t(e)、Defect、F(unction)Test、A(cceptance)Test、Overhead。Task的摘要信息包含:估计时间、实际执行时间、任务承担者等。可以分阶段为Task指定起始时间、结束时间、执行时延,这样可以反映“将一个Task细分为多个阶段”的实际情况,还可以根据情况为每个阶段定义最多两个执行人(在这里体现了pair programming实践)
- 支持在不同层次上(Project/Iteration/User Story/Task)将XPlanner数据导出成xml、pdf等格式;
- 在用户使用Xplanner期间,它会自动记录用户操作(谁什么时候做了什么事情),以及系统级事件(Container Event),作为跟踪项目过程执行情况的一个依据;
- 在定义Project/Iteration/User Story/Task时,其描述信息可以使用Wiki的语法格式。因而,也可以在描述中加入URL,以指向Xplanner之外的Web页面。在XPlanner中,与第三方工具的结合使用,主要也就是通过这样的简单形式加以实现的;
- XPlanner中有一个比较有意思的功能叫做Integration。它用于在软件版本(手工)集成期间,为所有的team member提供一个形象而直观的dashboard,以反映当前的集成情况。
1) 首先由集成者发起一个Integration操作,这样,其他人就可以通过XPlanner看到当前的集成是由谁在做的
2) 与当前集成相关的人员可以通过XPlanner所提供的功能“加入”到waiting line中来,而已经加入的,则可以选择leave line。
3) 最后,集成者可以选择finish或cancel当前的集成工作,这个集成过程被跟踪并记录下来。
- XPlanner利用Progress Bar来反映Iteration/User Story/Task的当前完成情况,还提供了诸多简单的图标统计功能:Matrics,Charts,来反映项目当前的执行情况。

这里是XPlanner的网站地址,http://www.xplanner.org

4/6/2005

一个并非无关痛痒的异常类的引入

Filed under: — site admin @ 9:27 pm

故事的起因是,最近,在我所负责的模块中时常于运行期间抛出NullPointException异常。经过仔细排查后,发现错误的源头位于某个业务层的controller中:

int public doOperation(int modelId) {
 Model model = dao.getModelById(modelId);
 model.getSomething();
 …
}

由于dao在根据给定的modelId查找数据库时没有发现相应记录,因此model取值为null,导致后续调用失败。进而发现,该dao对应于后台数据库中的一张表,该表从属于另一张主表。而当主表插入某条记录后(以modelId为主键),相应的,从表也会插入一条记录,且其主键即为modelId。从现象可以推得,两张表的一致性出现了问题——这是一个程序bug。

果不出所料,在涉及记录插入的相关代码中发现了导致数据不一致的漏洞——保持主从表一致的程序逻辑并未涵盖所有情况。

修改后的程序运行正常。但是,事情并没有结束。我依然对于整个系统心存疑虑,担心日后还会出现类似的数据不一致情形。是否应该考虑在dao获取model之后加上一个null值判断呢?很快,我打消了这个危险的念头。因为:首先,这是一个bug(数据不一致);其次,如果贸然吞掉异常,恐怕以后有关数据不一致的bug将会很难查找,这不是controller的责任;最后,如果一旦检测到null,doOperation方法的返回值将是很难定义的。因而,我还是选择了让异常抛出。不过,为了使异常更具informative,我将其做了转义,即:定义一个名为ModelNotFoundException的运行期异常。修改后的代码如下:

int public doOperation(int modelId) {
 Model model = dao.getModelById(modelId);
 if (null==model)
  throw new ModelNotFoundException(ErrorMessage);
 …
}

4/5/2005

Spring JPetStore学习小结(6)

Filed under: — site admin @ 9:39 am

※ 配置

以下是Spring JPetStore中使用的配置文件:

- web.xml

主要包含了如下内容:

定义log4j配置文件所在路径:

<context-param>
 <param-name>log4jConfigLocation</param-name>
 <param-value>/WEB-INF/log4j.properties</param-value>
</context-param>

定义application context配置文件的所在路径:

<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>
  /WEB-INF/dataAccessContext-local.xml /WEB-INF/applicationContext.xml
 </param-value>
 <!–
 <param-value>
  /WEB-INF/dataAccessContext-jta.xml /WEB-INF/applicationContext.xml
 </param-value>
 –>
</context-param>

几个重要的servlet,以及servlet-mapping定义:

<servlet>
 <servlet-name>context</servlet-name>
 <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
 <servlet-name>petstore</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <load-on-startup>2</load-on-startup>
</servlet>
<servlet>
 <servlet-name>action</servlet-name>
 <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
 <load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>petstore</servlet-name>
 <!–
 <servlet-name>action</servlet-name>
 –>
 <url-pattern>*.do</url-pattern>
</servlet-mapping>

- applicationContext.xml

与应用相关的context配置信息,包含bean定义,以及对email和remoting的配置,主要涉及中间层,也是其他servlet-specific context的root。在代码中可以通过WebApplicationContextUtils.getWebApplicationContext()访问到:

在该文件以及后面的dataAccessContext-local中都引入了形如“${}”的属性,这些属性是单独定义在外部属性文件中的,需要利用PropertyPlaceholderConfigurer来读入,该bean也在applicationContext.xml中定义。

<bean id="propertyConfigurer” class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 <property name="locations">
  <list>
   <value>WEB-INF/mail.properties</value>
   <value>WEB-INF/jdbc.properties</value>
  </list>
 </property>
</bean>

在声明事务时,applicationContext.xml文件中首先定义了一个名为baseTransactionProxy的bean,其中包含了有关事务的基本配置(对所有的insert方法和update方法使用PROPAGATION_REQUIRED,对其他方法使用PROPAGATION_REQUIRED,readOnly)。其abstract属性为true,作为parent bean被petStore bean所继承,并得到扩展。

<bean id="baseTransactionProxy” class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean”
 abstract="true">
 <property name="transactionManager"><ref bean="transactionManager"/></property>
 <property name="transactionAttributes">
  <props>
   <prop key="insert*">PROPAGATION_REQUIRED</prop>
   <prop key="update*">PROPAGATION_REQUIRED</prop>
   <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
  </props>
 </property>
</bean>
<bean id="petStore” parent="baseTransactionProxy">
 <property name="target">
  <bean class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
   <property name="accountDao"><ref bean="accountDao"/></property>
   <property name="categoryDao"><ref bean="categoryDao"/></property>
   <property name="productDao"><ref bean="productDao"/></property>
   <property name="itemDao"><ref bean="itemDao"/></property>
   <property name="orderDao"><ref bean="orderDao"/></property>
  </bean>
 </property>
</bean>

petSotre bean即对应于中间层的PetStoreImpl类,在其bean reference中所出现的Dao bean定义于dataAccessContext-local.xml中。

- dataAccessContext-local.xml/dataAccessContext-jta.xml

Spring JPetStore将对Dao bean的定义单独放到了一个文件中,这种按层分文件来描述context的方式值得借鉴。local后缀的文件用于单一数据库场景,jta后缀的文件用于多数据库场景。以dataAccessContext-local为例,除了Dao bean定义,该文件中还包括了dataSource的定义,所用的是apache的DBCP:

<bean id="dataSource” class="org.apache.commons.dbcp.BasicDataSource” destroy-method="close">
 <property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
 <property name="url"><value>${jdbc.url}</value></property>
 <property name="username"><value>${jdbc.username}</value></property>
 <property name="password"><value>${jdbc.password}</value></property>
</bean>

另外,还有关于ibatis sql map的配置信息:

<bean id="sqlMapClient” class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
 <property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
 <property name="dataSource"><ref local="dataSource"/></property>
</bean>

这里的sql-map-config.xml文件中包含了所有ibatis sql map文件所在的物理位置。

- petstore-servlet.xml

Spring web MVC的Web层context配置文件,依然是有关bean的定义,主要是Controller,定义了url与class的映射,以及一些属性,包括:对petStore bean的引用,successView,validator,viewName等。另外,viewResolver bean定义了view所在的位置和扩展名,以及对应的viewClass:

<bean id="viewResolver” class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
 <property name="prefix"><value>/WEB-INF/jsp/spring/</value></property>
 <property name="suffix"><value>.jsp</value></property>
</bean>

4/4/2005

Spring JPetStore学习小结(5)

Filed under: — site admin @ 9:34 am

※ Web层(续2)——分页机制

在Spring JPetStore中,为列表提供了简单的分页处理,这里使用了Spring的helper class:PagedListHolder。它虽是用于Web UI,不过实际上是针对bean list的维护,因此位于org.springframework.beans.support包中。在Web层中,PagedListHolder的实例会被当作session属性传递,然后在jsp页面中以model获取。

PagedListHolder itemList = new PagedListHolder(this.petStore.getItemListByProduct(productId));
itemList.setPageSize(4);
Product product = this.petStore.getProduct(productId);
request.getSession().setAttribute("ViewProductAction_itemList", itemList);
request.getSession().setAttribute("ViewProductAction_product", product);
model.put("itemList", itemList);
model.put("product", product);

在PageListHolder中提供了很多分页时经常用到的方法:isFirstPage、isLastPage、previousPage、nextPage、getPageList等。通过设置其成员变量sort,PageListHolder也支持排序,该成员变量是一个SortDefinition接口的实现类,缺省使用的是MutableSortDefinition。实际上MutableSortDefinition只不过维护了有关排序的一些状态信息,比如:以那个属性排序、降序还是升序、是否忽略大小写等。而真正的排序则是由PropertyComparator完成的,该类实现了java.util.Comparator接口,用于根据指定的bean property来比较两个bean的先后顺序。在PageListHolder的resort方法中调用了PropertyComparator的静态方法sort。

PagedHolderList提供了对不可更新的bean list的分页支持,如果需要处理可更新的bean list,可以使用RefreshablePagedListHolder。RefreshablePagedListHolder是PagedListHolder的子类,具备reloading功能。调用其refresh方法,能够根据Locale和filter的更新情况自动实现数据的reloading。为了让RefreshablePagedListHolder能够成功reload数据,我们还需要编写一个PagedListSourceProvider接口的实现类,因为RefreshablePagedListHolder会调用该接口的loadList方法。

Spring的org.springframework.beans.support包为Web分页机制提供了统一的解决方案,使用起来十分方便。不过,需要指出的是,使用PagedHolderList/RefreshablePagedHolderList的隐含前提是,你需要将后台数据表中的所有数据悉数全部取出来,然后再交由它们进行分页处理。

4/1/2005

Spring JPetStore学习小结(4)

Filed under: — site admin @ 10:11 am

※ Web层(续)

在众多Controller当中,有两个Controller比较特殊,它们涉及表单处理,分别是OrderFormController和AccountFormController,前者派生自AbstractWizardFormController,后者派生自SimpleFormController,而它们的父类则同为AbstractFormController。Spring Web MVC对一般的Form处理,从流程(workflow)的角度做了分类(form workflow),并以抽象类的形式封装与框架代码中。像OrderFormController和AccountFormController即是从这些抽象类中扩展派生而来,它们对部分callback方法做了覆盖。这是一个典型的Template Method Pattern的运用。当然,这也使得Web层的应用代码严重依赖于Spring Web MVC框架本身。

以SimpleFormController为例,从其核心方法之一processFormSubmission的实现代码中可以看出大致的处理流程:

protected ModelAndView processFormSubmission(
  HttpServletRequest request, HttpServletResponse response,
  Object command, BindException errors)
  throws Exception {
 if (errors.hasErrors() || isFormChangeRequest(request)) {
  return showForm(request, response, errors);
 }
 else {
  return onSubmit(request, response, command, errors);
 }
}

在表单验证之后,如果用户输入有误,或者表单需要再次刷新,则会重新导向当前表单页面,否则说明一切正常,调用onSubmit方法。onSubmit最终会调用doSubmitAction方法,然后调用getSuccessView获得后继视图的标识,配合errors.getModel(),组装成ModelAndView返回。而这里的doSubmitAction则是一个需要派生类覆盖的protected方法。

与上述Controller配套的还有两个Form Object:OrderForm,AccountForm,其所包含的实例变量对应于表单字段。

另一个值得一提的是拦截器SignOnInterceptor,它对部分用户操作实施了认证保护。代码实现逻辑大致如下:

public boolean preHandle(
  HttpServletRequest request, HttpServletResponse response, Object handler)
  throws Exception {
 UserSession userSession =
  (UserSession) WebUtils.getSessionAttribute(request, “userSession");
 if (userSession == null) {
  ModelAndView modelAndView = new ModelAndView("SignonForm");
  throw new ModelAndViewDefiningException(modelAndView);
 }
 else {
  return true;
 }
}

这里的UserSession是一个封装了包括帐号在内的用户信息的helper class。它被当作session属性在Web层的不同对象之间传递。在petstore-servlets.xml文件中有如下一段配置信息:

<bean id="secureHandlerMapping”
 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
 <property name="interceptors">
  <list>
   <ref bean="signonInterceptor"/>
  </list>
 </property>
 <property name="urlMap">
  <map>
   <entry key="/shop/editAccount.do"><ref local="secure_editAccount"/></entry>
   …
  </map>
 </property>
</bean>
<bean id="signonInterceptor”
 class="org.springframework.samples.jpetstore.web.spring.SignonInterceptor"/>
<bean id="secure_editAccount”
 class="org.springframework.samples.jpetstore.web.spring.AccountFormController">
 …
</bean>

由此,我们可以看到,SignonInterceptor的preHandle方法,将会在AccountFormController执行之前被调用。而当userSession为空时,则会被当作非法操作抛出ModelAndViewDefiningException异常,否则AccountFormController将会顺利执行。至于如何处理ModelAndViewDefiningException,则要“上溯”到Spring Web MVC的核心请求处理类:org.springframework.web.servlet.DispatcherServlet。在该Servlet的doDispatch方法中有如下一段代码:

try {
 …
}
catch (ModelAndViewDefiningException ex) {
 mv = ex.getModelAndView();
}
if (mv != null && !mv.isEmpty()) {
 render(mv, processedRequest, response);
}

由于ModelAndViewDefiningException中包含了出错以后的后继视图标识,因此最终将会导向该视图(在本例中,它导向用户登录页面)。

Powered by WordPress