参数传递策略 - 环境变量与命令行

Jan*_*nar 67 command-line process environment-variables argument-passing spawn

我们开发人员编写的大多数应用程序都需要在启动时进行外部参数化.我们传递文件路径,管道名称,TCP/IP地址等.到目前为止,我一直在使用命令行将这些传递给正在启动的应用程序.我不得不解析命令行main并将参数指向他们需要的地方,这当然是一个很好的设计,但很难维护大量的参数.最近我决定使用环境变量机制.它们是全局的,可以从任何地方访问,从架构的角度来看不太优雅,但限制了代码量.

这些是我对这两种策略的第一次(也可能是很浅的)印象,但我想听听更多有经验的开发人员的意见 - 使用环境变量和命令行参数将参数传递给进程的起伏是什么?我想考虑以下事项:

  1. 设计质量(灵活性/可维护性),
  2. 记忆限制,
  3. 解决方案可移植性

备注:

广告.这是我感兴趣的主要方面.

广告.这有点务实.据我所知,这是目前在Windows上一定的局限性巨大(超过32kB的两个命令行和环境块).我想这不是问题,因为如果需要,你应该使用一个文件来传递大量的参数.

广告.我几乎不知道Unix,所以我不确定这两种策略是否像在Windows上一样可用.如果你愿意,请详细说明.

Mat*_*ick 70

1)我建议尽可能避免环境变量.

环境变量的优点

  • 易于使用,因为它们可以从任何地方看到.如果许多独立程序需要一条信息,这种方法更加方便.

环境变量的缺点

  • 很难正确使用,因为它们可以从任何地方看到(可删除,可设置).如果我安装一个依赖环境变量的新程序,他们会踩到我现有的吗?昨天我在周围逛逛时,是否无意中搞砸了我的环境变量?

我的看法

  • 对于那些对程序的每次单独调用最有可能不同的参数使用命令行参数(即n为计算n的程序!)
  • 将配置文件用于用户可能合理想要更改的参数,但不经常使用(即弹出窗口时的显示大小)
  • 谨慎使用环境变量 - 最好只用于预期不会改变的参数(即Python解释器的位置)
  • 你的观点They are global and accessible from anywhere, which is less elegant from architectural point of view, but limits the amount of code让我想起了使用全局变量的理由;)

我的伤疤来自第一手环境变量过度使用的恐怖

  • 工作中需要的两个程序,由于环境冲突,不能同时在同一台计算机上运行
  • 多个版本的程序具有相同的名称但不同的错误 - 由于程序的位置被从环境中拉出,并且(默默地,巧妙地)错误,因此将整个研讨会拉到了几个小时.

2)限制

如果我正在推动命令行可以容纳的限制,或者环境可以处理什么,我会立即重构.

我过去曾使用JSON作为需要大量参数的命令行应用程序.能够使用字典和列表以及字符串和数字非常方便.该应用程序只使用了几个命令行参数,其中一个是JSON文件的位置.

这种方法的优点

  • 没有必要编写很多(痛苦的)代码来与CLI库进行交互 - 让许多公共库强制执行复杂的约束可能会很麻烦("复杂"我的意思比检查一个更复杂特定键或一组键之间的交替)
  • 不必担心CLI库对参数顺序的要求 - 只需使用JSON对象!
  • 易于表示复杂的数据(回答What won't fit into command line parameters?),如列表
  • 易于使用来自其他应用程序的数据 - 以编程方式创建和解析
  • 易于适应未来的扩展

注意:我想区别于.config文件方法 - 这不是用于存储用户配置.也许我应该将其称为'命令行参数文件'方法,因为我将它用于需要许多不适合命令行的值的程序.


3)解决方案的可移植性:我不太了解Mac,PC和Linux在环境变量和命令行参数方面的差异,但我可以告诉你:

  • 这三个人都支持环境变量
  • 它们都支持命令行参数

是的,我知道 - 这不是很有帮助.对不起.但关键的一点是,你可以期待一个合理的解决方案是可移植的,虽然你肯定想要为你的程序验证这一点(例如,命令行args在任何平台上是否区分大小?在所有平台上?我不知道).


最后一点:

正如Tomasz所提到的,对于参数来自的大多数应用程序来说,它都无关紧要.

  • 可以在我使用过的每个操作系统中为每个进程设置环境变量。`Foo=bar app & Foo=baz app` 将运行同一程序的两个实例,每个实例都提供不同的 Foo 值。因此,尽管环境变量发生冲突,您在工作中需要的这两个程序*可以*同时运行 - 只要您知道如何正确管理变量的范围。 (3认同)
  • 谢谢你,马特。这是我一直在寻找的一种意见。您最重要的建议是使用环境变量作为执行环境描述,它几乎不会改变,并使用 cmd-file 来传递实际执行的简单/复杂参数。很理性,谢谢。请注意,您可以使用“本地”环境变量,但这只会扰乱子进程。它与命令行参数传递非常相似,除了 Raymond 在 Tomasz 的回答下指出的以外。 (2认同)
  • 非常好的答案!关于可以从任何地方更改环境变量的缺点:还可以从应用程序的启动脚本(例如 Bash 或 Batch 脚本)在本地设置环境变量。在这种情况下,可以有系统范围的默认值,但应用程序可以根据需要将默认值更改为自定义值。您对此有何看法? (2认同)
  • 我同意桌面和 CLI 应用程序。对于有很多部署的云系统,环境变量是一个很好的选择,例如 12factor 指南中推荐的:https://12factor.net/config (2认同)
  • 同意@M-Pixel——感谢您指出这一点,而且说实话,我的答案还没有过时。我写它的时候考虑的是桌面/CLI 程序,但正如其他人指出的那样,这个建议对于在不同环境(例如容器)中运行没有任何意义。我会尝试想出一个好方法来编辑这个答案,以明确我正在考虑的背景。再次感谢您指出这一点! (2认同)

mdo*_*fe1 7

我认为这个问题已经得到了很好的回答,但我觉得它值得 2018 年更新。我觉得环境变量的一个未提及的好处是它们通常需要更少的样板代码来使用。这使得代码更清晰、更易读。然而,一个主要缺点是它们消除了同一台计算机上运行的不同应用程序之间的隔离层。我认为这就是 Docker 真正闪光的地方。我最喜欢的设计模式是专门使用环境变量并在 Docker 容器内运行应用程序。这消除了隔离问题。


Tom*_*icz 6

您应该使用策略模式抽象读取参数.使用以下实现创建一个名为ConfigurationSourcehaving readConfig(key) -> valuemethod(或返回一些Configuration对象/结构)的抽象:

  • CommandLineConfigurationSource
  • EnvironmentVariableConfigurationSource
  • WindowsFileConfigurationSource - 从配置文件加载 C:/Document and settings...
  • WindowsRegistryConfigurationSource
  • NetworkConfigrationSource
  • UnixFileConfigurationSource - 从配置文件加载 /home/user/...
  • DefaultConfigurationSource - 默认值
  • ...

您还可以使用Chain of responsibility模式在各种配置中链接源,例如:如果未提供命令行参数,请尝试环境变量,如果其他所有操作都失败,则返回默认值.

广告1.此方法不仅允许您抽象阅读配置,而且您可以轻松更改基础机制,而不会对客户端代码产生任何影响.此外,您可以同时使用多个源,后退或从不同来源收集配置.

广告2.只需选择适合的实施方式.当然,一些配置条目不适合例如命令行参数.

广告3.如果某些实现不可移植,则在不适合给定系统时,有两个,一个默默地忽略/跳过.

  • 请注意环境变量是继承的.假设你的程序有两个参数`ACTION`和一个可选的`NOTIFY`.程序A设置`ACTION = if owner = nobody set owner = bob`和`NOTIFY = send`然后运行你的程序.你的程序更新一个项目,然后看到设置了'NOTIFY`并运行`send`.`send`程序向Bob发送电子邮件,然后再次运行你的程序,设置`ACTION = set last_send = today`.它不需要任何通知,因此它不会设置"NOTIFY".但它*从程序A继承了*`NOTIFY`,所以你的程序将最后一次运行更新到今天,然后运行`send`.无限循环. (3认同)
  • 我的观点是:不要强迫用户使用命令行参数**或**环境变量。保持灵活性(同时保留可维护的代码)。我相信配置文件是存储配置的最佳位置(它可以任意长,包含注释等),但是有时使用命令行参数覆盖文件配置很有用。什么不适合命令行参数?如果您需要传递多个文件路径,它可能会起作用,但没有人喜欢过长的命令行。 (2认同)