Булево возвращение set.add (), если условно?

15

Оператор add класса set возвращает логическое значение, которое имеет значение true, если элемент (который должен быть добавлен) еще не существует, и false в противном случае. Пишет

if (set.add(entry)) {
    //do some more stuff
}

считается хорошим стилем с точки зрения написания чистого кода? Мне интересно, так как вы делаете две вещи одновременно. 1) добавление элемента и 2) проверка существования элемента.

Андреас Браун
источник
6
Вы говорите о стандарте java.util.Set, который возвращает true, addкогда элемент еще не был там, верно?
user2357112 поддерживает Monica
1
Я бы обычно рассматривал противоположный тест:if (!set.add(entry)) {// entry already present, possibly a case you want to handle}
njzk2
Есть ли что-то не так с выполнением двух вещей одновременно?
user207421
@ user2357112 да, все верно.
Андреас Браун

Ответы:

19

Да, это так.

Обычный момент, когда операция возвращает логическое значение, заключается в том, что вы можете использовать его для принятия решений, то есть внутри ifконструкции. Единственный способ реализовать этот потенциал - это сохранить возвращаемое значение в переменной, а затем сразу же повторно использовать его в переменной if, что просто глупо и, безусловно, ни в коем случае не предпочтительнее записи if(operation()) { ... }. Так что просто продолжайте и сделайте это, мы не будем судить вас (за это).

Килиан Фот
источник
Спасибо за Ваш ответ. Как @Niels vanReijmersdal в своем ответе указывает, что это нарушает разделение команд и запросов, не так ли? Тебе не кажется, что это имеет значение?
Андреас Браун
4
@AndreasBraun лично я думаю, что разделение командных запросов глупо. Часто логично, чтобы один метод выполнял действие и возвращал значение, связанное с этим действием. Обеспечение искусственного разделения между ними приводит к излишне многословному коду.
1
@ Dan1111: действительно. Кроме того, «разделение команд и запросов» приводит к «проверке, затем действию» и аналогичным анти-шаблонам, которые могут нарушаться в многопоточных контекстах. Довольно странно рекламировать такое разделение, когда остальной мир пытается одновременно создать атомные операции для надежных и эффективных решений SMP…
Хольгер,
2
@ Йорг Миттаг: страница 759 чего? Так как атомарная операция по своей природе защищена от паттерна «проверь, а потом действуй», не кажется выгодным разделить его на части, чтобы просто прочитать «целую главу» того, что нужно выяснить, как сделать поток конструкции безопасным опять таки. Поскольку вы не назвали никаких реальных аргументов, у меня нет «особых возражений против этих аргументов».
Хольгер
1
@ Jörg W Mittag: Ну, я говорю об ответе на этой странице, не одобряя использование в Set.addкачестве одной операции без указания альтернативы. Если предполагаемая операция заключается в добавлении одного элемента и выяснении, был ли он добавлен, то нет необходимости в более мощной альтернативе, которая все еще усложняет операцию без выгоды. Я надеюсь, что вы знаете, что Set.addэто встроенный метод JRE, который можно использовать без предварительного изучения книги на> 700 страницах.
Хольгер
12

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

Я бы предпочел следующее:

boolean added = set.add(entry);

if (added) {
    //do some more stuff
}

Да, немного более многословно, но компилятор должен генерировать в значительной степени тот же самый байт-код, и даже люди, которые годами не использовали наборы Java, могут следовать логике, не ища ничего.

Карл Билефельдт
источник
2
addвозвращает true, если элемент не был там, а не если он был. (Формулировка вопроса вводит в заблуждение.)
user2357112 поддерживает Monica
9
Поиск метода менее трудоемок, чем попытка понять намерение первоначального разработчика создать избыточную переменную, объявленную вне области, в которой он используется. Во всех современных Java IDE разработчик может навести указатель мыши на метод и мгновенно получить доступ к его документации. Хорошие разработчики делают это постоянно при работе с любым незнакомым API.
Кевин Крумвиде
9
Я второй @Holger. Если вы не знаете Set.add, вы неопытны, и вы должны искать эти методы, чтобы изучить их и стать более опытным.
Восстановите Монику
7
notAlreadyPresentне лучшая формулировка. Я бы использовал addedи ожидал, что читатель узнает, почему значение не будет добавлено в набор.
njzk2
3
@JustinTime: использование комментариев для описания того, что делает код, считается плохой практикой. Ваш код должен быть самоописанным. В обзоре кода я бы отметил ваш пример как неприемлемый.
BlueRaja - Дэнни Пфлюгофт
8

Если true означает успех, то это хороший, понятный код.

Существует широко распространенное соглашение, что функция или метод возвращает true (или что-то, что оценивается как true) в случае успеха. Пока ваш код следует этому, я думаю, что помещать метод в условное выражение - это нормально.

Код, подобный этому, излишне загроможден, на мой взгляд:

boolean frobulate_succeeded = thing.frobulate();

if (frobulate_succeeded) {
    ...
}

Такое чувство, что ты повторяешься.

Однако вопрос о значении возвращаемого значения неоднозначен. Вы говорите «логическое значение, которое указывает, существовал ли добавленный элемент», что может означать, что true означает, что элемент существовал (а добавление не произошло). Если это так, я бы в идеале изменил поведение метода при возврате, чтобы оно было более привычным. Если это невозможно, я бы добавил дополнительную промежуточную переменную, которая позволит вам четко обозначить возвращаемый результат в вашем коде (как это предлагают другие).


источник
Что означает успех в контексте добавления элемента в набор? Не выбрасывать исключение означает успех; true против false означает нечто более конкретное в этом контексте.
Восстановите Монику
@ Solomonoff'sSecret В этом случае исключение означает, что набор отказался добавить элемент, falseозначает, что элемент не был добавлен, потому что он уже содержался, и trueозначает, что элемент еще не содержался. As add()добавляет элемент в набор, если набор еще не содержит его, trueпоэтому означает, что элемент был успешно добавлен в набор.
Джастин Тайм - Восстановить Монику
@JustinTime Вы добавляете свои собственные оценочные суждения к двум случаям. Другая интерпретация заключается в том, что успех заключается в том, что элемент находится в наборе при возврате метода. Если предмет уже был в наборе, то addуспешно ничего не нужно делать. Если элемент еще не был в наборе, addуспешно, добавив элемент в набор. Какая интерпретация правильная, произвольно. Менее произвольное определение успеха - это определение языка: метод завершается успешно, если он возвращается нормально.
Восстановить Монику
1
Наименее произвольное определение успеха состоит в том, что метод успешно выполнил задачу, которую он намеревался выполнить. Если у меня есть метод firstLessThanSecond(int l, int r), и он возвращает, trueесли l > rили falseесли l <= r, то этот метод не является успешным, даже если он возвращает нормально.
Джастин Тайм - Восстановить Монику
1
@JustinTime add- серьезное сокращение контракта. Документация начинается с «Добавляет указанный элемент к этому набору, если он еще не существует», который удовлетворяет, был ли элемент уже в наборе. Вы можете интерпретировать возвращаемое значение так, как хотите, но в конечном итоге это только ваша интерпретация. А во втором примере метод оценивает, является ли условие истинным. Если условие ложно, метод определенно не потерпел неудачу, так как он успешно оценил условие.
Восстановите Монику
2

Я бы сказал, что это очень C-like. Большую часть времени я бы предпочел иметь переменную с описательным именем для результата мутации, а в ifусловии не было бы никакой мутации .

Компилятор исключит эту переменную, если она будет немедленно использована повторно. Человеку будет легче читать источник; для меня это важнее.

Если кому-то придется расширить условие, добавив условие and/ orк условию if, .add()в некоторых случаях он может не вызывать из-за оценки короткого замыкания. Если короткое замыкание не предусмотрено специально, это может привести к ошибке.

9000
источник
Если я напишу if (set.contains(entry)){set.add(entry); //do more stuff}, это также устранено компилятором?
Андреас Браун
1
@AndreasBraun: я так не думаю; компилятор не имеет представления о семантической связи между containsи add. Кроме того, он выполняет двойную работу по поиску entryв наборе; это может играть роль для очень больших наборов и очень легких петель.
9000
1
Я так понимаю, вы бы предпочли не писать, if (set.contains(entry)){set.add(entry); //do more stuff}а вместо этого использовать, например, ответ Карла Билефельда?
Андреас Браун
@AndreasBraun: точно (проголосовал за этот ответ).
9000
1
хороший момент. Мутации в ifs хитры, поскольку у будущего разработчика могут быть другие условия с короткими замыканиями.
njzk2
0

Кажется, ваш код нарушает разделение командных запросов . Это обсуждается в книге « Чистый код» и в видео « Структура функций» . Так что с точки зрения чистого кода я думаю, что это не считается хорошим стилем.

«Чистый код прост и понятен. Чистый код читается как хорошо написанная проза. Чистый код никогда не затеняет замысел дизайнера, а полон четких абстракций и простых линий контроля.

- Грэди Буч, автор «Объектно-ориентированного анализа и проектирования с приложениями»

Для меня цель вашего кода неясна. Выполняется ли if оба при успешном добавлении записи или когда она уже существует? Что add()возвращает? пункт? Код ошибки?

Нильс ван Реймерсдал
источник
2
Ну да, я тоже так думал. Но я также думаю, что @Kilian Foth имеет смысл. Если бы я хотел разделить запрос и команду, мне пришлось бы написать if (set.contains(entry)){set.add(entry); //do more stuff}что-то вроде глупости. Что вы думаете об этом?
Андреас Браун
Я не знаю домен и контекст этого кода, чтобы предложить хорошее имя, но я бы обернул это в функцию с ясным намерением. Например: didEntryExistOrCreateEntry (entry), это может содержать вашу логику contains () + add () и возвращать логическое значение. Подобный вопрос здесь: softwareengineering.stackexchange.com/questions/149708/…, который обсуждает эту практику вне чистого кода.
Нильс ван Реймерсдал
6
Я думаю, что правило разделения командных запросов глупо, особенно применительно к такому случаю, когда метод выполняет действие и возвращает состояние успеха действия. Но вы также не объясняете, что это за правило, или не объясняете его. Проголосовал по этим причинам.
1
"Что возвращает add ()?" Я ожидаю, что любой Java-разработчик, который делает обзор кода, знает CollectionAPI.
njzk2
3
Договорились с @ dan1111. Это особенно глупо, потому что именно такие вещи создают почву для условий гонки.
Пол Дрейпер