您应该在哪里放置常数,为什么?


34

在大多数大型应用程序中,通常只有几个“常量”位置:

  • 一类用于GUI和内部内容的内容(制表页标题,组框标题,计算因子,枚举)
  • 一类用于数据库表和列(此部分是生成的代码)以及它们的可读名称(手动分配)
  • 一类应用程序消息(日志,消息框等)

在这些类中,常量通常分为不同的结构。在我们的C ++应用程序中,仅在.h文件中定义常量,而在.cpp文件中分配值。

优点之一是,所有琴弦等都放在一个中央位置,每个人都知道在必须更改某些内容时可以在哪里找到它们。

这尤其是项目经理在人们来来往往时喜欢的东西,这样每个人都可以更改这些琐碎的事情,而不必深入研究应用程序的结构。

另外,您可以轻松地一次更改相似的“组框” /“选项卡页”等的标题。另一个方面是,您可以仅打印该类并将其交给非程序员,该非程序员可以检查字幕是否直观,向用户发送的消息是否太详细或太混乱等。

但是,我看到了某些缺点:

  • 每个类都与常量类紧密耦合
  • 添加/删除/重命名/移动常量需要重新编译至少90%的应用程序(注意:至少在C ++中,不需要更改值)。在我们的一个带有1500个类的C ++项目中,这意味着大约7分钟的编译时间(使用预编译的标头;如果没有它们,则大约为50分钟),再加上大约10分钟的链接到某些静态库。
  • 通过Visual Studio编译器构建速度优化的发行版最多需要3个小时。我不知道是否大量的类关系是源,但也有可能。
  • 您会被迫将临时硬编码的字符串直接转化为代码,因为您想非常快速地测试某项内容,并且不想仅等待15分钟来进行该测试(并且可能以后每次测试)。每个人都知道“以后我会解决”的想法。
  • 在另一个项目中重用一个类并不总是那么容易(主要是由于其他紧密的耦合,但是常量处理并没有使它更容易。)

您将在哪里存储这样的常量?为了使您的项目经理相信还有更好的概念也符合上面列出的优点,您还会提出什么论点?

随意给出特定于C ++或独立的答案。

PS:我知道这个问题是主观的,但是老实说,我不知道有比这个网站更好的地方。

有关此项目的更新

关于编译时间,我有个新闻:
在Caleb和gbjbaanb的帖子中,有空时,我将常量文件拆分为其他几个文件。我最终还将我的项目分成几个库,现在可以轻松得多。在发布模式下进行编译显示,包含数据库定义(表,列名等-超过8000个符号)并建立了某些哈希值的自动生成的文件在发布模式下造成了巨大的编译时间。

现在,通过停用包含DB常量的库的MSVC优化器,我们可以将发布模式下的项目(几个应用程序)的总编译时间从最多8小时减少到不到一小时!

我们还没有找到为什么MSVC很难优化这些文件,但是现在,由于我们不再只依赖夜间构建,因此此更改减轻了很多压力。

这个事实以及其他好处,例如紧密耦合少,可重用性更好等,也表明花时间拆分“常量”并不是一个坏主意;-)

更新2

由于这个问题仍然受到关注:
这是过去几年中我一直在做的事情:

将每个常量,变量等完全放在与其相关的范围内:如果仅在单个方法中使用常量,则可以在该方法中定义它。如果单个类对此感兴趣,请将其保留为该类的私有实现细节。这同样适用于名称空间,模块,项目,公司范围。我还将相同的模式用于助手功能等。(如果您开发一个公共框架,这可能不会100%适用。)

这样做可以提高可重用性,可测试性和可维护性,使您不仅花费更少的时间(至少在C ++中),而且花费在错误修复上的时间也更少,这使您有更多的时间实际开发新功能。同时,开发这些功能的速度会更快,因为您可以更轻松地重用更多代码。这在一定程度上胜过中央常量文件可能具有的任何优势。

如果您想了解更多,请特别看一下接口隔离原则单一职责原则

如果您同意,请支持Caleb的回答,因为此更新基本上是他所说的更一般的看法。


2
我个人根本不会在常量中包含UI标题或消息字符串。我将它们放在app.config
jk中。

1
我有点喜欢您现在的做法-我了解您的不利条件,但我们可能只需要解决这些问题即可。
bigtang 2012年

1
我已经在一个大型Java项目中看到了完全相同的问题……一个巨大的“常量”接口,对其进行任何更改,然后等待15分钟以重新编译Eclipse。我和Caleb在一起:将它们自然所属的常量归为一组,靠近使用它们的代码。因为它们是常量而将它们分开是没有用的OCD练习。
Michael Borgwardt'4

紧密耦合不是问题,因为这是您真正想要的。您希望常量文件中的一项更改可能影响很多源文件。(当然,您还有其他问题)。
gnasher729

@ gnasher729这仅在许多类使用相同的常量时才是正确的。您永远不会希望类与不相关的常量紧密耦合。乍一看似乎没有问题,直到您尝试在另一个项目中重用它而不复制它或运行隔离的测试
Tim Meyer

Answers:


29

特定于类的常量应进入该类的接口。

确实是配置选项的常量应该是配置类的一部分。如果为该类中的配置选项提供访问器(并使用它们代替其他地方的常量),则只需更改一些选项,就不必重新编译整个世界。

类之间共享但不是可配置的常量应该合理地确定范围-尝试将它们分解成具有特定用途的文件,以便各个类仅包含它们实际需要的内容。当您更改其中一些常量时,这将再次有助于减少编译时间。


1
如果您有兴趣:按照您的回答和其他信息,我用完成的内容更新了我的问题
Tim Meyer

6

我只想简单地说,您想将庞大的常量类拆分为许多较小的文件,例如,每种形式一个。这样可以确保您对常量文件没有太大的依赖性,因此添加或更新字符串将不需要完全重新编译。您仍然可以将这些文件存储在中央位置,但是(例如)有1个文件,其中每个对话框都有常量(适当命名)。然后,您只能将这些文件包括在相关的对话框文件中,从而大大减少了重新编译的时间。

我还建议您使用类似GNU GetText的工具来处理字符串,它是为翻译而设计的,但是对于将文本更改为其他内容也同样适用。您可以将它们放在字符串资源中,但是我发现它们使用ID进行键化时更难使用,而GetText utils则通过原始字符串进行键化-使事情非常容易开发。


我可以试一试。由于带有标题等的常量类被分为几个结构,因此我也许可以从每个结构开始一个类。不确定GNU事情,我们通常不希望在运行时(仅在开发期间)更改字符串。但是,如果将来我们不得不翻译成另一种语言,我们将使用Qt的翻译机制。
Tim Meyer 2012年

如果您有兴趣:按照您的回答和其他信息,我用完成的内容更新了我的问题
Tim Meyer

2

注意:我不是C ++开发人员...但是这是我的想法:您需要考虑遵循@jk关于使用配置文件之间的区别的注释。在DotNet中,有用于存储此类信息的资源文件。在Windows窗体中,从VS维护每个窗体的资源文件。

我看不到要放置在其使用范围之外的常量值,除非它是必须共享的全局常量。正如您提到的那样,至少在开发过程中很难保持这一点。另外,您可能会遇到名称冲突。另一件事是,可能很难知道谁在使用给定常量。

现在,如果您希望非程序员查看信息,那么对于GUI,您将捕获它们的屏幕。如果希望他们查看数据表条目,则可以将数据导出到Excel或类似的文件。

如果仍要使用集中式定位方法,并且要将所有常量放置在一个大文件中,则每个开发人员都可以使用一个共享文件,该共享文件在每个间隔结束时更新为一个中央文件。数据将来自开发中使用的单个文件。这可以轻松实现自动化或手动完成。但是,正如我说的那样,您不必冒险。


2

没有通用的解决方案。问问自己有关常数的性能,可用性,安全性和生命周期。

它们与范围的定义越接近,性能就越高。

它们在逻辑上进行分组的次数越多,超出其范围,则可重用性越高。

成本高的人越难以获得,安全性就越高。

常量的生存期越高,它对您将其放置在何处以得到可用性的关心就越少。

诸如版本号之类的常量将在某种清单中定义。错误函数的错误代码将在类内定义。错误代码很可能具有很高的生命周期(=几乎永不改变)。将其放在恒定文件中只会向文件中添加不必要的内容。

常量的性质越少,但变量(例如版本号)越多,则可以将其放在外面。常量的变量越少,则常量越多,应该放在范围内的范围就越大。在调试期间,将其放置在外部以减少编译时间是很有意义的。

但是,您最初的问题是编译时间。所以问题是您是否提出正确的问题。如果您的应用程序的编译时间太长,则最好考虑一种使其模块化的方法,以使各部分彼此独立工作。部分编译它并独立测试您的东西。如果您的单元测试正确完成并且功能完善(实际上是很多工作),那么您可以很轻松地转移内容,而不必担心。然后这个问题得到了完全不同的动力。


1

我建议将所有这些常量放入某种配置文件中。对于Java应用程序,我们通常使用.properties文件,这是一个简单的文本,每行的格式为“(key)=(value)”。例

MainPanel.Title =欢迎使用我们的应用程序
DB.table.users = TBL_USERS
logging.filename = application.log

然后,您可以在运行时加载此文件,并填充一个高速缓存,该高速缓存使您可以查找键并获取值。当需要常量时,可以查询缓存。您仍然需要将密钥放在某个地方,并且缓存需要全局访问,但是当您更改常量的实际时,无需重新编译,应该可以重新启动应用程序(或者您真的很想花哨,拥有多个.properties文件和缓存,并使应用程序能够在运行时重新加载缓存)。

对于实现,我发现了一个SO问题:https : //stackoverflow.com/questions/874052/properties-file-library-for-c-or-c(这是Google搜索的第一个热门项目-我还没有我自己实际使用过此软件)。


对于C ++项目,仅在更改值时才需要重新编译常量文件。这是因为在.cpp文件中分配了值。然而添加/删除/重命名/移动的恒定仍然需要完全重建
添迈耶

@TimMeyer:如果将常量拆分为多个文件,添加/删除常量只会影响依赖于该特定文件的文件,对吗?
FrustratedWithFormsDesigner 2012年

正确。主要问题是,如果我这样建议,人们往往会提到我列为“优势”的事情之一
Tim Meyer 2012年

但是,我实际上并没有考虑过将多个常量文件放在同一位置
Tim Meyer

+1。@Tim Meyer:为此,编译时检查的成本要比节省的多得多。此外,如果您需要国际化怎么办?
凯文·克莱恩
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.