Scrum 2011更新

  • 进行增量产出的一组开发人员组成一个开发团队(development team)。如果大家都在单打独斗的开发,那不叫团队.
  • 在冲刺计划会(sprint planning)上,团队不再承诺(commit)所有工作,而是建立一个相信能够完成任务的预期(forcast),但是这个预期在整个sprint中,随着对任务的深入了解而变化。
  • 不再必须用燃尽图(burndown chart)来监控进度。Scrum只需要每天对剩余工作进行估计,且在整个sprint中,持续维护任务完成的趋势即可。
  • 版本计划会(release planning)对使用scrum来说很重要,但不是scrum本身的要求。
  • 冲刺订单(sprint backlog)相当于从产品订单(product backlog)中选择出来的任务,加上交付任务的计划。冲刺订单任务(sprint backlog item)的概念不再需要,尽管这种技术可以做出伟大的计划。一个自我组织的开发团队总是有其自己的计划。
  • 产品订单是有序排列的,而不再要求是按优先级排列的。这样允许产品负责人(product owner)在特定环境下灵活的优化产品价值。

原文:

http://www.scrum.org/storage/Scrum%20Update%202011.pdf

Java NIO Socket实现C/S架构

一般的Server端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package thread.socket;  
import java.io.*;
import java.net.*;
import java.util.*;
public class Server extends ServerSocket {
private static final int SERVER_PORT = 10000;
private List<WorkerThread> workers;

public Server() throws IOException {
super(SERVER_PORT);
workers = new LinkedList<WorkerThread>();

try {
System.out.println("server is listening...");
while (true) {
Socket socket = accept();
workers.add(new WorkerThread(socket));
System.out.println(String.format("new worker created, total %d", getWorkerCount()));
}
} catch (IOException e) {
} finally {
close();
}
}
public synchronized int getWorkerCount() {
return workers.size();
}
public static void main(String[] args) throws IOException {
new Server();
}
}
// --- WorkerThread
class WorkerThread extends Thread {
private Socket client;
private BufferedReader in;
private PrintWriter out;
public WorkerThread(Socket s) throws IOException {
System.out.println(String.format("create a new thread. %s", s));
client = s;
in = new BufferedReader(new InputStreamReader(client
.getInputStream(), "GB2312"));
out = new PrintWriter(client.getOutputStream(), true);
out.println("--- Welcome ---" + client.getRemoteSocketAddress());
start();
}
public void run() {
try {
String line = in.readLine();
while (!line.equals("bye")) {
System.out.println("client " + client.getRemoteSocketAddress() + " says: " + line);
String msg = createMessage(line);
out.println(msg);
line = in.readLine();
}
out.println("bye");
System.out.println("client " + client.getRemoteSocketAddress() + " quit");
client.close();
} catch (IOException e) {
}
}
private String createMessage(String line) {
// ;
return "response to " + line;
}
}

使用NIO的Server端 (从1.5开始,Java对InputStream/OutputStream 进行了重新改写,用的就是NIO,因此,就算你不显示声明要用NIO,只要你的类继承了InputStream/OutputStream就已经在用NIO了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import java.io.BufferedWriter;  
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
public class SelectorServer
{
private static int DEFAULT_SERVERPORT = 6018;//默认端口
private static int DEFAULT_BUFFERSIZE = 1024;//默认缓冲区大小为1024字节
private static String DEFAULT_CHARSET = "GB2312";//默认码集
private static String DEFAULT_FILENAME = "bigfile.dat";
private ServerSocketChannel channel;
private LinkedList<SocketChannel> clients;
private Selector selector;//选择器
private ByteBuffer buffer;//字节缓冲区
private int port;
private Charset charset;//字符集
private CharsetDecoder decoder;//解码器


public SelectorServer(int port) throws IOException
{
this.port = port;
this.clients = new LinkedList<SocketChannel>();
this.channel = null;
this.selector = Selector.open();//打开选择器
this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
this.charset = Charset.forName(DEFAULT_CHARSET);
this.decoder = this.charset.newDecoder();

}

private class HandleClient
{
private String strGreeting = "welcome to VistaQQ";
public HandleClient() throws IOException
{
}
public String readBlock()
{//读块数据
return this.strGreeting;
}
public void close()
{

}
}
protected void handleKey(SelectionKey key) throws IOException
{//处理事件
if (key.isAcceptable())
{ // 接收请求
ServerSocketChannel server = (ServerSocketChannel) key.channel();//取出对应的服务器通道
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);//客户socket通道注册读操作
}
else if (key.isReadable())
{ // 读信息
SocketChannel channel = (SocketChannel) key.channel();
int count = channel.read(this.buffer);
if (count > 0)
{
this.buffer.flip();
CharBuffer charBuffer = decoder.decode(this.buffer);
System.out.println("Client >>" + charBuffer.toString());
SelectionKey wKey = channel.register(selector,
SelectionKey.OP_WRITE);//为客户sockt通道注册写操作
wKey.attach(new HandleClient());
}
else
{//客户已经断开
channel.close();
}
this.buffer.clear();//清空缓冲区
}
else if (key.isWritable())
{ // 写事件
SocketChannel channel = (SocketChannel) key.channel();
HandleClient handle = (HandleClient) key.attachment();//取出处理者
ByteBuffer block = ByteBuffer.wrap(handle.readBlock().getBytes());
channel.write(block);
// channel.socket().getInputStream().(block);
// PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
// channel.socket().getOutputStream())), true);
// out.write(block.toString());
}
}
public void listen() throws IOException
{ //服务器开始监听端口,提供服务
ServerSocket socket;
channel = ServerSocketChannel.open(); // 打开通道
socket = channel.socket(); //得到与通到相关的socket对象
socket.bind(new InetSocketAddress(port)); //将scoket榜定在制定的端口上
//配置通到使用非阻塞模式,在非阻塞模式下,可以编写多道程序同时避免使用复杂的多线程
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
try
{
while(true)
{// 与通常的程序不同,这里使用channel.accpet()接受客户端连接请求,而不是在socket对象上调用accept(),这里在调用accept()方法时如果通道配置为非阻塞模式,那么accept()方法立即返回null,并不阻塞
this.selector.select();
Iterator iter = this.selector.selectedKeys().iterator();
while(iter.hasNext())
{
SelectionKey key = (SelectionKey)iter.next();
iter.remove();
this.handleKey(key);

}
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
public static void main(String[] args) throws IOException
{
System.out.println("服务器启动");
SelectorServer server = new SelectorServer(SelectorServer.DEFAULT_SERVERPORT);
server.listen(); //服务器开始监听端口,提供服务
}
}

使用NIO的Client端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package thread.socket;  
import java.io.*;
import java.net.*;
public class Client {
Socket socket;
BufferedReader in;
PrintWriter out;
public Client() {
try {
socket = new Socket("127.0.0.1", 10000);
in = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader line = new BufferedReader(new InputStreamReader(
System.in));
String cmd = "";
while (!cmd.equals("bye")) {
System.out.println("server says: " + in.readLine());
out.println(cmd = line.readLine());
}
System.out.println("socket " + socket + " stop");
line.close();
out.close();
in.close();
socket.close();
} catch (IOException e) {
}
}
public static void main(String[] args) {
new Client();
}
}

Do you like my piece of paper?

What I see every day is people creating pieces of paper to take into a room for people to look at and decide if they like that piece of paper, and if they don’t totally like it they say what they would prefer to see on that piece of paper.

This repeats until everybody is happy with a piece of paper.

capture3

Read More

TDD中变形动作的优先顺序 - Transformation Priority Premise

在TDD循环中,重构(Refactoring)是不改变行为而改变内部结构的动作,保持测试常绿。而变形(Transformation)是改变内部实现来使测试由红变绿。这些变形的变化使代码形式从特殊specific到一般generic。

Uncle Bob的一篇文章。http://blog.8thlight.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html
中用了bowling game kata 和 Prime Factor kata来解释。

Read More

“设计”工作应该放在迭代中吗?

在教练敏捷团队的过程中,一些团队反映“某些用户故事不好拆分,无法在一个迭代内完成”。对于这些故事,一些团队的做法是先指定资深人员作为“设计师”,他从“需求分析师”或业务方拿到需求后,花上1个迭代的时间进行“设计”,然后由他将编码任务进一步分配给“开发人员”,并且负责验收代码。这些故事往往就会持续1~2个迭代才能完成,甚至还来不及测试。

Read More

所有代码都需要单元测试覆盖吗?

test-coverage

单元测试(unit testing)已经越来越得到广大开发者的认可。作为低成本、速度快、稳定度高的自动化测试手段,单元测试可以在类和函数级别对代码进行质量守护,有助于避免尴尬、耗时的错误。当然,相比功能测试(Functional testing)和端到端测试(end-to-end testing),单元测试能够寄予的产品级别的信心要略低一些,因而各个粒度的测试应该是相辅相成的,互为补充。

常常听到一些组织要求开发团队提高单元测试覆盖率,换来的却是怨声载道,或者是一堆应付差事的垃圾测试(没有断言的测试,都见过吧)。尽管,低测试覆盖率意味着对质量的信心不足,但是,单元测试覆盖率真的要达到100%才好吗?

Read More

精益软件度量

(题图感谢@杜伟忠)
该书由Thoughtworks的张松编著,试图回答敏捷转型中常被问到的”度量“问题。该问题常常隐藏着高级管理层的期待,但也包含敏捷怀疑者的抵触。作者从敏捷实践、团队成长、项目质量与价值等角度列出了一些手段,有一些大家都耳熟能详了,比如代码质量或故事点等。
然而,某些问题并不是那么容易回答的,或者只能用定性而非定量手段;或者反馈速度较慢。还有些问题,例如资源利用率(或人天成本等),作者恐怕也难以给出通用的答案。

此外,度量要耗费工作量,却并不产生价值;而且容易因为绩效考核的原因而走样,所以选择度量手段也需要斟酌。

Read More