契约式设计在类和单元测试中的应用

契约式设计的定义

  契约式设计(Design by Contract (DbC)),这种设计方式和商业契约的情况类似。契约作用于两方,每一方都会完成一些任务,从而促成契约的达成,但同时,每一方也会接受一些义务,作为制定契约的前提,有任意一方无视了必尽义的义务,则契约失败。

契约式设计的原则

  • 区分命令和查询。查询返回一个结果,但不改变对象的可见性质。命令改变对象的状态,但不一定返回结果。
  • 将基本查询和派生查询分开。派生查询可以用基本查询来定义。
  • 设定一个后验条件,使用一个或多个基本查询的结果来定义每个派生查询。这样我们只要知道基本查询的值,也就能知道派生查询的值。

例子

  上述原则多用于对进行设计,命令选择使用set函数设置,而查询选择直接通过对象获取。
通过URL类的接口函数作为例子:

typedef int bool;
#define true 1
#define false 0

/* 包含url各个部分的结构体。 */
typedef struct _url_t {
  char* protocol; /* 协议 */
  char* domain;   /* 域名 */
  int port;       /* 端口号 */
  char* vdir;     /* 虚拟目录 */
  char* filename; /* 文件名 */
  int para_num;   /* 参数数量 */
  char** para;    /* 参数 */
  char* anchor;   /* 锚 */
} url_t;

/**
 * @method url_create
 * 创建url结构体。(需要销毁url结构体请调用url_destroy函数)
 *
 * @return {url_t*} 返回url结构体指针(若无内存创建,返回值为空)。
 */
url_t* url_create(void);

/**
 * @method url_parse
 * 将url字符串解析为各个部分。
 * @param {const char*} str_url 需解析的url字符串。
 * @param {url_t*} url 已创建的url指针。
 *
 * @return {bool} 若返回true,则url字符串解析成功,否则为失败。
 */
bool url_parse(const char* str_url, url_t* url);

/**
 * @method url_set_protocol
 * 设置url结构体的protocol(协议)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {const char*} protocol 协议。
 *
 * @return {bool} 若返回true,则url的protocol成员设置成功,否则为失败。
 */
bool url_set_protocol(url_t* url, const char* protocol);

/**
 * @method url_set_domain
 * 设置url结构体的domain(域名)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {const char*} domain 域名。
 *
 * @return {bool} 若返回true,则url的domain成员设置成功,否则为失败。
 */
bool url_set_domain(url_t* url, const char* domain);

/**
 * @method url_set_port
 * 设置url结构体的port(端口号)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {int} port 端口号。
 *
 * @return {bool} 若返回true,则url的port成员设置成功,否则为失败。
 */
bool url_set_port(url_t* url, int port);

/**
 * @method url_set_vdir
 * 设置url结构体的vdir(虚拟目录)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {const char*} vdir 虚拟目录。
 *
 * @return {bool} 若返回true,则url的vdir成员设置成功,否则为失败。
 */
bool url_set_vdir(url_t* url, const char* vdir);

/**
 * @method url_set_filename
 * 设置url结构体的filename(文件名)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {const char*} filename 文件名。
 *
 * @return {bool} 若返回true,则url的filename成员设置成功,否则为失败。
 */
bool url_set_filename(url_t* url, const char* filename);

/**
 * @method url_set_anchor
 * 设置url结构体的anchor(锚)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {const char*} anchor 锚。
 *
 * @return {bool} 若返回true,则url的anchor成员设置成功,否则为失败。
 */
bool url_set_anchor(url_t* url, const char* anchor);

/**
 * @method url_set_para
 * 设置url结构体的para(参数)成员。
 * @param {url_t*} url 已创建的url指针。
 * @param {const char*} para 参数。
 * @param {int16_t} para_index 参数位置(para_index等于0为第一个参数,若para_index大于等于参数的个数,则会新增一个参数)。
 *
 * @return {bool} 若返回true,则url的para成员设置成功,否则为失败。
 */
bool url_set_para(url_t* url, const char* para, int16_t para_index);

/**
 * @method url_destroy
 * 将url结构体销毁。
 * @param {url_t*} url 已创建的url指针。
 */
void url_destroy(url_t* url);

契约式设计的约束条件

  • 前置条件(precondition):在执行函数体前,对参数进行检查,当参数无误后,再执行函数体。
  • 后置条件(postcondition):在函数体执行后,对函数体返回的结果进行检查。
  • 类不变项(class invariant):在函数体执行前后,对不会发生改变的条件进行检查。

例子

  因为用在实际程序中会降低效率,所以上述约束条件多用于单元测试
通过URL类的分析函数测试作为例子:

#define TEST_URL "https://www.kun.com:443/kun/dir/KUN?1&2#name"

TEST(UrlParse, UrlParse) {
  char* str = TEST_URL;
  url_t* url = url_create();
  /* 先验条件 */
  ASSERT_NE(nullptr, url);	       /* 类不变项 */
  /***********/
  bool ret = url_parse(str, url);  /* 函数体 */
  /* 后验条件 */
  ASSERT_EQ(true, ret);
  ASSERT_NE(nullptr, url);	       /* 类不变项 */
  /***********/

  ASSERT_STREQ("https", url->protocol);
  ASSERT_STREQ("www.kun.com", url->domain);
  ASSERT_EQ(443, url->port);
  ASSERT_STREQ("kun/dir", url->vdir);
  ASSERT_STREQ("KUN", url->filename);
  ASSERT_EQ(2, url->para_num);
  ASSERT_STREQ("1", url->para[0]);
  ASSERT_STREQ("2", url->para[1]);
  ASSERT_STREQ("name", url->anchor);
  url_destroy(url);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值