??xml version="1.0" encoding="utf-8" standalone="yes"?> 1?/span>首先下蝲apache2.2 下蝲地址Q?/font>http://mirrors.sohu.com/apache/httpd-2.2.16.tar.gz 2?/span>然后下蝲SVN a) 包:subversion-deps-1.6.12.tar.gz i. 地址Q?font face="Times New Roman">http://subversion.tigris.org/downloads/subversion-deps-1.6.12.tar.gz b) susubversion-1.6.12.tar.gz 地址Q?/font> i. http://subversion.tigris.org/downloads/subversion-1.6.12.tar.gz 3?/span>然后解析Apache2Q安?/font> 4?/span>tar -zxvf httpd-2.2.16.tar.gz 5?/span>安装Apache a) ./configure -prefix=/opt/web/apache2 --enable-mods-shared=all --enable-so b) Make c) Make install 6?/span>?font face="Times New Roman">APACHE是否安装成功Q?/font> 启动apache:/opt/web/apache2/bin/apachectl start 7?/span>安装Svn 8?/span>首先解压Q?nbsp;tar -zxvf subversion-deps-1.6.12.tar.gz 9?/span>然后解压Q?font face="Times New Roman">tar -zxvf susubversion-1.6.12.tar.gz 10?/span>安装命oQ?/span> a) ./configure --prefix=/opt/web/subversion --with-apxs=/opt/web/apache2/bin/apxs --with-apr=/opt/web/httpd-2.2.16/srclib/apr --with-apr-util=/opt/web/httpd-2.2.16/srclib/apr-util 11?/font>make make install 1、首先检查一下linux下是有存在一下YӞ如果不存在,必须这些Y件安?br />
rpm -q compat-libstdc++-33 elfutils-libelf elfutils-libelf-devel glibc glibc-common glibc-devel gcc- gcc-c++ libaio-devel libaio libgcc libstdc++ libstdc++-devel make sysstat unixODBC unixODBC-devel 然后创徏数据库的所有者: if [$USER = "Oracle"] ; then
]]>
]]>
]]>
2、以root方式登陆Q修?etc/sysctl.confQ在文g中增加一下参?br />
kernel.shmall = 2097152
kernel.shmmax = 2147483648
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
fs.file-max = 65536
net.ipv4.ip_local_port_range =1024 65000
net.core.rmem_default = 4194304
net.core.rmem_max = 4194304
net.core.wmem_default =262144
net.core.wmem_max =262144
groupadd oinstall
groupadd dba
useradd -g oinstall -G dba Oracle 创徏用户Oracle
passwd OracleQ修改Oracle的密码)
修改Oracle用户的限?br />
cd /etc/security
vi limits.conf
在该文g增加一下内?br />
Oracle soft nproc 2047
Oracle hard nproc 16384
Oracle soft nofile 1024
Oracle hard nofile 65536
在文?etc/pam.d/login文g增加一下内?br />
session required /lib/security/pam_limits.so
session required pam_limits.so
在文?etc/profile增加一下内?/p>
if [ $SHELL = " /bin/ksh" ]; then
ulimit -p 16384
ulimit -n 65535
else
ulimit -u 16384 -n 65536
fi
fi
创徏安装目录Q?br />
mkdir -p /opt/app/oracle
chown -P Oracle:oinstall /opt/app
chmod -R 755 /opt/app
执行命oQ解压安装文?Qunzip linux_11gR1_database.zip
然后q入解压后的目录Q执?br />
$ ./runInstaller
]]>
首先我们看看Directory 的类
public abstract class Directory {
volatile boolean isOpen = true;
/*** 持有一个LockFactory的实例(实现锁定q个目录实例Q?/
protected LockFactory lockFactory;
/**q回该目录下的所有文件数l?如果q个目录下没有文件存在,或者存在权限问题不能访问,该方法可能返回Null*/
public abstract String[] list() throws IOException;
/** * q回指定名称的文件是不是存在 */
public abstract boolean fileExists(String name)
throws IOException;
/**q回指定名称的文件最后修改的旉
public abstract long fileModified(String name) throws IOException;
/**讄指定文g名的文g最后的修改旉为当前时?/
public abstract void touchFile(String name) throws IOException;
/**删除指定文g?*/
public abstract void deleteFile(String name)throws IOException;
/**q回指定文g的长度?*/
public abstract long fileLength(String name) throws IOException;
/** 在当前目录下使用l定的名U创Z个空的文件。ƈ且返回一个流来写该文件?/
public abstract IndexOutput createOutput(String name) throws IOException;
/** * Lucene使用该方法确保所有的针对该文件的写操作都会存储到Index。ƈ且阻止machine/OS发生故障 破坏该index?/
public void sync(String name) throws IOException {}
/**获取已经存在的一个文件的IndexInput操作该文g?*/
public abstract IndexInput openInput(String name) throws IOException;
/** q回已经存在的一个文件、ƈ且用指定大的~冲的IndexInputQ但是当前目录也可能忽略该缓冲池的大,
* 当前主要是考虑CompoundFileReader和FSDirectory对于ơ参数的需求?/
public IndexInput openInput(String name, int bufferSize) throws IOException {
return openInput(name);
}
/** 创徏一个指定名U的?
public Lock makeLock(String name) {
return lockFactory.makeLock(name);
}
/**
* 清除指定的锁定(解锁和删除)q不仅要求在q个时候当前的锁一定不在用?*/
public void clearLock(String name) throws IOException {
if (lockFactory != null) {
lockFactory.clearLock(name);
}
}
/** l束q个store. */
public abstract void close() throws IOException;
/**讄LockFactoryQ此目录实例应光定执行。每个LockFactory实例只用于一个目录(卻I不要q一个实例在多个目录Q?*/
public void setLockFactory(LockFactory lockFactory) {
this.lockFactory = lockFactory;
lockFactory.setLockPrefix(this.getLockID());
}
/** 获得LockFactoryQ此目录例实例用其锁定执行。请注意Q这可能是无效的目录执行Q提供自己锁执行*/
public LockFactory getLockFactory() {
return this.lockFactory;
}
/**q去锁实例的唯一表示ID的字W串描述*/
public String getLockID() {
return this.toString();
}
/**// 拯源目录src下的文gQ复制到目的目录dest下面Q拷贝完成后关闭源目录src*/
public static void copy(Directory src, Directory dest, boolean closeDirSrc) throws IOException {
final String[] files = src.list();
if (files == null)
throw new IOException("cannot read directory " + src + ": list() returned null");
byte[] buf = new byte[BufferedIndexOutput.BUFFER_SIZE];
for (int i = 0; i < files.length; i++) {
IndexOutput os = null;
IndexInput is = null;
try {
// create file in dest directory
os = dest.createOutput(files[i]);
// read current file
is = src.openInput(files[i]);
// and copy to dest directory
long len = is.length();
long readCount = 0;
while (readCount < len) {
int toRead = readCount + BufferedIndexOutput.BUFFER_SIZE > len ? (int)(len - readCount) : BufferedIndexOutput.BUFFER_SIZE;
is.readBytes(buf, 0, toRead);
os.writeBytes(buf, toRead);
readCount += toRead;
}
} finally {
// graceful cleanup
try {
if (os != null)
os.close();
} finally {
if (is != null)
is.close();
}
}
}
if(closeDirSrc)
src.close();
}
从Directory抽象cȝ定义Q我们可以得到如下几点:
1、管理锁工厂及其锁实例;
2、管理Directory目录实例的基本属性,主要是通过文g名称q行理Q?br />
3、管理与操作该目录相关的一些流对象Q?br />
4、管理烦引文件的拯?br />
2?/span>我们首先看看IndexWriter的构造函?/font>
public IndexWriter(String path, Analyzer a, boolean create, MaxFieldLength mfl)
public IndexWriter(String path, Analyzer a, boolean create)(废弃Q不提倡?
public IndexWriter(File path, Analyzer a, boolean create, MaxFieldLength mfl)
public IndexWriter(File path, Analyzer a, boolean create)(废弃Q不提倡?
public IndexWriter(Directory d, Analyzer a, boolean create, MaxFieldLength mfl)
public IndexWriter(Directory d, Analyzer a, boolean create)(废弃Q不提倡?
public IndexWriter(String path, Analyzer a, MaxFieldLength mfl)
public IndexWriter(String path, Analyzer a)(废弃Q不提倡?
public IndexWriter(File path, Analyzer a, MaxFieldLength mfl)
public IndexWriter(File path, Analyzer a)(废弃Q不提倡?
public IndexWriter(Directory d, Analyzer a, MaxFieldLength mfl)
public IndexWriter(Directory d, Analyzer a)(废弃Q不提倡?
public IndexWriter(Directory d, boolean autoCommit, Analyzer a)(废弃Q不提倡?
public IndexWriter(Directory d, boolean autoCommit, Analyzer a, boolean create)Q废弃,不提倡?
public IndexWriter(Directory d, Analyzer a, IndexDeletionPolicy deletionPolicy, MaxFieldLength mfl)
public IndexWriter(Directory d, boolean autoCommit, Analyzer a, IndexDeletionPolicy deletionPolicy)Q废弃,不提倡?
public IndexWriter(Directory d, Analyzer a, boolean create, IndexDeletionPolicy deletionPolicy, MaxFieldLength mfl)
public IndexWriter(Directory d, boolean autoCommit, Analyzer a, boolean create, IndexDeletionPolicy deletionPolicy)Q废弃,不提倡?
l心的读者肯定会发现Q废弃的构造函C提倡用的构造函敎ͼ多了一个MaxFieldLength参数。带有该参数的构造函数都是允许正怋用的?/font>Q注释:废弃的构造函数将在Lucene3.0中移除)
仔细查看其构造函数的实现呢,最l都转化成一个私有的构造函敎ͼ如图Q?/font>
/**
* 该构造函C要是创徏一个IndexWrite对象
* d Q指定的存放建立索引文g的烦引目?/font>
* a QAnalyzer 分词分析?/font>
* create Q是否要重新写入索引文gQ如果ؓtrueQ则重写索引文gQ如果ؓfalseQ则q加写入索引文g
* closeDir Q一个boolean型变量,表示是否关闭索引目录Directory dQ与IndexWriter的一个成员变量相?/font>
* deletionPolicy Q指定删除烦引文件用的{略
* autoCommit Q徏立烦引文件后Q自动提交?/font>
* maxFieldLength Q?nbsp;表示索引中Field的最大长度?/font>
*/
private void init(Directory d, Analyzer a, final boolean create, boolean closeDir, IndexDeletionPolicy deletionPolicy, boolean autoCommit, int maxFieldLength)
throws CorruptIndexException, LockObtainFailedException, IOException {
this.closeDir = closeDir;
directory = d;
analyzer = a;
setMessageID(defaultInfoStream);//q里主要是指定infoStreamQ是一个PrintStream输出对?/font>
this.maxFieldLength = maxFieldLength; //指定Field数据的最大长?/font>
if (create) {
// 如果是重新创建烦引文?/font>Q?/font>清除写锁文gwrite.lock
directory.clearLock(WRITE_LOCK_NAME);
}
Lock writeLock = directory.makeLock(WRITE_LOCK_NAME);
if (!writeLock.obtain(writeLockTimeout)) // 获取写锁文g
throw new LockObtainFailedException("Index locked for write: " + writeLock);
this.writeLock = writeLock; //保存新的写锁文g
try {
if (create) {
// 如果create为trueQ表C重写烦引文件。重写烦引文件之前,要先d已经存在的烦引文Ӟq且要清除掉历史写入的segment信息
try {
segmentInfos.read(directory);
segmentInfos.clear();
} catch (IOException e) {
}
segmentInfos.commit(directory); // 向指定的索引存放目录中写入segment信息
} else {
segmentInfos.read(directory); //dsegment信息
// We assume that this segments_N was previously
// properly sync'd:
for(int i=0;i<segmentInfos.size();i++) {
final SegmentInfo info = segmentInfos.info(i);
List files = info.files();
for(int j=0;j<files.size();j++)
synced.add(files.get(j));
}
}
this.autoCommit = autoCommit; //执行提交写入索引的标?nbsp;
setRollbackSegmentInfos(segmentInfos); //克隆原来?/font>segment状态信息,q且信息保存到HashSet?/font>
docWriter = new DocumentsWriter(directory, this); //创徏一个DocumentsWriter对象
docWriter.setInfoStream(infoStream); //讄DocumentsWriter对象?/font>infoStream信息
docWriter.setMaxFieldLength(maxFieldLength); //讄DocumentsWriter对象?/font>maxFieldLength信息
//默认的删除策略实现类为KeepOnlyLastCommitDeletionPolicyQ它只是保证最q提交删除的索引文gQ提交删除动?nbsp;
// IndexFileDeleter deleter是IndexWritercȝ一个私有的成员变量Q它在org.apache.lucene.index包里面,主要对删除烦引文件进行实现和理
deleter = new IndexFileDeleter(directory,
deletionPolicy == null ? new KeepOnlyLastCommitDeletionPolicy() : deletionPolicy,
segmentInfos, infoStream, docWriter);
pushMaxBufferedDocs(); //hDocsBuffer?/font>
if (infoStream != null) { //如果infoStream是null
message("init: create=" + create);
messageState();
}
} catch (IOException e) {
this.writeLock.release();
this.writeLock = null;
throw e;
}
}
通过IndexWrite的构造函敎ͼ以及最l的实现Ҏ的init分发Q其主要是实CҎ指定的徏立烦引的方式(重写、追加写?Q通过create标志位来判断Q从而指定一U在操作索引文g的过E中删除索引文g的策略?/font>
在理解lucene的时_必须熟悉其初始化IndexWrite的原理,才能深入了解该框架在创徏索引的核心实现机制?/font>
FiledcL代码解析
首先Filed内部定义了三个静态类Store、Index、TermVector?br />
//定一个静态类Store Q主要ؓ了设|Field的存储属?br />
public static final class Store extends Parameter implements Serializable {
private Store(String name) {
super(name);
}
//使用压羃的方式来存储Field的?br />
public static final Store COMPRESS = new Store("COMPRESS");
//在烦引中存储Field的?br />
public static final Store YES = new Store("YES");
//在烦引中不存贮Field的?br />
public static final Store NO = new Store("NO");
}
//定一个静态类Index Q主要ؓ了设|Field的烦引属?br />
public static final class Index extends Parameter implements Serializable {
private Index(String name) {
super(name);
}
//不对Fieldq行索引Q所以这个Field׃能被索到(一般来_建立索引而它不被检索,q是没有意义?
//如果对该Fieldq设|了Field.Store为Field.Store.YES或Field.Store.COMPRESSQ则可以?br />
public static final Index NO = new Index("NO");
//对Fieldq行索引Q同时还要对其进行分?由Analyzer来管理如何分?
public static final Index ANALYZED = new Index("ANALYZED");
//废弃的属性,使用ANALYZED 来替?br />
public static final Index TOKENIZED = ANALYZED;
//对Fieldq行索引Q但是不对该Field使用分词
public static final Index NOT_ANALYZED = new Index("NOT_ANALYZED");
//废弃的属性,使用NOT_ANALYZED来替?br />
public static final Index UN_TOKENIZED = NOT_ANALYZED;
//即不对Field索引Q也不对其用Analyzer来分?br />
public static final Index NOT_ANALYZED_NO_NORMS = new Index("NOT_ANALYZED_NO_NORMS");
//废弃的属性,有NOT_ANALYZED_NO_NORMS来替?br />
public static final Index NO_NORMS = NOT_ANALYZED_NO_NORMS;
//对Field属性,使用分词Q但是不是用Analyzer来分?br />
public static final Index ANALYZED_NO_NORMS = new Index("ANALYZED_NO_NORMS");
}
q是一个与词条有关的类。因为在索的时候需要指定检索关键字Q通过Z个Fieldd一个TermVectorQ就可以在检索中把该Field索到?br />
public static final class TermVector extends Parameter implements Serializable {
private TermVector(String name) {
super(name);
}
//不存?br />
public static final TermVector NO = new TermVector("NO");
//为每个Document都存储一个TermVector
public static final TermVector YES = new TermVector("YES");
//存储Qƈ且存在位|信?br />
public static final TermVector WITH_POSITIONS = new TermVector("WITH_POSITIONS");
//存储Qƈ且存贮偏U量信息
public static final TermVector WITH_OFFSETS = new TermVector("WITH_OFFSETS");
//存储位置、偏U量{所有信?br />
public static final TermVector WITH_POSITIONS_OFFSETS = new TermVector("WITH_POSITIONS_OFFSETS");
}
构造函?br />
public Field(String name, String value, Store store, Index index)
public Field(String name, String value, Store store, Index index, TermVector termVector)
public Field(String name, Reader reader)
public Field(String name, Reader reader, TermVector termVector)
public Field(String name, TokenStream tokenStream)
public Field(String name, TokenStream tokenStream, TermVector termVector)
public Field(String name, byte[] value, Store store)
public Field(String name, byte[] value, int offset, int length, Store store)
程Q?br />
首先查name以及values不能为空以及Nnull?br />
然后查是否是存储以及是否分词如果两者都是NoQ则抛出异常?br />
然后查是否存储以及是否向量分词,如果两者都是No'Q则抛出异常?br />
然后该Filed的名U命令传递过来的名称源代码(this.name = name.internQ?br />
表示获取JVM String帔R池的地址
此时ҎStore的|来设|是否存储以及压~的?br />
然后ҎIndex的|来设|是否烦引、是否分词、omitNorms 的g及置是否是二q制
然后开始根据TermVector 的|来设|是否存储向量、是否存储偏U量、是否存储位|等信息?br />
以上信息可能存在分析不正,请大家给指证Q本语有不怎么好,
Document是lucene自己定义的一U文件格式,lucene使用docement来代替对应的物理文g或者保存在数据库中的数据。因此Document只能作ؓ数据源在Lucene中的数据存贮的一U文件Ş式?/font>
Document只是负责攉数据源,因ؓ不同的文件可以构建同一个Document。只要用户将不同的文件创建成Documentcd的文ӞLucenep快速找到查扑ƈ且用他们?/font>
对于一个Document文gQ可以同时增加多个Field。Lucene中对于每个数据源是用FieldcL表示的。多个Fieldl成一个DocumentQ多个Documentl成一个烦引文件。Document与Field关系如果一所C?/font>
此时Q我们去看看Documentq个cȝ源代码。Document采用默认不带参数的构造函敎ͼ但是我们他在创徏的时_
产生两个变量Q?/font>fields?nbsp;boost
其中fields是创Z一个arrayList,其主要是保存Fieldc?/font>
Boost主要是设|该doc的优先
其方法:addQFieldable fieldQ增加一个field对象
removeField(String name) ҎnameU除一个ield对象Q找C个就q回Q?/font>
removeFields(String name) ҎnameU除所有的field对象
Field getField(String name) Ҏ名字扑ֈ该Field对象?/font>
Fieldable getFieldable(String name) Ҏ名字扑ֈFieldable子类QFieldable?/font> 接口Q具体有Filed来实玎ͼ
String get(String name) Ҏ名字Q找到给Filed对象中包含的内容
public final byte[] getBinaryValue(String name) 主要查找Doc中包含有二进制field 数据Q如果不存在Q则q回nullQ?/font>
public final List getFields() 直接q回该Doc中包含的Field?br />
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.log4j.Logger;
/**
* @author Administrator
*
*/
public class DBConnectionFactory {
/**
* Logger for this class
*/
private static final Logger logger = Logger
.getLogger(DBConnectionFactory.class);
private static ObjectPool connectionPool=null;
private static String sqlJdbc = "";
private static String sqlUser = "";
private static String sqlPwd = "";
@SuppressWarnings("unchecked")
private static Class driverClass=null;
public static void proDBConnectionFactory(){
if(sqlJdbc.equals("")){
sqlJdbc = "jdbc:jtds:sqlserver://27.0.0.1:1433/finance;tds=8.0;lastupdatecount=true";
}
if(sqlUser.equals("")){
sqlUser = "sa";
}
if(sqlPwd.equals("")){
sqlPwd = "11111";
}
initDataSource();
}
public synchronized static void initDataSource(){
if(driverClass == null){
try {
driverClass = Class.forName("net.sourceforge.jtds.jdbc.Driver");
} catch (ClassNotFoundException e) {
logger.error("在注册驱动名U的旉发生错误Q?+e.getMessage(),e);
}
}
if(connectionPool == null){
setupDriver(sqlJdbc);
//printDriverStats();
}else{
System.out.print("q接池已l存?);
}
try {
// printDriverStats();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
proDBConnectionFactory();
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:apache:commons:dbcp:FinanceDBPool");
} catch(SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void printDriverStats() throws Exception {
PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
ObjectPool connectionPool = driver.getConnectionPool("FinanceDBPool");
System.out.println("zd的连? " + connectionPool.getNumActive());
System.out.println("I闲的连? " + connectionPool.getNumIdle());
}
public static void setupDriver(String connectURI){
try {
connectionPool = new GenericObjectPool(null);
ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectURI,sqlUser,sqlPwd);
@SuppressWarnings("unused")
PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,connectionPool,null,null,false,true);
Class.forName("org.apache.commons.dbcp.PoolingDriver");
PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
//driver.registerPool("FinanceDBPool",connectionPool);
driver.registerPool("FinanceDBPool",connectionPool);
} catch (ClassNotFoundException e) {
logger.error("注册q接池的旉发生错误:"+e.getMessage(),e);
} catch (SQLException e) {
logger.error("注册q接池的旉发生错误:"+e.getMessage(),e);
}
}
String s = new String("The Java platform is the ideal platform for network computing");
StringTokenizer st = new StringTokenizer(s);
System.out.println( "Token Total: " + st.countTokens() );
while( st.hasMoreElements() ){
System.out.println( st.nextToken() );
}
l果为:
Token Total: 10
The
Java
platform
is
the
ideal
platform
for
network
computing
String s = new String("The=Java=platform=is=the=ideal=platform=for=network=computing");
StringTokenizer st = new StringTokenizer(s,"=",true);
System.out.println( "Token Total: " + st.countTokens() );
while( st.hasMoreElements() ){
System.out.println( st.nextToken() );
}
l果为:
Token Total: 19
The
=
Java
=
platform
=
is
=
the
=
ideal
=
platform
=
for
=
network
=
computing
4Q?安全保密设计
说明在数据库的设计中Q将如何通过区分不同的访问者、不同的讉Kcd和不同的数据对象Q进行分别对待而获得的数据库安全保密的设计考虑?/P>
现在你正在设计其中的一个特性,已经发现了需求的一些问题。你可以用多U不同的方式解释需?5Q需? 的说明正好与需?1相反Q你因该怿哪一个?需?4非常含糊Q你Ҏ不明白它的意思;你不得不׃一个小时与2位开发h员讨论需?0Q只因ؓ你们对其各有各的理解Qƈ且,唯一能够澄清q些问题的客h有给你们{复。你被迫破解众多需求的含义Qƈ且你能预料到Q如果你错了Q你要做大量的重复工作?
许多软g需求说明书QSRSQ写得非常糟p。Q何品的质量需要其原始材料的质量保证,p糕的Y仉求说明书不可能ZU的Y件。不q的是,几乎没有开发h员受q与需求的抽象、分析、文、质有关的教肌Ӏ而且Q没有非常多的好需求可以借鉴学习Q部分原因是很少有工E可以找C个好的借鉴Q其他原因是公司不愿意将其品说明书攑֜公共区域?/P>
q篇文章描述了高质量需求叙q和说明的几个特性(特点Q。我们将用这些观Ҏ查一些有~陷的需求,带着痛楚重新~写。而且我会谈一些如何编写好的需求的提示。你也许想通过q些质量标准评估你的工程需求。对于修订,也许q了Q但你会学到一些有用的东西Qƈ帮助你的组在下ơ编写出更好的需求?
不要期望能够~写Z份能体现需求应具备的所有特性的SRS。无Z怎么l化、分析、评论和优化需求,都不可能辑ֈ完美。但是,如果你牢记这些特性,你就会编写出更好的需求,生出更好的产品?/P>
高质量需求叙q的Ҏ?/P>
我们如何从一些有问题的需求中分L出好的Y仉求?q一节将分别介绍需求叙q应体现?个特性,下一节将从整体上介绍SRS文应具备的Ҏ。判断每个需求是否具备应有的Ҏ的一U方式是由持有不同观点的工程资金理人所作的正规查。另一U有力的Ҏ是在~写代码前依据需求编写测试例子。测试例子能够明显现在需求中描述的品行为(Ҏ)Q能够显现缺陗冗余和含糊之处?
正确Q每个需求必ȝ描q要交付的功能。正性依据于需求的来源Q如真实的客h高别的pȝ需求说明书。一个Y仉求与其对应的pȝ需求说明书相抵触是不正的Q当Ӟpȝ需求说明书本n可能不正)?/P>
只有用户的代表能够决定用户需求的正确性,q就是ؓ什么在查需求时Q要包括他们或他们的代理的关键所在。不包括用户的需求检查就会导致开发h员的Q“这是没意义的”,“这可能是他们的意思”等众所周知的猜?
可行性:在已知的能力、有限的pȝ及其环境中每个需求必L可实现的。ؓ了避免需求的不可行性,在需求分析阶D应该有一个开发h员参与,在抽象阶D应该有市场人员参与。这个开发h员应能检查在技术上什么能做什么不能做Q哪些需要需要额外的付出或者和其他的权衡?/P>
必要性:每个需求应载明什么是客户实需要的Q什么要应于外部的需求,接口或标准。每个需求源于你认可、具有权说明需求的原始资料Q这是考虑必需的另外情形(译注Q此句翻译不,请参照原文:Another way to think of “necessary?is that each requirement originated from a source you recognize as having the authority to specify requirementsQ。跟t每个需求回溯到出处Q如用例Q系l需求,规章Q或来自其他用户的意见。如果你不能标识出处Q可能需求只是个镀金的例子Q没有真正的必须?/P>
优先权:Z表明在一个详l的产品版本中应包含哪些要点Q需要ؓ每个需求,特征Q或用例分配实现的优先权。客h其代理都应有强烈的责d立优先权。如果所有的需求都被视为同{重要,那么׃在开发中Q预削减,计划时或组员的dD新的需求时Q?目l理不能vC用。优先权的作用是提供l客L价|实现的相兌用,实现相关联的有关技术风险?
我是?U别的优先权:高优先权表明需求必M现在下一个品版本中Q中优先权表明需求是必须的,但是如果需要可以推q到晚一些的产品版本中,低优先权表明有它很好Q但我们必须认识到如果没有充的旉或资源,它可以被攑ּ掉?/P>
明确Q需求叙q的读者应只能从其得到唯一的解释说明,同样Q一个需求的多个读者也应达成共识。自然语a极易D含糊。要避免使用一些对于SRS作者很清楚但对于读者不清楚的主观词汇,如:用户友好性,ҎQ简单,快速,有效Q几个,艺术U,改善的,最大,最等{。每写一个需要都应简z,单,直观的采用用L知的语言Q不要采用计机术语。检查需求模p的有效方式包括需求说明书的正规检查,Ҏ需求写试Q徏立用L假想来说明品某个特定部分预期的Ҏ?
可证实:看你是否能够做出试计划或其他验证方式,如检查和实证Q来军_在品中每个需求是否正的实现。如果需求是不可验证的,军_需求是不是正确的实现就成了判断的事。需求之间不一_不可行,不明也能导致不可证实。Q何需求如果说产品要支持什么也是不可证实的?/P>
高质量需求说明的特征
一个完整的SRS不仅是包括长长的功能性需求列表,q包括外部接口描q和一些诸如质量属性,期望性能的非功能性需求。下面描qC高质量的SRS的一些特性?/P>
完整Q不应该遗漏要求和必需的信息。完整性也是一个需求应具备的。发现缺的信息很难Q因为根本不存在。在SRS中将需求以分层目录方式l织Q将帮助评审人员理解功能性描q的l构Q他们很容易指出遗q东西?/P>
在需求抽象时Q相对于pȝ功能Q你q多的注意用L业务Q将D在需求的全局观和引进不是真正必需的需求上昑־不。在需求抽象上Q应用用例方法会发挥很好的作用。能够从不同角度察看需求的囑Ş分析模型也可以检查出不完整性?
如果你知道已~少一些信息,使用TBDQto be determinedQ标准标志可以突些缺P当你在构Z品的相关部分Ӟ可以从一个给定的需求集中解x有的~陷?/P>
一致性:一致性需求就是不要于其他的Y仉求或高别的pȝQ商业)需求发生冲H。需求中的不一致必d开发开始前得到解决。只有经q调研才能确定哪些是正确的。修攚w求时一定要谨慎Q如果只审定修改的部分,没有审定于修改相关的部分Q就可能D不一致性?/P>
可修Ҏ:当每个需求的要求修改了或l护其历史更ҎQ你必须能够审定SRS。也是说每个需求必ȝ对于其他需求有其单独的标示和分开的说明,便于清晰的查阅。通过良好的组l可以需求易于修改,如:相关的需求分l,建立目录表,索引Q以及前后参考(照)?/P>
可追t:你应能将一个Y件与其原始材料相对应Q如高pȝ需求,用例Q用L提议{。也能够Y仉求与设计元素Q源代码Q用于构造实现和验证需求的试相对应。可q踪的需求应该具有独立标C,l密和结构化的编写,不应q大Q不应是叙述性的文字和公告式的列表?需求质量的评审
q些有关需求质量的Ҏ的描述在理Z都是非常好的Q但一个好的需求到底是个什么样子的呢?Z体现得更切合实际Q我们做个小l习。下面有几个从实际的工程选出的需求,依据上面的质量标准,评估每个需求,看看有什么问题,然后用更好的方式重写。我对每个例子都提q分析和改q的。也Ƣ迎你提Z同的见解。我所占优的只是我知道每个需求的出处。因Z我都不是真正的客P我们只能猜测每个需求的意图?/P>
?Q“品应在不于?0U的正常周期内提供状态信息?BR> q个需求是不完整的Q状态信息是什么,如何昄l用戗这个需求有几处含糊。我们在谈论产品的哪部分Q状态信息间隔真的假定ؓ不少?0U?Q甚者每10q显CZ条新的状态信息也可以Q也许它的意图是消息间隔不应过60U,那么1毫秒是不是太短?“每”这个词D了不定性。问题的后果Q就是需求的不可证实?BR>弥补~陷Q重写需求的一U方法:
1、状态信?BR> 1Q?后台d理器因该以误差上下不超q?0U的60U间隔,在用L面的指定位置昄状态信?BR> 1Q?如果后台q程处理正常Q那么应该显CZQ务已完成的百分数/?BR> 1Q?d完成Ӟ应显C相关的信息
1Q?后台d出错应该昄错误信息
Z分别试和追t,我将其分成了多个需求。如果将几个需求串接在一节中Q在构造和试时就很容易漏掉一个?/P>
?Q“品应瞬间在显C和隐藏不可打印字符间切换?
计算机在瞬间不能做Q何事Q所以这个需求不切实可行。它的不完整性表现在没有声明触发状态切换的条g。Y件要在某些条件下更改自己Q或者用户ؓ了模仿更改要做一些动作?而且Q在文档中改变显C的范围是多大:选中的文本,整个的文,或其他的Q这也是个模p的问题。不可打印字W合隐藏字符一样吗Q或者是一些属性标志或一些控制字W?问题的后果,是需求的不可证实?
象这L写需求也许更好一些:“用戯够在一个由特定触发条gȀzd于编辑的文中在昄和隐藏所有HTML标记间切换”。现在就很清楚,不可打印字符是HTML标记。由于没有定义触发条Ӟ需求对设计没有U束力。只有设计h员选定了触发条件后Q你才能~写试验证触发的正操作?/P>
?Q“HTML分析器可以生HTML标记错误报告Q帮助HTML入门者快速解决错误”。单词“快速”其模p,没 有加q错误报告的定义也是光完整。我不知道,你怎么验证q个需求。找一个自UCؓHTML的入门者,看看能不能根据错误报告快速解决错误?
试试q个Q“HTML分析器可以生一个错误报告,错误报告包含有在被分析文件中出错的HTML文本和行号以及错误的描述。如果没有错误,׃会生错误报告”。现在我们知道了Q什么会被加到出错报告中Q但是出错报告是个什么样子,则留p计h员决定。我们还指定了一个例外:如果没有发现错误Q不产生错误报告?/P>
?Q“如果可能,ȝL应通过联机校验Q而不是通过d体主号码列表校验”。真感到l望Q什么是“如果可能”:如果技术上可行Q如果主全体ȝL列表可以联机获得Q要避免象“应该”的q类不确切的词。客h需要这个功能性还是不需要。我曄q一些需求说明书Q采用诸如:应,,应该/要{一些词描述优先U的l微差别。但我更喜欢用“应”清楚的说明需求的意图Q指明优先。这是修改后的:pȝ应校验输入的ȝL而不通过联机的主全体dL列表。如果在列表中没有发C号码,会昄一条错误信息,也不接受指o?/P>
在理解各个已完成的糟p需求上Q开发h员将会遇到的N是:开发h员与客户会在审栔R求,未达成共识前发生Ȁ烈的争论。详l检查大的需求文不是一件轻杄事情。我清楚有h做过Q而且他们花在查上的每一分钟都是值得的。相对于开发阶D和用户的抱怨电话,在这个阶D修补缺h便宜的,
~写质量需求的斚w
~写优秀的需求是没有公式化的Ҏ的。这需要大量的l验Q要从你在过ȝ文档中发现的问题学习。请在组lY仉求文时Q严格遵从这些方针?/P>
句子和段落要短。采用主动语气。用正的语法Q拼写,标点。用术语,要保持一致性,q在术语表或数据字典中定义它?/P>
要看需求是否被有效的定义,可以以开发h员的观点看看。在内心“当你们做完了找我”这句加到文档尾部,看看能不能是你紧张v来。换句话_你是否需要SRS的编写者的额外解释帮助开发h员很好的理解需求,以便于设计和实现Q如果是的话Q在l箋工作前,需求还需要细化?/P>
需求编写者还要努力正地把握l化E度。要避免包含多个需求的长的叙述D落。有帮助的提C是~写独立的可试的需求。如果你认ؓ一部分测试可以验证一个需求的正确Q那么它已经正确的细化了。如果你预想到多U不同类的测试,几个需求可能已挤到了一P需要拆分开?
密切x多个需求合成了单个需求。一个需求中的连接词“和?“或”徏议几个需求合q。不要在一个需求中使用“和?“或”?/P>
通篇文l节上要保持一致。我曄见过多个需求说明书前后不一致。如Q“对于红色合法的颜色代码应是R”及“对于绿色合法的颜色代码应是G”就有可以以分散的需求分dQ而“品应能对来自语音~辑指示做出反应”应作ؓ一个子pȝQ不应作为单个的功能性需求?/P>
避免在SRS中过多的甌需求。在多处包含相同的需求可以文更易于阅读,但也会给文的维护增加困难。文档的多䆾文本要在同一旉内全部更斎ͼ避免不一致性?/P>
如果你遵从了q些斚wQ你能够早地经常正式或非正式的审查需求,q些需求对于品的构造,pȝ试以及最后的客户满意Q都会成为好的奠基石。ƈ且要CQ没有高质量的需求,软gp一盒y克力Q你永远不知道你会得C么?
字符 | 含义 |
---|---|
\cx | 匚w由x指明的控制字W。例如, \cM 匚w一?Control-M 或回车符。x 的值必Mؓ A-Z ?a-z 之一。否则,?c 视ؓ一个原义的 'c' 字符?/TD> |
\f | 匚w一个换늬。等价于 \x0c ?\cL?/TD> |
\n | 匚w一个换行符。等价于 \x0a ?\cJ?/TD> |
\r | 匚w一个回车符。等价于 \x0d ?\cM?/TD> |
\s | 匚wMI白字符Q包括空根{制表符、换늬{等。等价于 [ \f\n\r\t\v]?/TD> |
\S | 匚wM非空白字W。等价于 [^ \f\n\r\t\v]?/TD> |
\t | 匚w一个制表符。等价于 \x09 ?\cI?/TD> |
\v | 匚w一个垂直制表符。等价于 \x0b ?\cK?/TD> |
特别字符 | 说明 |
---|---|
$ | 匚w输入字符串的l尾位置。如果设|了 RegExp 对象?Multiline 属性,?$ 也匹?'\n' ?'\r'。要匚w $ 字符本nQ请使用 \$?/TD> |
( ) | 标记一个子表达式的开始和l束位置。子表达式可以获取供以后使用。要匚wq些字符Q请使用 \( ?\)?/TD> |
* | 匚w前面的子表达式零ơ或多次。要匚w * 字符Q请使用 \*?/TD> |
+ | 匚w前面的子表达式一ơ或多次。要匚w + 字符Q请使用 \+?/TD> |
. | 匚w除换行符 \n之外的Q何单字符。要匚w .Q请使用 \?/TD> |
[ | 标记一个中括号表达式的开始。要匚w [Q请使用 \[?/TD> |
? | 匚w前面的子表达式零ơ或一ơ,或指明一个非贪婪限定W。要匚w ? 字符Q请使用 \??/TD> |
\ | 下一个字W标Cؓ或特D字W、或原义字符、或向后引用、或八进制{义符。例如, 'n' 匚w字符 'n'?\n' 匚w换行W。序?'\\' 匚w "\"Q?'\(' 则匹?"("?/TD> |
^ | 匚w输入字符串的开始位|,除非在方括号表达式中使用Q此时它表示不接受该字符集合。要匚w ^ 字符本nQ请使用 \^?/TD> |
{ | 标记限定W表辑ּ的开始。要匚w {Q请使用 \{?/TD> |
| | 指明两项之间的一个选择。要匚w |Q请使用 \|?/TD> |
字符 | 描述 |
---|---|
* | 匚w前面的子表达式零ơ或多次。例如,zo* 能匹?"z" 以及 "zoo"? {h于{0,}?/TD> |
+ | 匚w前面的子表达式一ơ或多次。例如,'zo+' 能匹?"zo" 以及 "zoo"Q但不能匚w "z"? {h?{1,}?/TD> |
? | 匚w前面的子表达式零ơ或一ơ。例如,"do(es)?" 可以匚w "do" ?"does" 中的"do" ? {h?{0,1}?/TD> |
{n} | n 是一个非负整数。匹配确定的 n ơ。例如,'o{2}' 不能匚w "Bob" 中的 'o'Q但是能匚w "food" 中的两个 o?/TD> |
{n,} | n 是一个非负整数。至匹配n ơ。例如,'o{2,}' 不能匚w "Bob" 中的 'o'Q但能匹?"foooood" 中的所?o?o{1,}' {h?'o+'?o{0,}' 则等价于 'o*'?/TD> |
{n,m} | m ?n 均ؓ非负整数Q其中n <= m。最匹?n ơ且最多匹?m ơ。例如,"o{1,3}" 匹?"fooooood" 中的前三?o?o{0,1}' {h?'o?'。请注意在逗号和两个数之间不能有空根{?/TD> |
操作W? | 描述 |
---|---|
\ | 转义W?/TD> |
(), (?:), (?=), [] | 圆括号和Ҏ?/TD> |
*, +, ?, {n}, {n,}, {n,m} | 限定W?/TD> |
^, $, \anymetacharacter | 位置和顺?/TD> |
| | “或”操?/TD> |
字符 | 描述 |
---|---|
\ | 下一个字W标Cؓ一个特D字W、或一个原义字W、或一?向后引用、或一个八q制转义W。例如,'n' 匚w字符 "n"?\n' 匚w一个换行符。序?'\\' 匚w "\" ?"\(" 则匹?"("?/TD> |
^ | 匚w输入字符串的开始位|。如果设|了 RegExp 对象?Multiline 属性,^ 也匹?'\n' ?'\r' 之后的位|?/TD> |
$ | 匚w输入字符串的l束位置。如果设|了RegExp 对象?Multiline 属性,$ 也匹?'\n' ?'\r' 之前的位|?/TD> |
* | 匚w前面的子表达式零ơ或多次。例如,zo* 能匹?"z" 以及 "zoo"? {h于{0,}?/TD> |
+ | 匚w前面的子表达式一ơ或多次。例如,'zo+' 能匹?"zo" 以及 "zoo"Q但不能匚w "z"? {h?{1,}?/TD> |
? | 匚w前面的子表达式零ơ或一ơ。例如,"do(es)?" 可以匚w "do" ?"does" 中的"do" ? {h?{0,1}?/TD> |
{n} | n 是一个非负整数。匹配确定的 n ơ。例如,'o{2}' 不能匚w "Bob" 中的 'o'Q但是能匚w "food" 中的两个 o?/TD> |
{n,} | n 是一个非负整数。至匹配n ơ。例如,'o{2,}' 不能匚w "Bob" 中的 'o'Q但能匹?"foooood" 中的所?o?o{1,}' {h?'o+'?o{0,}' 则等价于 'o*'?/TD> |
{n,m} | m ?n 均ؓ非负整数Q其中n <= m。最匹?n ơ且最多匹?m ơ。例如,"o{1,3}" 匹?"fooooood" 中的前三?o?o{0,1}' {h?'o?'。请注意在逗号和两个数之间不能有空根{?/TD> |
? | 当该字符紧跟在Q何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面Ӟ匚w模式是非贪婪的。非贪婪模式可能少的匹配所搜烦的字W串Q而默认的贪婪模式则尽可能多的匚w所搜烦的字W串。例如,对于字符?"oooo"Q?o+?' 匹配单?"o"Q?'o+' 匹配所?'o'?/TD> |
. | 匚w?"\n" 之外的Q何单个字W。要匚w包括 '\n' 在内的Q何字W,请用象 '[.\n]' 的模式?/TD> |
(pattern) | 匚w pattern q获取这一匚w。所获取的匹配可以从产生?Matches 集合得到Q在VBScript 中?SubMatches 集合Q在JScript 中则使用 $0?9 属性。要匚w圆括号字W,请?'\(' ?'\)'?/TD> |
(?:pattern) | 匚w pattern 但不获取匚wl果Q也是说这是一个非获取匚wQ不q行存储供以后用。这在?"? 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 是一个比 'industry|industries' 更简略的表达式?/TD> |
(?=pattern) | 正向预查Q在M匚w pattern 的字W串开始处匚w查找字符丌Ӏ这是一个非获取匚wQ也是_该匹配不需要获取供以后使用。例如,'Windows (?=95|98|NT|2000)' 能匹?"Windows 2000" 中的 "Windows" Q但不能匚w "Windows 3.1" 中的 "Windows"。预查不消耗字W,也就是说Q在一个匹配发生后Q在最后一ơ匹配之后立卛_始下一ơ匹配的搜烦Q而不是从包含预查的字W之后开始?/TD> |
(?!pattern) | 负向预查Q在M不匹?pattern 的字W串开始处匚w查找字符丌Ӏ这是一个非获取匚wQ也是_该匹配不需要获取供以后使用。例?Windows (?!95|98|NT|2000)' 能匹?"Windows 3.1" 中的 "Windows"Q但不能匚w "Windows 2000" 中的 "Windows"。预查不消耗字W,也就是说Q在一个匹配发生后Q在最后一ơ匹配之后立卛_始下一ơ匹配的搜烦Q而不是从包含预查的字W之后开?/TD> |
x|y | 匚w x ?y。例如,'z|food' 能匹?"z" ?"food"?(z|f)ood' 则匹?"zood" ?"food"?/TD> |
[xyz] | 字符集合。匹配所包含的Q意一个字W。例如, '[abc]' 可以匚w "plain" 中的 'a'?/TD> |
[^xyz] | 负值字W集合。匹配未包含的Q意字W。例如, '[^abc]' 可以匚w "plain" 中的'p'?/TD> |
[a-z] | 字符范围。匹配指定范围内的Q意字W。例如,'[a-z]' 可以匚w 'a' ?'z' 范围内的L写字母字符?/TD> |
[^a-z] | 负值字W范围。匹配Q何不在指定范围内的Q意字W。例如,'[^a-z]' 可以匚wM不在 'a' ?'z' 范围内的L字符?/TD> |
\b | 匚w一个单词边界,也就是指单词和空格间的位|。例如, 'er\b' 可以匚w"never" 中的 'er'Q但不能匚w "verb" 中的 'er'?/TD> |
\B | 匚w非单词边界?er\B' 能匹?"verb" 中的 'er'Q但不能匚w "never" 中的 'er'?/TD> |
\cx | 匚w?x 指明的控制字W。例如, \cM 匚w一?Control-M 或回车符。x 的值必Mؓ A-Z ?a-z 之一。否则,?c 视ؓ一个原义的 'c' 字符?/TD> |
\d | 匚w一个数字字W。等价于 [0-9]?/TD> |
\D | 匚w一个非数字字符。等价于 [^0-9]?/TD> |
\f | 匚w一个换늬。等价于 \x0c ?\cL?/TD> |
\n | 匚w一个换行符。等价于 \x0a ?\cJ?/TD> |
\r | 匚w一个回车符。等价于 \x0d ?\cM?/TD> |
\s | 匚wMI白字符Q包括空根{制表符、换늬{等。等价于 [ \f\n\r\t\v]?/TD> |
\S | 匚wM非空白字W。等价于 [^ \f\n\r\t\v]?/TD> |
\t | 匚w一个制表符。等价于 \x09 ?\cI?/TD> |
\v | 匚w一个垂直制表符。等价于 \x0b ?\cK?/TD> |
\w | 匚w包括下划U的M单词字符。等价于'[A-Za-z0-9_]'?/TD> |
\W | 匚wM非单词字W。等价于 '[^A-Za-z0-9_]'?/TD> |
\xn | 匚w nQ其?n 为十六进制{义倹{十六进制{义值必Mؓ定的两个数字长。例如,'\x41' 匚w "A"?\x041' 则等价于 '\x04' & "1"。正则表辑ּ中可以?ASCII ~码? |
\num | 匚w numQ其?num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匚w两个q箋的相同字W?/TD> |
\n | 标识一个八q制转义值或一个向后引用。如?\n 之前臛_ n 个获取的子表辑ּQ则 n 为向后引用。否则,如果 n 为八q制数字 (0-7)Q则 n Z个八q制转义倹{?/TD> |
\nm | 标识一个八q制转义值或一个向后引用。如?\nm 之前臛_?nm 个获得子表达式,?nm 为向后引用。如?\nm 之前臛_?n 个获取,?n Z个后跟文?m 的向后引用。如果前面的条g都不满Q若 n ?m 均ؓ八进制数?(0-7)Q则 \nm 匹配八q制转义?nm?/TD> |
\nml | 如果 n 为八q制数字 (0-3)Q且 m ?l 均ؓ八进制数?(0-7)Q则匚w八进制{义?nml?/TD> |
\un | 匚w nQ其?n 是一个用四个十六q制数字表示?Unicode 字符。例如, \u00A9 匚w版权W号 (?)?/TD> |
正则表达?/TH> | 说明 |
---|---|
/\b([a-z]+) \1\b/gi | 一个单词连l出现的位置 |
/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/ | 一个URL解析为协议、域、端口及相对路径 |
/^(?:Chapter|Section) [1-9][0-9]{0,1}$/ | 定位章节的位|?/TD> |
/[-a-z]/ | A至z?6个字母再加一?受?/TD> |
/ter\b/ | 可匹配chapterQ而不能terminal |
/\Bapt/ | 可匹配chapterQ而不能aptitude |
/Windows(?=95 |98 |NT )/ | 可匹配Windows95或Windows98或WindowsNT,当找C个匹配后Q从Windows后面开始进行下一ơ的索匹配?/TD> |