twitter工程师的一点经验

Twitter 的 Fabric 是知名的注重质量的 SDK,并已部署在数十亿的设备。在这次 Øredev 演讲中,来自 Twitter 的 Ty Smith,揭示了 Fabric 团队创建他们 Fabric 的各种原则,特别是在 Android 方面。通过深入参与技术决策团队,Ty 了解到很多信息,他展示了团队在创建这个 SDK 过程中,学到的各种经验心得,关于稳定性、性能、SDK 体积控制、以及对于一些特殊情况的处理这些方面。无论你现在或将来想要建设一个 SDK,通过这次演讲你应该能收益很多关于设计 SDK 的伟大想法。
  作为开发者,我们中的大多数人不得不使用 SDK 和 API,但经常地,我们会遇到一些令人沮丧的或设计不好的东西。要使得开发伟大的应用程序更加轻松,作为核心的软件开发工具包还有很长的路要走。在 Fabric 上,我们很关心开发者的经验,我们花了大量的时间,使我们的 SDK 更容易和更有趣。我的名字是 Ty,在 Twitter 的 Android Fabric 工程师团队。我想与你们分享一些我们在开发 SDK 过程中学到的经验。
  那么    Fabric究竟是什么呢?它是一套 Twitter 开发的模块化开发工具包,Twitter 共享了一个共同的移动和Web平台,用于减少占用空间和提供一致性的解决问题方案,旨在提升第三方移动 App 的质量。我们去年发布了 Fabric,这是完全免费的。我们相信这是一个伟大的方式来引导你的移动应用程序。就在上个月,我们宣布了多个外部合作伙伴,现在正在基于Fabric 进行开发,未来将有更多高质量的内容提供给大家。  
  我们建立 Fabric SDK,我们保持了几个目标,帮助引导我们进行开发。这些原则决定了我们开发 API 和做决策的选择。这些想法可以融入你自己的SDK或甚至你的应用。我们很高兴能够看到大家离开这里后,在未来开发的你们的 SDK 中采纳和我们一致的想法。
  在我们进入 library 或 SDK 编码之前,我们有必要考虑几个方面。
  首先要考虑的是要找出你在 library 里的实际服务的对象是谁,是内部开发人员还是公共开发人员?谁会使用它?它带来的新价值是什么?市场上已经有了一个解决办法吗?如果是这样的话,你应该是去对其进行开发和贡献而不是重新创造一个“轮子”。
  考虑开源与闭源是一个大问题。开源通常会让你更好地通过社区,获得更稳定的软件,以及更热心的内部工程师。然而,需要思考的事,你的 SDK 仅仅是集中于一个工程点呢?还是说它是一个完整的产品,但有一个后台服务呢?
  因此,仔细考虑你将采用哪一种许可证(开源协议)。例如,如果你使用 GPL 许可,那么将会使得用了你的 SDK 或 library 的人也必须得使用 GPL 开源协议。更灵活的许可证可能是 Apache 2 或 MIT 许可。
  特别是对于 Android,打包你的代码并不一定是简单的。你有三个问题需要���虑。首先是对于一个标准的库项目,开发者将他们包含到他们的代码中,并且由 IDE 帮忙连接它们,它是非常灵活的,但是如果他们需要分叉(fork),他们得如何保持更新?
  在 Java 的世界 jar 包是另一个好例子——很标准的二进制包装。不幸的是,对于 Android,它们不能捆绑打包自己的资源文件,所以对于一些和视图相关的 library 一般都不能采用 jar 方式打包。
  最后,在 Android 世界还有一种打包方式即 aar,它是谷歌现在支持二进制打包方式。这是一个压缩的容器,包含了编译的源代码以及资源文件,它可以通过 Gradle Maven 依赖源从而非常方便快捷地分发给开发者。
   Hosting the artifacts  

  最后的考虑是在哪里托管你的打包结果。Maven Central,是标准的仓库。然而,他们都需要开源许可,因为他们想保护他们的服务的用户,他们不希望有人会隐式地拉下来一个二进制包,并选择一个他们没有得到审查的服务条款。如果使用了另外的资源库(如果你有一个专有的二进制文件),则开发人员必须手动添库到编译脚本中。
  在创造 Fabric SDK 的工作是一个梦幻般的学习过程。我们的目标就是涵盖这五大方面:易用、稳定、轻巧、灵活,很好的支持。我们相信伟大的 SDK 要实现这些得走很长的路。
  其中的一个关键就是可用性。我们认为产品应该是易于使用的。
  那么所谓的易用到底是什么呢?我们想创造一种最简单的方式,让人们在他们的应用中开始使用 Fabric. 如果它是易用的,它应该是不需要侵入太多你的代码或者你需要做很多繁琐的集成工作。只要在你的代码中新增一行我们的代码,就可以使用它了,类似这样:
  Fabric.with(this, new Crashlytics());  Get more development news like this
  但易用的同时,有时还得能够定制,许多开发者可能希望更多的定制。要做到这一点,我们使用的 Builder 生成器模式设置一些选项,比如设置一个监听器好让程序在应用程序崩溃之前通知你。
  Crashlytics crashlytics = new Crashlytics.Builder()      .delay(1)      .listener(createCrashlyticsListener())      .pinningInfo(createPinningInfoProvider())      .build();Fabric.with(this, crashlytics);  对于 Fabric SDK,我们需要一个 API key 作为连接我们网络服务器的验证密钥。这是我们要开发人员处理的事情,但需要尽量减少所需的工作量或者说繁琐度。我们的标准方法是:通过我们的构建插件提供的方式,并将其注入到清单(manifest)文件中。这里是一个例子,使用 metadata  在清单文件中插入数据:
            当 Fabric 在初始化的时候,我们可以通过 package manager 获取到我们插入在清单文件中的 API key 并且继续后续工作。另外,我们可以允许其他的方法来管理这个 API key 的值,对于开源项目这么做可能会更好(保护 key 的值,因为有时我们对项目进行开源,但 key 不想开源)。例如,您可以创建一个属性文件,然后我们将在运行时读取该目录文件内容。
  除了我刚才提到的实施细节,我们喜欢在设计 API 时考虑这些特点:第一个是     直觉 。如果一个接口调用的行为恰好是开发人员预期的方式,而无需参考文档。  
  我们发现,在你的 SDK API 中使用     一致 的命名,也是有助于使用者理解。使用平常的表达语言来命名你的方法,以及类似的设计模式。并且遵循各个平台约定俗成的命名规则,比如 iOS 和 Android 平台,它们各有不同的命名规则。  
  最后,如果 API     很难被误用 ,将可以防止一些错误的发生。验证输入的参数,和书写明确的文档,将使得开发者在使用的时候,能够有信心和避免错误。也会带来一个更愉快的体验。  
  让我们看一个反直觉的例子:
  URL url1 = new URL("http://foo.example.com");URL url2 = new URL("http://example.com");url1.equals(url2)  这一个感觉,将影响很深,也是 API 中最难的部分。当使用一个伟大的 API 时,我们可以猜测它是如何表现的。在这个例子里,我们将期望 equals 执行某种标准化的字符串比较。
  但是实际上反直觉的是,equals 代表如果这两个 URL 解析到相同的IP地址,在 Java 中的实现,将返回 true,这里的原因是这个 API 的实现十分有趣:它发射同步的 DNS 请求。谁会想到?阻塞调用线程是一个意外行为的例子,在 API 中应该是非常明确指出的。
  举一个例子,Fabric 和 Crashlytics 的初始化方式便都是一致的。在初始化 Fabric 或 Crashlytics,两个不同的二进制依赖库文件,正如我们之前看到的我们允许它们使用同一模式建造。用户可以使用无参数构造函数,或定义辅助方法来设置默认值,另外,这两者都提供了一个可用于重定义对象的生成器(builder)。
  最后该讲到如何防止误用了。例如,从 Fabric Builder 的构造函数我们可以得知,Context 对象是必须的,而其它一些 setter 是可选的。一旦我们在构建的阶段中创建实例,这些可选参数也就一并被初始化。
  这样设计的话,开发人员使用 API 将不能不提供 context,但可以使用其它 setters 在另外的闲暇时间。我们相信这样就很难被误用了。
  我们如何才能设计出高品质的 API 呢?让我们来看看我们的设计流程。设计 API 是很难的,它通常不只是一个工程师独自坐在一个黑暗的房间,决定该是什么样子,它需要整个团队付出大量的工作。
  我们在 Fabric 的 API 设计上第一个重点就着眼于我们将支持的几个平台。我们创建一个设计文档之前,任何实施工作都是这样做的,进行讨论在这些平台上,不同的方法的优点和缺点。
  有一句话我很喜欢:一个 API 就像一个婴儿。他们很有趣,但他们需要18年的支持。任何 API 我们都必须要长期地支持,所以我们要让大家感觉到,我们正走在正确的路上,才能才久坚持支持下去。
  最后,即使我们可以让 iOS 或者 Android 开发中愉快地使用我们的 API 了,我们还需要建立相关的平台,首要的就是让开发者们感到最舒服的。
  现在我们已经设计了一些很容易使用的东西,让我们来讨论一下我们如何能获得开发者的信任,相信这是非常重要的。因此,确保软件开发工具包是可靠的,他们不影响应用程序本身的稳定性。大家都知道,相比开发应用程序,开发一个 SDK 需要更高的稳定性要求。让我们来看看如果产生了一个错误将会有什么影响。
  如果一个应用程序有一个关键的错误,阻碍了它的用户使用,它可能仅仅需要发送一个新版应用程序给顾客进行更新即可。而如果是我们 SDK 发现了一个漏洞,我们很快修复它,它可能还需要一个月才能到达你的用户,在此期间,你的用户就会有很不好的体验了。
  显然,如果一个 SDK 有一个严重的 bug,它的修复更新到达时间要长得多。这可能需要几个月,用你的SDK应用程序的用户才能得到错误修正。应用程序开发人员可能需要数周才能注意或升级您的 SDK 版本,并进行修复、测试 bug。所以说确保一个 SDK 的稳定性是我们的最高优先事项之一。
  作为开发人员我们可以做什么,以确保尽可能高的稳定性?有一些事情是我们开发过程中的关键。首先,代码审查是非常重要的,必须得认真对待它们。然后,通过不断地问自己“这个代码有什么问题吗?”我们可以这样试着去问自己,以达到尽可能的防守。
  如果能够自动获得一些基本的正确性保证,也可以在早期帮助捕捉错误,所以单元测试是非常有用的。
  另一方面,人们经常忽略的是:在用户使用初次使用进行测试时候,使它能够运行你的一些 SDK 代码,这样做他们可以在你的 SDK 集成时进行捕捉 bug。
  最后,持续整合(译者注:维基百科词条 -    持续整合)和”吃你自家的狗粮”(译者注:维基百科词条 -    Eating your own dog food)也都可以作为你的保护层,可能有助于早期快速识别问题。  
   使 SDK 具备可测试性和可模拟性  

  有一些技巧可以让你的 SDK 具备更好的可测试性。其中,为了测试,有时我们需要进行模拟,模拟(mock)类作为真实类的仿制类,它没有真实操作,并且允许被重写调用和验证方式。
  通过避免静态方法,您可以允许在模拟实例上进行操作任何方法的调用。如果您将使用静态方法,需要确保它可以被隔离,并且您将提供所有的依赖关系,并且没有基于任何状态。  
  许多 mocking libraries 对于     final 的类也会产生许多问题 ,所以要考虑你的类扩展。在你的模拟类中不应该存在 public 属性,所以需要被访问的一切都应该通过一个访问的方法来运行。  
  在你的 API 中使用接口。如果您的输入点使用接口,设置类来测试将更容易。该接口允许开发人员进行重写的行为,比如契合模拟服务器或在内存中存储,来替代真实场景真实存储的开销。  
  最后,需要考虑到测试人员不需要构造多个层次深度的模拟。这个鼓励测试的原则应该被写入你的指引文档,并提供更稳定的测试框架。
  有一些 class 很难被模拟,比如 final 类型的,它将创建它自己的依赖关系,并且是一个基于状态的单例。这在 Java 中是很常见的,虽然它通常是一个反模式。这使得它在隔离测试中非常具有挑战性。那么,我们能做什么来解决它?其实只要有一些小修改,我们可以使这些难解的点变得更容易测试一些。
  public class Tweeter {  private Network network;  private public Tweeter(Network network) {    this.network = network;  }  public List getTweets() {    return getNetwork.getTweets();  }}  与其把它作为一个单例,为什么不把它做为一个实例?开发者可以自己去重用它,缓存,或者做其它的事情。删除在 class 中的 final    Mockito或其他框架就可以模拟它了,同时也能让 SDK 在初始化、构造时候管理它的依赖。依赖注入不仅仅是一个框架,它还是一个帮助组织代码的设计模式,使得代码更具有模块性和可测试性。  
  开发者经常是容易不耐烦的,所以有一些错误越尽早抛出就越好。如果你一直在使用 Gradle 你应该会明白我的意思,一些错误如果在 build 期间不能通过总是好于 build 完成之后5分钟才出现错误。你应该     把一些可以预期的异常抛出 ,以便于开发者能够尽快知道这些异常,比如在这种情况下,开发者试图设置一个 null 的 logger 到我们的 builder 里,我们得马上抛出一个异常,这样他们就可以很快知道并解决他们的错误。  
  然而,你得保证你的 SDK 在生产环境中绝不会出错,让你的代码持续运行在他们的应用中,是你保证开发者们信心的唯一方法。他们的应用程序往往是他们生计的依赖,所以他们不会喜欢去赌着使用一个经常崩溃的库。所以当他们在调试过程中出现问题的时候,你可以提供额外的信息,写清楚这个 Exception,但要隐藏在生产过程中可能出现的问题,这样允许他们的应用程序的其余部分继续运行。你的 SDK 的出现问题可能对你来说是一个大问题,但并不是世界末日。
  作为一个开发者,你使用 SDK,你的应用程序应该增加价值,而最糟糕的事情就是你引进了某物反而使得原本多价值降低了甚至完全破坏用户体验。开发者们,包括我自己,不需要任何人来帮助我们写一个糟糕的 App。
  除了稳定,用户不太可能下载大的应用程序,这意味着安装包的大小是一个关键内容。下载软件产生的流量需要用户去付钱,所以即使你的应用是完全免费的,用户也得为下载它付出流量。在许多新兴市场,因为下载速度太慢,所以很多用户不爱下载大型应用程序;在某些市场,用户主动选择更新的应用仅仅基于更新日志和添加新的特性是否值得,因为他们需要支付每千字节流量费用。
  让我们来讨论一些 Fabric 用于保持轻量的技术吧。有一些伟大的第三方库,可以真正给予贡献于你的应用程序,但当涉及到 size 规模和影响时,他们会他们显得不自由。例如,图像加载方面有各种不同体积大小的图片加载库。其中 Fresco,是比其他任何一个第三方库都还大量级的一个库。然而,它对于旧设备有更好的支持,加载速度快而且内存友好,并支持渐进式JPEG。
  作为一个 SDK 你应该努力平衡你的尺寸与功能。因此,要注意引入第三方库,以确保它们只满足所需的内容。
  使用开源库有很大的优势,因为这些库往往经过很多的测试了,大家都使用得很好,并且他们有定期向他们提供更新的社区。这通常提供了一个更好的方案。在我们的 Twitter SDK,我们利用 Square 的 RetroFit 这个库作为一个依赖来简化我们的 API,而且也能使我们提供更好的可扩展性,这是值得的。

  报告二进制文件(binary)的大小  


转自:http://c.colabug.com/forum.php?mod=viewthread&tid=1898994&highlight=sdk

你可能感兴趣的:(SDK,android开发)