前言
TDD開發實例
- 編譯通過,測試不過;(主要是準備測試用例,測試用例的構建是在準確分析了需求之上的,一句話就是將需求細化,明確至最小)
- 快速實現,運行通過;(在步驟一基礎上,快速實現功能代碼,使所有測試用例均能運行通過)
- 消除重複,重構優化,領域規則的抽象;(對代碼進行重構,消除重複,甚至進行領域規則的抽象。這個真的很難,往往在實現前兩步驟時,消除冗餘還好些,至於後面的抽象完全有想象,沒有方向。然後,我反正是第一次在寫代碼時候聽說領域規則這麼個詞,表示不那麼明白,後來自己理解就是提取出某一類相似的有規律的內容...)
1. 編譯通過,測試不過
/* test.cpp */
#include <assert.h>
extern int expr(const char *);
/* 演練過程中主要用到的代替接口 */
// extern int expr_muldiv(const char *);
// extern int expr_bracket_addsub(const char *);
// extern int expr_bracket_muldiv(const char *);
void main()
{
// test_pares_num
assert(expr("1") == 1);
assert(expr("2") == 2);
// test_addsub
assert(expr("1+2") == 3);
assert(expr("2+3+4") == 9);
assert(expr("2+3-4") == 1);
// test_muldiv
assert(expr("1") == 1); // assert(expr_muldiv("1") == 1);
assert(expr("4*5") == 20);
assert(expr("4*5*6") == 120);
assert(expr("4*5/2") == 10);
// test_mix
assert(expr("2+4*2") == 10);
assert(expr("2+4/2") == 4);
assert(expr("1+2+3*4") == 15);
assert(expr("2*3+4/2") == 8);
// test_mix_bracket
assert(expr("(1)") == 1); // assert(expr_bracket_addsub("1") == 1); or expr_bracket_muldiv()...
assert(expr("(1+1)") == 2);
assert(expr("(2+(3-4))") == 1);
assert(expr("(2*(5+1)") == 12);
assert(expr("(2+3)-(4-2)") == 3);
assert(expr("(2+3)*(4/2)") == 10);
// test_pow...
}
/* expr.cpp */
int expr(const char *str)
{
/* Nothing... */
return 0;
}
2. 快速實現,重構抽象
之所以將軍規中的二、三步合在一塊,是因爲培訓中都是完成一類測試用例功能,然後對比,重構、抽象,這都是自然一體的,沒有明顯地分割。=====(加減法)==============================華麗的分割線=====================================================
先從解析表達式的數字開始,即通過用例:assert(expr("1") == 1)...,然後實現加減法,中間的步驟都用註釋,代碼如下。
/* expr.cpp */
int expr(const char *str)
{
/* 代碼1 (表示expr.cpp中重要代碼變化的次序) */
// return str[0] - '0'; assert(expr("1") == 1)
/* 代碼2 */
/* assert(expr("1+2") == 3)
int i = 0;
int Result = str[0] - '0';
char Opt = str[1];
int Right = str[2] - '0';
Result = Result + Right;
return Result;
*/
}
OK,先優化這段代碼吧,覺得不爽的地方:數字太多,表達式重複。解決它,直接看代碼:(注意代碼變化的次序!)
/* expr.cpp */
/* 構造出這個結構,str意義不變,pos代表上面字符串數組的下標位置,這個不難理解 */
typedef struct
{
const char *str;
int pos;
} context;
int parse_opt(context &ctx)
{
return ctx.str[ctx.pos++];
}
int parse_num(context &ctx)
{
return ctx.str[ctx.pos++] - '0';
}
int expr(const char *str)
{
/* 代碼3 */
/* 這是優化前面兩個用例之後的代碼,然後繼續開始跑連加的用例, assert(expr("2+3+4") == 9)...,實現代碼4
context ctx = {str, 0};
int Result = parse_num(ctx);
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = Result + Right;
return Result;
*/
/* 代碼4 */
/*
context ctx = {str, 0};
int Result = parse_num(ctx);
while (ctx.str[ctx.pos] == '+') // 考慮到'-'與'+'同優先級,連減的測試用例通過過程一致,在這裏加條件,實現代碼5
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = Result + Right;
}
return Result;
*/
/* 代碼5 */
/* 考慮到'-'與'+'同優先級,連減的測試用例通過過程一致,看實現'+' 與 '-'代碼,實現代碼6
context ctx = {str, 0};
int Result = parse_num(ctx);
while (ctx.str[ctx.pos] == '+' || ctx.str[ctx.pos] == '+') // 條件太長,繼續優化,看下面代碼
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
// Result = Result + Right; 這裏Opt可以爲'+'、‘-’,所以這個表達式不合適,繼續提取
Result = calc(Result, Opt, Right); // calc()代碼往下看
}
return Result;
*/
/* 代碼6 */
context ctx = {str, 0};
int Result = parse_num(ctx);
while (is_add_or_sub(ctx)) // is_add_or_sub()代碼往下看
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
bool is_add_or_sub(context &ctx)
{
return ctx.str[ctx.pos] == '+' || ctx.str[ctx.pos] == '-';
}
int calc(int left, char opt, int right)
{
int Result = 0;
if (opt == '+')
{
Result = left + right;
}
else if (opt == '-')
{
Result = left - right;
}
else if (opt == '*')
{
Result = left * right;
}
else if (opt == '/')
{
Result = left / right;
}
return Result;
}
/* expr.cpp */
int add_sub(context &ctx)
{
int Result = parse_num(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
int expr(const char *str)
{
/* 代碼7 */
context ctx = {str, 0};
return add_sub(ctx);
}
/* expr.cpp */
bool is_mul_or_div(context &ctx)
{
return ctx.str[ctx.pos] == '*' || ctx.str[ctx.pos] == '/';
}
int mul_div(context &ctx)
{
int Result = parse_num(ctx);
while (is_mul_or_div(ctx))
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
int expr_muldiv(const char *str)
{
/* 代碼8 */
context ctx = {str, 0};
return mul_div(ctx);
}
/* expr.cpp */
int add_sub(context &ctx)
{
int Result = parse_num(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
int Right = mul_div(ctx); // 這一步解析的數字Right應該是8,這個8怎麼得來的?通過單乘 mul_div()得來。
Result = calc(Result, Opt, Right);
}
return Result;
}
// 同理,如果是測例assert(expr("2*4+2") == 10)...即最後是計算單加8+2=10;所以,代碼應該是:
/* expr.cpp */
int add_sub(context &ctx)
{
int Result = mul_div(ctx); // 這一步解析的數字Result應該是8,這個8怎麼得來的?通過單乘 mul_div()得來。
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
/* expr.cpp */
int add_sub(context &ctx)
{
/* 代碼9 */
int Result = mul_div(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
int Right = mul_div(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
int mul_div(context &ctx)
{
int Result = parse_num(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
int Right = parse_num(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
int expr(const char *str)
{
context ctx = {str, 0};
return add_sub(ctx);
}
/* 到這裏,可以看到expr_muldiv功能完全多餘,add_sub就已經提供了加減乘除四則運算了。
int expr_muldiv(const char *str)
{
context ctx = {str, 0};
return mul_div(ctx);
}
*/
觀察add_sub()、mul_div()可以發現,除了開頭解析數字和while循環條件外,其他並無不同。再分析:
/* expr.cpp */
/* 代碼10 */
typedef int (*HIGH_OPER)(context &ctx); // HIGH_OPER函數指針表示優先運算
typedef bool (*IS_LOW_OPT)(context &ctx); // 判斷優先級低的運算符
int oper(context &ctx, HIGH_OPER pHighOper, IS_LOW_OPT pIsLowOpt)
{
int Result = pHighOper(ctx);;
while (pIsLowOpt(ctx))
{
char Opt = parse_opt(ctx);
int Right = pHighOper(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
int mul_div(context &ctx)
{
return oper(ctx, parse_num, is_mul_or_div);
}
int add_sub(context &ctx)
{
return oper(ctx, mul_div, is_add_or_sub);
}
/* expr.cpp */
/* 代碼11 */
void parse_left_bracket(context &ctx)
{
if (ctx.str[ctx.pos] == '(')
{
ctx.pos++;
}
}
void parse_right_bracket(context &ctx)
{
if (ctx.str[ctx.pos] == ')')
{
ctx.pos++;
}
}
/* 帶括號的加減 */
int expr_bracket_addsub(const char *str)
{
context ctx = {str, 0}; // assert(expr("(1)") == 1); assert(expr("(1+1)") == 1)...
int Result = 0;
parse_left_bracket(ctx);
Result = parse_num(ctx);
parse_right_bracket(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
parse_left_bracket(ctx);
int Right = parse_num(ctx);
parse_right_bracket(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
/* 帶括號的乘除 */
int expr_bracket_muldiv(const char *str)
{
context ctx = {str, 0}; // assert(expr("(1)") == 1); assert(expr("(1*2)") == 1)...
int Result = 0;
parse_left_bracket(ctx);
Result = parse_num(ctx);
parse_right_bracket(ctx);
while (is_mul_or_div(ctx))
{
char Opt = parse_opt(ctx);
parse_left_bracket(ctx);
int Right = parse_num(ctx);
parse_right_bracket(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
1)無括號,表達式只有加減運算時,先取數字;表達式含加減乘除運算時,先算乘除;
2)有括號,只有加減運算時,先去左括號,再取數字,再去右括號...
那麼,有括號,加減乘除運算時???
這裏可以這麼思考,括號中的表達式又是一個新的子表達式,完全可以通過上面的無括號表達式計算方法運算。所以,照這個思路,代碼更改如下:
/* expr.cpp */
/* 代碼12 */
/* 帶括號的加減乘除 */
int expr_bracket_addsub(const char *str)
{
context ctx = {str, 0}; // assert(expr("(1)") == 1); assert(expr("(1+1)") == 1)...
int Result = 0;
parse_left_bracket(ctx);
Result = add_sub(ctx);
parse_right_bracket(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
parse_left_bracket(ctx);
int Right = add_sub(ctx);
parse_right_bracket(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
/* expr.cpp */
/* 代碼13 */
int parse_bracket_num(context &ctx)
{
int Result = 0;
if (ctx.str[ctx.pos] == '(')
{
ctx.pos++;
Result = add_sub(ctx);
parse_right_bracket(ctx);
} else if (ctx.str[ctx.pos] >= '0' && ctx.str[ctx.pos] <= '9')
{
Result = ctx.str[ctx.pos++] - '0';
}
return Result;
}
int mul_div(context &ctx)
{
return oper(ctx, parse_bracket_num, is_mul_or_div);
}
/* 帶括號的加減乘除 */
int expr_bracket_addsub(const char *str)
{
context ctx = {str, 0}; // assert(expr("(1)") == 1); assert(expr("(1+1)") == 1)...
int Result = parse_bracket_num(ctx);
while (is_add_or_sub(ctx))
{
char Opt = parse_opt(ctx);
int Right = parse_bracket_num(ctx);;
Result = calc(Result, Opt, Right);
}
return Result;
}
=====(擴展:冪乘運算)==============================華麗的分割線=====================================================
/* expr.cpp */
/* 代碼12 */
typedef struct
{
const char *str;
int pos;
} context;
typedef int (*HIGH_OPER)(context &ctx);
typedef bool (*IS_LOW_OPT)(context &ctx);
void parse_right_bracket(context &ctx)
{
if (ctx.str[ctx.pos] == ')')
{
ctx.pos++;
}
}
int parse_opt(context &ctx)
{
return ctx.str[ctx.pos++];
}
int parse_bracket_num(context &ctx)
{
int Result = 0;
if (ctx.str[ctx.pos] == '(')
{
ctx.pos++;
Result = add_sub(ctx);
parse_right_bracket(ctx);
} else if (ctx.str[ctx.pos] >= '0' && ctx.str[ctx.pos] <= '9')
{
Result = ctx.str[ctx.pos++] - '0';
}
return Result;
}
bool is_mul_or_div(context &ctx)
{
return ctx.str[ctx.pos] == '*' || ctx.str[ctx.pos] == '/';
}
bool is_add_or_sub(context &ctx)
{
return ctx.str[ctx.pos] == '+' || ctx.str[ctx.pos] == '-';
}
int calc(int left, char opt, int right)
{
int Result = 0;
if (opt == '+')
{
Result = left + right;
}
else if (opt == '-')
{
Result = left - right;
}
else if (opt == '*')
{
Result = left * right;
}
else if (opt == '/')
{
Result = left / right;
}
return Result;
}
int oper(context &ctx, HIGH_OPER pHighOper, IS_LOW_OPT pIsLowOpt)
{
int Result = pHighOper(ctx);;
while (pIsLowOpt(ctx))
{
char Opt = parse_opt(ctx);
int Right = pHighOper(ctx);
Result = calc(Result, Opt, Right);
}
return Result;
}
int mul_div(context &ctx)
{
return oper(ctx, parse_bracket_num, is_mul_or_div);
}
int add_sub(context &ctx)
{
return oper(ctx, mul_div, is_add_or_sub);
}
int expr(const char *str)
{
context ctx = {str, 0};
return add_sub(ctx);
}
冪乘比乘除優先級更大,運算符的優先級更高,所以,我們擴展並修改下這麼一段代碼:
/* expr.cpp */
/* 代碼13 */
bool is_pow(context &ctx)
{
return ctx.str[ctx.pos] == '^' ;
}
int pow(context &ctx)
{
return oper(ctx, parse_bracket_num, is_pow);
}
int mul_div(context &ctx)
{
return oper(ctx, pow, is_mul_or_div);
}
int calc(int left, char opt, int right)
{
int Result = 0;
if (opt == '+')
{
Result = left + right;
}
else if (opt == '-')
{
Result = left - right;
}
else if (opt == '*')
{
Result = left * right;
}
else if (opt == '/')
{
Result = left / right;
} if (opt == '^') // 擴展的代碼
{
Result = left;
for (int i = 1; i < right; i++)
{
Result *= left;
}
}
return Result;
}
先分析什麼是異常,目前那肯定就只能是表達式的異常,那又有多少類異常??當時,教練們讓我們自己枚舉...然後就可以腦補當時場景了。再然後,聽了N種異常後,教練只說了一句淡定的話,你們說了這麼多,就是解析表達式的數字、運算符解析不出來麼??一語中的!!!
那麼爲什麼解析不出來?就是本應該是數字或者運算符的那一個位置出現了別的字符。OK,按照這個思路,我們修改下解析數字功能函數的功能不就可以了??直接看修改代碼:
/* expr.cpp */
/* 代碼14 */
typedef struct
{
const char *str;
int pos;
int errno; // 增加錯誤碼標識異常
} context;
int parse_bracket_num(context &ctx)
{
int Result = 0;
if (ctx.str[ctx.pos] == '(')
{
ctx.pos++;
Result = add_sub(ctx);
parse_right_bracket(ctx);
} else if (ctx.str[ctx.pos] >= '0' && ctx.str[ctx.pos] <= '9')
{
Result = ctx.str[ctx.pos++] - '0';
} else // 解析括號,解析數字都沒成功,解析運算符有專門pares_opt。。所以剩下的情況自然異常
{
ctx.errno = -1;
}
return Result;
}
int expr(const char *str)
{
context ctx = {str, 0};
int Result = add_sub(ctx);
if (ctx.errno == -1) // 異常處理
{
return -1;
}
return Result;
}