??xml version="1.0" encoding="utf-8" standalone="yes"?>
]]>
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFFooter;
import org.apache.poi.hssf.usermodel.HSSFHeader;
import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.hssf.util.Region;
import cc.dynasoft.bean.Department;
///import org.apache.poi.hssf.record.HeaderRecorder;
public class OutputExcel {
public static boolean outputExcel(ExcelArgs args, List title, List list) {
try {
int cellNum = args.getCellNum(); // workbook
int rowNum = args.getRowNum();
/**
* 建立表格讄?
*/
HSSFWorkbook wb = new HSSFWorkbook(); // create the new Workbook
HSSFSheet sheet = wb.createSheet(args.getSheetName()); // create
/**
* 打印讄
*/
HSSFPrintSetup hps = sheet.getPrintSetup();
hps.setPaperSize((short) 9); // 讄A4U?
// hps.setLandscape(true); // 页面设|ؓ横向打印模式
sheet.setHorizontallyCenter(true); // 讄打印面为水q_?
// sheet.setVerticallyCenter(true); // 讄打印面为垂直居?
wb.setPrintArea(0, "$A$2:$e$" + rowNum + 2);// 打印区域讄.
/**
* 讄表的Footer
*/
HSSFFooter footer = sheet.getFooter();
// 讄footer的位|和昄的内?
footer.setCenter("Time:" + HSSFFooter.date());
footer.setRight("Page " + HSSFFooter.page() + " of "
+ HSSFFooter.numPages());
/**
* 讄表的Header
*/
// 讄header的位|?共有三种位置和相应的昄讄
HSSFHeader header = sheet.getHeader();
// header.setRight("Center Header");
// header.setLeft("Left Header");
header.setCenter(HSSFHeader.font("Stencil-Normal", "Italic")
+ HSSFHeader.fontSize((short) 30) + args.getHeaderTitle());
// header.endDoubleUnderline();
header.startUnderline();
/**
* 讄列的宽度
*/
sheet.setColumnWidth((short) 2,
(short) ((30 * 8) / ((double) 1 / 10)));
sheet.setColumnWidth((short) 3,
(short) ((40 * 8) / ((double) 1 / 10)));
sheet.setColumnWidth((short) 4,
(short) ((50 * 8) / ((double) 1 / 20)));
/**
* 创徏W一?也就是显C的标题, 可以高置的高?单元格的格式,颜色,字体{设|? 同时可以合ƈ单元?
*/
HSSFRow row0 = sheet.createRow(0); // 创徏0?
row0.setHeight((short) 0x300); // 讄行的高度.
HSSFFont font2 = wb.createFont(); // 创徏字体格式
font2.setColor(HSSFFont.SS_NONE); // 讄单元格字体的颜色.
font2.setFontHeight((short) 700); // 讄字体大小
font2.setFontName("Courier New"); // 讄单元格字?
HSSFCell cell0 = row0.createCell((short) 0); // 创徏0??
HSSFCellStyle style3 = wb.createCellStyle(); // 创徏单元格风?
style3.setAlignment(HSSFCellStyle.VERTICAL_CENTER); // 垂直居中
style3.setAlignment(HSSFCellStyle.ALIGN_CENTER); // /水^居中
style3.setFont(font2); // 字体格式加入到单元格风格当?
// cell0.setCellType()
cell0.setCellStyle(style3); // 讄单元格的风格.
cell0.setCellValue(args.getHeaderTitle()); // 讄单元的内?
sheet.addMergedRegion(new Region(0, (short) 0, 0,
(short) (cellNum - 1)));// 指定合ƈ区域,前二个参Cؓ开始处X,Y坐标.后二个ؓl束的坐?
/**
* 讄其它数据 讄风格
*/
HSSFCellStyle style = wb.createCellStyle();
style.setBorderBottom(HSSFCellStyle.BORDER_THIN); // 讄单无格的Ҏ为粗?
style.setBottomBorderColor(HSSFColor.BLACK.index); // 讄单元格的Ҏ颜色Q?
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style.setLeftBorderColor(HSSFColor.BLACK.index);
style.setBorderRight(HSSFCellStyle.BORDER_THIN);
style.setRightBorderColor(HSSFColor.BLACK.index);
style.setBorderTop(HSSFCellStyle.BORDER_THIN);
style.setTopBorderColor(HSSFColor.BLACK.index);
// style.setWrapText(true);//文本区域随内容多自动调?
// style.setFillForegroundColor(HSSFColor.LIME.index);
// style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
/**
* 讄风格1
*/
HSSFCellStyle style1 = wb.createCellStyle();
style1.setBorderBottom(HSSFCellStyle.BORDER_THIN); // 讄单无格的Ҏ为粗?
style1.setBottomBorderColor(HSSFColor.BLACK.index); // 讄单元格的Ҏ颜色Q?
style1.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style1.setLeftBorderColor(HSSFColor.BLACK.index);
style1.setBorderRight(HSSFCellStyle.BORDER_THIN);
style1.setRightBorderColor(HSSFColor.BLACK.index);
style1.setBorderTop(HSSFCellStyle.BORDER_MEDIUM);
style1.setTopBorderColor(HSSFColor.BLACK.index);
style1.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);// 最好的讄Pattern
// 单元D景的昄模式Q?
style1.setFillForegroundColor(new HSSFColor.RED().getIndex()); // 讄单元D景色;
style1.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 水^寚w方式
// style1.setWrapText(true);//文本区域随内容多自动调?
// style.setFillPattern(HSSFCellStyle.//);
// 讄字体Color,首先创徏Font对象,后对font讄,然后做ؓ参数传给style
HSSFFont font = wb.createFont();
font.setColor(HSSFFont.SS_NONE);
// font.setFontHeightInPoints((short)24);
font.setFontName("Courier New");
// font.setItalic(true);
// font.setStrikeout(true);//l字体加上删除线
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
style1.setFont(font);
/**
*
* 讄W零行表D明行
*
*
*
*/
HSSFRow row1 = sheet.createRow((short) 1);
for (int j = 0; j < cellNum; j++) {
HSSFCell cell = row1.createCell((short) j);
cell.setCellValue((String) title.get(j));
cell.setCellStyle(style1);
}
// style.setFillPattern(HSSFCellStyle.NO_FILL);
/**
* 讄表的内容M
*/
Iterator iter = list.iterator();
for (int i = 2; iter.hasNext(); i++) {
Department dep = (Department) iter.next();
HSSFRow row = sheet.createRow((short) i);
HSSFCell cell5 = row.createCell((short) 0);
HSSFCell cell1 = row.createCell((short) 1);
HSSFCell cell2 = row.createCell((short) 2);
HSSFCell cell3 = row.createCell((short) 3);
HSSFCell cell4 = row.createCell((short) 4);
cell5.setCellValue(dep.getId());
cell5.setCellStyle(style);
cell1.setCellValue(dep.getParentId());
cell1.setCellStyle(style);
cell2.setCellValue(dep.getName());
cell2.setCellStyle(style);
cell3.setCellValue(dep.getDescription());
cell3.setCellStyle(style);
cell4.setCellValue(dep.getImagePath());
cell4.setCellStyle(style);
}
// Write the output to a file}
// FileOutputStream fileOut = new
// FileOutputStream(args.getPath()+args.getFileName());
/**
* Ҏ件进行输出操作?
*/
FileOutputStream fileOut = new FileOutputStream(args
.getPathAndName());
wb.write(fileOut);
// fileOut.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
}
}
该命令的功能是将l出的文件或目录拯到另一文g或目录中Q同MSDOS下的copy命o一P功能十分强大?nbsp;
语法Q?cp [选项] 源文件或目录 目标文g或目?nbsp;
说明Q该命o把指定的源文件复制到目标文g或把多个源文件复制到目标目录中?nbsp;
该命令的各选项含义如下Q?
- a 该选项通常在拷贝目录时使用。它保留链接、文件属性,q归地拷贝目录,其作用等于dpR选项的组合?nbsp;
- d 拯时保留链接?nbsp;
- f 删除已经存在的目标文件而不提示?nbsp;
- i 和f选项相反Q在覆盖目标文g之前给出提C求用L认。回{y时目标文件将被覆盖,是交互式拯?nbsp;
- p 此时cp除复制源文g的内容外Q还把其修Ҏ间和讉K权限也复制到新文件中?nbsp;
- r 若给出的源文件是一目录文gQ此时cp递归复制该目录下所有的子目录和文g。此时目标文件必Mؓ一个目录名?nbsp;
- l 不作拯Q只是链接文件?nbsp;
需要说明的是,为防止用户在不经意的情况下用cp命o破坏另一个文Ӟ如用h定的目标文g名已存在Q用cp命o拯文g后,q个文g׃被新源文件覆盖,因此Q徏议用户在使用cp命o拯文gӞ最好用i选项?/p>
java.util
+Collection q个接口extends?--java.lang.Iterable接口
+List 接口
-ArrayList c?br />
-LinkedList c?br />
-Vector c?nbsp; 此类是实现同步的
+Queue 接口
+不常用,在此不表.
+Set 接口
+SortedSet 接口
-TreeSet c?br />
-HashSet
+Map 接口
-HashMap c?(除了不同步和允许使用 null ?g??Hashtable 大致相同.)
-Hashtable c?此类是实现同步的,不允怋?null 键?br />
+SortedMap 接口
-TreeMap c?/font>
以下对众多接口和cȝ单说明:首先不能不先说一下数l(ArrayQ?br /> 一、Array Q?Arrays
Java所?#8220;存储及随问一q串对象”的做法,array是最有效率的一U?/font>
1?br /> 效率高,但容量固定且无法动态改变?br /> arrayq有一个缺ҎQ无法判断其中实际存有多元素,length只是告诉我们array的容量?/font>
2、Java中有一个Arraysc,专门用来操作array?br /> arrays中拥有一lstatic函数Q?br /> equals()Q比较两个array是否相等。array拥有相同元素个数Q且所有对应元素两两相{?br /> fill()Q将值填入array中?br /> sort()Q用来对arrayq行排序?br /> binarySearch()Q在排好序的array中寻扑օ素?br /> System.arraycopy()Qarray的复制?/font>
二、Collection Q?Map
若撰写程序时不知道究竟需要多对象,需要在I间不时自动扩增容量,则需要用容器类库,array不适用?/font>
1、Collection ?Map 的区?/strong>
容器内每个ؓ之所存储的元素个C同?br /> Collectioncd者,每个位置只有一个元素?br /> Mapcd者,持有 key-value pairQ像个小型数据库?/font>
2、Java2容器cȝ库的用途是“保存对象”Q它分ؓ两类Q各自旗下的子类关系
Collection
--ListQ将以特定次序存储元素。所以取出来的顺序可能和攑օ序不同?br />
--ArrayList / LinkedList / Vector
--Set Q?不能含有重复的元?br />
--HashSet /TreeSet
Map
--HashMap
--HashTable
--TreeMap
Map----一l成对的“键值对”对象Q即其元素是成对的对象,最典型的应用就是数据字典,q且q有其它q泛的应用。另外,Map可以q回其所有键l成的Set和其所有值组成的CollectionQ或光值对l成的SetQƈ且还可以像数l一h展多lMapQ只要让Map中键值对的每?#8220;?#8221;是一个Map卛_?br />
Collection?1.q代?br />
q代器是一U设计模式,它是一个对象,它可以遍历ƈ选择序列中的对象Q而开发h员不需要了解该序列的底层结构。P代器通常被称?#8220;轻量U?#8221;对象Q因为创建它的代价小?br />
Java中的Iterator功能比较单,q且只能单向UdQ?br />
(1) 使用Ҏiterator()要求容器q回一个Iterator。第一ơ调用Iterator的next()ҎӞ它返回序列的W一个元素?font color="#0000ff">注意Q?/font>iterator()Ҏ是java.lang.Iterable接口,?font color="#0000ff" size="2">Collectionl承?/font>
(2) 使用next()获得序列中的下一个元素?br />
(3) 使用hasNext()查序列中是否q有元素?br />
(4) 使用remove()P代器新返回的元素删除?br />
Iterator是Javaq代器最单的实现QؓList设计的ListIteratorh更多的功能,它可以从两个方向遍历ListQ也可以从List中插入和删除元素?br />
2.List的功能方?br />
List(interface): ơ序是List最重要的特点;它确保维护元素特定的序。List为Collectiond了许多方法,使得能够向List中间插入与移除元?只推荐LinkedList使用)。一个List可以生成ListIteratorQ用它可以从两个方向遍历ListQ也可以从List中间插入和删除元素?br />
ArrayList: 由数l实现的List。它允许对元素进行快速随问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayListQ而不是用来插入和删除元素Q因比LinkedList开销要大很多?br />
LinkedList: 由列表实现的List。对序讉Kq行了优化,向List中间插入与删除得开销不大Q随问则相对较慢(可用ArrayList代替)。它hҎaddFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()Q这些方?没有在Q何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用?br />
3.Set的功能方?br />
Set(interface): 存入Set的每个元素必L唯一的,q也是与List不同的,因ؓSet不保存重复元素。加入Set的Object必须定义equals()Ҏ以确保对象的唯一性。Set与Collection有完全一L接口。Set接口不保证维护元素的ơ序?br />
HashSet: HashSet能快速定位一个元素,存入HashSet的对象必d义hashCode()?br />
TreeSet: 保持ơ序的SetQ底层ؓ树结构。用它可以从Set中提取有序的序列?br />
LinkedHashSet: hHashSet的查询速度Q且内部使用链表l护元素的顺?插入的次?。于是在使用q代器遍历SetӞl果会按元素插入的次序显C?br />
HashSet采用散列函数对元素进行排序,q是专门为快速查询而设计的QTreeSet采用U黑树的数据l构q行排序元素QLinkedHashSet内部使用散列以加快查询速度Q同时用链表维护元素的ơ序Q得看h元素是以插入的顺序保存的。需要注意的是,生成自己的类ӞSet需要维护元素的存储序Q因此要实现Comparable接口q定义compareTo()Ҏ?/p>
3、其他特?/strong>
* ListQSetQMap持有对象一律视为Object型别?br /> * Collection、List、Set、Map都是接口Q不能实例化?br /> l承自它们的 ArrayList, Vector, HashTable, HashMap是具象classQ这些才可被实例化?br /> * vector容器切知道它所持有的对象隶属什么型别。vector不进行边界检查?/font>
三、Collections
Collections是针寚w合类的一个帮助类?/font>提供了一pd静?/font>Ҏ实现对各U集合的搜烦、排序、线E完全化{操作?br /> 相当于对Arrayq行cM操作的类——Arrays?br /> 如,Collections.max(Collection coll); 取coll中最大的元素?br /> Collections.sort(List list); 对list中元素排?br />
四、如何选择Q?/strong>
1、容器类和Array的区别、择?/strong>
* 容器cM能持有对象引用(指向对象的指针)Q而不是将对象信息copy一份至数列某位|?br />
* 一旦将对象|入容器内,便损׃该对象的型别信息?/font>
2?/strong>
* 在各ULists中,最好的做法是以ArrayList作ؓ~省选择。当插入、删除频J时Q用LinkedList()Q?br />
VectorL比ArrayList慢,所以要量避免使用?br />
* 在各USets中,HashSet通常优于HashTreeQ插入、查找)。只有当需要生一个经q排序的序列Q才用TreeSet?br />
HashTree存在的唯一理由Q能够维护其内元素的排序状态?br />
* 在各UMaps?br />
HashMap用于快速查找?br />
* 当元素个数固定,用ArrayQ因为Array效率是最高的?/font>
l论Q最常用的是ArrayListQHashSetQHashMapQArray。而且Q我们也会发C个规律,用TreeXXX都是排序的?/strong>
注意Q?/font>
1?font color="#3366ff">Collection没有get()Ҏ来取得某个元素。只能通过iterator()遍历元素?br />
2?font color="#3366ff">Set和Collection拥有一模一L接口?br />
3?font color="#3366ff">ListQ?font color="#3366ff">可以通过get()Ҏ来一ơ取Z个元?/font>。用数字来选择一堆对象中的一个,get(0)...?add/get)
4、一般用ArrayList?font color="#3366ff">用LinkedList构造堆栈stack、队列queue?/p>
5?font color="#3366ff">Map?put(k,v) / get(k)Q还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value?br />
HashMap会利用对象的hashCode来快速找到key?br />
* hashing
哈希码就是将对象的信息经q一些{变Ş成一个独一无二的int|q个值存储在一个array中?br />
我们都知道所有存储结构中Qarray查找速度是最快的。所以,可以加速查找?br />
发生撞Ӟ让array指向多个values。即Q数l每个位|上又生成一个梿表?/p>
6、Map中元素,可以key序列、value序列单独抽取出来?br /> 使用keySet()抽取key序列Q将map中的所有keys生成一个Set?br /> 使用values()抽取value序列Q将map中的所有values生成一个Collection?/p>
Z么一个生成SetQ一个生成CollectionQ那是因为,keyL独一无二的,value允许重复?/p>
单地_一是准Ӟ二是预算控制在既定的范围内;三是质量得到l理和用户们的赞许。项目经理必M证项目小l的每一位成员都能对照上面三个标准来q行工作?
2、Q何事都应当先规划再执?
项目管理而言Q很多专家和实践人员都同意这样一个观点:需要项目经理投入的最重要的一件事是规划。只有详l而系l的由项目小l成员参与的规划才是目成功的唯一基础。当现实的世界出C一U不适于计划生存的环境时Q项目经理应制定一个新的计划来反映环境的变化。规划、规划、再规划是目l理的一U生zL式?
3、项目经理必M自己的实际行动向目组成员传递一U紧q感
׃目在时间、资源和l费上都是有限的Q项目最l必d成。但目组成员大多有自q爱好Q项目经理应让项目小l成员始l关注项目的目标和截止期限。例如,可以定期查,可以召开例会Q可以制作一些提醒的标志|于目的场所?
4、成功的目应用一U可以度量且被证实的目生命周期
标准的信息系l开发模型可以保证专业标准和成功的经验能够融入项目计划。这cL型不仅可以保证质量,q可以重复力_降到最低程度。因此,当遇到时间和预算压力需要削减项目时Q项目经理应定一U最佳的目生命周期?
5、所有项目目标和目zd必须生动形象地得以交和沟?
目l理和项目小l在目开始时应当Ş象化地描q项目的最l目标,以确保与目有关的每一个h都能C。项目成本的各个l节都应当清楚、明、毫不含p,q确保每个hҎ都达成了一致的意见?
6、采用渐q的方式逐步实现目标
如果试图同时完成所有的目目标Q只会造成重复力_Q既费旉又浪贚w。俗话说Q一口吃不成个胖子。项目目标只能一点一点地d玎ͼq且每实C个目标就q行一ơ评伎ͼ保整个目能得以控制?
7、项目应得到明确的许可,q由投资方签字实?
在实现项目目标的q程中获得明的许可是非帔R要的。应投资方的签字批准视为项目的一个出发点。道理很单:M有权拒绝或有权修攚w目目标的人都应当在项目启动时审查和批准这些项目目标?
8、要惌得项目成功必d目目标q行透彻的分?
研究表明Q如果按照众所周知记录在案的业务需求来设计目的目标,则该目多半会成功。所以,目l理应当坚持q样一个原则,卛_l织机构启动目之前Q就应当目在业务需求中扑ֈ充分的依据?
9、项目经理应当责权对{?
目l理应当寚w目的l果负责Q这一点ƈ不过分。但与此相对应,目l理也应被授予够的权利以承担相应的责Q。在某些时候,权利昑־特别重要Q如获取或协调资源,要求得到有关的中企业的配合Q做相应的对目成功有h值的决策{等?
10、项目投资方和用户应当主动介入,不能被动地坐享其?
多数目投资方和用户都能正确地要求和行批准Q全部或部分Q项目目标的权力。但伴随q个权力的是相应的责仠Z—主动地介入目的各个阶Dc例如,在项目早期要帮助定目目标Q在目q行中,要对完成的阶D|目标进行评伎ͼ以确保项目能利q行。项目投资方应帮助项目经理去讉K有关的中企业和目标֮的成员,q帮助项目经理获得必要的文g资料?
11、项目的实施应当采用市场q作机制
在多数情况下Q项目经理应自q成是卖主Q以督促自己完成投资方和用户交付的Q务。项目计划一旦批准项目经理应当定期提醒项目小l成员该目必须满的业务需求是什么,以及该怎样工作才能满q些业务需求?
12、项目经理应当获得项目小l成员的最佳h?
最佳h选是指受q相应的技能培训,有经验,素质高。对于项目来_获得最佳h选往往能I补时间、经Ҏ其它斚w的不뀂项目经理应当ؓq些最佳的目成员创造良好的工作环境Q如帮助他们免受外部q扰Q帮助他们获得必要的工具和条件以发挥他们的才能?br />
故事一
有两个Y仉目(姑且UC?#8220;目 A”?#8220;目 B”Q,它们在开始时的预都?50 个h月,旉?5 个月?/p>
目 A ?5 个月后完工,耗费成本 50 人月
目 B ?6 个月后完工,耗费成本 70 人月
在Y件圈子里摸爬滚打多年的读者们对这个故事一定有自己的判断——而且我可以大致猜出是什么样的判断。不q先别着急,我们q有另一个故事?/p>
故事?/p>
有两个Y仉目(仍然姑且UC?#8220;目 A”?#8220;目 B”Q,它们在开始时的计划都是交?200 功能?/p>
目 A 在项目结束时一ơ性交付了最初计划的 200 功能,但客户发现其中大U?30 功能没有太大用处,而另?30 Ҏ用的功能要等C一个项目才能实现?
目 B 在第一个月l束时交付了W一个版本,此后每两周交付一个新的版本。在目q行的过E中Q客戯行了一ơ业务调_加入?90 Ҏ的功能,q搁|了 50 用处不大的功能。最l该目交付?240 功能?
聪明的读者大概注意到了,前后两个故事讲的是同一回事Q同L两个目。现在我的问题来了:请问哪个目是更成功的项目?
q个问题q不Ҏ回答——实际上它没有标准答案。站在很多Y件企业的立场上,目 A 是一个理想的成功目Q按旉、按成本完成预先U定的Q务。请注意Q我用了“理想?#8221;q个词,E后我还会解释这个有的词,因ؓ实际上的软g目往往没有那么理想?/p>
而如果换一个角度,站在客户的立Z呢?也许付钱购买软g的客户会有一些不同的x。项?B 从开始之后一个月便交付了W一个可工作的版本,从那时v客户开始用这个Y件的部分功能Qƈ且不断地把自׃用的感受反馈l开发团队。在真实的业务运营过E中Q客L臛_C一U新的盈利模式,q进行了一ơ大规模的业务调_q次调整的结果也直观C现在软g目中。虽焉目B的整体交付速率低于目 AQ但它提供的所有功能都是客L正需要的Q它们ؓ客户提供实实在在的h值——更不用_客户提前好几个月开始用这个Y件?/p>
实际上,q是一关于Y件h值的文章。和“成功目”一P对于“软g的h?#8221;Q不同的Z会有不同的定义。不q作Zp买Y件的客户Q他对于软g价值的定义是一目了然的Q他能够从用Y件中创造多h|软g能够Z的业务提供多h|q就是Y件的价倹{或者说得更明一点:
软g价值源自?br /> q正是ؓ什么很多客户青?#8220;目 B”的原因——我q不打算声称所有客户都有同L观点Q稍后我也会丑և反例Q但臛_支持q一观点的客户不在少数。因Z们处在一个残酯快速变化的商业环境中:他们的供应商在变化,他们的客户在变化Q他们所处的l济环境和政{环境也在变化。这一切的变化q他们的业务也要随之变化。更要命的是Q今天这个经全球化的时代是一?#8220;快鱼吃慢?#8221;的时代,客户q切希望新的软gpȝZ们带来竞争优劎쀔—哪怕这个Y件系l尚未完成,只要能够投入使用。最后,客户对于新的软gpȝI竟应该是什么样子ƈ没有癑ֈ之百的把握,他们的想法往往要在真正使用软g之后才会现成型。几斚w的因素加在一P使得q些客户更愿意尽快开始用Y件、提出反馈、ƈ不断完善软gQ而不是提Zl需求、然后坐{几个月之后原封不动地拿到这些功能?/p>
一个真实的案例
?ThoughtWorks 的一个项目中Q开发者们在项目开始之后一个月内就发布了第一个版本——只有一些简单的数据采集功能。在发布展示会上Q发生了q样的对?#8230;…
开发者:q是我们的第一个功能。我们从文本文g、Excel 数据表和遗留数据库采集数据,现在我们的数据库中有q些数据……Q展C数据库l构Q?
客户Q唔……有意思。要是你能做q样一个查询(写出查询要求Q,得到的结果可能会有用?
开发者:可是我们的界面上没有地方做这L查询操作?
客户Q啊Q我不需要操作界面,只要每天深夜做一ơ查询,把报表发到我的信就可以了?
开发者:q样?#8230;…另一个问题是Q这需要花我们几天旉?
客户Q不要紧Q把别的d往后放几天好了Q我很想看到q䆾报表?
开发者:那好吧,下周我们׃开始提供这个报表?
猜猜l果怎么P一周之后客户就开始每天接收这份报表,q根据报表内容做一些分析和决策。仅仅几个月之后Q这份报表给客户带来的收益就已经过了整个项目的投资——这旉目其他部分的开发甚臌没有完成?/p>
xq个客户会怎么定义一?#8220;成功的Y仉?#8221;Q好吧,也许q个目过了预期的旉Q也许投入了更多的h力,但这些ƈ不意味着“目p|”——只是付出更高的成本。关键在于,他投入的q些成本能够带来多大的收益,他的投资回报率是否划。对于这个客戯言Q如果项目能够随时给他提供可用的、能够创造最大h值的软gQ能够随时让——就像故事中提到的——这U有价值的x得以实现Q这是一个成功的目?/p>
所以,亲爱的读者,请你忘记本文标题上出现的“敏捷”二字Q我们在q里所说的不是别的Q就是一Uؓ客户创造最大化价值的软g开发方法。这LҎ有很多种Q但它们有一个共同的特点Q尽快、尽可能频繁C付可以工作的软gQ让客户快开始用YӞ从用中创造h倹{厘清思\、提出反馈。仍然以 ThoughtWorks 的项目ؓ例,q些目通常在启动开发阶D之后一个月内就会发布第一个版本,随后每一周或每两周发布一个新版本——每个版本都是一个可以工作的软gQ每个版本都比前一个版本具有更丰富的功能,q且每个版本都包含客戯今ؓ止最有h值的那些功能。用软g开发的“黑话”Q?#8220;开发下一个版?#8221;的过E叫?#8220;q代”Q这些开发方法最大的共同点就?#8220;q代式开?#8221;——不是一股脑C付全部功能,而是每次增加一炏V渐q地交付最有h值的功能?/p>
软g开发的梦想与真?/p>
回到文章开始处的两个故事。我曄说过Q对于很多Y件企业而言Q项?A 是一?#8220;理想?#8221;成功目。那么,是什么让情况变得不那么理惻I
{案是一个所有Y件开发者耳熟能详的词Q需求变更。在真实的项目中Q客户通常不会{到最后一天再照单全收整个目Q因Z知道自己的业务正在发生变化。这旉求变更就出现了,伴随着来回的扯皮和讨hqh。更p的是,大量的需求变更发生在目晚期——因为直到这时客h真正看到、用到q个软gQ他的很多想法才真正现成型。随着q种“最后一分钟的需求变?#8221;Q项目超期、超出预也成了家怾饭。能够像目Aq样完工交付的,实在是凤毛麟角的q运ѝ?/p>
Z对付需求变更这个噩梦,软g开发者们q发明了另一个词Q变更控制。这个有的词暗C着Q需求变更是一U?#8220;不好”的东西,是需?#8220;控制”的东ѝ然而站在客L角度上想惻I他在亲n使用了Y件之后提出的要求Q难道不是最有h值的东西吗?把这U真正创造业务h值的要求“控制”hQ难道是合理的吗Q?/p>
在前面我也暗CQƈ非所有的客户都一定青睐P代式开发。那么,哪些软g目不一定需要P代式开发呢Q从整篇文章的内容不隄出,如果客户的业务绝对不会变化,如果客户的需求巨l靡遗非常明,如果客户不需要尽快开始用Y件以便收回成本,那么q代式开发对他的帮助׃得多。不q,如果读者认真思考的话,q样的例子也许ƈ不多——也许比你最初认为的要少得多。一个很好的例子?#8220;州六号”火箭使用的计机控制pȝ。还有多这L例子Q读者不妨试着自己x?/p>
如果我够幸q的话,也许一些读者已l被q篇文章吊v了胃口:既然有这么好的Y件开发方法,既然它能够ؓ我们创造更大的价|那还{什么呢Q我们马上就动手吧。事情不会那么简单。ؓ了让q代式开发能够成为现实,Z保快、尽可能频繁C付,Z保每次交付的都是最有h值的功能Q我们——包括Y件开发者、Y件企业和客户——需要很多的改变。这里既有职责与权利的划分,也有开发过E和团队的重l,q有技术层面的实践指导。这些正是敏h法学所늛的内宏V缺了q些东西Q?#8220;为客户创造最大h?#8221;只能成Z句空话。在后箋的文章里Q我们将l合 ThoughtWorks 的实늻验,逐步介绍敏捷Ҏ的方斚w面?/p>