Стратегия передачи аргументов — переменные среды и командная строка

Большинство приложений, которые мы, разработчики, пишем, нуждаются во внешней параметризации при запуске. Мы передаем пути к файлам, имена каналов, адреса TCP/IP и т. д. До сих пор я использовал командную строку, чтобы передать их запускаемому приложению. Мне пришлось разобрать командную строку в main и направить аргументы туда, где они нужны, что, конечно, является хорошим дизайном, но трудно поддерживать для большого количество аргументов. Недавно я решил использовать механизм переменных среды. Они глобальны и доступны из любого места, что менее элегантно с архитектурной точки зрения, но ограничивает объем кода.

Это мои первые (и, возможно, довольно поверхностные) впечатления от обеих стратегий, но я хотел бы услышать мнения более опытных разработчиков -- Каковы плюсы и минусы использования переменных среды и аргументов командной строки для передачи аргументов в процесс? Я хотел бы принять во внимание следующие вопросы:

  1. качество дизайна (гибкость/ремонтопригодность),
  2. ограничения памяти,
  3. переносимость решения.

Примечания:

Объявление. 1. Это главный аспект, который меня интересует.

Объявление. 2. Это немного прагматично. Я знаю о некоторых ограничениях Windows, которые в настоящее время огромны (более 32 КБ для как командная строка, так и блок окружения). Я думаю, это не проблема, поскольку вы просто должны использовать файл для передачи множества аргументов, если вам это нужно.

Объявление. 3. Я почти ничего не знаю об Unix, поэтому не уверен, что обе стратегии так же применимы, как и в Windows. Разработайте это, если хотите.


person Janusz Lenar    schedule 16.09.2011    source источник
comment
Не могли бы вы дать больше конкретики, как в фактическом количестве параметров? а есть ли к ним группировки или они все рандомные? а это для какого языка? java, c++ и т. д. Причина, по которой я прошу такой уровень детализации, заключается в том, что, хотя это может быть проблемой для любого языка, может существовать конкретное решение для языковой реализации, о котором вы не знаете.   -  person James Drinkard    schedule 28.09.2011
comment
Просто упомянем ОС * nix, в них нет ничего похожего на глобальную переменную среды, и каждая переменная env наследуется от родительского процесса к дочернему процессу во время разветвления. Таким образом, global не является преимуществом для env var над командной строкой, по крайней мере, для этих ОС.   -  person shr    schedule 29.09.2011
comment
Привет, @jamesDrinkard. Меня интересует общий подход. Если бы вы хотели передать 20 различных помеченных строковых/целочисленных/вещественных аргументов из скрипта Python, запущенного 32-битным интерпретатором, в 64-битное приложение, написанное на C++, какой метод вы бы использовали?   -  person Janusz Lenar    schedule 29.09.2011
comment
Привет, @шр. Спасибо за примечание *nix. Как отметил Раймонд ниже, для этой задачи такая глобальность вовсе не профи.   -  person Janusz Lenar    schedule 29.09.2011
comment
Это может быть актуально и поддерживает переменные среды: devcenter.heroku.com/articles/config-vars   -  person eyeApps LLC    schedule 20.12.2016


Ответы (4)


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, ПК и Linux в отношении переменных окружения и аргументов командной строки, но могу сказать вам:

  • все три поддерживают переменные окружения
  • все они поддерживают аргументы командной строки

Да, я знаю, это не очень помогло. Мне жаль. Но ключевым моментом является то, что вы можете ожидать, что разумное решение будет переносимым, хотя вы определенно хотели бы проверить это для своих программ (например, чувствительны ли к регистру аргументы командной строки на любых платформах? на всех платформы? не знаю).


Последний пункт:

Как упомянул Томаш, для большинства приложений не должно иметь значения, откуда пришли параметры.

person Matt Fenwick    schedule 28.09.2011
comment
Спасибо, Мэтт. Это своего рода мнение, которое я искал. Самый важный ваш совет — использовать переменные окружения для описания среды выполнения, которое практически не меняется, и cmd-файл для передачи простых/сложных аргументов фактического исполнения. Очень рационально, спасибо. Однако обратите внимание, что вы можете использовать «локальные» переменные среды, которые могут только испортить дочерние процессы. Это очень похоже на передачу аргументов командной строки, за исключением того, что Рэймонд указал в ответе Томаша. - person Janusz Lenar; 29.09.2011
comment
Очень хороший ответ! Что касается недостатка, заключающегося в том, что переменные среды можно изменить откуда угодно: существует также возможность локально установить переменные среды из стартового сценария (например, Bash или пакетного сценария) для приложения. В этом случае может быть общесистемное значение по умолчанию, но приложение может при необходимости изменить значение по умолчанию на пользовательское значение. Что ты думаешь по этому поводу? - person Lii; 07.07.2015
comment
Есть ли плюсы и минусы при рассмотрении вопроса о том, как передавать секреты/учетные данные? - person iamyojimbo; 15.11.2016
comment
Я согласен для настольных приложений и приложений CLI. Для облачной системы с большим количеством развертываний переменные env являются хорошей альтернативой и, например, рекомендуются в руководстве по 12factor: 12factor.net/config - person Lars Grammel; 17.06.2020

Вы должны абстрагироваться от параметров чтения, используя шаблон Стратегия. Создайте абстракцию с именем ConfigurationSource, имеющую метод readConfig(key) -> value (или возвращающую некоторый объект/структуру Configuration) со следующими реализациями:

  • CommandLineConfigurationSource
  • EnvironmentVariableConfigurationSource
  • WindowsFileConfigurationSource - загрузка из конфигурационного файла от C:/Document and settings...
  • WindowsRegistryConfigurationSource
  • NetworkConfigrationSource
  • UnixFileConfigurationSource - - загрузка из конфигурационного файла из /home/user/...
  • DefaultConfigurationSource - по умолчанию
  • ...

Вы также можете использовать шаблон Цепочка ответственности для объединения источников в различные конфигурации, например: Аргумент командной строки не указан, попробуйте переменную среды и, если ничего не помогает, верните значения по умолчанию.

Дополнение 1. Этот подход не только позволяет вам абстрагироваться от конфигурации чтения, но и позволяет легко изменить базовый механизм без какого-либо влияния на клиентский код. Также вы можете использовать несколько источников одновременно, отступая или собирая конфигурацию из разных источников.

Объявление 2. Просто выберите подходящую реализацию. Конечно, некоторые записи конфигурации не подходят, например, для аргументов командной строки.

Дополнение 3. Если некоторые реализации не переносимы, создайте две, одна из которых будет игнорироваться/пропускаться, если она не подходит для данной системы.

person Tomasz Nurkiewicz    schedule 16.09.2011
comment
Спасибо, это вообще хорошая идея. Но это не помогает решить, использовать ли среду или командную строку. Было бы полезно уточнить 'некоторые элементы конфигурации, например, в аргументах командной строки' вашего Ad.2. Что не влезет в строку? Если он не подходит, его, вероятно, следует передать косвенно в виде файла, не так ли? - person Janusz Lenar; 16.09.2011
comment
Моя точка зрения такова: не заставляйте пользователя использовать параметры командной строки или переменные среды. Будьте гибкими (и при этом сохраняйте поддерживаемый код). Я считаю, что файл конфигурации является лучшим местом для хранения конфигурации (он может быть сколь угодно длинным, содержать комментарии и т. д.), однако иногда полезно переопределить конфигурацию файла с помощью параметров командной строки. Что не вписывается в параметры командной строки? Если вам нужно передать несколько путей к файлам, возможно, это сработает, но слишком длинные командные строки никому не нравятся. - person Tomasz Nurkiewicz; 16.09.2011
comment
Файл конфигурации лучше всего подходит для аргументов - это ценное мнение, и поддержка комментариев - хорошая причина для его использования, спасибо. Если вы используете переменные среды при запуске приложения из пакетного сценария, вы можете получить очень удобочитаемую форму, используя rem и set. Если вы порождаете процесс, вы просто setenv делаете то, что хотите, перед spawnl-ингом. Это удобно, читабельно и гибко. Почему вы используете .config вместо среды? Вот в чем вопрос. - person Janusz Lenar; 16.09.2011
comment
Помните, что переменные среды наследуются. Предположим, ваша программа имеет два параметра ACTION и необязательный параметр NOTIFY. Программа A устанавливает ACTION=if owner=nobody set owner=bob и NOTIFY=send, затем запускает вашу программу. Ваша программа обновляет элемент, затем видит, что NOTIFY установлено, и запускает send. Программа send отправляет электронное письмо Бобу, а затем снова запускает вашу программу, устанавливая ACTION=set last_send = today. Ему не нужны никакие уведомления, поэтому он не устанавливает NOTIFY. Но он унаследовал NOTIFY от программы A, поэтому ваша программа обновляет последний запуск до сегодняшнего дня, а затем запускает send. Бесконечная петля. - person Raymond Chen; 24.09.2011
comment
Спасибо, @Раймонд. Область действия переменных окружения опасно широка. Хорошая точка зрения. - person Janusz Lenar; 29.09.2011
comment
Это должен быть канонический ответ для java. Вот почему я не люблю java. - person j-a; 18.12.2018

Я думаю, что на этот вопрос уже был дан довольно хороший ответ, но я чувствую, что он заслуживает обновления 2018 года. Я чувствую, что неупомянутое преимущество переменных окружения заключается в том, что для их работы обычно требуется меньше стандартного кода. Это делает код более чистым и читаемым. Однако основным недостатком является то, что они удаляют уровни изоляции от разных приложений, работающих на одном компьютере. Я думаю, что именно здесь Docker действительно сияет. Мой любимый шаблон проектирования — использовать исключительно переменные среды и запускать приложение внутри контейнера Docker. Это устраняет проблему изоляции.

person mdornfe1    schedule 09.05.2018

В целом я согласен с предыдущими ответами, но есть еще один важный аспект: удобство использования.

Например, в git вы можете создать репозиторий с каталогом .git вне его. Чтобы указать это, вы можете использовать аргумент командной строки --git-dir или переменную окружения GIT_DIR.

Конечно, если вы измените текущий каталог на другой репозиторий или унаследуете переменные окружения в скриптах, вы получите ошибку. Но если вам нужно набрать несколько git команд в отдельном репозитории в одной терминальной сессии, это очень удобно: вам не нужно повторять аргумент git-dir.

Другой пример — GIT_AUTHOR_NAME. Кажется, что у него даже нет партнера по командной строке (однако у git commit есть аргумент --author). GIT_AUTHOR_NAME переопределяет параметры конфигурации user.name и author.name.

В общем, использование аргументов командной строки или окружения одинаково просто в UNIX: можно использовать аргумент командной строки

$ command --arg=myarg

или переменная окружения в одной строке:

$ ARG=myarg command

Также легко захватить аргументы командной строки в alias:

alias cfg='git --git-dir=$HOME/.cfg/ --work-tree=$HOME'  # for dotfiles
alias grep='grep --color=auto'

Обычно большинство аргументов передаются через командную строку. Я согласен с предыдущими ответами, что это более функционально и прямолинейно, и что переменные окружения в сценариях похожи на глобальные переменные в программах.

GNU libc говорит следующее:

Механизм argv обычно используется для передачи аргументов командной строки, характерных для конкретной вызываемой программы. Окружающая среда, с другой стороны, отслеживает информацию, которая используется многими программами, редко изменяется и реже используется.

Помимо того, что было сказано об опасностях переменных среды, есть хорошие примеры их использования. GNU make имеет очень гибкую обработку переменных окружения (и таким образом очень интегрирован с оболочкой):

Каждая переменная среды, которую make видит при запуске, преобразуется в переменную make с тем же именем и значением. Однако явное назначение в make-файле или с аргументом команды переопределяет среду. (-- и есть возможность изменить это поведение)...

Таким образом, установив переменную CFLAGS в вашей среде, вы можете заставить все компиляции C в большинстве make-файлов использовать предпочитаемые вами параметры компилятора. Это безопасно для переменных со стандартными или обычными значениями, потому что вы знаете, что ни один make-файл не будет использовать их для других целей.

Наконец, подчеркну, что для программы важнее всего не программист, а пользовательский опыт. Возможно, вы включили это в аспект дизайна, но внутренний и внешний дизайн — совершенно разные сущности.

И несколько слов о аспектах программирования. Вы не написали, какой язык вы используете, но давайте представим, что ваши инструменты позволяют вам наилучший анализ аргументов. В Python я использую argparse, который очень гибкий и богатый. Чтобы получить проанализированные аргументы, можно использовать команду вида

args = parser.parse_args()

args можно дополнительно разделить на проанализированные аргументы (скажем, args.my_option), но я также могу передать их в свою функцию целиком. Это решение абсолютно не сложно поддерживать для большого количества аргументов (если ваш язык это позволяет). Действительно, если у вас много параметров и они не используются при разборе аргументов, передавайте их в контейнере до конечного пункта назначения и избегайте дублирования кода (что приводит к негибкости).

И самое последнее замечание: гораздо проще анализировать переменные окружения, чем аргументы командной строки. Переменная окружения — это просто пара VARIABLE=value. Аргументы командной строки могут быть намного сложнее: они могут быть позиционными или ключевыми аргументами или подкомандами (например, git push). Они могут захватывать ноль или несколько значений (вспомните команду echo и флаги вроде -vvv). Дополнительные примеры см. в разделе argparse.

И еще кое-что. Твое беспокойство по поводу памяти немного тревожит. Не пишите слишком общие программы. Библиотека должна быть гибкой, но хорошая программа полезна и без аргументов. Если вам нужно передать много, это, вероятно, данные, а не аргументы. Как считывать данные в программу — гораздо более общий вопрос, не имеющий единого решения для всех случаев.

person Yaroslav Nikitenko    schedule 03.02.2021