与YAGNI相比,删除硬编码的值和防御性设计


10

首先介绍一下背景。我正在从“年龄->费率”中查找代码。有7个年龄段,因此查找表是3列(From | To | Rate)和7行。这些值很少更改-它们是已保持3年不变的立法费率(第一列和第三列)。我认为存储该表而不对其进行硬编码的最简单方法是在数据库中的全局配置表中,因为包含CSV的单个文本值(因此“ 65,69,0.05,70,74,0.06” 65-69和70-74层)。比较容易解析然后使用。

然后,我意识到要实现这一点,我将必须创建一个新表,一个要包装它的存储库,用于仓库的数据层测试,围绕将CSV展开到表中的代码的单元测试以及围绕查找本身的测试。所有这些工作的唯一好处是避免对查找表进行硬编码。

与用户交谈时(当前他们直接使用查找表-通过查看纸质副本)时,人们几乎认为“费率永远不会改变”。显然,这实际上是不正确的-费率是三年前才创建的,过去,“永不改变”的习惯已经改变了-因此,对于我来说,应该进行防御性编程,我绝对不应该在其中存储查找表应用程序。

除了我想YAGNI的时候。我要实现的功能未指定费率会改变。如果费率确实发生变化,它们仍将变化得很少,以至于甚至不需要考虑维护,并且该功能实际上并不十分重要,以至于如果在费率变化和更新的应用程序之间存在延迟,任何事情都将受到影响。

我几乎已经决定,如果我对查询进行硬编码,就不会失去任何价值,而且我不太担心我对这一特定功能的处理方式。我的问题是,作为专业人士,我是否可以合理地证明这一决定?硬编码值是不好的设计,但是麻烦从应用程序中删除值似乎违反了YAGNI原则。

编辑为了澄清这个问题,我不关心实际的实现。我担心自己可能会做出快速,糟糕的事情并通过说出YAGNI来证明其合理性,或者我会采取一种防御性更高的努力方法,即使在最好的情况下,最终收益也很小。作为一名专业程序员,我实施我知道有缺陷的设计的决定是否仅仅取决于成本/收益分析?

编辑虽然所有的答案都非常有趣,因为我认为这归结为个人的设计选择,我认为最好的答案是@科尔宾的@EZ哈特为他们带来了的事情,我没有在这个问题考虑:

  • 错误的二分法是通过将其移动到数据库来“正确删除硬编码的值”,而不是使用硬编码来“有效地应用YAGNI”。第三种选择是将查找表放入应用程序配置中,这不会产生正确方法的开销,而且没有YAGNI的效率。我们通常不限于任何一个或一个决定,然后可以归结为成本/收益决定。
  • 代码生成可以减少将硬编码值移动到数据库的开销,并且还可以消除我过度设计的决定,即将CSV处理到表中的决定。如果查询方法的基本要求发生变化,则可能还会对生成的代码造成长期维护问题。这一切都只会影响成本/收益分析,而且如果我拥有自动化功能,那么我什至都不会考虑对这种东西进行硬编码。

我将@Corbin的答案标记为正确,因为它改变了我对开发成本的假设,并且我可能会在不久的将来向我的军械库中添加一些代码生成工具。


别忘了,如果费率发生变化,而您所拥有的全部都是硬编码值,那么当费率发生变化时,您可能会弄乱历史记录的计算(无论您的客户告诉您什么,它们都会发生)。
安迪

Answers:


6

您发现开发过程中存在缺陷。当做一件正确的事(创建表,回购,回购测试,扁平化测试...)很痛苦时,开发人员将找到解决之道。这通常涉及做错事。在这种情况下,您很容易将应用程序数据视为应用程序逻辑。不要这样 而是在您的开发过程中添加有用的自动化。我们使用CodeSmith生成没人想编写的无聊的样板代码。创建表后,运行CodeSmith,它会生成DAO,DTO,并为每个测试存根单元测试。

根据您使用的技术,您应该有类似的选择。许多ORM工具将根据现有架构生成模型。Rails迁移的方向相反-模型表。动态语言的元编程在消除样板代码方面特别强大。如果您具有复杂的多层应用程序,则必须付出更多的努力才能生成所需的一切,但这是值得的。不要让这种感觉“哇,这是脖子上的疼痛”阻止您做正确的事。

哦,不要以需要额外处理(CSV)的格式存储数据。这只会增加需要您注意和测试的额外步骤。


我不会说Rails迁移从模型创建表... Rails迁移描述了对表的更改。执行迁移会更改数据库。与表结构匹配的模型属性在运行时创建。
凯文·克莱恩

这让我很感兴趣,我没有考虑过将最初工作的一部分自动化到那个水平。而拥有这种自动化将意味着我可以设置一个完整的表,而不是使用CSV来节省时间。我担心的是生成代码的长期维护。通过预先生成它,我早就假定进行查询的方法永远不会改变。我想如果有这种可能,我应该将其作为成本/收益的一部分考虑在内,从而降低样板成本。+1
丽贝卡·斯科特

问题是,“当客户说值不变时,我应该对值进行硬编码吗?” 而您的回答是“不,实际上您应该对问题进行ORM”。我不同意。有一些更简单的选项可以使OP避免进行硬编码。对体系结构进行大量添加以支持显式指定为不必要的事物是不道德的。不幸的是,许多开发人员倾向于这样做。我必须说,我不同意您的建议,即“不要让这种感觉'哇,这是脖子上的痛苦',会阻止您做正确的事”,这是100%的。那些感觉很重要!
user1172763

10

要取消和扩展@ThorbjørnRavn Andersen的答案:将计算/查找放在一个位置是一个好的开始。

您对防守与YAGNI的思考过程是一个常见问题。在这种情况下,我建议您再注意两件事。

首先-如何提出用户要求?他们是否指定了费率的可编辑性?如果不是,增加的复杂性是否属于您可以负担的费用?(或者,如果您在工作人员上,您可以将时间花在其他工作上?)如果是这样,那么一定要继续进行并交付合理的要求。

其次,也许更重要的是,面对法律的变更,仅凭可编辑性就不可能满足实际要求。如果费率确实发生变化,当费率发生变化时,很可能会有过渡日期,并且必须进行一些前后处理。此外,如果该接口必须随后进行任何类型的回溯索赔处理,则可能必须根据条目的生效日期而不是实际日期来查找正确的汇率。

简而言之,我要指出的是,实际的可编辑需求可能非常复杂,因此,除非或直到充实它,否则简单性可能会更好。

祝好运


1
+1为第二点。我不会怀疑立法机构将来可能会做些什么。
David Thornley

在库函数中执行此操作。可以只有一个用户。何时以及如果需求发生变化,您将有一个变化的地方。(您可能需要添加一个函数来查询特定日期的值。)
BillThor 2011年

最好,最完整的答案,+ 1
Andy

7

使实际的查询成为库函数。然后,您可以使用源存储库中的该查找查看所有代码,因此,您可以知道在更改费率时需要升级哪些程序。


查找将仅在单个位置(PensionRateLookup可能类似于类)中实现,然后在全球范围内使用。无论是将其存储在应用程序外部还是对其进行硬编码,我都将这样做,这样仅需PensionRateLookup维护类的实现即可。我的问题是我如何使用YAGNI得出结论,硬编码查找表是可以接受的。
丽贝卡·斯科特

谢谢您的回答。将查找实现放在一个地方绝对是好的设计。
丽贝卡·斯科特

仅当您在费率更改时可以运送新的二进制文件时,YAGNI才有效。如果不能,但是可以更改配置,则将其配置为启动时读取的配置值,并以当前文本表示形式为默认值。

我通常每周都会发布一个新版本,因此实际上更新查询不是问题。实际上,将其放入客户端配置会更糟,因为我无法使用新的二进制文件更改客户端配置。我看到的另一种选择是将其放入数据库中,这意味着很多工作。
丽贝卡·斯科特

那是什么问题呢?当价格变化时,您更新代码并运送给所有客户?

2

让我看看我是否正确回答了您的问题。您有2种选择来实现功能:要么对值进行硬编码并且功能易于实现(但是您不喜欢硬编码部分),要么就付出很大的努力来“重做”许多已完成的事情这样您就可以以干净的方式开发功能。那是对的吗?

我想到的第一件事是“了解良好做法的最重要的事情就是知道什么时候没有这些就会变得更好”。

在这种情况下,付出的努力非常高,因此您可以以一种干净的方式进行操作,这种变化产生附带影响的机会很大,而您获得的回报却很小(正如您所解释的那样,似乎不会更改)。

我将使用硬编码方法(但要使其在将来变得灵活),并在将来更改此速率的情况下,利用此机会来重构代码的所有不良设计部分。因此,可以正确估算时间和成本,并且更改硬编码值的成本将降至最低。

这就是我的方法:)


谢谢@奥斯卡。技术部分是def。正确。我同意通过仅在需要时进行重构来解决该问题。因此,您是说作为专业人员,我们可以并且应该仅基于成本/收益来选择我们的设计原则吗?说得通。
丽贝卡·斯科特

@本斯科特:不客气:)。是的,在我看来,创建/编录设计原则并不是因为它们看起来很漂亮,而是因为它们带来了代码“简洁”,灵活性,健壮性等好处。但是总会有两个问题:1-我是否需要?2-我的限制(时间,技术等)是否允许我实施?这通常是导致我选择设计原则的原因。过度设计也很糟糕;)ps:如果您认为这是一个有趣的答案,请投票赞成,以便其他人也更有可能阅读。谢谢!:)
JSBach

赞成;-)
丽贝卡·斯科特

2

这是一个直到改变都不会改变的项目。不可避免的是它将改变,但是时间可能还差得远。

您的辩解是正确的。目前,客户并未要求能够轻松更改这些费率。因此,YAGNI。

但是,您不需要的是访问您的费率并解释分散在整个代码库中的结果的代码。好的OO设计将使您将费率的内部表示封装在一个类中,并且仅公开使用数据所必需的一种或两种方法。

您将需要使用这种封装来防止复制和粘贴错误,或者当您需要更改内部表示形式时,必须对使用费率的所有代码执行重构。当您采取最初的预防措施时,更复杂的方法可以是简单的交换并替换为功能更强大的版本。

此外,向客户指出当前设计的局限性。告诉他们,为了维护计划,您对值进行了硬编码-这需要更改编码才能更新它们。这样,当他们意识到由于新法规而导致的未决费率变化时,他们可以选择简单地更新查找表或在那时执行更复杂的更改。但是把这个决定放在他们的腿上。


感谢@berin,我没有在问题中提及SRP,但它已在计划中。将问题的所有权交还给客户的好点。
丽贝卡·斯科特

在我看来,将来发生问题时要怪罪。
安迪

@安迪,这是指责的哪一部分?向客户提出设计局限性使他们有机会要么现在就优先安排复杂的工作,然后取消其他工作,推迟截止日期,要么立即接受有限的设计,因为工作台上的其他事情对他们而言更为重要。您使客户可以选择他们的产品。使您的客户意识到您想要做出的选择的风险/回报,可以使项目运行更加顺利。
Berin Loritsch

@BerinLoritsch但是,这个特殊的设计决定类似于使用不合格的零件,说“我可以用水管工腻子塞住这个漏水的管道,这是您最便宜的选择!” 费率将改变。这是必然的,对于专业人员建立不允许的系统是不负责任的。在项目成本的宏伟计划中,节省的费用可能微不足道。将数据放在表中;获取查询数据的代码略有不同,逻辑上使用哪种速率的逻辑是相同的。唯一的其他决定是在……之后,如何处理历史数据
Andy

费率更改,通常只需将费率复制到相关记录即可解决。此时不需要管理屏幕,然后系统可以在费率更改时进行处理,并且通过简单的脚本即可更改费率。所有这些都不应该超过几个小时,但是它将防止明年腻子失效时地下室被淹。
安迪

2

分割差异并将速率数据放入配置设置中。您可以使用已经拥有的CSV格式,避免了不必要的数据库开销,并且如果有必要进行更改,那么客户应该能够进行更改而无需重新编译/重新安装并且不会弄乱在数据库中-他们只能编辑配置文件。

通常,当您在两个极端之间做出选择(违反YAGNI与动态数据进行硬编码)时,最佳答案是在中间。当心错误的二分法。

假设所有配置都在文件中;如果它像注册表一样困难,您可能应该忽略此建议:)


我确实考虑过将其放在配置设置中,但将其放在一旁是因为用户在编辑CSV字符串方面不是很熟练,所以我将是为N个用户更新配置的人(因为我在以及组织),在工作方面,进行前期工作将其放入我可以从一个地方进行管理的数据库中会更加容易。我想如果这不是考虑因素(如果用户能够管理自己的个人配置),那么我就不会遇到这个问题。很好,谢谢。
丽贝卡·斯科特

如果将其与集中逻辑的其他建议结合起来,那么这是一个很好的答案。真正使查找更坚硬,而不是采用允许通过配置更改查找的方式纯属邪恶。每当其他开发人员遇到它时,他们都会讨厌您。即使是一个人的开发商店,也应该考虑如果他们被公交车撞到了会发生什么,并且应该使下一个开发人员在没有完整软件发布的情况下轻松更改值。只有当您讨厌客户并讨厌所有其他开发人员时,才应该对它进行硬核化。所以基本上永远不会。
simbo1905

2

我认为存储该表而不对其进行硬编码的最简单方法是在数据库中的全局配置表中,因为单个文本值包含CSV(所以“ 65,69,0.05,70,74,0.06” 65-69和70-74层将被存储。

刚刚创建了一个数据库表。(您想将整个表存储在一个表中,您疯了吗?)

该表需要2个字段,而不是3。年龄和比率。下一行包含较高的值!您正在不规范化甚至不知道!

这是Sql,用于获取67岁年龄段的人的价格。

Select * from RateTable where Age in (Select max(age) from RateTable where age <=67) 

不要打扰维护屏幕,因为它不在范围内。如果他们稍后要求,则发出更改请求并执行。

编辑:正如其他人所说,保持代码集中在整个Rate结构变化的情况下,将速率集中。


嗨@Morons,谢谢您的回答。该表实际上需要3列。这是每一个速度范围内年龄,最小年龄最大年龄(65至69岁的年轻人都在5%)。而且我仅出于有限的目的存储少量数据,那么对结构进行假设并选择CSV而不是整个专用表又有什么问题呢?我可能会添加行定界符,然后按行然后按列进行拆分,然后将列拉入必填字段。总是要阅读比写更多的东西,因此我不必担心完整的表。
丽贝卡·斯科特

下一行包含范围的上限值...这是重复数据。说<69和> 7是同一回事。拥有两个值的唯一原因是范围可以具有“孔”。...我说要使用表格,因为这样比较容易(并且设计更好)。我不明白为什么您认为将csv存储在表中会节省您的时间或精力,您仍然需要进行DB调用才能获取该字符串,然后必须对其进行解析。正如我在上面显示的那样,您可以通过一个数据库调用来获得费率。
白痴” 2011年

是啊 我一直在使表格本身与原始资料相似...但我想不到年龄是您回答的上限,因为我有悲伤,而我却给了您悲伤。
丽贝卡·斯科特

1
“你甚至在不知情的情况下令人沮丧!” -起初我以为这是一个错字,并且您的意思是“非正规化”,但是我想得越多,您似乎就越可能是对的:)
EZ Hart

@ez哈特哈哈true。
丽贝卡·斯科特

1

我同意给出的大多数答案。我还要补充一点,与应用程序其余部分的一致性很重要。如果这是唯一的代码的地方已经硬编码值恐怕也会惊讶的维护者。如果它是一个大型,稳定的代码库,则尤其如此。如果这是您维护的小程序,那么决定就不那么重要了。

我读过一个著名的敏捷/ OOP专家(例如Dave Thomas或Kent Beck或其他人),他们记忆犹新,他们说他们的代码重复经验法则是两次,而且只有两次:第一次重构时,请勿重构您一生只能写两次。但是第三次

因为您在质疑YAGNI,但这并不能完全解决问题,但我认为这表明了敏捷规则的一般灵活性。敏捷的目的是适应形势并向前发展。


感谢@Dave,这很有帮助。从一开始,我就是唯一的维护者,但是它是一个庞大,相对稳定的代码库(成千上万的文件,超过100个表等),我仍然不断感到惊讶(感到沮丧)。一致性绝对是我未来的目标之一。
丽贝卡·斯科特

0

函数中的硬代码。

  • 当价值改变时,您可以再次向客户收费
  • 值更改时,表格格式必须更改,做CSV会浪费时间
  • 易于实施,在当前合同中被预算的机会更高
  • 可以轻松找到何时需要更新

让我安装这个不合标准的值,这样,当它肯定在一年内失效时,我就会得到回头客。听起来很阴暗,因为是。
安迪
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.