Retorno booleano de set.add () em se condicional?

15

O operador add da classe set retorna um booleano que é verdadeiro se o elemento (que deve ser adicionado) já não estava lá e falso caso contrário. Está escrevendo

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

considerado bom estilo em termos de escrita de código limpo? Eu estou pensando desde que você faz duas coisas ao mesmo tempo. 1) adicionando o elemento e 2) verificando se o elemento existia.

Andreas Braun
fonte
6
Você está falando sobre o padrão java.util.Set, que retorna verdadeiro addquando o elemento já não estava lá, certo?
User2357112 suporta Monica
1
Eu geralmente consideram o teste oposto:if (!set.add(entry)) {// entry already present, possibly a case you want to handle}
njzk2
Há algo errado em fazer duas coisas ao mesmo tempo?
user207421
@ user2357112 sim, está certo.
Andreas Braun

Respostas:

19

Sim, ele é.

O ponto usual de uma operação retornar um valor booleano é que você pode usá-lo para tomar decisões, ou seja, dentro de uma ifconstrução. A única outra maneira de realizar esse potencial seria armazenar o valor de retorno em uma variável e, em seguida, reutilizá-lo imediatamente em um if, o que é simplesmente bobo e certamente não preferível à escrita if(operation()) { ... }. Então vá em frente e faça isso, não vamos julgá-lo (por isso).

Kilian Foth
fonte
Obrigado pela sua resposta. Como @Niels vanReijmersdal aponta em sua resposta, isso quebra o comando e consulta a separação, não é? Você não acha que isso importa?
Andreas Braun
4
@AndreasBraun pessoalmente, acho que a separação da consulta de comando é burra. Geralmente, é lógico que um método execute uma ação e retorne um valor relacionado a essa ação. A imposição de uma separação artificial entre os dois leva a códigos desnecessariamente detalhados.
1
@ dan1111: de fato. Além disso, a “separação de comandos e consultas” leva ao “check then act” e a antipadrões similares, que podem ocorrer em contextos multithread. É um pouco estranho para anunciar uma separação tal, quando o resto do mundo está tentando construir operações fundidos atômicas para soluções SMP robustos e eficientes, ao mesmo tempo ...
Holger
2
@ Jörg W Mittag: página 759 de quê? Como uma operação atômica é intrinsecamente imune ao antipadrão "verifique e aja", não parece benéfico dividi-la apenas para ler um "capítulo inteiro" de tudo o que descobrir, como tornar o thread de construção seguro novamente. Como você não nomeou nenhum argumento real, não tenho “objeções específicas a esses argumentos”.
Holger
1
@ Jörg W Mittag: Bem, eu estou falando sobre a resposta nesta página, desencorajando o uso Set.addcomo operação única sem nomear uma alternativa. Se a operação pretendida for adicionar um único elemento e descobrir se foi adicionado, não há necessidade de uma alternativa mais poderosa que ainda complique a operação sem benefício. Espero que você esteja ciente de que Set.addé um método JRE embutido que pode ser usado sem antes estudar um livro com mais de 700 páginas.
Holger
12

Eu diria que não é o mais limpo possível, porque força o mantenedor a já saber ou procurar o que o valor de retorno significa. Isso significa que o valor já existia, não existia, foi inserido com sucesso? Se você não o usa muito, não saberá, e mesmo se o fizer, é muito mais carga mental.

Eu preferiria o seguinte:

boolean added = set.add(entry);

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

Sim, um pouco mais detalhado, mas o compilador deve gerar exatamente o mesmo bytecode, e mesmo pessoas que não usam conjuntos de Java há anos podem seguir a lógica sem procurar nada.

Karl Bielefeldt
fonte
2
addretorna true se o elemento já não estava lá, não se estivesse. (Redação da pergunta é enganosa.)
user2357112 suporta Monica
9
Procurar um método é menos oneroso do que tentar entender a intenção do desenvolvedor original de uma variável redundante declarada fora do escopo em que é usada. Em todos os Java IDEs modernos, um desenvolvedor pode passar o ponteiro do mouse sobre um método e acessar instantaneamente sua documentação. Bons desenvolvedores fazem isso constantemente ao trabalhar com qualquer API desconhecida.
Kevin Krumwiede
9
Eu segundo @Holger. Se você não sabe Set.add, é inexperiente e deve procurar esses métodos para aprendê-los e se tornar mais experiente.
Restabeleça Monica
7
notAlreadyPresentnão é a melhor expressão. Eu usaria addede espero que o leitor saiba por que um valor não seria adicionado a um Conjunto.
Njzk2
3
@ JustinTime: Usar comentários para descrever o que o código faz é amplamente considerado uma prática ruim. Seu código deve ser auto-descritivo. Em uma revisão de código, eu sinalizaria seu exemplo como inaceitável.
BlueRaja # Danny Pflughoeft
8

Se verdadeiro significa sucesso, é um código bom e claro.

Existe uma convenção generalizada de que uma função ou método retorna true (ou algo que é avaliado como true) com êxito. Desde que seu código siga isso, acho que colocar o método na condicional é bom.

Código como este é desnecessariamente confuso na minha opinião:

boolean frobulate_succeeded = thing.frobulate();

if (frobulate_succeeded) {
    ...
}

Parece que você está se repetindo.

No entanto, a pergunta é ambígua sobre o significado do valor de retorno. Você diz "um booleano que indica se o elemento adicionado já existia", o que pode implicar que true significa que o elemento existia (e a adição não ocorreu). Se for esse o caso, eu idealmente mudaria o comportamento de retorno do método para ser mais convencional. Se isso não for possível, eu adicionaria uma variável intermediária extra que permite rotular claramente o resultado de retorno no seu código (conforme sugerido por outras pessoas).


fonte
O que significa sucesso no contexto de adição de um item a um conjunto? Não lançar uma exceção significa sucesso; verdadeiro vs. falso significa algo mais específico neste contexto.
Restabelecer Monica
@ Solomonoff'sSecret Nesse caso, uma exceção significa que o conjunto se recusou a adicionar o elemento, falsesignifica que o elemento não foi adicionado porque já estava contido e truesignifica que o elemento ainda não estava contido. Como add()adiciona o elemento ao conjunto, se o conjunto ainda não o contiver, truesignifica que o elemento foi adicionado com êxito ao conjunto.
Justin Time - Restabelece Monica
@JustinTime Você está adicionando seus próprios julgamentos de valor aos dois casos. Outra interpretação é que o sucesso é que o item está no conjunto quando o método retorna. Se o item já estava no conjunto, será addbem-sucedido sem ter que fazer nada. Se o item ainda não estava no conjunto, será possível addadicionar o item ao conjunto. Qual interpretação está correta é arbitrária. A definição menos arbitrária de sucesso é a da linguagem: o método será bem-sucedido se retornar normalmente.
Restabeleça Monica
1
A definição menos arbitrária de sucesso é que o método executou com êxito a tarefa que se propôs a executar. Se eu tiver um método firstLessThanSecond(int l, int r)e retornar truese l > rou falsese l <= r, esse método não será bem-sucedido, mesmo que retorne normalmente.
Justin Time - Restabelece Monica
1
@JustinTime addé uma abreviação grave do contrato. A documentação começa com "Adiciona o elemento especificado a este conjunto, se ele ainda não estiver presente", que é satisfeito se o item já estava no conjunto ou não. Você é livre para interpretar o valor de retorno da maneira que desejar, mas, no final das contas, é apenas sua interpretação. E no seu segundo exemplo, o método avalia se uma condição é verdadeira. Se a condição for falsa, o método certamente não falhou, pois avaliou com êxito a condicional.
Restabelecer Monica
2

Eu diria que é muito parecido com um C. Na maioria das vezes, eu preferiria ter uma variável com nome descritivo para um resultado de mutação e nenhuma mutação acontecendo em uma ifcondição.

Um compilador eliminará essa variável se for reutilizada imediatamente. Um ser humano terá mais facilidade em ler a fonte; para mim, é mais importante.

Alguém deve estender a condição adicionando um andor cláusula / à condição if, pode acabar não ligando .add()em certos casos devido à avaliação de curto-circuito. A menos que o curto-circuito seja especificamente previsto, isso pode acabar como um bug.

9000
fonte
Se eu escrever, if (set.contains(entry)){set.add(entry); //do more stuff}isso também será eliminado pelo compilador?
Andreas Braun
1
@AndreasBraun: Acho que não; o compilador não tem idéia sobre o link semântico entre containse add. Além disso, ele duplica o trabalho de procurar entryno conjunto; isso pode desempenhar um papel para conjuntos muito grandes e loops muito leves.
9000
1
Entendo que você prefere não escrever, if (set.contains(entry)){set.add(entry); //do more stuff}mas sim, por exemplo, com a resposta de Karl Bielefeldt?
Andreas Braun
@AndreasBraun: exatamente (votou positivamente nessa resposta).
9000
1
um bom argumento. Mutações nos ifs são complicadas, pois um desenvolvedor futuro pode ter outras condições com curtos-circuitos.
Njzk2
0

Seu código parece interromper a separação de consultas de comando . Isso é discutido no livro Código Limpo e no vídeo Estrutura da Função . Então, da perspectiva do Código Limpo, acho que não é considerado um bom estilo.

“O código limpo é simples e direto. Código limpo parece uma prosa bem escrita. O código limpo nunca obscurece a intenção do designer, mas é cheio de abstrações nítidas e linhas de controle diretas.

- Grady Booch, autor de Análise Orientada a Objetos e Design com Aplicações ”

Para mim, a intenção do seu código não é clara. Os dois são executados quando a entrada é adicionada com êxito ou também quando ela já existe? O que add()retorna? o item? O código de erro?

Niels van Reijmersdal
fonte
2
Bem, sim, foi o que eu pensei também. Mas também acho que a @Kilian Foth tem razão. Se eu quisesse separar consultas e comandos, teria que escrever o if (set.contains(entry)){set.add(entry); //do more stuff}que parece meio bobo. Qual é a sua opinião sobre isso?
Andreas Braun
Não conheço o domínio e o contexto deste código para sugerir um bom nome, mas gostaria de envolvê-lo em uma função com intenção clara. Por exemplo: doesEntryExistOrCreateEntry (entry), isso pode conter sua lógica contains () + add () e retornar um booleano. Pergunta semelhante aqui: softwareengineering.stackexchange.com/questions/149708/…, que discute essa prática fora do código limpo.
Niels van Reijmersdal
6
Eu acho que a regra de separação de consulta de comando é burra, principalmente quando aplicada a um caso como este, onde um método executa uma ação e retorna o estado de sucesso da ação. Mas também, você não explica qual é a regra nem fornece muita justificativa para isso. Votado por esses motivos.
1
"O que add () retorna?" Espero que qualquer desenvolvedor Java que faça revisão de código conheça a CollectionAPI.
Njzk2
3
De acordo com @ dan1111. É especialmente idiota porque é o tipo de coisa que cria terreno fértil para as condições da corrida.
Paul Draper