设计关键算法:实现

  1. 设计
  2. 实作
  3. 积分


当我刚开始职业发展职业时,我不明白为什么需要开源。就此而言,我也不理解辅助项目。毕竟,为什么要免费提供有价值的工作?多年以来,通过从事开源项目以及与Apex.AI,ROS 2和Autoware.Auto的合作,我对开源有了一些了解。



工程师喜欢创造。人们想要认可和感激。



将这些因素结合在一起,便可以实现开源。如果我要构建满足我的创意需求的东西,为什么不让其他人欣赏我的作品并在其中找到实际用途和价值呢?毕竟,我不是为了钱而这样做。



对于附带项目,只有在我开始专业发展并更仔细地研究工作的各个方面之后,我才意识到它们的魅力。为了创建人们愿意支付的可靠产品,通常必须人为地限制工作流程。设计分析。代码审查。编码标准,样式指南,测试覆盖率指标等。不要误会我的意思-这些都是开发优质产品所必需的好东西。只是有时候开发人员想要一点自由。开发人员可能想要创建自己想要的东西,自己想要的方式以及想要的时间。没有会议,评论或业务案例。



那么,在开发安全或高质量算法时,如何结合这些方面?自由是开源世界的一大魅力,而有助于确保开发出更可靠代码的实践限制了自由。



我发现的答案是遵循开放和一致的纪律,并自由地使用来自开源世界的许多很酷的工具。



规划开源项目



工程师需要一套特定的技能来解决这些问题。工程师需要专注,他们需要良好的解决问题的能力。工程师还需要能够分离问题,并具有获得上述所有知识所需的扎实的基本技能。



这种特殊的技能会使我们的工程师有些不知所措。



对于工程师具有的所有技术能力,最终他们的能力将受到限制。我认为大多数开发人员在编写单独的代码行时都无法记住整个项目。而且,我认为大多数开发人员在不忘记总体业务目标的情况下就无法编写程序并保留更广泛的项目。



这就是项目管理的黑魔法起作用的地方。



尽管我们的开发人员与人力资源,技术或项目经理之间可能存在争议的关系,但应该认识到,所有这些人都在从事重要的工作。管理专业的最佳代表确保开发人员不会忽略重要任务,恼人的刺激因素也不会阻止我们使用所有武器射击问题。



虽然我们了解不同管理人员的重要性,但是具有这些技能的人员通常不会参与以娱乐为目标的典型开源项目。



那我们该怎么办呢?



好吧,我们的开发人员可能会手忙脚乱,花点时间计划未来。



我不会进行这些对话,因为在上一篇文章中,我详细介绍了设计和开发计划阶段。最重要的是,考虑到设计和体系结构(通常,这些设计和体系结构由许多组件组成并形成一些依赖性),在您自己的项目中,您可以自己设计并单独收集其组件。



回到项目计划方面,我想从依赖最少的组件开始(花一点时间!),然后继续工作,在必要的地方添加实现存根以保持开发的进行。按照这种工作顺序,通常可以创建许多票证(如果其中的某些依赖项与体系结构的依赖项相对应-如果任务跟踪器具有这种功能)。这些故障单可能包含一些一般性注释,在您更详细地研究任何任务之前,请记住这些注释。门票应尽可能小且具体。面对现实吧-我们关注焦点和保持上下文的能力是有限的。开发任务分解得越细,越容易-那么为什么不尝试使困难的任务尽可能简单呢?



随着项目的发展,您的工作将是按照优先顺序获取票证并完成分配给他们的任务。



显然,这是项目管理的极大简化版本。真正的项目管理还有很多其他方面,例如资源,计划,竞争性业务案例等等。开源项目管理可以更简单,更自由。也许在开源开发的世界中,存在着成熟的项目管理案例。



开放式开发



输入了一堆票,形成工作计划并了解所有细节之后,我们便可以进行开发。



但是,在开放式开发的狂野西部存在的许多自由因素不应该在安全代码的开发中。通过使用开源工具并有一定纪律(并拥有朋友),您可以避免很多陷阱。



我大力支持以纪律作为提高工作质量的一种手段(毕竟,纪律在我的StrengthsFinder中排名第六))。有了足够的纪律来使用开源工具,倾听其他人的声音,根据结果采取行动并坚持工作流程,我们就可以克服开源世界的牛仔方法所潜入的许多缺陷。



简而言之,以下工具和实践的使用(有些警告,可以在任何项目中轻松应用)有助于提高代码的质量:



  1. 测试(或者更好的是,测试驱动的开发)
  2. 静态分析
  3. 持续集成(CI / CD)
  4. 代码审查


在直接编写代码时,我还将给出一些我遵循的原则:



  1. 充分利用语言和图书馆
  2. 该代码应易于阅读且清晰明了。


我将尝试将本文与NDT本地化算法的实际实现联系起来,该算法我的好同事Yunus10个合并请求中完成的他太忙于直接工作,所以我可以写一些关于他的工作的虚构奖牌。 另外,为了说明一些过程和实践,我将举一个为MPC控制器开发开源算法的示例它是在大约30多个合并请求中以稍微宽松(牛仔)的样式开发的,不包括主要工作结束后进行的其他编辑和改进。







测试中



让我们谈谈测试。



按照我的标准,我与测试有着长期而艰难的关系。当我最初担任开发人员并从事第一个项目时,我绝对不相信我的代码在工作,因此我是团队中第一个开始编写至少一些有意义的单元测试的人。但是,我绝对没错,我的代码无法正常工作。



从那时起,我与测试的混乱关系经历了许多值得一夜之间拍到的曲折。有时我喜欢。有时我讨厌这一切。我写了太多测试。复制粘贴过多,冗余测试过多。然后测试成为日常工作,这是开发的另一部分。首先,我编写了代码,然后为它编写了测试,这是事情的顺序。



现在,我与测试有正常关系。无论我使用什么应用程序,它都是我工作流程的组成部分。



对我来说,改变了的是我在mpc项目中开始使用的测试驱动开发技术。在之前的文章中,



我简短地谈到了测试驱动的开发,但是这里是对该过程的另一种描述:



  1. 制定规范(用例,需求等)。
  2. 实施API /架构
  3. 根据API和设计规范编写测试;他们必须失败。
  4. 实施逻辑;测试必须通过


此过程中存在一些迭代(测试不会在存根上失败,实现失败的测试,API可能很笨拙,等等),但是总的来说,我认为它非常有用。



我已经讨论了很多有关在实施之前进行计划的问题,而测试驱动的开发为您提供了这一机会。指出了第一点。然后,您考虑架构和API,并将它们映射到用例。这为您提供了一个接近代码的绝好机会,但仍然可以从总体上考虑问题。指出了第二点。



然后我们继续编写测试。在实施之前编写测试有用的原因有很多,我认为它们都很重要:



  1. 测试应作为第一优先级对象而不是附加组件来编写。
  2. , – .
  3. API , .
  4. , , , , .


总的来说,我认为测试驱动开发的好处是巨大的,我再次强烈建议大家至少尝试一下。



让我们回到Autoware.Auto。 Yunus虽然不坚持测试驱动的开发方法,但在NDT开发过程中为每个合并请求编写了测试。同时,测试代码的数量等于(有时甚至超过)实现代码的数量,这很好。相比之下,SQLite可能是测试的基准(不仅仅是按照开源项目的标准),其测试代码比实现代码多662倍... 在Autoware.Auto中,我们还不处于这个阶段,但是如果您查看与NDT相关的合并请求的历史记录,您会发现测试代码的数量缓慢增加,直到覆盖率达到90%(尽管此后它已经下降了)。由于其他设计和外部代码)。



那很酷。



同样,我的mpc项目对所有内容都进行了测试,包括测试本身此外,我总是仔细进行回归测试,以确保该错误已修复且不会再次出现。



我很好。



静态分析



许多概念很好奇,因为它们中包含的定义可以大大扩展。例如,测试远远超出了手写功能测试。实际上,样式检查或错误查找也可以视为一种测试形式(本质上是一种检查,但是如果扩展了定义,则可以称为测试)。



这种“测试”在工作上有些痛苦和费力。毕竟,验证,验证制表符/空格是否对齐?不用了,谢谢。



但是,关于编程的最令人愉快和最有价值的事情之一就是能够使痛苦且耗时的过程自动化。同时,该代码可以比任何人更快,更准确地获得结果。如果我们可以对代码中的错误和有问题的或容易出错的结构进行相同的处理,该怎么办?



好吧,我们可以-使用静态分析工具。



我在之前的博客文章中写了足够多的关于静态分析的文章,所以我不会深入研究它的好处和可以使用的工具。



在Autoware.Auto中,我们使用ament_lint集的较小版本来自ROS 2的密友。这些工具为我们带来了很多好处,但也许最重要的是我们的代码自动格式化以消除样式争执-公正的工具告诉我们什么是正确的,什么是错误的。如果您有兴趣,我会注意到clang-formatuncrustify更严格



在mpc项目中,我走得更远。在其中,除了来自clang-tidyClang 静态分析器的所有警告之外,我还使用了Clang编译器Weverything标志。出人意料的是,商业发展需要禁用多种选择(由于多余的警告概念上的混乱))。与外部代码进行交互时,我不得不禁用许多检查-它们导致不必要的噪音。



最终,我意识到使用广泛的静态分析不会严重干扰正常的开发(在编写新代码的情况下以及在学习曲线上经过某个点之后)



很难量化静态分析的价值,尤其是从一开始就使用它时。关键是很难在引入静态分析之前猜测错误是否存在。



但是,我认为使用警告和静态分析是其中之一,即使正确使用,也无法确定它们根本没有做任何事情。... 换句话说,您无法确定打开静态分析器时的值,但是哎呀,您会发现它不存在。



CI / CD



尽管我喜欢严格的测试和静态/动态代码分析,但是如果不运行,所有测试和检查都是毫无价值的。CI可以用最小的开销来应对这些挑战。



我想每个人都同意,拥有CI / CD基础结构是现代开发的重要组成部分,并且使用版本控制系统并具有开发标准(至少是样式指南)。但是,良好的CI / CD管道的价值在于其操作必须可重复。



CI / CD管道至少应在将代码推送到存储库之前构建代码并运行测试。毕竟,没有人愿意成为破坏程序集或某种测试并必须快速而可耻地修复所有问题的那个人(或女孩或角色)。 CI(以及您亲爱的DevOps工程师)保护您免受这种耻辱。



但是CI可以为您做更多的事情。



借助强大的CI管道,您可以测试操作系统,编译器和体系结构的任意数量的组合(在某些限制下,请考虑组合测试)。您还可以执行构建,运行测试以及其他操作,这些操作可能会占用大量资源或使开发人员无法手动执行。你不能跳过你的头。



回到最初的陈述,在您的开源项目中拥有CI / CD管道(我们在Autoware.Auto使用的管道)将有助于应对难以管理的开发。如果代码未生成或未通过测试,则将无法进入该项目。如果您遵守严格的测试纪律,则可以始终确保该代码有效。



在Autoware.Auto中,CI:



  1. 收集代码
  2. 运行测试(样式,棉绒检查,功能测试)。
  3. 测量测试范围
  4. 检查代码是否已记录




反过来,我匆匆在mpc项目中编译了CI



  1. 收集代码
  2. 执行扫描(lang静态分析)
  3. 运行测试(但如果测试失败,则不会停止CI)。


由经验丰富的DevOps工程师(例如我们的J.P. SamperHao Peng!)组成的CI管道可以做的更多。因此,请珍惜您的DevOps工程师。它们使我们(作为开发人员)的生活更加轻松。



代码审查



基准,分析器和CI都很棒。您可以运行测试,分析所有内容,并确保使用CI完成这些测试,对吗?



抱歉不行。



同样,世界上所有的测试如果不好则毫无价值。那么,如何确保测试良好?



不幸的是,我没有神奇的答案。实际上,我将回到旧的工程技术,同行评审。特别是要进行代码审查。



通常认为,两个负责人比一个负责任。实际上,我认为这个概念不仅得到文学的支持,而且也得到理论的支持。



方法集合在机器学习中说明了这一理论。可以相信,使用一组方法是提高统计模型性能的快速简便的方法(众所周知的增强方法就是一个例子)。同样,从纯粹的统计角度来看,方差越小(假设),您拥有的样本越多。换句话说,如果您联系更多的员工,您更有可能更接近事实。



您可以通过进行团队建设练习来通过现场示例尝试该技术。一个不太好玩的版本可能涉及分别和分组猜测随机统计信息。



除了理论和团队建设,代码审查是一个重要而强大的工具。毫不奇怪,代码审查是任何专业开发过程中不可或缺的一部分,甚至是ISO 26262标准的



建议,所有这些都表明一个孩子总是有七个保姆的危险。此外,有时代码审查可能会导致某些困难。



但是,我认为,如果审阅者和同行审阅者都记住以下几点,则代码审阅将是令人愉快且轻松的:



  1. 您不是您的代码。
  2. 您正在与另一个人聊天。
  3. 要有礼貌
  4. 每个人都朝着同一目标努力;代码审查不代表任何竞争(尽管有时在编程中发生


许多比我更聪明,更好的人写了有关如何正确进行代码审查的文章,我邀请您来看看他们的工作我要说的最后一件事是,如果您希望代码更可靠,则应该进行代码审查。





我详细介绍了可用于创建开发环境的过程和工具:检查和执行检查并确保代码足够好的工具。



接下来,我想快速讨论一下编程能力,并就编写单独的代码行背后的过程和意图分享一些想法。



有几个概念帮助我极大地改进了代码。这些概念之一是记忆意图,语义和可读性的能力,我将在稍后讨论。另一个是对OOP关注点分离的理解。最后一个重要的想法是DRY(不要重复自己)或“不要重复自己”的原则。



DRY是学校里教的东西,就像其他很多东西一样,我们把这种想法放在很远的地方,并且在考试之外对它不怎么重视(至少对我而言)。但是,就像学校其他许多事情一样,我们不会像那样学习任何东西。实际上,这是一个好习惯。



简而言之,如果您发现自己经常复制和粘贴代码,或者经常编写非常相似的代码,则这很好地表明了可重复代码应成为某种抽象的功能或一部分。



但是,DRY比检查是否应将某些代码移入函数更进一步。这个概念也可以作为某些架构决策的基础。



尽管此方法与某些体系结构概念(例如别名,连接性和关注点分离)相交,但在我的mpc项目中可以看到有关DRY如何应用于体系结构的示例。在开发mpc控制器的过程中,我注意到,如果我编写另一个控制器,则必须重复一些代码。它与用于跟踪状态,帖子,订阅,转换等的样板代码有关。换句话说,这似乎是与mpc控制器分开的任务。



这很好地表明了我应该将常规设计和功能分成一个单独的类。投资回报是双重的:MPC控制器 100%专注于与MPC相关的代码,并且与之关联的模块只是一个配置模板。换句话说,由于体系结构的抽象,在使用其他控制器时,我不必重写所有内容。



世界是由灰色阴影构成的,因此应谨慎和正确地考虑这些设计决策。否则,您可能会走得太远,开始在不需要它们的地方创建抽象。但是,如果开发人员牢记这些抽象模型的概念,则DRY是用于制定体系结构决策的强大工具。在我看来,DRY是保持代码干净且密集的主要概念。



毕竟,代码的主要好处之一是它具有执行重复任务的能力,那么为什么不将重复转移到设计良好的函数和类上呢?



充分利用语言和图书馆



在我看来,DRY是一个非常重要且普遍的概念,以至于这一点实际上只是DRY对话的延续。



如果您的语言支持某些功能,那么通常应该使用内联实现,除非您有充分的理由选择退出。 C ++有很多内置的东西



编程是一项技能,技能水平存在很大差异。我只抓到的一瞥的山有多高这个技能,一般我发现谁制定标准的人都在实施共同的模式比我更好。



可以对库的功能进行类似的讨论(尽管可能不是绝对的)。其他人已经做了同样的事情,并且可能已经取得了不错的成绩,因此没有理由重新发明轮子。



但是,与其他许多段落一样,本段是一个建议,而不是一个严格而紧急的规则。尽管不值得重新设计轮子,并且尽管标准实现通常非常好,但试图将方形块挤压到圆孔中没有意义。想一想。



可读代码



帮助我提高编程技能的最后一个概念是,编程与其说是与交流有关,不在于编写代码。如果不与其他开发人员进行交流,那么将来会与您自己进行交流。当然,您需要考虑内存,数学以及Big O的复杂性,但是一旦完成,就需要开始考虑意图,语义和清晰度。关于此主题,



有一本非常著名且广受推荐的书,即“ 清洁代码”,因此我在该主题上没有太多补充。以下是一些我在编写代码和进行代码审查时所参考的常规信息:



  1. 尝试使您的课程清晰,专注:

    • 最小化障碍
    • ()



      • const, noexcept? ? (, )




    • , (, , ).
    • (, )
    • .
  2. -



    • «», , .
    • (, ).
    • ( ) ().
  3. ? ()



    • , ().
    • , , , (, ).


解决此类问题的另一个重要资源是ISO C ++核心指南



我要重申的是,这些原则都不是革命性的,新颖的或独特的,但是,如果这些原则的编写具有价值(或者有人在阅读时会说“啊哈”),那么我就不会浪费时间和精力撰写这篇文章。 ...



回头看



这些是我们在开发和实现NDT本地化算法以及在MPC控制器上工作时使用的一些工具,原理和过程。完成了许多工作,这很有趣,谈论它并不是那么有趣。



总体而言,我们充分利用了我提到的工具和实践,但并不完美。



因此,例如,在开发NDT时,我们没有遵循测试驱动开发的习惯用法(尽管我们已经对所有内容进行了完美的测试!)。反过来,我的确在MPC上遵循了测试驱动的开发技术,但是该项目没有从Autoware.Auto中内置的更强大的CI中受益。而且,MPC项目不是公开的,因此没有获得代码审查的好处。



这两个项目都可以从静态分析,更详细的测试和更多的反馈中受益。但是,我们谈论的是一个人创建的项目,因此我认为完成的测试和收到的反馈就足够了。当涉及静态分析时,更好的和更具针对性的形式通常会落入产品开发兴趣的领域,并远离开源开发人员社区(尽管可能会出现一些有趣的想法)。



关于两种算法的同步开发,我无话可说-我们竭尽所能,坚持了我上面概述的原则。



我认为我们在将大问题分解成较小的部分方面做得非常出色(尽管NDT算法中的MR组件可能较小),并且已经进行了广泛的测试。我认为初步结果说明了一切。



向前移动



实施之后,就该进行集成了。有必要连接到具有自己复杂组件的更大系统。该系统将获取您的输入,将其摘要并生成算法的结果。集成可能是开发算法中最困难的部分,因为您需要跟踪总体系统计划并解决低级问题。您的许多代码行中的任何错误都可能阻止算法集成。



我将在本系列的第三篇也是最后一篇文章中对此进行介绍。



作为预览,我将说在开发过程中,在使用和集成mpc控制器期间未发现一个大错误。没错,编写脚本,组装,测试中跳过了输入数据验证,并且QoS设置不兼容也存在问题,但是代码中没有什么可怕的。



事实上,它是能够运行(绕过QoS的不兼容性和设置选项)几乎开箱



这同样适用于无损检测算法,它遇到了许多像小问题协方差的不稳定误差在搜索字符串在现有的代码,并正确对齐地图。无论如何,它也可以开箱即用



对于设计给所有人的产品来说还不错。



订阅频道:

@TeslaHackers — Tesla-, Tesla

@AutomotiveRu — ,







图片



- automotive . 2500 , 650 .



, , . ( 30, ), -, -, - (DSP-) .



, . , , , . , automotive. , , .


:






All Articles