防止在截止日期后编译不赞成使用的代码[关闭]


68

在我的团队中,我们已经在一个大型整体项目中清理了很多旧东西(整个类,方法等)。

在执行这些清理任务时,我想知道是否存在比平时更好的注释或库@Deprecated@FancyDeprecated如果您在特定日期过去之后仍未清除旧的未使用的代码,则这将阻止项目的构建成功。

我一直在Internet上搜索,但找不到具有以下功能的任何东西:

  • 应该是注释或类似内容,以便在特定日期之前放入要删除的代码中
  • 在此日期之前,代码将被编译,并且一切将正常运行
  • 在此日期之后,代码将无法编译,并且会向您显示一条消息,警告您有关该问题的信息

我想我正在寻找一个独角兽...任何程序语言都有类似的技术吗?

作为一个计划,BI正在考虑通过对打算删除的代码进行一些单元测试来制造魔术的可能性,这些测试在“最后期限”开始失败。你怎么看待这件事?有更好的主意吗?


168
不推荐使用版本而不是日期。如果您希望人们停止使用不赞成使用的功能,请发布没有这些功能的版本。那会引起他们的注意,如果他们还不能这样做,他们总是可以回滚。很多人都坚持使用旧版本。打破他们的程序不是要走的路。
isanae

4
计划B听起来不错。利用附加的优势,您可以在单元测试中添加有关不推荐使用原因的注释。将注释添加到源代码中并不会有助于提高整体可读性
dwana

53
这听起来像是一个非常邪恶的独角兽。您有一些可以很好地编译,通过所有测试的软件,并且已将其交付给客户。然后有一天,即使在完全相同的开发平台上,您也再次签出该软件并且该软件无法构建。现在,您不得不修改以前通过正式测试的代码,或者进行邪恶的黑客攻击,例如回滚计算机的时钟。
西蒙B

6
将其设置为@ Deprecated_RemoveMe_2018_06_01,然后在2018-06-01删除注释本身。瞧,您所有使用注释的代码将不再编译!(因为找不到注释)
immibis

65
未来几年,新雇用的开发人员会问:为什么将构建服务器上的时间设置为2016年1月?在那里待了10年的家伙会告诉他,这是必要的,或者构建在随机的地方中断。叹。
威尔伯特'18

Answers:


62

当它确实禁止编译时,我认为这不是有用的功能。当在2018年1月6日大部分代码无法编译前一天编译的代码时,您的团队将迅速再次删除该注释,无论是否清理代码。

但是,您可以在代码中添加一些自定义注释,例如

@Deprecated_after_2018_07_31

并构建一个小的工具来扫描这些注释。(如果您不想利用反射,则只需在grep中使用一个简单的内衬即可。)在Java以外的其他语言中,可以使用适合“抓紧”的标准注释或预处理程序定义。

然后在特定日期之前或之后不久运行该工具,如果仍然找到该注释,请提醒团队紧急清理这些代码部分。


9
@Bergi:嘿,这只是一个例子,OP可以使用版本或日期标签,无论他们希望控制什么。
布朗

4
与仅使用常规的弃用功能并查看特定日期的编译器输出相比,这样做没有任何优势。似乎很浪费开发人员时间来重新实现几乎已经存在的功能。即使您已经有很多警告(值得您花时间清理),也可以肯定地让编译器将所有警告吐到文本文件中,然后进行grep或搜索。
jpmc26,2016年

4
@jpaugh我建议不要将时间(注:时间=金钱)花在构建新工具上,以执行可以使用现有工具有效完成的任务,无论这些工具可能是什么。
jpmc26,2016年

3
@ jpmc26:我看到两个(小的)优点:没有收到大量的弃用警告(这可能会导致忽略更重要的警告),并且有可能为不同的代码段引入不同的弃用标准(日期,版本等)。此外,OP明确要求将弃用与日期结合起来。
布朗

7
如果您只在示例中使用YYYY-MM-DD命令作为日期,则我会+1,因为我只见过不明确的??-??-YYYY命令会引起痛苦和误解。
mtraceur

284

这将构成一个称为定时炸弹的功能不要创建时间炸弹。

如果代码的寿命超过一定年龄,无论代码的结构和文档编写得如何好,它都会变成一个难以理解的近似神话的黑匣子。未来任何人所需要的最后一件事是另一种奇怪的故障模式,该模式会在最坏的时间完全使他们感到意外,并且没有明显的补救措施。绝对没有借口故意制造这样的问题。

以这种方式看待它:如果您对代码库的组织和认识足够到足以关心过时并继续进行下去,那么您就不需要代码使用某种机制来提醒您。如果不是这样,很有可能您还没有掌握代码库的其他方面的最新信息,并且可能无法及时正确地响应警报。换句话说,定时炸弹对​​任何人都无济于事。拒绝吧!


74
+1这咬你。几个月后。就在星期五,就像您尝试进行紧急部署一样。这是一个漫长的周末,所以所有对此一无所知的人都不在办公室。不要这样
罗杰·利普斯科姆

3
同意。过时是组织问题,而不是编码问题。我仍然可以插入Apple] [并编写BASIC,没有什么阻止我。但是,我吗?

19
正确的解决方案是按照“有条理和有意识”的指示进行操作,是在错误跟踪系统中引发一个错误,并说“删除<诸如此类>”,并在注释中给出建议的时间范围。然后在6个月内进行bug审查,以找出下一个版本应修复的bug,如果迫在眉睫,那么您就说“是时候这样做了”。这是基本的项目管理。
Lightness Races in Orbit

1
实际上,如果您的发布时间表可以充分预测,那么现在就里程碑。
Lightness Races in Orbit

13
请参阅isanae的评论,以寻求替代方法:“ 如果您希望人们停止使用不赞成使用的功能,请发布没有这些功能的版本。这将引起他们的注意,并且如果他们还无法做到,则可以随时回滚。
Stevoisiak

70

在C#中,您可以通过ObsoleteAttribute以下方式使用:

  • 在版本1中,您将发布功能。方法,类,随便。
  • 在版本2中,您将提供更好的功能以替换原始功能。您在功能上放置了“过时”属性,将其设置为“警告”,并显示一条消息,指出“此功能已被弃用。请改用更好的功能。在此库的版本3中,该特性将在日期,使用此功能将是一个错误。” 现在,该功能的用户仍然可以使用它,但是有时间更新他们的代码以使用新功能。
  • 在版本3中,您将属性更新为错误而不是警告,并更新消息,说明“此功能已弃用。请改用更好的功能。在此库的版本4中,它将在此类库中发布日期,此功能将抛出。” 未能注意您先前的警告的用户仍然会收到一条有用的消息,告诉他们如何解决问题,因此必须解决此问题,因为现在他们的代码无法编译。
  • 在版本4中,您更改了功能,使其引发一些致命异常,并更改消息以说该功能将在下一版本中完全删除。
  • 在版本5中,您会完全删除该功能,如果用户抱怨,嘿,您给了他们三个发布警告的周期,如果他们对版本2持强烈的态度,他们总是可以继续使用版本2。

此处的想法是使受影响的用户尽可能轻松地进行重大更改,并确保他们可以继续在至少一个版本的库中使用该功能。


2
我相信这是正确的方法,但我会改变一件事……那就是将“如果用户抱怨”改为“当用户抱怨时”
Liath

10
在转换为错误的同时移除主体似乎很奇怪。不推荐使用错误版本的优点之一是,它允许针对早期版本构建的代码继续运行,同时防止新编译的代码使用它。因此,您将保持与ABI兼容,同时故意破坏API兼容性。当然,理想世界中您将拥有semver样式版本控制等,这样您就永远无法使用先前编译的应用程序运行新代码,但是许多项目永远都无法达到理想的状态。
凯文·卡斯卡特

@KevinCathcart:很好。也许还有一个阶段是必要的!我将更新文本。
埃里克·利珀特

1
实际上,如果您正在执行SemVer,则可以直接从XY0版本中弃用的版本(任何弃用版本必须是次要版本)转到X + 1.0.0中完全删除。我想推迟一点(到X + 2.0.0?)是很好的,但是这个五步过程可能使百合花ly了。“警告”之后的下一步应该是“致命错误”(因为已弃用的功能已替换为产生错误的代码)。由于libfoo.so.2并且libfoo.so.3可以彼此共存,您的下游可以继续使用旧库,直到它们被切换为止。
蒙迪·哈德

@MontyHarder:好的。事件的确切顺序可以由开发人员及其用户社区的需求来确定。不过,更大的一点是,应该有一个考虑周全的政策来处理此类版本控制问题,并明确告知利益相关者。
埃里克·利珀特

24

您误解了“不赞成使用”的含义。已弃用是指:

可以使用,但被认为已过时且最好避免使用,通常是因为已被取代。

牛津字典

根据定义,不推荐使用的功能仍会编译。

您希望在特定日期删除该功能。没关系。您这样做的方法是在该日期将其删除

在此之前,将其标记为已弃用,已过时,或标记为您的编程语言所称的名称。在消息中,包括它将被删除的日期和替换它的东西。这将生成警告,指示其他开发人员应避免新用法,并应尽可能替换旧用法。那些开发人员要么遵守它,要么忽略它,并且当它被删除时,有人将不得不处理它的后果。(根据情况,可能是您自己,也可能是使用它的开发人员。)


对下降投票者不同意的东西感兴趣。
jpmc26,2016年

2
+1。如果您使用JIRA进行敏捷开发,请创建一个任务并将其放入将来的sprint中。适应您遵循的任何其他项目管理工具和方法。最好用非技术方法解决。
马修(Matthew)阅读了

1
并确保Class / Lib在Xx版本中保留为已贬值和/或删除的文件
Phil M,

12

不要忘记,您需要保留构建和调试旧版本代码的能力,以支持已经发布的软件版本。在特定日期之后破坏构建意味着您还有冒险防止自己将来进行合法的维护和支持工作。

而且,在编译之前将机器的时钟调回一两年似乎是一种微不足道的解决方法。

请记住,“已弃用”是警告,将来会消失。当您要强制阻止人们使用该API时,只需删除相关的代码。如果某种机制使其无法使用,则将代码留在代码库中毫无意义。删除代码将为您提供所需的编译时检查,并且没有简单的解决方法。

编辑:我看到您在您的问题中引用“旧的未使用的代码”。如果代码确实未使用,则不建议弃用它。只需删除它。


到目前为止,最好的答案可能是强调删除代码是尽快解决问题的最佳方法。
克林特

6

我以前从未见过这样的功能-在特定日期之后开始生效的注释。

@Deprecated就足够了,但是。在CI中捕获警告,并使其拒绝接受构建(如果存在)。这将责任从编译器转移到了构建管道,但是具有的优点是,您可以(通过添加其他步骤)(半)轻松地更改构建管道。

请注意,此答案不能完全解决您的问题(例如,尽管有警告,开发人员计算机上的本地构建仍将成功执行),并假定您已设置并正在运行CI管道。


4

您正在寻找日历或待办事项列表

另一种选择是使用自定义编译器警告或编译器消息,前提是您设法在代码库中几乎没有警告。如果警告太多,则需要花费额外的精力(大约15分钟?),并且必须在构建报告中获取编译器警告,您的持续集成会在每个构建中提供该警告。

提醒人们需要修复代码是好的,也是必要的。有时这些提醒确实有严格的现实期限,因此也可能需要将它们放在计时器上。

目的是不断提醒人们问题存在,并且需要在给定的时间范围内解决-一种仅在特定时间中断构建的功能不仅不会这样做,而且该功能本身就是需要解决的问题在给定的时间范围内固定。


1
同意。但是,如果存在问题并且需要在给定的时间范围内解决问题,那么它就必须在适当的时间成为某人的头等大事。我记得在一次会议上,我的经理给了我“最高优先级”,然后又说:“ 是您的第一要务”(有所不同)。我的主管说:“他不能有两个第一要务!” 经理大吃一惊。我想他以为...我可以。规划要在“正确的时间”修复的问题正计划用光时间。

3

思考此问题的一种方法是您所指的时间/日期?计算机不知道这些概念是什么:它们必须以某种方式进行编程。这是很常见的代表在“自纪元秒”的UNIX格式的时间,这是常见的通过OS调用养活一个特定的值到程序。但是,无论这种用法有多么普遍,重要的是要记住,这不是“实际”时间:它只是逻辑表示。

正如其他人指出的那样,如果您使用此机制创建了“最后期限”,则在不同的时间投入并打破该“最后期限”是微不足道的。对于更复杂的机制(例如询问NTP服务器)(甚至通过“安全”连接,因为我们可以替换自己的证书,证书颁发机构甚至修补加密库),也是如此。乍一看,这些人似乎在为您的机制工作上有过错,但可能是出于充分的理由自动完成了。例如,拥有可复制的build是一个好主意,而有助于此的工具可能会自动重置/拦截此类不确定的系统调用。libfaketime正是这样做的,将所有文件的时间戳设置为1970-01-01 00:00:01,Qemu的记录/重播功能会伪造所有硬件交互作用,等等。

这类似于古德哈特定律:如果您使程序的行为取决于逻辑时间,那么逻辑时间就不再是“实际”时间的良好度量。换句话说,人们通常不会弄乱系统时钟,但是如果您给他们理由,他们会的。

时间还有其他逻辑表示形式:时间之一是软件的版本(您的应用程序或某些依赖项)。这比UNIX时间更适合作为“最后期限”表示,因为它更特定于您关心的事情(更改功能集/ API),因此不太可能践踏正交问题(例如,将UNIX时间摆弄到在截止日期前工作可能会破坏日志文件,cron作业,缓存等)。

正如其他人所说,如果您控制库并希望“推送”此更改,则可以推送一个不推荐使用该功能的新版本(引起警告,以帮助消费者找到并更新其用法),然后另一个新版本删除该功能。完全功能。如果愿意,您可以立即将它们彼此立即发布,因为(再次)版本只是时间的逻辑表示,因此它们不必与“实际”时间相关。语义版本控制可能会有所帮助。

替代模型是“拉”更改。这就像您的“计划B”:将测试添加到使用中的应用程序,该应用程序检查此依赖项的版本至少为新值。与往常一样,红色/绿色/重构可通过代码库传播此更改。如果功能不是“不好”或“错误”,而只是“不适合此用例”,则这可能更合适。

“拉”方法的一个重要问题是,依赖项版本是否算作“(功能)的单位” ,因此值得进行测试;还是仅仅是“私有”实现细节,都应该作为实际的(功能)单元测试的一部分来执行。我会说:如果依赖项版本之间的区别确实算作您应用程序的功能,则进行测试(例如,检查Python版本> = 3.x)。如果没有,那就不要添加测试(因为它将是脆弱的,缺乏信息性的并且过于严格);如果您控制该库,则走“推”路线。如果您不控制库,则只需使用提供的任何版本即可:如果您的测试通过,则不值得限制自己;如果他们不及格,那就是您的“截止日期”!

还有一种方法,如果您想阻止对依赖项功能的某些使用(例如,调用某些无法与其余代码完美配合的功能),尤其是在您无法控制依赖项的情况下:请禁止使用编码标准/不鼓励使用这些功能,并将检查添加到您的短毛猫中。

这些都将适用于不同的情况。


1

您可以在程序包或库级别进行管理。您可以控制程序包并控制其可见性。您可以随意撤消可见性。我已经在大型公司内部看到了这一点,并且仅在尊重软件包所有权的文化中才有意义,即使软件包是开源的或免费使用的。

这总是很混乱,因为客户团队根本不想更改任何东西,因此当您与特定客户一起商定迁移的最后期限时,您通常只需要进行几轮白名单,可能会向他们提供支持。


我喜欢支撑部分。如果促进变革的人们提供支持,那么所有变革都会更加顺利,愉快地进行,并且可能会减少困难。这部分是心理上的影响:良好的支持是合作的;所有相关人员都必须认真对待。相比之下,在不涉及相关人员的情况下自上而下进行的更改使他们感到被忽略和不合作。(尽管必须将友善与坚持不懈的努力相结合,以使变革得以通过。)
Peter A. Schneider

1

一个要求是在构建中引入时间概念。在使用类似C的预处理器1的 C,C ++或其他语言/构建系统中,可以通过在构建时为预处理器定义引入时间戳CPPFLAGS=-DTIMESTAMP()=$(date '+%s')。这很可能会在makefile中发生。

在代码中,如果时间到了,将比较该令牌并导致错误。请注意,使用函数宏会遇到某些人未定义的情况TIMESTAMP

#if TIMESTAMP() == 0 || TIMESTAMP() > 1520616626
#   error "The time for this feature has run out, sorry"
#endif

或者,只要时间到了,就可以简单地“定义”相关代码。如果没有人使用它,它将允许该程序编译。假设我们有一个定义api的标头“ api.h”,并且不允许old()在一定时间后调用:

//...
void new1();
void new2();
#if TIMESTAMP() < 1520616626
   void old();
#endif
//...

类似的构造可能会old()从某些源文件中消除的功能。

当然,这并非万无一失。TIMESTAMP如果在其他地方提到的星期五晚上的紧急建筑,则可以简单地定义一个旧的。但我认为那是比较有利的。

显然,这仅在重新编译时有效-之后,过时的代码在库中根本不再存在。但是,这不会阻止客户端代码链接到过时的二进制文件。


1 C#仅支持对预处理器符号的简单定义,而没有数值,这使该策略不可行。


还值得注意的是,此预处理器定义将强制TIMESTAMP在所有构建上重新编译使用的所有代码。它还将使像这样的工具失效ccache。这意味着增量构建的典型编译时间将显着增加,具体取决于此方式弃用的功能影响多少代码库。
mindriot

@mindriot这是一个有趣的方面。我想这对每种在代码中引入时间概念的方法都是正确的-OP在“特定日期过去之后”明确表示。但是,人们可以在构建系统中处理时间方面的问题,而不必理会代码,这是事实。但是OP明确要求“在代码中添加一些内容”。我的解决方案具有独立于任何特定构建方法的优势。它可以被骗,但是您必须以一种或另一种方式来迎合它。
彼得·施耐德

你是绝对正确的。实际上,这里的解决方案是唯一可以为OP的实际问题提供实用解决方案的解决方案。不过,我发现指出不利之处很重要。由OP权衡利弊。我认为,甚至可以通过将TIMESTAMP值除以例如86400来获得每日粒度,从而减少编译次数,从而获得一个健康的中间立场。
mindriot

0

在Visual Studio中,可以设置在特定日期后引发错误的预生成脚本。这将阻止编译。这是一个在2018年3月12日或之后引发错误的脚本(取自此处):

@ECHO OFF

SET CutOffDate=2018-03-12

REM These indexes assume %DATE% is in format:
REM   Abr MM/DD/YYYY - ex. Sun 01/25/2015
SET TodayYear=%DATE:~10,4%
SET TodayMonth=%DATE:~4,2%
SET TodayDay=%DATE:~7,2%

REM Construct today's date to be in the same format as the CutOffDate.
REM Since the format is a comparable string, it will evaluate date orders.
IF %TodayYear%-%TodayMonth%-%TodayDay% GTR %CutOffDate% (
    ECHO Today is after the cut-off date.
    REM throw an error to prevent compilation
    EXIT /B 2
) ELSE (
    ECHO Today is on or before the cut-off date.
)

使用此脚本之前,请确保阅读本页上的其他答案。


-1

我了解您要执行的操作的目标。但是正如其他人提到的那样,构建系统/编译器可能不是执行此操作的正确位置。我建议执行此策略的更自然的层是SCM或环境变量。

如果您要进行后者,则基本上添加一个功能标记,以标记预弃用运行。每次构造不推荐使用的类或调用不推荐使用的方法时,请检查功能标记。只需定义一个静态函数assertPreDeprecated()并将其添加到每个不赞成使用的代码路径即可。如果已设置,请忽略断言调用。如果没有抛出异常。一旦日期过去,请在运行时环境中取消设置功能标志。对代码的任何不推荐使用的过时调用将显示在运行时日志中。

对于基于SCM的解决方案,我假设您使用的是git和git-flow。(如果不是,则该逻辑很容易适应其他VCS)。创建一个新分支postDeprecated。在该分支中,删除所有不推荐使用的代码,然后开始删除所有引用,直到编译为止。任何正常的更改都会继续对master分支进行。继续将所有不推荐使用的相关代码更改合并master回去,postDeprecated以最大程度地减少集成方面的挑战。

弃用日期结束后,preDeprecated从中创建一个新分支master。然后合并postDeprecatedmaster。假设您的发行版已脱离master分支机构,那么您现在应该在该日期之后使用已弃用的分支机构。如果发生紧急情况,或者您无法及时交付结果,则始终可以回滚到preDeprecated,并在该分支上进行任何需要的更改。


7
这种方法听起来像是一场后勤噩梦。如果您开始维护几乎重复的并行分支,则最终将花费所有时间。总浪费。
Lightness Races in Orbit

6
我不能同意维护并行分支,在您实际上要从产品中删除不赞成使用的功能之前,仅使用其中一个就删除许多版本的过时功能在任何方面都是“行业标准”。目的是什么?是的,VCS可自动执行一些合并,但合并与开发商的眼球,以解决冲突的逻辑来执行(和当你甚至不能被词法解决冲突会发生什么?)。每天都有一个任意合并的构建系统只是……毫无意义。是时候删除该功能了。
Lightness Races in Orbit

3
因为有充分的理由维护发行分支(向后移植重要的补丁程序)。没有充分的理由维持“将来我可能会做的事情”分支。这是开销,没有好处。就那么简单。
Lightness Races in Orbit

4
我在哪里买的?从字面上看,它是过时的定义。您不赞成将来可能要删除的内容。因此,没有区别,没有目标的移动,当然也没有为了“赢得争论”而编造的东西。在我要工作的任何组织中,这不是“ SCM分支的教科书用例”!我想我们将不得不同意不同意。晚上好!
Lightness Races in Orbit

2
@lightness-有很多原因导致正式弃用代码不只是“我们在处理代码时可能会做的事情”。也许不推荐使用的代码使用的是一个库,正式支持在该库中被删除。也许不建议使用的代码是IP,其许可证在固定日期后到期。可能在给定日期之后,出现了新规定,并且该代码不符合要求。如果您住在象牙塔中,那么您的解决方案就很好。但是现实世界中的组织始终会处理这类情况。软件需要满足业务需求,而不是相反。
user79126
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.