是否可以在我的代码中硬编码复杂的数学逻辑
本文关键字:复杂 编码 我的 代码 是否 | 更新日期: 2023-09-27 18:30:53
是否有一种普遍接受的最佳方法来编码复杂的数学?例如:
double someNumber = .123 + .456 * Math.Pow(Math.E, .789 * Math.Pow((homeIndex + .22), .012));
这是可以硬编码数字的点吗?还是每个数字都应该有一个与之关联的常量?或者甚至还有另一种方法,例如将计算存储在配置中并以某种方式调用它们?
会有很多这样的代码,我正在努力保持它的可维护性。
注意:上面显示的示例只是一行。会有几十或几百行这样的代码。不仅数字可以改变,公式也可以改变。
通常,有两种常量 - 一种对实现有意义,另一种对业务逻辑有意义。
对第一种常量进行硬编码是可以的:它们是理解你的算法的私有的。例如,如果使用三元搜索并且需要将间隔分为三个部分,则按硬编码3
划分是正确的方法。
另一方面,具有程序代码之外含义的常量不应该是硬编码的:给它们明确的名称可以让在你离开公司后维护你的代码的人进行正确的修改,而不必从头开始重写东西或通过电子邮件向你寻求帮助。
"可以吗"?确定。据我所知,没有准军事警察部队围捕那些违背编程唯一真正信仰的人。(然而。
明智吗?
好吧,有各种各样的方法可以决定这一点 - 性能,可扩展性,可扩展性,可维护性等。
在可维护性规模上,这是纯粹的邪恶。它使可扩展性变得非常困难;性能和可伸缩性可能不是一个大问题。
如果您留下了一个包含与上述类似的行的负载的方法,那么您的继任者将没有机会维护代码。他建议重写是对的。
如果你像
public float calculateTax(person)
float taxFreeAmount = calcTaxFreeAmount(person)
float taxableAmount = calcTaxableAmount(person, taxFreeAmount)
float taxAmount = calcTaxAmount(person, taxableAmount)
return taxAmount
end
每个内部方法都有几行长,但你在那里留下了一些硬编码值 - 嗯,不出色,但并不可怕。
但是,如果其中一些硬编码值可能会随时间而变化(如税率),则将它们保留为硬编码值是不行的。太可怕了。
我能给出的最好的建议是:
- 花一个下午与Resharper,并使用其自动重构工具。
- 假设从你那里捡到这个的人是一个挥舞着斧头的疯子,他知道你住在哪里。
我通常会问自己,在编写代码六个月后,我是否可以在凌晨 3 点维护和修复代码。它对我很有帮助。看看你的公式,我不确定我能不能。
很久以前,我在保险业工作。我的一些同事的任务是将精算公式转换为代码,首先是FORTRAN,后来是C.数学和编程技能因同事而异。 我学到的是以下内容,回顾了他们的代码:
- 在代码中记录实际公式;没有它,多年后您将难以记住实际公式。外部文档丢失、过时或根本无法访问。
- 将公式分解为可记录、重用和测试的离散组件。
- 使用常量记录方程式;幻数的上下文很少,通常需要现有知识才能让其他开发人员理解。
- 尽可能依靠编译器来优化代码。一个好的编译器将内联方法,减少重复并针对特定体系结构优化代码。在某些情况下,它可能会复制公式的某些部分以获得更好的性能。
也就是说,有时硬编码只是简化了事情,特别是如果这些值在特定上下文中被很好地理解。例如,将某物除以(或乘以)100 或 1000,因为您要将值转换为美元。另一个是当您想将小时转换为秒时,将某些东西乘以 3600。它们的意义往往从更大的背景中隐含出来。 下面没有说太多关于魔术数字 100 的信息:
public static double a(double b, double c)
{
return (b - c) * 100;
}
但以下内容可能会给你一个更好的提示:
public static double calculateAmountInCents(double amountDue, double amountPaid)
{
return (amountDue - amountPaid) * 100;
}
正如上面的评论所述,这远非复杂。
但是,您可以将幻数存储在常量/app.config 值中,以便下一个开发人员更轻松地编写您的代码。
在存储此类常量时,请务必向下一个开发人员(在 1 个月内阅读自己)解释您的想法是什么,以及他们需要记住什么。
还要解释实际计算的用途和作用。
不要像这样内联。
恒定,因此您可以重用、轻松查找、轻松更改,并在有人第一次查看您的代码时提供更好的维护。
如果可以/应该自定义,您可以进行配置。客户更改价值会产生什么影响? 有时最好不要给他们这个选择。 他们可以自己改变它,然后在事情不起作用时责怪你。 再说一次,也许他们比你的发布时间表更频繁
值得注意的是,C# 编译器(或者它是 CLR)将自动内联 1 行方法,因此,如果您可以将某些公式提取到一个行中,则可以将它们作为方法提取而不会造成任何性能损失。
编辑:
常量等或多或少取决于团队和使用量。显然,如果您多次使用相同的硬编码数字,请保持它。但是,如果您正在编写一个可能只有您才能编辑的公式(小团队),那么硬编码值就可以了。这完全取决于您的团队对文档和维护的看法。
如果行中的计算为下一个开发人员解释了某些内容,那么您可以保留它,否则最好在代码或配置文件中计算常量值。
我在生产代码中找到了一行,如下所示:
int interval = 1 * 60 * 60 * 1000;
没有任何评论,不难的是,原始开发人员的意思是以毫秒为单位1 hour
,而不是看到 3600000
的值。
IMO 对于这种情况,可能省略计算更好。
可以出于文档目的添加名称。所需的文档数量在很大程度上取决于目的。
请考虑以下代码:
float e = m * 8.98755179e16;
并将其与以下一个进行对比:
const float c = 299792458;
float e = m * c * c;
即使变量名称在后者中不是很"描述性",你也会更好地了解代码在第一个代码中做什么 - 可以说没有必要将c
重命名为speedOfLight
,m
重命名为mass
,e
能量,因为这些名称在其域中具有解释性。
const float speedOfLight = 299792458;
float energy = mass * speedOfLight * speedOfLight;
我认为第二个代码是最清晰的代码 - 特别是如果程序员可以期望在代码中找到STR(LHC模拟器或类似的东西)。总而言之 - 你需要找到一个最佳点。代码越详细,您提供的上下文就越多 - 这可能有助于理解含义(什么是e
和c
vs. 我们用光速做某事)和模糊大局(我们平方 c 并乘以 m 与扫描整行得到方程的需要)。
大多数常数都有一些更深层次的表示法和/或既定的符号,所以我会考虑至少按照约定来命名它(c
表示光速,R
表示气体常数,sPerH
表示小时数的秒数)。如果符号不清楚,则应使用较长的名称(sPerH
在名为Date
或Time
的类中,当它不在Paginator
中时可能没问题)。真正明显的常量可以被硬编码(比如 - 在合并排序中计算新的数组长度时除以 2)。