GitHub License GitHub Tag Swift Package Manager Compatible Written in Swift Supported platforms Build Status

需求

以声明式、易读的格式描述需求。

问题

当定义应用程序应该如何工作时,需要将许多需求实现在源代码中。 显然,每个需求都可以用人类友好的语言描述,也可以用编程语言(计算机友好的语言)正式化。

现有的解决方案

通常,需求是作为任务/模型等的一部分批量实现的,而没有将特定需求直接转换为应用程序中的确切代码行/范围的明显对应关系。

在大多数情况下,规范(任务定义)中的每个单个需求都被转换为数据模型或业务逻辑中的一些代码,仅此而已。 这意味着此类实现提供的语义不多 - 如果未满足此需求,则不清楚如何以正式的方式向外部范围报告问题,以及/或以人类友好的格式向用户(通过 GUI)报告。 如果实现了此类报告,通常会导致需求实现分散到几个不同的部分:实际需求检查、用于向外部范围(或 API 用户)报告的错误描述,以及用于通过 GUI 向用户报告的人类友好的表示形式。

这种需求实现方式难以测试/验证、难以随着时间推移保持一致(当给定需求发生微小更改时),并且使源代码难以理解和推理。

期望清单

理想情况下,应该有一个工具可以:

  1. 将需求的人类友好描述(可变长度文本)及其计算机友好的正式表示形式(一段代码)绑定在单个语句中;
  2. 专注于内容,尽量减少包装表达式;
  3. 自动化需求验证和成功/失败报告,同时面向外部范围和 GUI。

方法概述

可以针对给定的数据值评估每个需求(该数据值可以是原子数据类型或复杂数据类型)。 换句话说,每个需求定义都可以表示为采用一个或多个输入参数并返回 Boolean 值的函数 - true 表示使用提供的输入值满足需求,false 表示相反。

如何安装

推荐使用 SwiftPM 进行安装,但也支持开箱即用的 Carthage

工作原理

这是一个小巧且非常简单的库,但功能强大。

Requirement 是主要的数据类型,它实际上表示单个需求。 请注意,这是一个 struct,因此一旦创建,它就作为一个单一的原子值工作。

要定义需求,请创建 Requirement 的实例。 它的构造函数接受两个必要的参数 - 以 String 形式表示的人类友好的描述,以及实现正式表示的闭包。 此外,Requirement 是一种泛型类型,Input 泛型类型表示闭包的预期输入参数的类型。

如何使用

这是一个如何创建需求的示例,即整数不应等于零。

let r = Requirement<Int>("Non-zero") { $0 != 0 }

可以使用辅助类型别名 Require 来实现相同的目的

let r = Require<Int>("Non-zero") { $0 != 0 }

在上面的示例中,我们创建了一个 Requirement 实例,该实例应评估 Int 类型的值。 我们传递一个字符串作为构造函数函数的唯一参数,而第二个参数(闭包)作为尾随闭包传递。 该闭包包含每次需要评估此需求时将调用的代码,并将需要检查的相应值作为唯一的输入参数。

请注意,如果需求包含诸如 ANDOR 或任何其他逻辑运算符之类的短语,则此类需求被分成独立的需求。

创建需求后,这是一个如何使用它来检查潜在合适值的示例。

if
    r.isFulfilled(with: 14) // returns Bool
{
	// given value - 14 (Int) - fulfills the requirement

	// r.title - the description that has been provided
	// during requirement initialization
	
    print("\(r.title) -> YES")
}
else
{
	// this code block will be executed,
    // if 0 will be passed into the r.isFulfilled(...)
	
    print("\(r.title) -> NO")
}

可以通过利用 Swift 错误处理 来完成相同的检查,请参见下面的示例。

do
{
    try r.check(with: 0) // this will throw exception
}
catch
{
    print(error) // error is of 'RequirementNotFulfilled' type
}

RequirementNotFulfilled 数据类型有两个参数

内联助手

虽然 Requirement 本身可能更适合于实现 数据模型,但有一些助手使用了相同的想法,但提供了特殊的 API,该 API 在实现 业务逻辑 时更方便内联使用。 这些助手被封装到名为 REQ 的特殊 enum 中,当需求未满足时,它们都会抛出 VerificationFailed 错误的实例,其中一些可能会返回可在代码中进一步使用的值。

当您有一个 Optional 值或者您有一个产生 Optional 值的函数/闭包,并且您只需要在它不是 nil 时才需要该值,否则抛出错误

// the following expression will throw
// if the value from closure is 'nil' or just return
// unwrapped value of the optional from closure overwise
let nonNilValue = try REQ.value("Value is NOT nil") {
	
	// return here an optional value,
	// it might be result of an expression 
	// or an optional value captured from the outer scope
}

与上述相同,但不返回任何内容。 当您有一个 Optional 值或者您有一个产生 Optional 值的函数/闭包,并且您需要确保该值不是 nil,否则抛出错误

// the following expression does not return anything,
// it will throw if value IS 'nil'
// or pass through silently otherwise
try REQ.isNotNil("Value is NOT nil") {
	
	// return here an optional value,
	// it might be result of an expression 
	// or an optional value captured from the outer scope
}

当您有一个 Optional 值或者您有一个产生 Optional 值的函数/闭包,并且您需要确保该值是 nil,否则抛出错误

// the following expression does not return anything,
// it will throw if value is NOT 'nil'
// or pass through silently otherwise
try REQ.isNil("Value IS nil") {
	
	// return here an optional value,
	// it might be result of an expression 
	// or an optional value captured from the outer scope
}

当您有一个 Bool 值或者您有一个产生 Bool 值的函数/闭包,并且您只想在它是 true 时才继续,否则抛出错误(如果它是 false

// the following expression does not return anything,
// it will throw if value is 'false'
// or pass through silently otherwise
try REQ.isTrue("Value is TRUE") {
	
	// return here a boolean value,
	// it might be result of an expression 
	// or an boolean value captured from the outer scope
}

VerificationFailed 错误类型只有一个参数