数据质量(面向开发人员)
5.00/5 (6投票s)
快速了解数据质量,以及如何使用表达式树自动化 QA 数据测试
引言
本文快速介绍了数据质量的概念,并提供了一个解决方案,帮助您以(相对)无痛的方式将数据质量集成到您的解决方案中。您的用户和客户会感谢您——继续阅读!
背景
大多数开发人员,只要不是对现实视而不见,至少都会有一些关于代码测试的经验。这是一项你可能会纠结一段时间的事情,但一旦你开始实践,并意识到它的好处,你就会 wonder 怎么没有它。
很多时候,你会因为一长串的借口而忽略代码测试:
- 太难了
- 浪费时间
- 我的经理不同意
- 客户认为浪费时间
- 我个人认为浪费时间
- 我的经理认为浪费时间
- 我不理解
- 我没时间学习(太忙着看 YouTube 了,咳咳!)
- 等等……你懂的……别说你不知道 :)
当你不进行代码测试时,项目开始变大,事情会很快失控。在你意识到之前,这里的一个代码更改,就会在那里产生意想不到的影响,你就会得到一个虽然只有几个月大,但却麻烦不断、令人沮丧的“遗留”应用程序。
可以理解的是,客户会生气地喊道:“X 上周还能用,然后你更改/修复了别的东西,现在 X 就坏了……”而你最终会因为在这个“遗留”应用程序(实际上,在这个庞大的体系中它只是个新手)上工作而感到压力重重,不开心。
这很糟糕,你自己也知道……但它仍然太难,浪费时间,而且你的经理真的不会同意,
更不用说客户了……(叹气、哭泣、哦天哪,又来一天……)
就像代码测试一样,数据质量通常是我们直到它来咬我们一口之前才不会去关注的事情,而当它发生时,通常是客户注意到的,而我们这些倒霉的开发者总是要付出代价。我正在开始一个数据质量项目,所以我想也许最好谈谈它是什么,以及我们如何建立一些简单的检查和平衡机制来帮助我们管理数据,并提高其质量。
不要更改数据
我们可以从多个层面来处理数据质量。首先,我们可以建立一些简单、易于实现的措施,以帮助我们在出现问题时维护数据完整性,从而保证数据质量。第一个黄金法则,就是简单地不要更改数据。我的意思是,一旦数据被提交,在进行任何更改之前,始终保留一份数据状态的可靠记录。这可能是在宏观层面,如数据备份(在大更改之前),或者,它可能是一个数据审计跟踪系统,将数据视为不可变的,在每次检测到更改时存储原始数据并创建一份实际“工作数据”的副本。有些数据库提供了内置功能(MS SQL 的新Temporal table 功能在这方面非常强大)——如果你没有这个选项,你总是可以实现自己的表/记录审计系统(我关于在 .net 应用程序中创建简单审计跟踪的文章可能会有所帮助)。
使用审计/日志系统的一个巨大好处是,尤其是在大型系统中,你至少有了一种方法,可以从一个事件倒推回去,找出是什么真正导致了*那*一部分数据发生更改(你知道,客户发现的那个,而且让你*非常*痛苦……)。数据备份固然好。当然,你可以将数据库的状态重置到过去某个时间点,但还有什么其他东西也变了?你实际上用你的恢复重置了什么?如果你只恢复了那个出问题的特定部分,是否也会导致其他意想不到的影响?……如果你经历过这种情况,你就会知道,不幸的是,你的问题似乎会引出更多问题,而我们仍然有一个不满意的客户,而这*让我们*感到*难过*。
一旦你有了这样一个作为坚实后盾的方法,以防万一出现问题,你就可以变得更“花哨”(时尚!)一点,开始实现一个基于数据质量核心规则的规则引擎。
设定规则
多年来,许多聪明的人都解决了数据质量问题,在 Google Scholar 上快速搜索“data quality” 会找到超过 630 万篇学术文献,其中排名前几篇有数千次引用(这在某种程度上是学术质量的良好指标!)。当你开始研究数据质量领域时,你会遇到一个主题,那就是所谓的“数据质量核心规则”。这些规则可以从多个角度来看。就本文而言,我们将从验证的角度来看待它们,无论是在将数据导入系统时,还是在将其用于使用之前进行评估时。根据我们特定的需求,我们可以评估数据的质量,然后决定是“放行/不放行”,是“好/坏”数据,或者,我们可以决定实施一种交通灯系统,其中有些数据通过,有些有警告,有些则未能通过质量检查。
规则类别
数据质量规则分为四类:
- 业务实体规则
- 数据元素
- 依赖关系
- 有效性
作为开发人员,我们非常熟悉业务实体规则。这些规则关系到我们期望实体具有某种唯一性(姓名、客户 ID 等),并描述了实体之间的关系。例如,一个实体可能是另一个实体的父公司,因此它们之间存在关系。
数据元素通常是我们数据本身的辅助角色。这里有两个主要方面:元素之间的继承关系(数据的对象动物版本,有四条腿的动物 vs 有两条腿的动物……)以及允许值的集合或集合。对于后者,可以想象在网上购物时,你必须从受限列表中选择发货国家。
依赖关系正如你所料,是控制数据的规则。换句话说,根据数据的状态或值,可能适用特定的规则。合理地说,除非数据显示有协议允许,否则客户不应被允许透支其支票账户。
通过编码,我们也习惯于在数据录入表单中测试用户数据的有效性,所以我们对此很熟悉。我们检查数据是否完整、正确、准确等。
这些都很有用,而且大部分取决于你正在开发的特定业务是如何运作的。现在我们将要研究的是所谓的“基本质量检查”。要将你的系统提升到一个新的水平,并使其真正强大,你可以考虑在每次更改或摄入数据时,将这些类型的检查构建到你的系统中。虽然你可以针对以下内容变得非常详细和特定于领域,但总的来说,在这个级别上对数据进行通用化处理,并将这些规则和检查结合起来,可以显著提高你的数据质量。底线是我们寻求确保我们的数据处于干净的状态,然后才允许它进入生产环境或进行分析。
数据质量/数据清理的顶级基本检查
值分布
我们首先要做的是检查我们的数据值是否在给定领域内以合理的方式分布。分布是指我们的数据值落在什么范围内。如果我们考虑 ATM 卡取款,我们可能会期望 70% 的取款金额在 10-100 美元之间,20% 在 100-250 美元之间,10% 大于此(那是你给你妈妈买生日礼物的时间!)。在此检查中,你可以预先设置预期的范围,或者指定一个值的滑动窗口。我的意思是,你期望你的数据聚集成一定数量的范围,每个范围与其他范围都有特定的关系。
意外值
在这里,我们试图识别不符合预期的不符合预期的值。为了做到这一点,我们需要预先了解我们预期什么,并将此构建到我们对数据的观察中。例如,我们知道会收到一些意外的数据,但只允许一定比例。在这种情况下,我们可以说我们愿意接受 3% 的偏差,但任何进一步的偏差都会发出警报。寻找数据群(例如:14% 的数据在一个特定范围内)和数据中的空白(分布*不*符合预期)都是很好的标志。
这不可能!
你可以设置一些规则,这些规则可以避免很多“丢脸”的时刻,并处理对于系统而言本不应该存在的数据。这可能包括一个人的出生日期大于今天,或者一个折扣值大于 100%。
类型检查
这个很简单——在这里我们监控预期的与我们收到的数据类型。如果我们期望一个数字值,而数据却是文本或日期。
被误用、被误导和被误解
数据可能以奇怪的方式到达我们。我曾被告知“永远不要低估用户(滥)用系统以适应他们特定需求的能力”。例如,我见过一种对特定字段“客户姓名”的常见误用,其中在客户姓名旁边输入了诸如“无信用,欠款”之类的东西……(是的,当有人群发邮件时,这个也让他们陷入了麻烦!)。在这里,你需要根据数据的含义检查不寻常的情况。需要关注的事情包括“NA”、“n/a”、“TBC”是什么意思,“%”或分号“;”或星号“*”是否意味着什么,它们是否应该存在(或不存在)。
超出范围/范围
在许多情况下,我们期望数据在特定范围内。例如,摄氏度温度可能在 -50(brrr!)到 +50(OMG…空调在哪儿!)之间,建筑物中办公楼层的数量在 1..163 之间(是的,真的……查看一下)。如果值超出此范围,则检查失败。
异常值
异常值与范围外非常相似,异常值可能在范围内,但完全偏离了正在测试的范围的中位数或标准差。当然,它们也可以完全超出范围/范围。
丢失了
很多时候,我们期望某些值存在于数据集中,而实际上它们却因缺失而引人注目。这种检查可以帮助我们了解当我们期望某些值遵循某个趋势时*没有*发生什么。在混合类型的数据中,也可能缺少一部分。假设我们期望一个货币符号和一个小数来表示单价……我们期望的是“$10”,但得到的是“10”。
无效逻辑
这一点通常特定于领域,但也可以是常识。例如,在调查中,我们通常不期望获得超过 100% 的回复率,而看到重复的客户记录(令人惊讶的是,这些东西似乎会不知不觉地出现!)也会发出警报。
拼写检查
真的吗……!令人惊讶的是,各种各样的事情都可以通过。虽然进行标准的拼写检查很好,但我一直对针对特定领域的拼写检查感兴趣,这些拼写检查会给后续的数据集成和映射带来问题。
自动化数据质量
所以,现在我们对一般概念有了一些了解(而且,一如既往,这只是冰山一角!……),让我们来看看如何着手自动化质量监控。
在考虑自动化数据质量监控时,有两个方面需要考虑。首先是问题的识别,其次是针对已识别问题的处理。
以自动化的方式识别问题……
我们真正*不想*做的一件事是构建一个需要大量维护的解决方案。一如既往,使其健壮且面向未来(但同时,不要过度!)非常重要。一种解决问题的方法是构建一系列 IF/THEN 语句。单独使用,这会很快变得混乱,尤其是在添加规则和出现特定边缘情况时。
IF 规则说“值必须是字符串”<br /> THEN 尝试解析字符串……<br /> 失败时……执行 X
一种更整洁的方法是使用表达式树。当然,你采取的具体方法取决于你自己的特定需求。这里有一种构建基本规则引擎的方法(基于Stack Overflow 上的代码)。
表达式树允许运行时评估表达式。这意味着我们不需要在编译代码之前就知道我们要问什么(这很有用)。相反,我们可以允许用户生成一组规则,将其存储在数据库表中,然后在需要时针对某些数据运行它们。概念很简单。我们使用表达式树在运行时创建一个函数,然后将其应用于输入数据,从而得到一个“通过/不通过”的结果。这意味着我们可以将“问什么”的决定交给用户,而我们可以继续做其他编码任务,而不必担心 QA 代码的持续调整。
让我们通过一些*基本*的评估来看一个例子
首先,我们设置一个简单的用户类
public class User
{
public int Age { get; set; }
public string UserName { get; set; }
public string SkillLevel { get; set; }
}
添加一些示例数据
var user1 = new User
{
Age = 13,
UserName = "Jacques",
SkillLevel = "1"
}
;
var user2 = new User
{
Age = 33,
UserName = "Fred",
SkillLevel = "2"
}
;
var user3 = new User
{
Age = 65,
UserName = "Prakesh",
SkillLevel = "1"
};
var user4 = new User
{
Age = 34,
UserName = "Mary",
SkillLevel = "ONE"
};
现在我们将创建一个类来存储我们的质量规则
public class Rule
{
public string RuleName { get; set; }
public string FieldMatch { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
}
如上所示,我们可以给规则一个名称(例如,“年龄必须大于 15 岁”),一个 FieldMatch(这用于映射到我们正在评估的记录/对象的字段/成员名称,例如“Age”),一个 Operator(大于、等于……)和一个值(“15”)。下面是一些规则示例:
| 规则名称 | 字段名称匹配 | 运算符 | 值 |
| 合法驾驶年龄 | 年龄 | 大于 | 15 |
| 飞行资质 | 职业 | 等于 | 飞行员 |
| 最低银行存款余额 | 余额 | 大于 | 100 |
为了创建我们的规则,我们需要某种方式告诉编译器我们的运算符是什么,以及如何使用它们。在此示例中,它的做法是使用来自Linq Expression Type Enumerations 的现有运算符。我们将使这个示例保持简单,并创建两个规则。
规则 1 - 年龄限制。
在这个规则中,我们使用“GreaterThan”枚举来说明此 QA 测试的年龄限制为 20 岁。
var rule1 = new Rule();
rule1.RuleName = "Age limit";
rule1.FieldMatch = "Age";
rule1.Operator = ExpressionType.GreaterThan.ToString();
rule1.Value = "20";
规则 2 - 测试姓名值。
在这里,我们只允许名字是“Prakesh”的人通过(幸运的家伙!)。
var rule2 = new Rule();
rule2.RuleName = "Name match";
rule2.FieldMatch = "UserName";
rule2.Operator = ExpressionType.Equal.ToString();
rule2.Value = "Prakesh";
在我们继续展示这个东西如何工作之前,让我们看看一个规则的实现。我们这样做……首先,我们将规则“编译”——这会返回(做好准备)一个函数……是的!……就像我们在处理 Javascript 或 F# 一样。一旦我们收到编译后的函数,我们就可以通过传入数据进行评估来使用它,对新的编译规则进行评估。
看这里——我们传入“rule1”(我们的“> 年龄”规则),它返回一个函数“compiledRule_Age”,该函数包含了该规则,准备好对数据进行评估。我们对“user1”和“user2”也这样做。
Func<user, bool=""> compiledRule_Age = CompileRule<user>(rule1);
var ageResult1 = compiledRule_Age(user1);
var ageResult2 = compiledRule_Age(user2);
</user></user,>
规则规定“通过,当字段“Age”的值大于“20”时”。
由于 user1.age = 13,规则失败。
由于 user2.age = 33,规则通过。
这说得通。让我们看看“CompileRule”实际上是如何工作的。
public static Func<T, bool> CompileRule<T>(Rule r)
{
var paramUser = Expression.Parameter(typeof(User));
Expression expr = BuildExpr<T>(r, paramUser);
return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
}
该方法接收一个规则,并构建一个 lambda 函数,该函数将“User”映射到“Bool”,然后对其进行编译。表达式本身是使用一个单独的函数构建的,该函数使用反射。
static Expression BuildExpr<T>(Rule rule, ParameterExpression param)
{
var left = MemberExpression.Property(param, rule.FieldMatch);
var tProp = typeof(T).GetProperty(rule.FieldMatch).PropertyType;
ExpressionType tBinary;
// assumes the operator is a known linq operator.
// see: https://msdn.microsoft.com/en-us/library/bb361179(v=vs.110).aspx
ExpressionType.TryParse(rule.Operator, out tBinary);
var right = Expression.Constant(Convert.ChangeType(rule.Value, tProp));
return Expression.MakeBinary(tBinary, left, right);
}
我们正在构建的表达式有三个部分: left eval-operator right
- 我们将等式/比较的左侧定义为映射到规则“FieldMatch”(例如,“Age”)的参数。
- 用于评估等式/规则的运算符从 rule.Operator 中提取,并解析为 ExpressionType 枚举。
- 最后,右侧是我们正在等式中考虑的值(我们将其转换为与左侧的 rule.FieldMatch 的属性类型匹配)。
现在一切都已就绪,我们可以比较“苹果对苹果”,该方法返回一个 Expression,可以对其进行编译和使用。
这是一个非常基础的介绍——我附加了一个示例项目,你可以下载并查看运行情况。
有关构建满足你特定需求的规则引擎的更多信息,可以看看这些优秀的例子:
用 C# 构建规则引擎(篇幅较长/系列文章,但非常值得一读)
使用 LinqExpressions 构建一个 MicroRuleEngine(简洁,效果不错)
如何在 C# 中实现规则引擎(确保阅读评论,其中包含有价值的信息)
采取自动操作
一旦我们识别出数据中的问题,我们就拒绝数据或尝试修复它。如果我们决定修复数据,我们可能会有一些硬编码的解决方案,这里有一些例子:
| 问题 | 解决方案 |
| 在邮政编码中发现“N/A” | 替换为空字符串 |
| 发现数字零应该在的位置是“O”(大写字母“o”) | 替换为数字零 |
| 在一行中发现“\n”换行符 | 替换为 null |
这样会很好。我们可以预先考虑很多事情(如上),并将其编码进去。然而,随着时间的推移,数据开始流动,会有其他事情被注意到。对于这些情况,我发现自动映射/替换解决方案可能很有用。这就是你识别问题,并将问题交给用户解决。如果用户确定他们可以为该问题创建规则,那么他们可以从规则到解决方案创建映射。简单地说,这可能是:
| 问题 | 解决方案 |
| 找到姓氏为 ALAN | 替换为 ALLAN |
| 在一行中发现“\n”换行符 | 替换为<br/> |
一个非常有用的工具是你可能依赖的正则表达式。它们是检测和修复数据问题的极其灵活的方式。如果你不熟悉 RegEx,那么这是一个深入研究它们的好机会!
学习 RegEx 的一些有用资源
Regex Storm - 一个 .net 专注于的在线沙盒/工具,用于测试和学习 RegEx 模式
RegexR - 全面的学习沙盒 - 非常直观
总结
希望我们对数据质量学到了一些东西,以及如何在你的解决方案中建立一些检查和平衡机制,不仅可以提高你的数据质量,还可以降低你自己的压力水平 :)
历史
版本 1 - 2016 年 12 月 21 日
