paulwong

#

Spring MVC如何防止重复提交?类似Struts Token机制!

首先,需要将继承了SimpleFormController之类的sessionForm设为true。这样,在显示一个新表单时,Spring会将command存放在session中,而在提交表单时,Spring会从session中取出此command,随后立即从session中删除存放command的attribute。如果发现在session中没有command,Spring将其断定为重复提交,转而执行handleInvalidSubmit(request, response),可覆盖此方法负责防止重复提交的任务。

可以这么说,当setSessionForm(true)之后,如果没有先后经历显示表单、提交表单的过程,就会被认为是重复提交表单。

而有一些情况下却必须重复提交表单,如,修改数据库的数据后,试图写入数据库时因某些异常失败,如果此时异常被当前页面捕获并依旧返回当前页面,由于command已经被Spring在后台从session中移走,因此,就被认为是无效重复提交,从而导致第二次经修改后的记录无法正确提交到数据库中。handleInvalidSubmit()必须考虑到这种情况。

posted @ 2012-02-20 22:57 paulwong 阅读(3593) | 评论 (0)编辑 收藏

SPRING MVC

SPRING MVC就是和STRUTS等一样,是实现了MVC的框架,但性能比STRUTS要好,STRUTS对于每个请求都是新建一个ACTION处理,而SPRING MVC是对应到不同的方法。以下为一些核心概念:
  1. ACTION SERVLET:前端控制器,和所有的WEB框架一样,是所有的请求的中心入口
  2. MAPPING HANDLER: 比对URL,找出负责处理的控制器
  3. CONTROLLER:控制器,负责处理前端的请求,返回MODELVIEW
  4. VIEW RESOLVER:根据CONTROLLER返回的MODEL VIEW找出负责展现的VIEW
  5. VIEW:由于展现内容可以有不同方式,如JSP,FREEMARKER等,VIEW就负责展现,分两步,取得要展现的模版的路径,使用解释器解释并取得最终内容。一般一个SPRING就一个展现器,如JSTLVIEW,对于不同的URL,只是JSP页面路径不同,从CONTROLLER返回的MODEL VIEW中取得JSP路径,输出最终内容
  6. FORM HANDLER:页面如果有表单,就涉及到如何从表单中读取数据或将数据绑定到表单中,表单处理器已经和CONTROLLER结合在一起了,只须继承SIMPLE FORM HANDLER就可以,在JSP中配置COMMANDNAME值,就可以此为KEY,从MODELVIEW中取表单值或绑定值到表单中
在STRUTS中,会有一配置文件:STRUTS-COMFIG.XML,配置了所要用到的BEAN的内容,好处是直观,但项目大了,免不了配置文件数量庞大,为了减少配置文件的数量,引入注释,实际上可以理解为配置文件不用手写,由容器在启动时动态帮你生成,只须在相应代码,如类名,方法上加上注释,容器在解释这些类的时候就会动态生成一虚拟的配置文件,供后续使用。具体的注释有@CONTROLLER/@SERVICE/@REQUESTMAPPING等。



Spring MVC 3 深入总结
http://www.blogjava.net/qcyycom/archive/2013/07/11/401467.html

posted @ 2012-02-20 22:53 paulwong 阅读(405) | 评论 (0)编辑 收藏

APPLE开发指引

https://developer.apple.com/library/ios/#referencelibrary/GettingStarted/RoadMapiOS/Introduction/Introduction.html

posted @ 2012-02-20 21:27 paulwong 阅读(236) | 评论 (0)编辑 收藏

项目经理攻略

每个人都期待职位的提升和别人的认可

在国内,项目经理应该是大多数程序员比较想要的职位

这篇文章将会告诉你,要成为项目经理的一些技巧。或者说,从另外的一个角度来看项目经理这个职位。

项目经理要懂技术

这个问题,在iteye上被讨论了很久。各有各的观点,我在这边不做评论。

技术做为一个人可以掌握的技能,当然是越多越好,所以从技能方面说,懂点技术当然比啥都不懂要好

另外一方面,项目经理要和技术人员沟通,而技术人员的难以沟通是出了名的。如果脱离一些基本层面的术语,沟通到一些具体的解决方案的时候,往往和技术人员的沟通有更好的效果。而这里就会用到很多技术。

所以,从这方面说,项目经理掌握一些技术,可以说是必须的。。。

当然,类似外资公司的项目经理,完全只考虑项目进度的,那就另外说。。。

项目经理要有口才

项目经理需要在团队士气低迷的时候,鼓舞士气

项目经理需要在客户沟通的时候,保持不卑不亢的气势

项目经理需要说服客户和领导给予更多的资源

项目经理要有谋略

办公室政治我就不在这边罗嗦了

我想要说的是,作为项目经理,你经常面临很多办公室政治的挑战。

你至少要做到团结可以团结的力量。包括你的领导,你的下属,你的战友,销售,客户等等

你可以把你的泡妞经历和技术人员分享,来换取他们的支持

你也可以把你赚钱的股票和他们分享

你甚至什么都不用付出,只要时不时的体现一下你对他们的关心。

光说不练,这边整个练习题

做项目的人经常碰到的,客户要在10天之内,完成一个功能模块,在技术人员开看基本上是不可能完成的任务

那做为项目经理,需要怎么样处理这件事??

我们都知道处理结果,要么增加周期,把10天换成20天,要么删减功能。那我们采取什么样的手段来实现这个结果??一方面不能得罪甲方,另外一方面要实现我们的预期。

方法:把技术人员和客户负责人叫到一起沟通,名义上解决一些gap,实际上是让有冲突的双方直接面对。作为甲方,你是不能得罪的,做为技术人员,你可以站在甲方的立场和技术人员争吵。这个时候,你要把甲方的负责人凉在一边,让他看你们争吵,你们在争吵的过程中最好要用到解决方案的具体细节,让他听不懂。然后时不时的回过神来,用甲方的口气和技术人员说,这个时间已经决定了的。你和技术人员可以事先沟通好,要尽量顶,甚至发火。然后你们的争吵就会让负责人很尴尬,退也不是不退也不是。
结果:一般1-2个小时以后,负责人就会让步,一般是延长周期,如果周期他决定不了,那一般是删减功能。
思考:我们没有得罪甲方,但是甲方顺利让步。但是我们得罪了技术人员,所以在事后,一定要诚恳道歉,除非你们已经很娴熟

另外,这个方法是考虑到如下因素

很可能这边提出的10天,不是最终的时间点,而是那个负责人为了自己的performance提出的,如果技术人员反对很严重,这个防线肯定会崩溃

还有可能是负责人不清楚最终的实现,时间也不是他定的,他也需要和上级沟通。如果他看到技术人员如此抵制,考虑到这个功能计算在自己的performance下面,他会帮你争取更多的资源。这个时候,让负责人和他的领导去谈,会取得更好的效果。

上面都是一些基本素质,如果具备下面的几点,会事半功倍哦!!!

幽默感:团队粘合剂,也是化解尴尬的最好的东西

真诚:如果你做不到欺骗一辈子,那你就坦率点

尝试用对方的角度思考:让你发现自己的问题,发现别人的弱点。

最后,留下一个问题,如果一个团队成功的做完了一个项目,各方面评价都很好,你猜测一下,谁在这个成功的项目中,获利最大?

http://www.iteye.com/topic/1120697

posted @ 2012-02-20 16:45 paulwong 阅读(191) | 评论 (0)编辑 收藏

Spring基于注解的缓存配置--web应用实例

基于方法级别的缓存:通常如果一个带有参数的方法执行后会返回一个Object,那可以认为如果参数相同,则返回的结果一样,这样就可以为这个方法建立一个缓存Map,参数就是KEY,返回的结果就是VALUE,调用此方法前可以先判断参数是否和之前的一样,如果是则从Map中取出结果返回调用者,而无需再进入此方法内执行得到,从而节省了此方法执行时的时间。

http://hanqunfeng.iteye.com/blog/605123

经验证,如果要支持分布式的缓存,用spring-modules-cache不好使,必须转用com.googlecode.ehcache-spring-annotations

全套MAVEN的POM:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
<modelVersion>4.0.0</modelVersion>
    
<groupId>EhCache-Cluster-Tester</groupId>
    
<artifactId>EhCache-Cluster-Tester</artifactId>
    
<version>0.0.1-SNAPSHOT</version>
    
<packaging>war</packaging>
    
<dependencies>
        
<dependency>
            
<groupId>net.sf.ehcache</groupId>
            
<artifactId>ehcache-jgroupsreplication</artifactId>
            
<version>1.4</version>
        
</dependency>
        
<!-- Spring framework -->
        
<dependency>
            
<groupId>org.springframework</groupId>
            
<artifactId>spring</artifactId>
            
<version>${spring.version}</version>
        
</dependency>

        
<!-- Spring MVC framework -->
        
<dependency>
            
<groupId>org.springframework</groupId>
            
<artifactId>spring-webmvc</artifactId>
            
<version>${spring.version}</version>
        
</dependency>
        
<dependency>
            
<groupId>javax.servlet</groupId>
            
<artifactId>servlet-api</artifactId>
            
<version>2.4</version>
            
<scope>provided</scope>
        
</dependency>
        
<dependency>
            
<groupId>org.slf4j</groupId>
            
<artifactId>slf4j-jdk14</artifactId>
            
<version>1.5.6</version>
            
<type>jar</type>
            
<scope>compile</scope>
        
</dependency>
        
<dependency>
            
<groupId>log4j</groupId>
            
<artifactId>log4j</artifactId>
            
<version>1.2.13</version>
            
<type>jar</type>
            
<scope>compile</scope>
        
</dependency>
        
<dependency>
            
<groupId>cglib</groupId>
            
<artifactId>cglib-nodep</artifactId>
            
<version>2.2.2</version>
            
<type>jar</type>
            
<scope>compile</scope>
        
</dependency>
        
<!-- 
        <dependency>
            <groupId>org.springmodules</groupId>
            <artifactId>spring-modules-cache</artifactId>
            <version>0.9</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        
-->
        
<dependency>
            
<groupId>com.googlecode.ehcache-spring-annotations</groupId>
            
<artifactId>ehcache-spring-annotations</artifactId>
            
<version>1.2.0</version>
            
<type>jar</type>
            
<scope>compile</scope>
            
<exclusions>
                
<exclusion>
                    
<artifactId>spring-aop</artifactId>
                    
<groupId>org.springframework</groupId>
                
</exclusion>
                
<exclusion>
                    
<artifactId>spring-expression</artifactId>
                    
<groupId>org.springframework</groupId>
                
</exclusion>
            
</exclusions>
        
</dependency>
    
</dependencies>
    
<build>
        
<finalName>cluster-test</finalName>
        
<plugins>
            
<plugin>
                
<groupId>org.apache.maven.plugins</groupId>
                
<artifactId>maven-compiler-plugin</artifactId>
                
<configuration>
                    
<source>1.6</source>
                    
<target>1.6</target>
                    
<encoding>UTF-8</encoding>
                
</configuration>
            
</plugin>
            
<plugin>
                
<groupId>org.mortbay.jetty</groupId>
                
<artifactId>jetty-maven-plugin</artifactId>
                
<version>8.0.4.v20111024</version>
                
<!-- 
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.26</version>
                
-->
                
<configuration>
                    
<contextPath>/cluster-test</contextPath>
                    
<scanIntervalSeconds>3</scanIntervalSeconds>
                    
<scanTargetPatterns>
                        
<scanTargetPattern>
                            
<directory>src/main/webapp/WEB-INF</directory>
                            
<excludes>
                                
<exclude>**/*.jsp</exclude>
                            
</excludes>
                            
<includes>
                                
<include>**/*.properties</include>
                                
<include>**/*.xml</include>
                            
</includes>
                        
</scanTargetPattern>
                    
</scanTargetPatterns>
                
</configuration>
            
</plugin>
        
</plugins>
    
</build>
    
<properties>
        
<spring.version>2.5.2</spring.version>
    
</properties>
</project>

posted @ 2012-02-16 17:04 paulwong 阅读(2847) | 评论 (0)编辑 收藏

SPRINGMVC 2.5 例子

http://www.mkyong.com/spring-mvc/spring-mvc-hello-world-annotation-example/

posted @ 2012-02-16 02:14 paulwong 阅读(315) | 评论 (0)编辑 收藏

删除LINUX虚拟网卡

在LINUX安装分布式缓存时,发现了一个奇怪的IP:192.168.122.1,这个IP并无手动配置,怎么会出现呢?输入命令
ifconfig,发现多了个网卡:

virbr0    Link encap:Ethernet  HWaddr 00:00:00:00:00:00
          inet addr:
192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          inet6 addr: fe80::
200:ff:fe00:0/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:
1500  Metric:1
          RX packets:
0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:
116713 errors:0 dropped:0 overruns:0 carrier:0
          collisions:
0 txqueuelen:0
          RX bytes:
0 (0.0 b)  TX bytes:15780234 (15.0 MiB)

经查这是虚拟网卡,于是除之
yum erase libvirt

重启,搞定。

posted @ 2012-02-15 12:47 paulwong 阅读(2401) | 评论 (0)编辑 收藏

Ehcache分布式缓存

从1.2版本开始,Ehcache可以使用分布式的缓存了。

分布式这个特性是以plugin的方式实现的。Ehcache自带了一些默认的分布式缓存插件实现,这些插件可以满足大部分应用的需要。如果需要使用其他的插件那就需要自己开发了,开发者可以通过查看distribution包里的源代码及JavaDoc来实现它。

尽管不是必须的,在使用分布式缓存时理解一些ehcahce的设计思想也是有帮助的。这可以参看分布式缓存设计的页面。
以下的部分将展示如何让分布式插件同ehcache一起工作。

下面列出的是一些分布式缓存中比较重要的方面:

你如何知道集群环境中的其他缓存?分布式传送的消息是什么形式?什么情况需要进行复制?增加(Puts),更新(Updates)或是失效(Expiries)?采用什么方式进行复制?同步还是异步方式?

为了安装分布式缓存,你需要配置一个PeerProvider、一个CacheManagerPeerListener,它们对于一个CacheManager来说是全局的。每个进行分布式操作的cache都要添加一个cacheEventListener来传送消息。

正确的元素类型

只有可序列化的元素可以进行复制。

一些操作,比如移除,只需要元素的键值而不用整个元素;在这样的操作中即使元素不是可序列化的但键值是可序列化的也可以被复制,

成员发现(Peer Discovery)

Ehcache进行集群的时候有一个cache组的概念。每个cache都是其他cache的一个peer,没有主cache的存在。刚才我们问了一个问题:你如何知道集群环境中的其他缓存?这个问题可以命名为成员发现(Peer Discovery)。

Ehcache提供了两种机制用来进行成员发现,就像一辆汽车:手动档和自动档。

要使用一个内置的成员发现机制要在ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class属性为net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory。

自动的成员发现

自动的发现方式用TCP广播机制来确定和维持一个广播组。它只需要一个简单的配置可以自动的在组中添加和移除成员。在集群中也不需要什么优化服务器的知识,这是默认推荐的。

成员每秒向群组发送一个“心跳”。如果一个成员 5秒种都没有发出信号它将被群组移除。如果一个新的成员发送了一个“心跳”它将被添加进群组。

任何一个用这个配置安装了复制功能的cache都将被其他的成员发现并标识为可用状态。

要设置自动的成员发现,需要指定ehcache配置文件中cacheManagerPeerProviderFactory元素的properties属性,就像下面这样:

peerDiscovery=automatic multicastGroupAddress=multicast address | multicast host name multicastGroupPort=port 
# (timeToLive属性详见常见问题部分的描述)
timeToLive=0-255

示例

假设你在集群中有两台服务器。你希望同步sampleCache1和sampleCache2。每台独立的服务器都要有这样的配置:
配置server1和server2

<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties
="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
multicastGroupPort=4446, timeToLive=32"
/>

手动进行成员发现

进行手动成员配置要知道每个监听器的IP地址和端口。成员不能在运行时动态地添加和移除。在技术上很难使用广播的情况下就可以手动成员发现,例如在集群的服务器之间有一个不能传送广播报文的路由器。你也可以用手动成员发现进行单向的数据复制,只让server2知道server1而server1不知道server2。

配置手动成员发现,需要指定ehcache配置文件中cacheManagerPeerProviderFactory的properties属性,像下面这样:

peerDiscovery=manual rmiUrls=//server:port/cacheName, 
rmiUrls配置的是服务器cache peers的列表。注意不要重复配置。

示例

假设你在集群中有两台服务器。你要同步sampleCache1和sampleCache2。下面是每个服务器需要的配置:
配置server1

<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties
="peerDiscovery=manual,
rmiUrls=//server2:40001/sampleCache11|//server2:40001/sampleCache12"
/>

配置server2
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties
="peerDiscovery=manual,
rmiUrls=//server1:40001/sampleCache11|//server1:40001/sampleCache12"
/>

配置CacheManagerPeerListener

每个CacheManagerPeerListener监听成员们发向当前CacheManager的消息。
配置CacheManagerPeerListener需要指定一个CacheManagerPeerListenerFactory,它以插件的机制实现,用来创建CacheManagerPeerListener。

cacheManagerPeerListenerFactory的属性有:
class – 一个完整的工厂类名。
properties – 只对这个工厂有意义的属性,使用逗吃分隔。

Ehcache有一个内置的基于RMI的分布系统。它的监听器是RMICacheManagerPeerListener,这个监听器可以用RMICacheManagerPeerListenerFactory来配置。

<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties
="hostName=localhost, port=40001,
socketTimeoutMillis=2000"
/>

有效的属性是:
hostname (可选) – 运行监听器的服务器名称。标明了做为集群群组的成员的地址,同时也是你想要控制的从集群中接收消息的接口。

在CacheManager初始化的时候会检查hostname是否可用。

如果hostName不可用,CacheManager将拒绝启动并抛出一个连接被拒绝的异常。

如果指定,hostname将使用InetAddress.getLocalHost().getHostAddress()来得到。

警告:不要将localhost配置为本地地址127.0.0.1,因为它在网络中不可见将会导致不能从远程服务器接收信息从而不能复制。在同一台机器上有多个CacheManager的时候,你应该只用localhost来配置。

port – 监听器监听的端口。
socketTimeoutMillis (可选) – Socket超时的时间。默认是2000ms。

配置CacheReplicators

每个要进行同步的cache都需要设置一个用来向CacheManagerr的成员复制消息的缓存事件监听器。这个工作要通过为每个cache的配置增加一个cacheEventListenerFactory元素来完成。

<!-- Sample cache named sampleCache2. -->
<cache name="sampleCache2"
maxElementsInMemory
="10"
eternal
="false"
timeToIdleSeconds
="100"
timeToLiveSeconds
="100"
overflowToDisk
="false">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties
="replicateAsynchronously=true,
replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "
/>
</cache>

name:缓存名称。通常为缓存对象的类名(非严格标准)。

maxElementsInMemory:设置基于内存的缓存可存放对象的最大数目。

maxElementsOnDisk:设置基于硬盘的缓存可存放对象的最大数目。

eternal:如果为true,表示对象永远不会过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false;

timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期。当对象过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态。

timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期。当对象过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义。

overflowToDisk:如果为true,表示当基于内存的缓存中的对象数目达到了maxElementsInMemory界限后,会把益出的对象写到基于硬盘的缓存中。

class – 使用net.sf.ehcache.distribution.RMICacheReplicatorFactory

这个工厂支持以下属性:
replicatePuts=true | false – 当一个新元素增加到缓存中的时候是否要复制到其他的peers. 默认是true。
replicateUpdates=true | false – 当一个已经在缓存中存在的元素被覆盖时是否要进行复制。默认是true。
replicateRemovals= true | false – 当元素移除的时候是否进行复制。默认是true。
replicateAsynchronously=true | false – 复制方式是异步的(指定为true时)还是同步的(指定为false时)。默认是true。
replicateUpdatesViaCopy=true | false – 当一个元素被拷贝到其他的cache中时是否进行复制(指定为true时为复制),默认是true。

你可以使用ehcache的默认行为从而减少配置的工作量,默认的行为是以异步的方式复制每件事;你可以像下面的例子一样减少RMICacheReplicatorFactory的属性配置:

<!-- Sample cache named sampleCache4. All missing RMICacheReplicatorFactory properties default to true -->
<cache name="sampleCache4"
maxElementsInMemory
="10"
eternal
="true"
overflowToDisk
="false"
memoryStoreEvictionPolicy
="LFU">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>
</cache>

常见的问题Windows上的Tomcat

有一个Tomcat或者是JDK的bug,在tomcat启动时如果tomcat的安装路径中有空格的话,在启动时RMI监听器会失败。参见http://archives.java.sun.com/cgi-bin/wa?A2=ind0205&L=rmi-users&P=797和http://www.ontotext.com/kim/doc/sys-doc/faq-howto-bugs/known-bugs.html。

由于在Windows上安装Tomcat默认是装在“Program Files”文件夹里的,所以这个问题经常发生。

广播阻断

自动的peer discovery与广播息息相关。广播可能被路由阻拦,像Xen和VMWare这种虚拟化的技术也可以阻拦广播。如果这些都打开了,你可能还在要将你的网卡的相关配置打开。

一个简单的办法可以告诉广播是否有效,那就是使用ehcache remote debugger来看“心跳”是否可用。

广播传播的不够远或是传得太远

你可以通过设置badly misnamed time to live来控制广播传播的距离。用广播IP协议时,timeToLive的值指的是数据包可以传递的域或是范围。约定如下:

0是限制在同一个服务器
1是限制在同一个子网
32是限制在同一个网站
64是限制在同一个region
128是限制在同一个大洲
255是不限制

译者按:上面这些资料翻译的不够准确,请读者自行寻找原文理解吧。

在Java实现中默认值是1,也就是在同一个子网中传播。改变timeToLive属性可以限制或是扩展传播的范围。

原文地址为 http://ehcache.sourceforge.net/documentation/distributed_caching.html

posted @ 2012-02-14 15:46 paulwong 阅读(9003) | 评论 (2)编辑 收藏

更改MAVEN默认使用的JDK版本

需填加PLUGIN:
  <build>  
    
<plugins>  
      
<plugin>  
        
<groupId>org.apache.maven.plugins</groupId>  
        
<artifactId>maven-compiler-plugin</artifactId>  
        
<configuration>  
          
<source>1.6</source>  
          
<target>1.6</target>  
        
</configuration>  
      
</plugin>  
    
</plugins>  
</build>
再UPDATE PROJECT CONFIGURATION

posted @ 2012-02-14 14:36 paulwong 阅读(3607) | 评论 (0)编辑 收藏

我们应当怎样做需求调研:迭代

前面我一直在反复强调这样一个观点,需求分析不是一蹴而就的,是一个反复迭代的过程。它将从第一次需求分析开始,一直持续到整个项目生命周期。为什么这样说呢?让我们一起来分析分析。

在第一次的需求分析阶段,我们在一段时期内需要与客户进行反复地讨论,这个过程往往是这样一个反复循环的过程:需求捕获->需求整理->需求验证->再需求捕获••••••

需求捕获,就是我们与客户在一起开研讨会,讨论需求的活动。客户可能会描述他们的业务流程,这时我们在纸上绘制简单的流程草图,及时地记录下来;客户在描述业务的同时,可能会反复提到一些业务名词,详细询问这些名词的含义,以及它们与其它名词的关系,用类图或者对象图绘制简单的草图;客户在描述业务的同时,还会提出今后的软件希望实现的功能,如能够展示某个报表、能够导出文件,以需求列表的形式记录下来。一个功能,在需求列表中会有多个需求,而每个需求应当能够用1、2句话,在20个字以内就可以描述清楚。需求列表是客户提出的最最原始的需求,他不掺杂任何分析设计,是我们的每项功能必须实现的内容。需求列表是需求验证以及日后的用户验收测试的依据,不论我们今后如何分析和设计这些功能,都要能如实地实现这个列表中提出的需求。(需求列表应当如何编写,将在后面的章节详细描述。)

需求整理,就是在需求研讨会后,需求分析人员对研讨内容的分析和整理的过程。首先,需求分析人员应当通过用例模型,划分整个系统的功能模块,以及各个模块的业务流程。用例模型分析是一个由粗到细的过程,这样一个过程也是符合人类认识世界的思维习惯的一个过程。最先,我们应当对整个系统绘制用例图,设计用例场景,并依次对这些用例进行用例描述、流程分析、角色分析等分析过程。当然,在整体用例分析的同时,我们还应当进行一个整体的角色分析,绘制一个角色分析图,进行一个流程分析,绘制一个流程分析图(可以是传统的流程图、UML中的行动图,甚至一个简单的示意图,等等)。

然后,我们再在整体用例图的基础上,依次对每个用例绘制用例图。每个用例图中,会更细致地划分出多个用例,并依次进行用例描述、流程分析、角色分析等分析工作。如此这般地不断细化,直到我们认为需求已经描述清楚为止。

在一个系统中,用例需要细化几次,是由这个用例的业务复杂程度决定的。对于一个简单的用例,只需要细化一次就够了;而对于比较复杂的用例,则需要细化2~3次,甚至更多。

用例分析的过程,之所以称之为分析,它掺入了很多需求分析人员对业务的理解与设计:模块如何划分、流程如何设计、业务如何转换,等等。用例分析,还需要让需求分析员与架构师、设计师等技术人员共同协作来完成,因为用例分析还包含对业务需求的技术可行性分析。只有一份可行的需求分析,才能为后续的设计开发扫清障碍,有效降低项目风险。最后,需求分析员应当将需求列表中的内容,逐一地与用例进行核对,以避免分析人员忽略用户的某项业务需求。(后面将详细描述用例模型的搭建过程。)

在用例分析的同时,需求分析人员还需要对业务中的相关事物,制作领域模型。领域模型,是对用户业务领域中相关事物、相互关系、相互行为操作的描述,它是以对象图和类图的形式表达的。需求人员对领域模型的分析,对业务理解的深度,对日后软件的设计,以及软件的功能扩展、升级演化,都起到了至关重要的作用。(后面将更加详细地讲述领域模型。)

最后,当我们完成了一系列的分析整理并形成文档以后,应当对及时地与客户进行反馈,确认我们的理解是否正确,也就是需求验证工作。需求验证工作应当贯穿整个研发周期,并且在不同时期表现出不同的形式。首先,在需求分析阶段,需求验证工作表现为对需求理解是否正确的信息反馈。需求分析人员与客户再次坐在一起,一项一项描述我们对需求的整理和理解,客户则时不时地对一些问题进行纠正,或者更加深入地加以描述。我们则认真地记录,回来整理,并等待下一次的验证。在需求分析后期,我们还可以制作一些简单的原型,更加形象地描述我们对需求的理解,会使我们与客户的沟通更加顺畅。随后的设计开发阶段,我们则应当以迭代开发的形式进行。每开发完一个迭代周期,将开发的成果与客户反馈。这样做的结果是,客户可以及时地提出我们对需求理解的偏差,或者及时提出对我们设计不满意的地方,使我们存在的问题得到及时地发现与解决。问题及时的解决,使我们修复问题的代价得以降至最小。之后,当开发进入到验收测试阶段,我们则是与客户一道,一项一项地验证我们的软件是否满足需求列表中要求的业务需求。最后,当软件迎来下一次升级开发时,我们将开启另一次轮回。

因此,需求分析就是按照这样的过程,每次多理解一些,再多理解一些,更多理解一些,逐渐深入的过程。每深入一步,我们的软件就更接近客户的满意。

我们应当怎样做需求分析
我们应当怎样做需求调研:初识
我们应当怎样做需求调研:拜访
我们应当怎样做需求调研:研讨会
我们应当怎样做需求调研:需求研讨
我们应当怎样做需求调研:迭代
(续)

posted @ 2012-02-14 01:12 paulwong 阅读(547) | 评论 (0)编辑 收藏

仅列出标题
共114页: First 上一页 87 88 89 90 91 92 93 94 95 下一页 Last