Uber工程如何在运行时用你已经使用的注释验证数据

Uber工程如何在运行时用你已经使用的注释验证数据

Uber的高速增长迫使我们的开发者不得不这么做工程师稳定使用各种各样的技术。

例如,在2016年,我们创建了and开源运行时注释验证引擎(RAVE),一个数据模型验证框架,使用Java注释处理来解决Android应用崩溃的头号原因:NullPointerException年代属此列。npe是Java等语言的一个常见问题,这些语言中没有内置nullability类型系统。通过利用您已经使用的注释,RAVE充当了一个盾牌,防止因无效数据而导致崩溃或难以发现的bug。

为了纪念今天的释放大2,我们将探索如何使用这个强大的工具消除应用程序中的绝大多数npe。

NullPointerExceptions在Uber的模型中

在Android应用程序中,当访问空数据时,经常会抛出npe模型静态分析这样的工具推断出帮助在编译时捕获npe,但不能确定在运行时接收的数据(即来自网络或存储)是否符合模型中注释所描述的期望集。

图1:上图所示的崩溃对话框,当抛出NPE时,会在驱动程序应用程序用户界面(UI)上弹出。

在我们的Android应用程序中,NPEs的最大贡献者之一来自对我们在我们的应用程序中使用的数据的假设模型。考虑如下所示的Rider模型对象:

这个模型使用nullness注释通知使用者是否可以返回返回类型null。而这些注释能够警告使用集成开发环境(IDE)当空类型未检查或不正确使用时,它们在运行时不会提供任何安全性。例如,当一个应用程序接收数据并使用它来膨胀模型对象时,没有强制要求数据符合模型中呈现的注释。

当使用时从网络反序列化对象时,经常会发生这种情况Gson。由于Gson不检查它创建的模型对象是否尊重为空的注释,所以当您尝试访问带注释的时,可能会发生NPE@NonNull数据和API返回为了一件应该是@NonNull。即使当api按照它们的规范运行时,它们的边缘案例有时也没有很好的文档记录,或者是未知的,或者是长期更改,这可能会导致npe。(例如,如果你期望一个API在没有数据可返回时返回一个空数组,但它却返回了null)。

疯狂地去救援

为了解决这个问题,优步创建了RAVE。在防止因使用无效数据而导致的npe和bug方面,RAVE有各种各样的用例。一些应用程序包括:

  • 验证网络响应以确保它们符合客户机的期望
  • 避免从磁盘获取数据时陈旧模式导致的错误
  • 验证模型在突变后仍然有效
  • 确保第三方api在提供意外数据时不会使应用程序崩溃

RAVE在运行时验证模型对象,通过使用注释处理生成验证代码来实现这一点Android支持注释在你的模型。然后在运行时接收数据时执行验证代码。

为了更好地说明RAVE是如何工作的,让我们定义一个应用程序的边界作为数据接收的边界(来自网络数据请求,设备磁盘存储,等等)RAVE确保进入你的应用程序的数据符合你的模型的注释所描述的期望集。不管数据来自哪里,它都可以这样做,如下所示:

图2:RAVE确保进入应用程序的数据符合模型注释所描述的期望集。

使用大

狂欢与注释- - - - - -nullness,值约束,类型定义-你已经在你的Android应用程序中使用了。它还提供了两个我们自己构建的注解来支持自定义验证:@MustBeTrue@MustBeFalse

要使用RAVE,您必须使用@Validated注释,如下所示:

@Validated注释了类文字一个狂欢ValidatorFactory,实现RAVE的具体类ValidatorFactory接口你需要创建一个ValidatorFactory对于每个模块,您想要验证类RAVE,如下所示:

生成验证器当您提示RAVE通过调用Rave.validate ()API。你应该总是使用Rave.getInstance ()API。

Rave.validate ()叫,锐舞用RaveValidatorFactory_Generated_Validator以确保MyModel.getSomeString ()不返回MyModel.customValidationLogic ()返回true。如果这两个条件都不满足,RAVE就会抛出检查异常,RaveException。此异常返回错误消息和详细信息,帮助查明bug。

Android应用的狂热

当我们将RAVE整合到应用中两个最大的数据入口点(磁盘和网络)后,NPEs便从Android应用崩溃的主要原因中消失了。让我们来看看两个具体的RAVE用例,来强调这个框架是如何与Android应用程序集成的:

磁盘的验证

在读取或写入数据到磁盘之前,RAVE确保对象符合它们的注释,从而防止在应用程序版本之间的模型模式更改时发生NPE崩溃。我们用KeyValueStoreAPI,它的实现在对象被读取之后和被写入之前验证对象。

KeyValueStore.putObject (),我们的实现KeyValueStore调用Rave.validate(对象)。如果对象没有启用rave或不符合其注释,RaveException抛出,供开发人员处理。这样做是为了确保无效的模型不会被持久化。

我们也称之为Rave.validate ()在将数据返回给消费者之前KeyValueStore.getObject ()被称为。这可以确保,如果模型定义在客户机的最新版本中被更新,那么从磁盘读取的数据仍然符合更新后模型包含的注释。如果它不符合这些注释,RAVE将抛出一个RaveException供消费者处理。

网络验证

我们使用改造2作为在Android应用程序中进行网络调用的接口。为了使用带有Retrofit 2的RAVE,我们编写了一个定制程序转炉厂,RaveConverterFactory,它验证由能够反序列化JSON的转换器返回的网络响应。

此转换器在下列情况下验证反序列化模型Rave.validate ()被称为。在模型没有通过验证的情况下,aRaveException被抛出给调用网络调用的使用者。的样例应用程序包含在RAVE存储库中演示了这个转换器的使用,并使用Retrofit 2和RAVE验证来自GitHub API的网络响应。

有兴趣为这个项目做贡献吗?分享你自己的自定义验证注解为GitHub上的RAVE和帮助我们增加Android的稳定性。

贝鲁兹·霍拉沙迪(Behrooz Khorashadi)、埃里克·梁(Eric Leung)和沃伦·史密斯(Warren Smith)是Uber移动开发团队的软件工程师。

评论