- 論壇徽章:
- 0
|
在《測(cè)試驅(qū)動(dòng)開發(fā)》(Kent Beck)的附錄B,Kent Beck用了兩頁紙的篇幅,演示了一次完全以測(cè)試驅(qū)動(dòng)的方式,開發(fā)計(jì)算斐波納契數(shù)列。
先簡(jiǎn)短的抄一下代碼,再談?wù)勎业目捶ā?
第一個(gè)測(cè)試與第一次的代碼- public void testFibonacci();
- assertEquals(0,fib(0););;
- }
- int fib(int n);{
- return 0;
- }
復(fù)制代碼 第二個(gè)測(cè)試與第二次的代碼
Java代碼- public void testFibonacci();
- assertEquals(0,fib(0););;
- assertEquals(1,fib(1););;
- }
-
- int fib(int n);{
- if(n==0); return 0;
- return 1;
- }
- public void testFibonacci();
- assertEquals(0,fib(0););;
- assertEquals(1,fib(1););;
- }
- int fib(int n);{
- if(n==0); return 0;
- return 1;
- }
復(fù)制代碼 對(duì)測(cè)試代碼進(jìn)行改進(jìn),使之更為通用
Java代碼- public void testFibonacci();{
- int cases[][]={{0,0},{1,1}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
- public void testFibonacci();{
- int cases[][]={{0,0},{1,1}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
復(fù)制代碼 再增加n=2的測(cè)試
Java代碼- public void testFibonacci();{
- int cases[][]={{0,0},{1,1},{2,1}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
- public void testFibonacci();{
- int cases[][]={{0,0},{1,1},{2,1}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
復(fù)制代碼 不需要修改代碼,測(cè)試就通過了。
再增加n=3的測(cè)試
Java代碼- public void testFibonacci();{
- int cases[][]={{0,0},{1,1},{2,1},{3,2}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
- public void testFibonacci();{
- int cases[][]={{0,0},{1,1},{2,1},{3,2}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
復(fù)制代碼 測(cè)試失敗,于是修改代碼,還是如法炮制
Java代碼- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return 2;
- }
- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return 2;
- }
復(fù)制代碼 然后,最為神奇的部分在下面的四次修改:
1:
Java代碼- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return 1+1;//注意這里
- }
- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return 1+1;//注意這里
- }
復(fù)制代碼 2:
Java代碼- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return fib(n-1);+1;//注意這里
- }
- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return fib(n-1);+1;//注意這里
- }
復(fù)制代碼 3:
Java代碼- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return fib(n-1);+fib(n-2);;//注意這里
- }
- int fib(int n);{
- if(n==0); return 0;
- if(n<=2); return 1;
- return fib(n-1);+fib(n-2);;//注意這里
- }
復(fù)制代碼 4:
Java代碼- int fib(int n);{
- if(n==0); return 0;
- if(n==1); return 1;//注意這里
- return fib(n-1);+fib(n-1);;
- }
- int fib(int n);{
- if(n==0); return 0;
- if(n==1); return 1;//注意這里
- return fib(n-1);+fib(n-1);;
- }
復(fù)制代碼 這是一個(gè)非常棒的過程。我們的討論也從這里開始。
最后得到的這個(gè)函數(shù),是一個(gè)遞歸函數(shù),非常的簡(jiǎn)潔,但是往往會(huì)有效率問題。
(打住,告訴過你多少次了,不要考慮效率!)
不是我要考慮效率,只是這么簡(jiǎn)單的例子,要尋找別的設(shè)計(jì)方式,我只能從效率方面來說事。
OK,繼續(xù)。假設(shè)我們要求9的斐波納契數(shù)列的值,那么,fib函數(shù)就會(huì)去計(jì)算fib(8 )+fib(7)。然后我們?cè)僬归_。
fib(9)=fib(8 )+fib(7)
fib(9)=(fib(7)+fib(6))+(fib(6)+fib(5))
注意,這里fib(6)就要被計(jì)算兩遍。
fib(9)=((fib(6)+fib(5))+(fib(5)+fib(4)))+((fib(5)+fib(4))+(fib(4)+fib(3)))
注意,這里fib(5)要被計(jì)算3遍,fib(4)要被計(jì)算3遍。
理解我的意思了嗎?這樣的算法,存在嚴(yán)重的效率隱患。
如果我們要考慮效率,會(huì)如何寫代碼呢?
Java代碼- public int fib(int n);{
- int value0=0;
- int value1=0;
- int value=0;
- for(int i=0;i<=n;i++);{
- if(i==1);{
- value1=1;
- value=1;
- } else {
- value=value0+value1;
- value0=value1;
- value1=value;
- }
- }
- return value;
- }
- public int fib(int n);{
- int value0=0;
- int value1=0;
- int value=0;
- for(int i=0;i<=n;i++);{
- if(i==1);{
- value1=1;
- value=1;
- } else {
- value=value0+value1;
- value0=value1;
- value1=value;
- }
- }
- return value;
- }
復(fù)制代碼 這個(gè)算法我就不解釋了。有人也許會(huì)說,你這樣不是TDD,你先寫了程序!
不要緊,我可以假裝先寫了測(cè)試代碼
Java代碼- public void testFibonacci();{
- int cases[][]={{0,0},{1,1},{2,1},{3,2}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
- public void testFibonacci();{
- int cases[][]={{0,0},{1,1},{2,1},{3,2}};
- for(int i=0;i<cases.length;i++);{
- assertEquals(cases[i][1],fib(cases[i][0]););;
- }
復(fù)制代碼 然后再把剛才的那個(gè)程序?qū)懗鰜,這樣有什么問題嗎?這樣還算是TDD嗎?
我仔細(xì)看了書了,Kent Beck說過“步伐”問題。我這樣也可以算是TDD的,只是步子大了點(diǎn)。
那么我想說明什么問題呢?
1、無論先寫測(cè)試還是先寫代碼,都需要考慮設(shè)計(jì)問題
2、在寫測(cè)試之前考慮設(shè)計(jì)問題,不是什么罪過
3、考慮設(shè)計(jì)思路的深入與否,決定了步伐的大小
4、步伐太小的設(shè)計(jì)考慮,可能會(huì)陷入死角,無法再優(yōu)化下去。從上面的代碼可以看到,要想使遞歸算法變成循環(huán)算法,不是重構(gòu)能夠做到的。
最終的結(jié)論是:
代碼就像你的左腳,測(cè)試就像你的右腳。
你可以先邁左腳,再邁右腳。然后一直走下去。
也可以先邁右腳,再邁左腳。然后一直走下去。
只要你不是一直單腳跳著前進(jìn),你都會(huì)走得很穩(wěn),而且沒有人看得出區(qū)別來。
原文http://www.javaeye.com/topic/6551 |
|