SDK给您,SDK给我,SDK给所有人! 如何制作SDK以及为什么需要它


大家好!


我们公司提供用于存储和处理来自工业设备(泵,钻头和其他工业设备)的数据的服务。 我们存储客户的数据,并提供对其进行分析的功能:生成报告,图形等。


在工作过程中,我们注意到每个新客户的集成都大大延迟了,各种错误的数量也在不断增加。 然后很明显该是时候应对了。 如对情况的分析所示,我们每个客户的IT部门都开发了自己的解决方案,用于从设备本地收集数据并将其发送给我们的服务。 考虑到行业的具体情况,它并不总是可以访问Internet,因此必须将其存储在本地并尽快发送,这一事实使情况变得复杂。 并且,这种细微差别的数量足够多,这导致错误数量的增加。


然后我们意识到,在这种情况下最好的解决方案是开发一个SDK并将其提供给客户。 我立即开始寻找关于SDK开发的最佳实践和考虑因素,并且感到非常惊讶-RuNet几乎没有关于它的信息,但是Basurman Internet上的信息很少,而且信息分散。 好吧,任务很明确,经过深思熟虑并得以实施。


但是正是由于缺乏有关此主题的信息,才使人们希望将有关SDK开发的想法,决策和结论告诉社区。 本文讨论了.NET的解决方案,但这是一个概念,因此对许多人来说都将很有趣。 细节剪下!


是时候确定了


让我们先定义一下SDK是什么以及为什么需要它。


SDK(来自英语软件开发套件)是一组开发工具,可让软件专家为特定的软件包,基本开发软件,硬件平台,计算机系统,游戏机,操作系统和其他平台创建应用程序。 SDK利用了每个平台的优势,并减少了集成时间。
...
软件工程师通常会从目标系统的开发人员那里获得SDK。

嗯,这是合乎逻辑的。 简而言之,SDK是一组库,因此客户端可以轻松快速地开始使用您的系统(在本文中我们将讨论我们的服务,但本文中描述的所有内容都适用于其他类型的SDK)或执行相同的操作。


但是,像任何方法一样,SDK Path既有优点也有缺点。


好处


新客户的高速集成 -您的客户需要编写更少的代码。


代码重用 -同一代码一次在多个地方使用。 我们可以说这是上一段的重复,但是我们谈论的是工作逻辑无处不在的事实,这意味着


行为的可预测性 -使用相同的库可使系统的行为达到一定的标准,从而极大地方便了错误的查找和消除。


代码质量是许多人希望节省测试的地方(对预算,截止日期和其他原因感到抱歉)。 显然,在现实世界中,使用测试来测试项目的所有部分是一项非常艰巨的任务。 但是定性测试所有SDK模块,然后使用它们是增加测试覆盖率的一种方法,这将减少错误数量。


该文档与测试的场景相同。 记录整个项目是很成问题的。 重复使用SDK可以增加文档覆盖率,从而降低新员工进入项目的门槛,并且通常会对生活有所帮助。


实际上,所有优势都是最重要的后果- 我们编写高质量的代码一次,然后再使用它


缺点


SDK代码的高质量要求是主要优势的结果。 SDK中的错误将在使用它的所有系统中生成错误。


设置约束 -SDK是用于实现标准脚本的一组库。 有时,SDK开发人员认为,除了实现所提供的方案之一之外,客户端不需要任何东西,与从SDK的拐杖建立基座相比,客户端自己进行从头开始的一切都容易。


依赖性和更新 -扩展功能时(例如,为特定客户端定制解决方案),您将发布该库的新版本。 但是存在依赖关系,不同客户端的库版本集不同,因此您需要仔细监视向后兼容性或严格的版本控制。


何时真正需要SDK


您有不时重新实施的几种标准方案 -实际上,就是我们的情况。


内部开发 -在不同的项目中,您是否使用日志记录系统,系统配置,使用HttpRequest,数据库,文件? 构建一个内部SDK-一组供内部使用的库。 您可以随时扩展SDK功能,但是开发新项目的速度,测试和文档的覆盖率将提高,并且新开​​发人员进入的门槛将降低。


当SDK可能多余时


使用方案未定义或在不断变化 -将定制解决方案的实施留给客户并为他们提供帮助。 无需神童,这只会造成干扰。 与年轻的公司和创业公司非常相关。


您不知道该如何定性 -我有一个坏消息:该学习了。 但是给客户做出错误的决定是非常非常错误的。 毕竟,必须尊重客户。


因此,我们决定了什么是SDK,以及它的优缺点和何时需要。 如果之后您意识到确实需要SDK-我邀请您踏上“ SDK路径”,弄清楚它应该是什么,该怎么做?


“你爱乐高吗?” -模块化


想象一下使用SDK的所有可能情况(您已经确定了为什么需要它,对吗?)并为该库编写脚本。 没有什么选择? 但这是一种不好的方法,因此我们不会这样做。 我们将像这样:


  • 将所有脚本分成步骤
  • 确定共同的步骤
  • 建立一个清单,以实现所有可能的步骤(一个模块负责实现特定的功能,例如,使用配置)

例如,考虑到任务的细节,我们需要从配置中设置所有逻辑。 我们实现了用于配置的模块(读,写,更新,验证和处理配置),并将在所有其他模块中使用它。


为了实现标准方案,我们实际上将使用相同SDK的其他模块来制作模块-例如“控制”模块,每个模块都实现一个特定方案。 因此,对于标准场景的实现,客户端只需要连接脚本的控制模块(他将拉出所有依赖项),而对于非标准场景的实现,我们使用基本模块,还重用了代码。


这正是SDK不应该成为一个库的原因(我理解,尽管我真的很想。毕竟,当整个SDK都在一个库中时,您可以忘记依赖关系以及与它们相关的所有内容),而是成为一组库。 这种方法的另一个优点是可以减少客户端程序的“重量”-它可以提取重量级的SDK,并且仅提取必要的模块。


但是您无论如何都不应该生成模块,因为模块越多,依赖于它们的麻烦就越多! 即 重要的是正确地将逻辑划分为模块,在决策“多合一”和“每个模块都有自己的模块”之间保持平衡。


“所以有可能吗?!” -多功能性


为客户端提供各种接口来使用您的库。 我举一个例子:


public void LoadConfiguration(string filename); public async Task LoadConfigurationAsync(string filename); 

如果仅提供同步版本,则在实现异步应用程序时,将强制客户端对同步方法进行异步包装。 如果仅提供异步版本,则情况类似。 把这两个都给客户,他会谢谢你的。


泛型将是一个不错的加分。 例如,我们有一个用于配置的类,该类实现了将配置打包到字符串中,从文件加载配置等的方法。 特定模块的配置将继承自我们的基类,但要使用新类,我们还需要提供拆包方法。


 class BaseConfiguration{ public BaseConfiguration FromString(string source){...} public BaseConfiguration FromString(string source,Type configurationType){...} public T FromString<T>(string source) where T:BaseConfiguration } class CustomConfiguration : BaseConfiguration{} 

因此,我们为客户提供了他可以使用的三种实现。 泛型非常方便,但是使用动态类型时,只能通过反射来调用它们,这是无利可图的。 我希望普遍性的一般原则是明确的。


“父母1,父母2,孩子[]”-命名


程序员中最困难的部分是什么? 发明变量的名称。

但是...模块,类,属性和方法的正确命名将极大地帮助那些将使用您的SDK的人。 无需注释的示例:


Kinect 2.0 SDK示例


 var skeletons = new Skeleton[0]; using (var skeletonFrame = e.OpenSkeletonFrame()) { if (skeletonFrame != null) { skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength]; skeletonFrame.CopySkeletonDataTo(skeletons); } } if (skeletons.Length == 0) { return; } var skel = skeletons.FirstOrDefault(x => x.TrackingState == SkeletonTrackingState.Tracked); if (skel == null) { return; } var rightHand = skel.Joints[JointType.WristRight]; XValueRight.Text = rightHand.Position.X.ToString(CultureInfo.InvariantCulture); YValueRight.Text = rightHand.Position.Y.ToString(CultureInfo.InvariantCulture); ZValueRight.Text = rightHand.Position.Z.ToString(CultureInfo.InvariantCulture); 

从类和方法的名称中,一切都是清楚的。 而且,如果您的IDE中有代码完成功能,那么即使一切都已准备就绪,通常也有可能不去看一下文档。


“我确信,如果死亡知道官僚机构是什么,人们将永远不会死,永远站在一起……”-文档


但是,即使所有模块,类,方法和属性的名称都非常精美且紧急,您仍然需要编写文档。 首先,它将极大地节省您的神经(客户问题的数量减少了一个数量级。所有内容都在文档中),其次,总是很清楚为什么这样做,而没有其他事情。


SDK中的文档通常简单明了。 它通常分为两部分:教程-以“在10分钟之内建成城市”风格的分步教程和参考部分-对使用此SDK可以完成的所有操作的参考。

我们选择了最简单的方法-摘要+文章。 我们为在intellisense中发光的方法和类添加Xml属性作为工具提示。 使用Docfx,我们可以基于这些属性构建文档,并获得详细而方便的文档,并在描述用例和示例的文章中对其进行补充。


“-保持清洁!-我将如何用叉子清洁它?” -测试


作为SDK讨论的一部分,关于测试可以说什么...必须具备! 最好的解决方案是TDD(尽管我对这种方法持否定态度,在这种情况下,我决定使用它)。 是的,很长一段时间。 是的,无聊。 但是将来,您将不会因为SDK的不断崩溃以及这种下降的后果而陷入困境。


这种情况的主要后果是,通过将SDK提供给客户端,您将失去控制:您无法快速修复该错误,很难发现此错误,并且在这种情况下您会显得很愚蠢。 因此-测试。 测试更好。 还有一次。 以防万一,测试您的测试。 并测试测试。 因此,我有些惊讶,但是我希望测试SDK的重要性是显而易见的。


“无法抗拒过去的受害者被他吞噬了”-Logi


由于您将SDK交给了第三方公司,因此您失去了对情况的控制权,如果发生错误(在测试阶段,您都决定要这样做,对吗?),一个漫长而痛苦的过程正在等待您寻找这个错误。 这是日志为您提供帮助的地方。


记录所有内容 ,绝对记录所有内容,如果发生错误,请向您的客户端询问日志。 这样,您将节省大量时间,并且无法在客户面前擦脸。


“警报!Achtung!注意!” -错误



在考虑很多错误的同时,我得出了一个有趣的结论- 您的SDK中没有一个方法应该给出文档中未描述的错误 。 同意,当您连接第三方库以使用HttpRequest时,这是非常不愉快的,并且会向您抛出NullPointerException和StackTrace,这会导致该库的烦恼。 而且,您必须钻研这些“毛发”,试图了解兔子洞有多深以及实际上是什么问题。


因此,我提出以下解决方案-声明可能的异常的封闭列表并记录它们。 但是,因为 您无法确定是否已提供所有内容,将方法包装在try-catch中,并且将捕获的错误包装在已声明的错误中。 例如,将包含InnerException的ConfigurationException是捕获的错误。 这将使第三方开发人员可以捕获所有可能的错误,但是如果发生某些情况,请迅速找出问题所在。


版本或“如何不咬尾巴”


为了避免将来出现问题,我强烈建议您使用严格的版本控制。 选择适合您的版本控制系统并使用它。 但是,如果新版本的库不具有向后兼容性,则必须指出。 如何解决-您认为。 但是您绝对应该考虑一下。


“可能的火车”-部署


对文档和版本的相关性的需求引起了对部署正确性的要求。 在我们的决定中,我们使用以下解决方案(拐杖,但它们可以工作)。
当有必要发布新版本时,开发人员会用发行版号,然后是批处理文件来拉动bat'nik。


  • 构建版本
  • 将所有库放入存档
  • 生成最新版本的文档(docfx)
  • 在文档和档案名称中指示发行版本
  • 将所有最新鲜的放入git仓库
  • MS Azure上的WebApp提取新的git hook提交并发布更改

在输出中,我们获得了包含文档的站点的更新版本,您可以从此处下载具有最新版本SDK的存档。
未来的计划包括将所有内容打包到Nuget软件包中并将其发布到本地Nuget存储库中。


我建议注意这一点,因为您可以大大减少由于缺少有关新版本库的相关信息而引起的头痛。


“-那么你可以吗?-胡说。看看应该怎么做!” -示例和工具包


文档中的重点是用法示例。 但是除此之外,通常还需要不提供库,而是提供实现最标准方案的应用程序。 我建议使用开放源代码和注释良好的源代码来创建这些应用程序,这将使您可以一箭双雕杀死两只鸟-提供一个可运行的应用程序,并提供一个使用SDK的示例。


结论


SDK开发对我来说已经成为一项有趣的新任务,它提出了许多重要的体系结构问题。 (对我而言)文章中描述的大部分内容都是显而易见的事情,但我认为,重要的是要宣布即使是显而易见的事情,也可以清晰地展现出来。


聚苯乙烯


感谢您的阅读,我将很高兴您的评论。 希望本文对您有所帮助。

Source: https://habr.com/ru/post/zh-CN430940/


All Articles