关闭

实习日常(1)——提高代码质量:过度保护以及TDD,BDD

标签: 测试tddbdd过度保护易读性
148人阅读 评论(0) 收藏 举报
分类:

Beginning

  • TDD,BDD:在刚到公司的时候,重构java旧项目,花了漫长时间,写了与原代码运行路径一致,但很繁复的代码,导致在一天之后难以阐述逻辑。被质疑逻辑错误,当时委屈。最后修改抽出了一个子方法,并且将原if else语句进行了精简,代码逻辑清晰。当时看了一篇还不错的关于代码质量的文章事实上,TDD,BDD中提到的偶发复杂性(TDD遵循,仅仅编写足以使测试代码通过的代码,保持简单设计并适合目的)和代码质量导致的难以阅读是不同的,但是追求的易读性是共同的。
    • 有时我们喜欢通过产出偶发复杂性(可以替换为简单设计的复杂设计)的设计来展示自己的精神能力,确使自己都理解不了那些设计。(——《有效的单元测试》)
    • 认知超负荷是程序员特别常见的问题,因为编程本质上是繁重的记忆力活动。(——《有效的单元测试》)

TDD与BDD

1. 定义 (引自《有效的单元测试》——Lasse Koskela)

TDD

  • TDD(Test-Driven-Development, 测试驱动开发):在编写出能够证明代码存在的失败测试之前,不写生产代码。
  • 优点:
    • 代码变得可用——代码的设计和API适合于你的使用场景。
    • 代码变得精益——生产代码仅仅实现场景所需要的功能(代码质量的罪魁祸首之一,以及使开发者生产力停滞的主要因素,就是称为偶发复杂性:即不必要的复杂性。可以通过替换为简单的设计来避免它,同时仍然满足需求。)

BDD

  • BDD(Behaviour-Driven-Development,行为驱动开发):是在TDD上发展衍生来的。TDD思想和词汇表中所讲的“测试”会误导人们。

    作为BDD实践者,我们小心地以例子的形式编写验收测试,是任何团队成员都能够理解。我们遵循这个过程,编写例子来从业务干系人那里获得反馈,在动手之前就能了解我们是否在构建正确的东西。(——《Cucumber:行为驱动开发指南》)

    • 也就是说。今天的BDD语境和领域远远超出了代码——最引人注目的是将BDD提升到需求层面,与业务分析和需求行为结合起来。
  • 也就是说我们不是一定要用“测试”来作为工具(但并不是说测试不是TDD的本质——所产生的函数和方法是一种有效确保代码工作的方式。然而,如果不能全面地描述系统的行为,那么它们会带给你一种虚假的安全感)。事实上,我在看过这个概念后,发现在工作时,部门所用的thrift这种RPC,在写代码前先定义好主要的功能函数,包括输入输出,再进行开发,就是一个TDD&&BDD的过程。

2. 例子 : 以下是thrift的IDL(可以理解为是TDD,但是定义这个thrift通过了需求沟通,各部门协调,就可以算是BDD)。

    //服务
    service FavoriteSrv {
        //功能
        /**
         * 是否已收藏
         */
        IsFavorResp isFavor(1: IsFavorReq req) throws(1: SrvException e)
    }
    //返回值
    struct IsFavorResp{
        /**
         * true表示已收藏, false表示未收藏
         */
        1: bool isFavor
    }
    //输入参值
    struct FavorReq{
        1: ReqHeader header
        /**
         * 1: 收藏医院, 2: 收藏医生
         */
        2: i32 favorType
        /**
         * 操作类型 0: 取消收藏, 1: 收藏
         */
        3: i32 operType
        4: i64 objId
        5: i64 accountId
    }

过度保护

同在《有效的单元测试》一书中,也是因为娄哥在测试语音识别测试时念了过分保护一章,引起了我的兴趣从而翻看。当天正好有一个代码要加上错误处理,而错误处理一直在工作中和其他同事有分歧,即某个地方是否需要加上错误处理。以下是个人体悟总结而不是书本摘抄,可能有误

1. 结论:在何时需要增加守卫语句(空值或错误检查)。

  • 不加上无法或难以定位到错误发生的所在地
  • 会引发业务异常而不发生错误

2.例子:go不完全代码(是否需要处理string转date的错误)

    //将req.DateYM(string)类型转换为date类型
    startYM, err := time.ParseInLocation(constants.DateFormatYYYYMM, req.DateYM, time.Local)
    if err != nil {
        return nil, err
    }
    //将endYM设置为本月的最后一天,通过加一个月同时减去一天
    endYM := startYM.AddDate(0, 1, -1)
  • 这个例子的错误处理是必须的,如果不处理:
    • 可能得到一个错误的与业务不相符的错误数据,而不是引起程序异常结束。
    • 可能在下面AddDate时才发生错误,而不是在Parse时,从而难以定位错误。
    • 在go中,如果不处理err,并且不处理发生错误后的panic,会引起进程的异常关闭。(我遇到的情况已经recover接受了panic,如果以上两点不满足,是可以不处理err的,而是直接读取panic信息进行debug)
  • 可以不处理的情况如下:
    • 如果err的错误会直接引起程序异常结束(不会输出错误业务数据),那我们能够直接定位到错误(在go中就是panic后的信息),而不是难以定位(书作者中提到选择调试的难度和代码的简洁,他选择代码简洁)。

go的题外话:我们应该在错误发生时及时处理还有一个原则,不能引起程序panic,或者说panic后有专门的处理方法(推荐或者一般都会在函数中写上recover处理panic防止进程突然关闭,但是我们在init失败时应该在程序直接关闭而不是用recover阻止。具体前上篇文章。)

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2255次
    • 积分:112
    • 等级:
    • 排名:千里之外
    • 原创:9篇
    • 转载:0篇
    • 译文:0篇
    • 评论:1条