Requirement
以声明性、可读性强的格式描述要求。
问题
当涉及到定义应用程序应该如何工作时,有许多应该实现到源代码中的 要求。显然,每一个要求都可以用人类友好的语言描述,同时也可以用编程语言(电脑友好的语言)进行规范化。
现有解决方案
通常,要求作为任务/模型等的一部分批量实现,没有明显地将特定要求直接翻译为应用程序中确切的行/范围。
在大多数情况下,每个规格(任务定义)中的要求都转换为数据模型或业务逻辑中的某些代码,就是这样。这意味着这种实现提供的语义非常少——如果这个要求没有得到满足,不清楚怎么正式地向上级或用户(通过GUI)报告问题(以友好方式格式化)。如果实现了这样的报告,通常会 导致需求实现分散到几个不同的部分:实际需求检查、错误描述以便向上级或API用户报告,以及通过GUI以友好方式向用户报告的人类可读文本。
这样的需求实现很难进行测试/验证,难以保持随时间一致(当特定要求发生微小变化时),并使得源代码难以理解和推理。
愿望清单
理想情况下,应该有一个工具能够
- 将具有可变长度文本的人类友好描述和它的计算机友好正式表示(一段代码)绑定在一起的单个语句
- 专注于内容,尽量减少包装表达式
- 自动验证需求并向外部范围和GUI报告成功/失败。
方法概览
每个需求都可以针对给定数据值(可以是原子或复杂数据类型)进行评估。换句话说,每个需求定义都可以表示为一个函数,该函数接受一个或多个输入参数并返回布尔值——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
类型的值。我们传递一个字符串作为构造函数的仅参数,而第二个参数(闭包)作为尾随闭包传递。闭包包含在每次需要评估此需求时将调用的代码,需要检查的相应值作为唯一的输入参数。
注意,如果需求包含例如 AND、OR 或任何其他逻辑 运算符 的短语,则此类需求 应该 被分割成独立的需求。
当创建需求时,以下是如何使用它来检查潜在合适值的示例。
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
数据类型有两个参数
let requirement: String
包含需求的描述;let input: Any
包含已经评估但未满足需求的准确输入数据值。
内联辅助工具
虽然 Requirement
本身可能更适用于实现 数据模型,但还有一些辅助工具使用了相同的思想,但提供了更方便的内联 API,在实现 业务逻辑 时更加方便。这些辅助工具封装在特殊的 enum
REQ
中,当需求未满足时,它们都抛出一个 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
错误类型只有一个参数
let description: String
包含传递给相应REQ.*
函数的要求描述。