序言
对用户来说,通过直观,简单的界面呈现出恰当特性的程序就是美的。对软件设计者说,被简单,直观的分割,并具有最小内部耦合的内部结构就是美的。对开发人员和管理者来说,每周都会取得最大进展,并且生产出无缺陷代码的具有活力的团队就是美的。
对于开发者来说,单纯的实现功能,而不去考虑代码结构,性能等一系列问题开发出的软件是没有美感可言的,我们应该从设计原则,软件设计的基本模式去完善代码和设计,从有助于团队融为一个有机整体的敏捷实践中去提高软件开发效率,达成软件之美,这样在日后的维护中可以起到事半功倍的效果。
敏捷开发
敏捷开发(Agile Development) 是一种面临迅速变化的需求快速开发软件的能力。
设计原则,设计模式,敏捷开发实践都是重要的,但是如果项目要想取得成功,就必须构建起具有合作精神的,自组织的团队,因为有凝聚力的团队将具有最强大的软件开发力量。
敏捷实践
软件产品开发是多变的,不确定的。好的实践会让项目开发有条不紊的进行,最终完美交付。不良的实践往往会产生很多问题,进度缓慢延期,预算增加,质量低劣,开发人员长时间的加班缺生产出来更加差的软件产品,开发人员感到沮丧,客户感到失望。要是这个时候去规范过程,可能会导致效率更加低下,成本变高等一系列问题。
这个时候就需要一套方案去解决这个问题,敏捷开发应运而生。
敏捷软件开发宣言是:
-
个体和沟通胜过过程和工具
合作,沟通能力默契的平均水平团队>
沟通少的高水平团队>
过程和工具的选择 -
可以工作的软件胜过面面俱到的文档
优秀的代码和团队>
短小,主题突出的系统原理和结构文档>
详细的文档说明(文档不断地同步代码需要大量的时间,因此除非迫切需要时候,再去编制文档) -
客户合作胜过合同谈判
特别是对于软件服务类的公司,市场部门和商务部门进行需求的调研工作,然后形成需求文档,这样有时候对于需求的开展和项目的开发是很困难的,没有客户的参与,很容易做出客户不满意的产品。 -
响应变化胜过遵循计划
世界上唯一不变的是变化,在软件项目中,把一个项目吃从头规划到尾,做到事无巨细是比较困难的,因此,我们要做到响应变化,简化长期计划,细化短期计划,不断地进行调整,改进。
下面是敏捷开发的一些原则:
- 我们最优先要做的是通过尽早的,持续的交付有价值的软件来使客户满意
- 即使到了开发的后期,也欢迎改变需求,敏捷过程利用变化来为客户创造竞争优势
- 经常性地交付可以工作的软件,交付的间隔可以从几周到几个月,交付的时间间隔越短越好
- 在整个项目开发期间,业务人员和开发人员必须天天都在一起工作
- 围绕被激励起来的个人来构建项目,给他们提供所需要的环境和支持,并信任他们能够完成工作
- 在团队内部,最具有效果并富有效率的传递信息的方法,就是面对面的交流
- 工作的软件是首要的进度度量标准
- 敏捷过程提倡可持续的开发速度,责任人,开发者和用户应该能够保持一个长期的,恒定的开发速度
- 不断地关注优秀的技能和好的设计会增加敏捷能力
- 简单--使未完成的工作最大化的艺术是根本的
- 最好的架构,需求和设计出自自组织的团队
- 每隔一段时间,团队会在如何才能有效的工作方面进行反省,然后形影的对自己的行为进行调整。
极限编程
作为开发人员,我们应该记住,XP并非唯一选择
极限编程(eXtreme programming,XP)是敏捷方法中最著名的一个,由一系列简单缺互相依赖的实践组成。
下面是极限编程的通用软件开发方法,我们可以在实际项目使用中进行修改,选择适合自己的:
1. 客户作为团队成员
对于现在,客户很难和开发在一起工作,目前就是产品经理应该是项目的一部分,和开发人员始终保持同步
2. 用户故事
客户需求的一种描述方式,他是一个计划工具,产品经理可以根据它的优先级和估算代价来进行计划,并安排该需求的时间
3. 短交付周期
不断迭代,不断修改,不断确认,确保大方向不变。。
- 迭代计划
- 发布计划
4. 验收测试
5. 结对编程
6. 测试驱动的开发方法
7. 集体所有权
8. 持续集成
9. 可持续的开发速度
10. 开放的工作空间
11. 计划游戏
划分业务人员和开发人员之间的职责,业务人员决定feature的重要性,开发人员决定实现一个feature所华飞德代价。
12. 简单的设计
最小实现该迭代的代价
去除重复代码
13. 重构
在不改变功能的前提下,对代码进行小的改进。
14. 隐喻
将整个系统联系在一起的全局视图。
计划
当你能够度量你所说的,并且能够用数字去表达它是,就表示你了解了它;若你不能重复它,不能用数字去表达它,那么说明你的知识就是匮乏的,不能令人满意的。
通过一次次的迭代和发布,项目进入了一种可预测的,舒适的开发节奏。每个人都知道将要做什么,以及如何去做,也能实实在在的看到项目的进展。以提高工作质量
在项目开始时,客户人员和客户会尽量的确定出所有真正的用户故事,也就是真实需求。后面不断地去进行调整。
- 探究,分解和速度
- 发布计划
- 迭代计划
-
任务计划
迭代的中点,进行回顾,进行计划的调整。 -
迭代
敏捷迭代计划:sprint plan
对任务难度等级进行评分,1,2,3,5,8…..
任务板,TODO,doing,done
测试
编写单元测试是一种验证行为,也是一种设计行为,可以对功能进行验证,以及后续对代码的修改起到保护,形成一种良性的循环。
-
测试驱动的开发方法(TDD)
在设计测试用例的时候,进行功能代码的设计,对功能进行划分,能设计出更好的软件代码
测试可作为文档,可以清晰的看到功能调用。
代码可控,方便后续进行重构
重构
重构是在不改变代码外在行为的前提条件下对代码作出修改,以改进代码的内部结构的过程。
每一个软件模块都有三项职责:
- 它运行起来所完成的功能
- 他要应对变化
- 要和阅读它的人进行沟通
因此我们需要根据一些设计模式和设计原则来设计出易于阅读,易于修改的代码。不断地对之前的代码进行回顾,进行重构。
下面进行一个素数产生程序来示例重构:
初始TestCase:
public class TestGeneratePrimes {
@Test
public void testPrimes() {
int[] nullArray = GeneratePrimes.generatePrimes(0);
Assert.assertEquals(nullArray.length, 0);
int[] minArray = GeneratePrimes.generatePrimes(2);
Assert.assertEquals(minArray.length, 1);
Assert.assertEquals(minArray[0], 2);
int[] threeArray = GeneratePrimes.generatePrimes(3);
Assert.assertEquals(threeArray.length, 2);
Assert.assertEquals(threeArray[0], 2);
Assert.assertEquals(threeArray[1], 3);
int[] centArray = GeneratePrimes.generatePrimes(100);
Assert.assertEquals(centArray.length, 25);
Assert.assertEquals(centArray[24], 97);
}
}
生成素数类(未重构之前):
public class GeneratePrimes {
/**
* @param maxValue is the generation limit
* @return
*/
public static int[] generatePrimes(int maxValue) {
if (maxValue >= 2) {
//声明
int s = maxValue + 1;
boolean[] f = new boolean[s];
int i;
//初始化数组为ture
for (i = 0; i < s; i++) {
f[i] = true;
}
//去除已知的非素数
f[0] = f[1] = false;
//筛选出素数
int j;
for (i = 2; i < Math.sqrt(s) + 1; i++) {
for (j = 2 * i; j < s; j += i) {
f[j] = false;
}
}
//看有多少个素数
int count = 0;
for (i = 0; i < s; i++) {
if (f[i]) {
count++;
}
}
//将素数放到结果中
int[] primes = new int[count];
for (i = 0, j = 0; i < s; i++) {
if (f[i]) {
primes[j++] = i;
}
}
//返回素数
return primes;
} else {
return new int[0];
}
}
}
看看上面的代码是不是难以阅读,一个方法里面堆满了整个方法的实现。完全的面向过程编程了;
重构后的TestCase:
public class TestGeneratePrimes {
@Test
public void testPrimes() {
int[] nullArray = PrimeGenerate.generatePrimes(0);
Assert.assertEquals(nullArray.length, 0);
int[] minArray = PrimeGenerate.generatePrimes(2);
Assert.assertEquals(minArray.length, 1);
Assert.assertEquals(minArray[0], 2);
int[] threeArray = PrimeGenerate.generatePrimes(3);
Assert.assertEquals(threeArray.length, 2);
Assert.assertEquals(threeArray[0], 2);
Assert.assertEquals(threeArray[1], 3);
int[] centArray = PrimeGenerate.generatePrimes(100);
Assert.assertEquals(centArray.length, 25);
Assert.assertEquals(centArray[24], 97);
}
@Test
public void testExhaustive() {
for (int i = 2; i < 500; i++) {
verifyPrimeLIst(PrimeGenerate.generatePrimes(i));
}
}
private void verifyPrimeLIst(int[] list) {
for (int aList : list) {
verifyPrime(aList);
}
}
private void verifyPrime(int n) {
for (int factor = 2; factor < n; factor++) {
assert (n % factor != 0);
}
}
}
重构后生成素数的代码:
public class PrimeGenerate {
private static boolean[] crossedOut;
private static int[] result;
/**
* @param maxValue is the generation limit
* @return
*/
public static int[] generatePrimes(int maxValue) {
if (maxValue < 2) {
return new int[0];
} else {
uncrossIntegersUpTo(maxValue);
crossOutMultiples();
putUncrossedIntegerIntoResult();
return result;
}
}
private static void uncrossIntegersUpTo(int maxValue) {
crossedOut = new boolean[maxValue + 1];
for (int i = 2; i < crossedOut.length; i++) {
crossedOut[i] = false;
}
}
/**
* 筛选素数
*/
private static void crossOutMultiples() {
int limit = determineIterationLimit();
for (int i = 2; i <= limit; i++) {
if (notCrossed(i)) {
crossOutMultiplesOf(i);
}
}
}
private static int determineIterationLimit() {
double iterationLimit = Math.sqrt(crossedOut.length);
return (int) iterationLimit;
}
private static void crossOutMultiplesOf(int i) {
for (int multiple = 2 * i; multiple < crossedOut.length; multiple += i) {
crossedOut[multiple] = true;
}
}
private static boolean notCrossed(int i) {
return !crossedOut[i];
}
private static void putUncrossedIntegerIntoResult() {
result = new int[numberOfUncrossedIntegers()];
for (int j = 0, i = 2; i < crossedOut.length; i++) {
if (notCrossed(i)) {
result[j++] = i;
}
}
}
private static int numberOfUncrossedIntegers() {
int count = 0;
for (int i = 2; i < crossedOut.length; i++) {
if (notCrossed(i)) {
count++;
}
}
return count;
}
}
重构后代码阅读起来比之前舒服了,也更容易理解。重构的目的是为了每天清洁你的代码,不要让脏乱累积。定时对代码进行重构整理,这样就能通过最小的努力就能够对我们的系统进行扩展和修改。
网友评论