有期限的待办事项?


51

背景

我正在一个团队中实施零停机时间部署。为了实现此目标,我们正计划使用蓝/绿部署策略。我在进行研究时意识到的一件事是进行数据库更改变得多么复杂。重命名列之类的简单操作可能需要3个完整的发布周期,直到完成!

在我看来,全面实施变更需要多个发布周期,这会带来很多潜在的人为错误。在链接的文章中,它表明2个发行版需要更改代码,而3个发行版需要数据库迁移。

我在寻找什么

当前,如果我们想记住要做的事情,可以在我们的问题管理系统中创建票证,这会造成混乱,并且还可能被管理层转移到以后的冲刺或待办事项中;或者我们可以创建一个TODO注释,它可能会完全被遗忘。

我正在寻找的方式是TODO注释可以有一个截止日期,并且,如果该截止日期已过期,我们的持续集成系统(当前将使用的未定)将拒绝该构建。

例如,如果我们重命名列,则可以为其创建初始迁移,然后创建两个TODO注释以确保创建其余的两个迁移:

// TODO by v55: Create migration to move constraints to new column, remove references to old column in app
// TODO by v56: Create migration to drop old column

这似乎很容易实现,但是我想知道是否已经存在类似的东西,因为我不想重新发明轮子。

其他想法

考虑到滚动部署和蓝/绿部署被认为是最佳实践,我觉得自己可能在这里遇到了XY问题,但我找不到能够减轻数据库更新麻烦的解决方案,这似乎很奇怪。如果您认为我正在完全研究错误的内容,请在评论中告诉我!就是说,我给出的数据库示例只是一个示例,我认为带有到期日期的TODO注释在其他情况下也将很有用,因此即使我正在接近这种特定情况,我还是很想回答我的问题也是实际问题。谢谢!

编辑:我只是想这可能会有所帮助的另一种情况。如果在准备就绪时使用Feature Toggles来打开应用程序的某些部分,则必须小心清理它们,否则最终可能会出现Toggle Debt。带有截止日期的评论可能是记住这一点的好方法。


19
TODO问题更多是纪律问题,而不是工具问题。
布兰登

16
我认为人类都会犯错误,而使用工具可以减轻这种情况。
约书亚·沃尔什


3
以编程方式执行此操作怎么样。我的意思是在班级成员中写下您的版本。如果版本等于== 56且消息“类y”的应用程序启动失败,则需要具有此功能,您可以具有此类消息的列表。你怎么看?
Tomer Ben David

6
针对反对者,我不同意:我们的代码库依赖于许多其他我们不使用的组件,因此我们使用它TODO <Bug#>:来跟踪其他组件问题的解决方法。清除其中一个组件的错误后,您可以轻松找到并解决相关变通办法。它不会替代问题跟踪器,因此更易于维护。
TemporalWolf's

Answers:


53

这个问题实际上是两个问题合而为一。

待办事项

在所有追踪行动项目的方式中,这是最糟糕的。TODO注释在积极工作中或作为对维护人员的建议是很好的,“将来可能会有所改进”。但是,如果您依靠TODO注释来完成工作,那么注定会失败。

该怎么办

TODO注释基本上是技术债务,因此应像处理其他任何技术债务一样处理。如果有时间,可以立即解决它们,也可以将它们放入积压中,以便对其进行跟踪和确定优先级。

一般而言,这是完全自以为是的并且可以辩论,TODO注释可以被认为是代码异味。如果TODO注释使它进入了版本控制范围,那么您必须问问自己,您现在是否真的要继续进行下去?如果没有,那没关系。只要对自己诚实一点,然后将其放入待办事项列表中即可。

您如何管理此积压工作取决于业务流程,公司政治以及某些个人自主权。但是您仍然需要跟踪并按优先级排序的待办事项以确保发生。

数据库变更

是的,使用零停机策略对数据库进行更改非常棘手。一些技巧可以帮助减轻痛苦:

部署后流程

创建一个作为同一版本的一部分运行的部署后流程。但是,您希望它能正常工作。在我工作的最后一个系统上,我设计了一个四阶段部署:

  1. preapp数据库脚本
  2. 网络应用
  3. postapp数据库脚本
  4. 维护窗口数据库脚本

这个想法是,只要有可能,我们就会将尽可能多的数据库更改放入preapp中。

Postapp被保留用于我们需要进行不兼容的架构更改的异常情况。在这些情况下,preapp将进行足够的更改以使新的应用程序代码兼容(也许为兼容性创建一个临时视图),而postapp将清除所有此类临时工件。

维护窗口阶段保留用于真正需要停机时间或不值得进行实时部署的风险或成本的更改。例如,更改大量数据的脚本可能需要锁定整个表。

经常部署

如果您足够频繁地部署新发行版,则可以轻松实现对2个或3个发行版进行更改。较长的发布周期会增加数据库更改的成本。


18
待办事项注释是跟踪工作和确定工作优先级的一种糟糕方法。它们是解释为什么一半的代码随风飘动的有效方法。在理想的世界中,没有代码可以做到这一点。同时,在这个……中
candied_orange

6
……有时有一种跟踪技术债务的方法,这很不错,而降低债务的优先级几乎不会隐藏。当然,您将无法获得修复的功劳。有时您还是要修复它。
candied_orange

3
因此,应用程序后的策略是,一旦应用程序部署全部成功后,这些迁移便开始运行?那代码呢?假设您要将一列从last_name重命名为surname。您的旧代码使用last_name。您迁移数据库以添加姓氏,并更改代码以使用姓氏(如果可用),否则使用last_name。完全部署完成后,运行下一个迁移,删除旧的last_name列。但是您的代码仍然包含last_name的代码,该代码现在未使用,因此存在技术债务。您如何执行清理工作?
约书亚·沃尔什

3
尽管在评论中管理动作项确实是一种糟糕的方法,但是让评论在跟踪系统中自动创建问题可能是一个不错的工具,因为您目前正在编码某些东西并且不想硬切换,所以不要忘记这样做问题跟踪系统的上下文。
PlasmaHH

6
恕我直言,这个答案缺少重点。OP提出了一个解决方案,CI会在忘记进行重要的清理时通知CI,而又不会弄乱“问题管理系统”(TODO注释仅是示例,也许不是理想的解决方案)。OP给出了一些他为什么不想在此之前使用它的充分理由。然而,这个答案表明完全依靠积压,在OP的情况下,只有他的“问题管理系统”。因此恕我直言,这个答案忽略了问题的核心,没有提出解决方案。
布朗

24

不要使用TODO。您的项目中已经有一个TODO列表。它称为问题跟踪器。

我认为真正的问题在于这句话:

我们可以在我们的问题管理系统中创建票证,这会造成混乱,并且还可能被管理层转移到以后的冲刺或待办事项中。

如果您的问题跟踪程序造成了很多混乱,请找到解决方法。也许是涉及较少仪式的特殊发行类型/标签。也许是子问题。也许总共更少的仪式。我们真的不能说。但是,如果您的问题跟踪程序创建了如此多的工作,以至于人们宁愿在公共论坛上提出一个详尽的问题,而不是仅仅添加该问题,那是严重的错误。

如果您的管理层不适当地延迟了任务的最后一部分,则有两种选择:

  1. 与您的管理人员讨论为什么这是一个坏主意。

  2. 作为一个单独的任务来处理。这可能是黄金标准解决方案。在一个完美的世界中,您应该能够在每个步骤中进行所需的三个更改。将一个应用到master分支,使其构建和部署。同时,将第二个应用到master分支,让它构建和部署,依此类推,以便所有事情都发生在同一个sprint中,如果没有,那就不会完成。在逻辑上进行一次部署的地方,甚至自动的东西也很有意义,但实际上它分为三部分。


好的建议,我将考虑使问题管理系统为此目的而工作的方式。我也非常喜欢“也许在逻辑上进行一次部署时,甚至自动的东西也是有意义的”的想法,我正在尝试思考我们可以做到的方式。我不确定这是否现实可行。
约书亚·沃尔什

11
对表单进行注释是完全合理的// TODO(#12345): Frobnicate the sprocket before passing it along,前提是错误#12345是“真实”的问题编号,并且该问题已分配给某人。通过澄清这一点,可以使源代码更易于阅读:“不,frobnicate步骤没有隐藏在任何一种辅助方法中,只是完全没有实现。请查看错误#12345以获取更多上下文。” 理想情况下,当然,您应该每天在代码库上运行linter,以查找已关闭或无效的问题编号。
凯文

9

我正在寻找的方式是TODO注释可以有一个截止日期,并且,如果该截止日期已过期,我们的持续集成系统(当前将使用的未定)将拒绝该构建。

如果您愿意做这项工作并坚持下去,那么您的要求就是可行的。

// v55的TODO:创建迁移以将约束移至新列,在应用中删除对旧列的引用// v56的TODO:创建迁移以删除旧列

grep命令//TODO by v55时,它的时间来部署V55。Deploy build运行一个脚本,将其作为集成测试。

您可以将55绑定到版本跟踪中,也可以仅提示输入。

如果您想在执行55时通过v54检查// TODO,就会变得很有趣。然后将代码库搜索55次,只需搜索// TODO by。然后将该结果过滤为1到55。现在56不会触发失败。

您可能会认为“哦,我们不需要。只要有支票,我们就会每次都解决”。不,你不会。


4
如果这样做,我们在这里不做建议。
candied_orange

3
如果可以提供这种名称的通用名称,但如果您阅读了该页面,则链接了有关建议的行,您将指向以下内容:softwareengineering.meta.stackexchange.com/a/6487/131624
candied_orange

6
明确地说,我反对的是您的意见,而不是整个问题。
candied_orange

2
@YM_Industries SE网站通常是独立的,建议基本上是简单的答案,带有指向外部网站的链接,或者邀请您使用Google而不是链接来搜索它,但最终还是一样。它们可能会过期并死亡。因此,有关推荐的问题是不合时宜的,但是如果有人想提及该工具作为答案或简单评论的补充,那么他可以做到。
Walfrat

2
“我想知道是否存在现有解决方案”-尝试在softwarerecs.stackexchange.com上
Mawg '17

4

我们团队中有一个非常相似的问题。为了解决这个问题,我们编写了一个静态分析检查,通过检查它们引用的JIRA问题或Git问题来处理这些TODO。当指定的“问题”移到“开发中”列时,我们的构建将失败。

因此,我们可以轻松拥有TODO,而不必担心它们会被遗忘。

我用Java创建了一个开源实现。是的,我写的是免责声明,但是就像我说的那样,它是完全开源和许可的。

该工具称为Westie,而README.md上有一个Jira问题检查器的示例。另请参见GitIssueAnalyser。

如果您还有其他问题,为防止自我提升,请给我发消息。如果您决定使用它并有任何建议,请在github上提出任何问题。


1
这很酷!我们也使用JIRA,我可能会考虑使用它。它并不能真正解决我对在我们的问题管理系统中造成混乱的担忧,但至少可以保证不会忘记这些问题。
约书亚·沃尔什

@YM_Industries我很高兴。我很乐意接受任何贡献或在提出的任何问题上开展工作。
tjheslin1

4

不要做。现在做。

TLDR:现在(而不是稍后)编写(和测试)您的数据库脚本;只需对它们进行编码,以便其执行取决于数据库版本。

例如,假设您希望将列名从更改SSNTaxID,这是国际化时的常见要求。

为了实现这一点,也许您会暂时拥有a TaxID和一SSN列。同时支持两个版本时,您将具有一个触发器来相互更新。但是您不希望无限期地保持该触发器,因此,稍后,当不再需要向后兼容时,您希望该触发器被删除(SSN列被删除)。我们将在不需要ToDo项的情况下预先编写所有代码。

在我们的示例中,我们将部署版本102(具有新列),同时保持与版本101(不具有)的兼容性。

步骤如下。

1.设置版本表

  1. 添加称为一个表Configuration有两列,NameValue

  2. 添加带有Name“ TargetVersion” 的行,并将设置Value为要部署的新版本的版本。

  3. 添加带有Name“ CompatibleWith” 的行,并将设置为Value与部署必须兼容的最低版本号。

每次部署之前,请检查并更新这些行。

2.修改部署脚本

  1. 添加一个脚本,该脚本创建一个新的列TaxID,与并排放置SSN,并从SSN列中填充它。将此代码放在If检查TargetVersion 的语句中;如果目标版本太低(即TaxID不需要),请跳过。

    SELECT @TargetVersion = TargetVersion FROM Configuration
    IF @TargetVersion < '102' THEN RETURN
    ALTER TABLE Customer ADD COLUMN taxID VarChar(12) NOT NULL
    UPDATE Customer SET TaxID = SSN
    
  2. 添加一个脚本,该脚本创建一个TaxID在插入或更新时填充的触发器,SSN反之亦然。将此代码括在If检查目标版本和兼容版本的语句中。如果TargetVersion太低(TaxID不需要)或CompatibleWith版本太高(SSN不需要字段),则跳过。

    SELECT @TargetVersion  = TargetVersion,
           @CompatibleWith = CompatibleWith 
    FROM Configuration
    IF @TargetVersion  < '102' THEN RETURN
    IF @CompatibleWith > '101' THEN RETURN
    CREATE TRIGGER SSNAndTaxIDTrigger ON Customer etc.
    
  3. 添加脚本以删除该SSN列。If仅在CompatibleWith版本足够高(SSN不再需要)时,才使用封闭该语句的语句。

    SELECT @CompatibleWith = CompatibleWith FROM Configuration
    IF @CompatibleWith <= '101' THEN RETURN
    IF OBJECT_ID('SSNAndTaxIDTrigger') IS NOT NULL DROP TRIGGER SSNAndTaxIDTrigger
    IF EXISTS (SELECT * FROM syscolumns c JOIN sysobject o ON o.id = c.is WHERE o.Name = 'Custeomr' AND c.Name = 'SSN') BEGIN
        ALTER TABLE Customer DROP COLUMN SSN
    END
    

3.测试

确保使用希望在生产中支持的蓝色/绿色版本号的任意组合来测试您的部署。通过Configuration在QA环境中操作表格,您可以在代码准备就绪后立即进行测试。

4.在您的部署手册中

为工程师添加一个步骤以更新CompatibleWith版本和TargetVersion行。如果要部署到Blue,请将TargetVersion设置为Blue的版本号,并将CompatibleWith版本设置为Green的版本号;如果您要部署Green,则将其反转。

陷阱

您的部署脚本可以引用并依赖该数据库表中保存的那些版本号是可以的。不是运行时代码。

如果您开始编写运行时代码以检查版本号,那么您将在应用程序中引入新的复杂性水平,这可能会成为一个巨大的可维护性问题。每个运行时执行路径都必须经过测试;如果您继续解决这些问题,则质量检查人员必须将痛苦矩阵汇总在一起以便每次发布时都可以对其进行验证。我的建议是仅将此类条件保留在部署脚本中。

所有这一切的结果

最后,您应该能够预先编写所有代码(并对其进行测试),而不必担心它执行得太早。同样,该代码将在时间到来时清除向后兼容触发器,而无需您进一步担心。

这样,您就可以在考虑时预先编写和测试所有代码,而无需处理那些凌乱的ToDo注释。


我真的很喜欢这种方法,它比ToDo评论更优雅。我问了这个问题后不久便想到了这一点,当时我正考虑再发表一篇文章,询问如何最好地实现这一点,但我想我要先做自己的研究。对我们来说,诀窍在于我们正在使用Phinx进行数据库迁移,但它实际上并不支持此功能。当我有时间的时候,我会寻找一种扩展它以支持这种工作流程的方法。这种方法并不能解决如何确保从我的应用程序层中删除向后兼容代码的问题,但是对于数据库问题来说却很优雅。
约书亚·沃尔什

1

您对TODO想法有很多反对意见,但是我个人认为这没有问题。最后,确保迁移进入生产的最佳(最简单)方法是通过未通过单元测试的方法。从字面上看,您需要不到一分钟的时间就可以删除一个空的迁移函数,如果版本为55或更高(或任何要求),该函数将引发异常。

然后,如果您尝试发布它,最终将导致测试失败,并且有人将不得不将该异常转换为实际的迁移代码。


1
是的,我理想上是希望将过期的TODO视为失败的测试。抵制TODO的数量让我感到有些惊讶,我知道它们不能替代问题管理系统,但是鉴于TDD / BDD的盛行,很明显,在代码中定义需求并使用代码来强制执行并没有真正的问题。功能完成。
约书亚·沃尔什

-2

似乎没有人关注他的抱怨的根源,因为事实是数据库更改可能需要太多的发布周期。他想继续执行他的蓝/绿部署计划,解决方案应该已经存在,但是除非我遗漏了某些东西,否则他的描述似乎表明只有一个数据库可供两个系统共享。如果是这种情况,则不是真正的蓝/绿系统。由于数据库似乎是帐篷中的长杆,因此也应该复制该数据库,以便无论在脱机系统上实施数据库更改需要花费多长时间或多少发布周期,它们都不会生效,直到完成并完成。经过全面测试。在临时脱机系统中,脚本可以使脱机数据库每天保持完整更新。


1
在蓝/绿色部署中复制数据库会引起很多麻烦。当我的产品环境介于蓝色和绿色之间时(例如,将50%的负载分配给每个绿色),即使它们的架构不同,我也需要使复制代码使两个数据库保持同步。根据我所做的研究,似乎现实世界中的大多数人在他们的蓝色和绿色堆栈之间都有一个共享的数据库实例。只要数据库迁移相当迅速,我就不会将其视为主要问题。蓝色/绿色堆栈固有地需要共享一些资源,至少是负载平衡器/反向代理。
约书亚·沃尔什
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.