Я пишу программу на Java, где в какой-то момент мне нужно загрузить пароль для моего хранилища ключей. Ради интереса я попытался сделать свой пароль на Java как можно более коротким, выполнив следующее:
//Some code
....
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");
{
char[] password = getPassword();
keyStore.load(new FileInputStream(keyStoreLocation), password);
keyManager.init(keyStore, password);
}
...
//Some more code
Теперь я знаю, что в этом случае это довольно глупо. Есть куча других вещей, которые я мог бы сделать, большинство из них на самом деле лучше (я бы вообще не использовал переменную).
Тем не менее, мне было любопытно, был ли случай, когда это было не так глупо. Единственное, о чем я могу подумать, это если вы хотите повторно использовать общие имена переменных, такие как count
, или temp
, но хорошие соглашения об именах и короткие методы делают маловероятным, что это будет полезно.
Есть ли случай, когда использование блоков только для уменьшения области видимости имеет смысл?
Ответы:
Сначала поговорим о базовой механике:
В области видимости C ++ == время жизни b / c деструкторы вызываются при выходе из области видимости. Далее, важное различие в C / C ++ мы можем объявить локальными объектами. Во время выполнения для C / C ++ компилятор обычно выделяет кадр стека для метода, который настолько велик, насколько это необходимо, заранее, а не выделяет больше места в стеке при входе в каждую область (которая объявляет переменные). Таким образом, компилятор разрушает или выравнивает области видимости.
Компилятор C / C ++ может повторно использовать пространство стека для локальных компьютеров, которые не конфликтуют во время использования (обычно он использует анализ фактического кода, чтобы определить это, а не область действия, b / c, которая является более точной, чем область действия!).
Я упоминаю, что синтаксис C / C ++ b / c Java, то есть фигурные скобки и область видимости, по крайней мере частично получены из этого семейства. А также потому, что C ++ возник в вопросе комментариев.
В отличие от этого, в Java невозможно иметь локальные объекты: все объекты являются объектами кучи, а все локальные / формальные объекты являются либо ссылочными переменными, либо примитивными типами (кстати, то же самое верно и для статики).
Кроме того, в Java область видимости и время жизни точно не приравниваются: находиться в области видимости и вне ее - это в основном концепция времени компиляции, идущая к доступности и конфликтам имен; ничего не происходит в Java при выходе из области видимости при очистке переменных. Сборка мусора в Java определяет (конечную точку) время жизни объектов.
Кроме того, механизм байт-кода Java (вывод компилятора Java) имеет тенденцию повышать пользовательские переменные, объявленные в ограниченных областях, на верхний уровень метода, поскольку на уровне байт-кода отсутствует функция области видимости (это похоже на обработку C / C ++ для стек). В лучшем случае компилятор может повторно использовать слоты локальных переменных, но (в отличие от C / C ++), только если их тип одинаков.
(Тем не менее, чтобы быть уверенным, что базовый JIT-компилятор во время выполнения может повторно использовать одну и ту же область памяти стека для двух разных типов (и разных слотов) локальных систем, с достаточным анализом байт-кода.)
Говоря о преимуществах программиста, я склонен согласиться с другими, что (приватный) метод является лучшей конструкцией, даже если он используется только один раз.
Тем не менее, нет ничего плохого в том, чтобы использовать его для устранения конфликтов имен.
Я делал это в редких случаях, когда создание отдельных методов - это бремя. Например, при написании интерпретатора с использованием большого
switch
оператора в цикле, я мог бы (в зависимости от факторов) ввести отдельный блок для каждого,case
чтобы отдельные случаи были более отделены друг от друга, вместо того, чтобы делать каждый случай отдельным методом (каждый из который вызывается только один раз).(Обратите внимание, что как код в блоках, отдельные случаи имеют доступ к операторам "break;" и "continue;", относящимся к замкнутому циклу, тогда как в качестве методов потребуется возвращать логические значения и использовать условные выражения вызывающей стороны для получения доступа к этому потоку управления заявления.)
источник
{ int x = 42; String s="blah"; } { long y = 0; }
, сlong
переменной будет повторно использоваться слоты обеих переменныхx
иs
всех широко используемых компиляторов (javac
иecj
).На мой взгляд, было бы более понятно, чтобы вытащить блок в свой собственный метод. Если вы оставите этот блок внутри, я надеюсь увидеть комментарий, разъясняющий, почему вы ставите этот блок в первую очередь. В этот момент становится понятнее, что вызов метода, в
initializeKeyManager()
котором переменная пароля ограничена областью действия метода, показывает ваше намерение с помощью блока кода.источник
Они могут быть полезны в Rust с его строгими правилами заимствования. И вообще, в функциональных языках, где этот блок возвращает значение. Но в таких языках, как Java? В то время как идея кажется изящной, на практике она редко используется, поэтому путаница с ее просмотром затмевает потенциальную ясность ограничения области действия переменных.
Есть один случай, когда я нахожу это полезным - в
switch
заявлении! Иногда вы хотите объявить переменную вcase
:Это, однако, не удастся:
switch
операторы странные - они контрольные операторы, но из-за их падения они не вводят области видимости.Итак, вы можете просто использовать блоки для создания областей:
источник
Обычно считается хорошей практикой держать методы короткими. Сокращение области действия некоторых переменных может быть признаком того, что ваш метод достаточно длинный, достаточно для того, чтобы вы думали, что область действия переменных должна быть уменьшена. В этом случае, вероятно, стоит создать отдельный метод для этой части кода.
Случаи, которые я нахожу проблематичными, это когда эта часть кода использует больше, чем несколько аргументов. Существуют ситуации, когда вы можете использовать маленькие методы, каждый из которых принимает много параметров (или требуется обходной путь для имитации возврата нескольких значений). Решением этой конкретной проблемы является создание другого класса, который хранит различные переменные, которые вы используете, и реализует все шаги, которые вы имели в виду, в своих относительно коротких методах: это типичный вариант использования шаблона Builder .
Тем не менее, все эти руководящие принципы кодирования могут иногда применяться свободно. Иногда бывают странные случаи, когда код может быть более читабельным, если вы сохраняете его в несколько более длинном методе, вместо того, чтобы разбивать его на небольшие суб-методы (или шаблоны компоновщика). Как правило, это работает для задач среднего размера, достаточно линейных и в которых слишком большое расщепление фактически затрудняет читабельность (особенно если вам нужно переключаться между слишком многими методами, чтобы понять, что делает код).
Это может иметь смысл, но это очень редко.
Вот ваш код:
Вы создаете
FileInputStream
, но никогда не закрываете его.Одним из способов решения этой проблемы является использование оператора try-with-resources . Он ограничивает область действия
InputStream
, и вы также можете использовать этот блок для уменьшения области действия других переменных, если хотите (в пределах разумного, поскольку эти строки связаны):(Кстати, часто лучше использовать,
getDefaultAlgorithm()
а неSunX509
.)Кроме того, хотя совет по разделению кусков кода на отдельные методы, как правило, звучит разумно. Это редко стоит, если это всего за 3 строки (во всяком случае, это случай, когда создание отдельного метода ухудшает читабельность).
источник
По моему скромному мнению, это обычно следует избегать в основном для производственного кода, потому что у вас, как правило, нет соблазна делать это, за исключением отдельных функций, выполняющих разрозненные задачи. Я склонен делать это в некотором коде, используемом для тестирования, но не вижу соблазна сделать это в производственном коде, где я заранее подумал о том, что должна делать каждая функция, так как тогда функция, естественно, будет иметь очень ограниченный охват в отношении его местного состояния.
Я никогда не видел примеров использования анонимных блоков (не включая условные выражения,
try
блок для транзакции и т. Д.) Для значительного уменьшения области действия в функции, которая не задала вопрос о том, почему она не может разделить далее на более простые функции с уменьшенной областью действия, если это действительно принесло пользу практически с подлинной точки зрения SE от анонимных блоков. Обычно это эклектичный код, который делает кучу слабо связанных или очень не связанных вещей, к которым мы наиболее склонны достигать этого.Например, если вы пытаетесь сделать это, чтобы повторно использовать переменную с именем
count
, то это предполагает, что вы считаете две разные вещи. Если имя переменной будет таким же коротким, какcount
для меня, имеет смысл связать его с контекстом функции, которая потенциально может просто считать один тип вещи. Затем вы можете мгновенно посмотреть на имя функции и / или документацию, увидетьcount
и мгновенно узнать, что это означает в контексте того, что делает функция, не анализируя весь код. Я не часто нахожу хороший аргумент для функции для подсчета двух разных вещей, использующих одно и то же имя переменной таким образом, что анонимные области / блоки становятся такими привлекательными по сравнению с альтернативами. Это не значит, что все функции должны учитывать только одну вещь. Я говорю, что вижу малоинженерная выгода для функции, использующей одно и то же имя переменной для подсчета двух или более элементов и использующей анонимные блоки для ограничения объема каждого отдельного подсчета. Если функция проста и понятна, это еще не конец света, когда две переменные count будут иметь разные имена, а первая может иметь на несколько строк видимости области видимости больше, чем это требуется в идеале. Такие функции, как правило, не являются источником ошибок, в которых отсутствуют такие анонимные блоки, чтобы еще больше уменьшить минимальный объем своих локальных переменных.Не предложение для лишних методов
Это не означает, что вы должны принудительно создавать методы, просто чтобы уменьшить область. Возможно, это так же плохо или хуже, и то, что я предлагаю, не должно вызывать потребность в неуклюжих частных «вспомогательных» методах больше, чем потребность в анонимных областях. Мы слишком много думаем о коде, как он есть сейчас, и о том, как уменьшить область видимости переменных, а не о том, как концептуально решить проблему на уровне интерфейса способами, которые естественным образом обеспечивают чистую, короткую видимость состояний локальных функций без глубокого вложения блоков. и 6+ уровней отступа. Я согласен с Бруно в том, что вы можете помешать удобочитаемости кода, принудительно вставляя 3 строки кода в функцию, но это начинается с предположения о том, что вы развиваете функции, которые вы создаете на основе существующей реализации, вместо того, чтобы проектировать функции без запутывания в реализациях. Если вы сделаете это последним способом, я обнаружил небольшую потребность в анонимных блоках, которые не преследуют никакой цели, кроме сокращения области видимости переменной в данном методе, если только вы не ревностно пытаетесь уменьшить область действия переменной всего на несколько строк безвредного кода где экзотическое введение этих анонимных блоков, возможно, вносит столько интеллектуальных издержек, сколько и устраняет.
Попытка уменьшить минимальные области еще больше
Если целесообразно сократить область видимости локальных переменных до абсолютного минимума, тогда должно быть широкое признание такого кода:
... поскольку это приводит к минимальной видимости состояния, даже не создавая переменные для ссылки на них в первую очередь. Я не хочу показаться догматичным, но на самом деле я думаю, что прагматичное решение состоит в том, чтобы избегать анонимных блоков, когда это возможно, точно так же, как избегать чудовищной строки кода выше, и если они кажутся абсолютно необходимыми в производственном контексте из с точки зрения корректности и поддержки инвариантов внутри функции, тогда я определенно думаю, что стоит разобрать, как вы организуете свой код в функции и разрабатываете свои интерфейсы. Естественно, если ваш метод имеет длину 400 строк, а область видимости переменной на 300 строк кода больше, чем нужно, это может стать настоящей инженерной проблемой, но это не обязательно проблема с анонимными блоками.
Если ничто иное, использование анонимных блоков повсюду - это экзотика, а не идиоматизм, а экзотический код несет в себе риск быть ненавидимым другими, если не вами, годы спустя.
Практическая полезность уменьшения объема
Конечная полезность сокращения области видимости переменных состоит в том, чтобы вы могли правильно управлять состоянием и сохранять его корректным, а также легко рассуждать о том, что делает любая конкретная часть кодовой базы - чтобы иметь возможность поддерживать концептуальные инварианты. Если локальное управление состоянием отдельной функции настолько сложно, что вам нужно принудительно уменьшить область действия с помощью анонимного блока в коде, который не предназначен для доработки и исправления, тогда, опять же, это признак того, что сама функция должна быть пересмотрена , Если вам сложно рассуждать об управлении состоянием переменных в локальной области функций, представьте себе сложность рассуждения о частных переменных, доступных для каждого метода целого класса. Мы не можем использовать анонимные блоки, чтобы уменьшить их видимость. Для меня это помогает начать с принятия того факта, что переменные, как правило, имеют немного более широкую область действия, чем они в идеале должны иметь во многих языках, при условии, что они не выйдут из-под контроля до такой степени, что вам будет трудно поддерживать инварианты. Это не то, что нужно решать столько с анонимными блоками, сколько я вижу это как принятие с прагматической точки зрения.
источник
Я думаю, что мотивация является достаточным основанием для введения блока, да.
Как заметил Кейси, блок - это «запах кода», который указывает на то, что вам лучше извлечь блок из функции. Но вы не можете.
Джон Кармарк написал памятку о встроенном коде в 2007 году. Его заключительное резюме
Поэтому подумайте , сделайте выбор с целью и (необязательно) оставьте доказательства, описывающие вашу мотивацию для сделанного вами выбора.
источник
Мое мнение может быть спорным, но да, я думаю, что, безусловно, бывают ситуации, когда я бы использовал этот стиль. Однако «просто уменьшить область действия переменной» не является допустимым аргументом, потому что это то, что вы уже делаете. И делать что-то только для этого не является веской причиной. Также обратите внимание, что это объяснение не имеет значения, если ваша команда уже решила, является ли этот тип синтаксиса традиционным или нет.
Я в первую очередь программист на C #, но я слышал, что в Java возвращают пароль,
char[]
который позволяет сократить время пребывания пароля в памяти. В этом примере это не поможет, поскольку сборщику мусора разрешено собирать массив с того момента, когда он не используется, поэтому не имеет значения, выходит ли пароль из области действия. Я не спорю, является ли это жизнеспособным, чтобы очистить массив после того, как вы закончили с ним, но в этом случае, если вы хотите сделать это, определение области действия имеет смысл:Это действительно похоже на оператор try-with-resources , поскольку он ограничивает ресурс и завершает его после того, как вы закончите. Опять же, обратите внимание, что я не спорю о паролях подобным образом, просто если вы решите это сделать, этот стиль будет разумным.
Причина этого в том, что переменная больше не действительна. Вы создали его, использовали его и сделали недействительным его состояние, чтобы оно не содержало значимой информации. Не имеет смысла использовать переменную после этого блока, поэтому разумно использовать ее.
Другой пример, который я могу вспомнить, - это когда у вас есть две переменные, которые имеют одинаковое имя и значение, но вы работаете с одной, а затем с другой, и вы хотите сохранить их отдельно. Я написал этот код на C #:
Вы можете утверждать, что я могу просто объявить
ILGenerator il;
в начале метода, но я также не хочу повторно использовать переменную для различных объектов (своего рода функциональный подход). В этом случае блоки облегчают разделение выполняемых заданий как синтаксически, так и визуально. Это также говорит, что после блока я закончил,il
и ничто не должно получить к нему доступ.Аргумент против этого примера - использование методов. Возможно, да, но в этом случае код не такой длинный, и разделение его на разные методы также должно было бы передать все переменные, которые используются в коде.
источник