Projetando métodos relacionados ao banco de dados, o melhor é retornar: verdadeiro / falso ou linha afetada?

10

Eu tenho alguns métodos que executam algumas alterações de dados em um banco de dados (inserir, atualizar e excluir). O ORM que estou usando retorna valores int afetados por linha para esse tipo de método. O que devo retornar para "meu método", a fim de indicar o estado de sucesso / falha da operação?

Considere o código que está retornando um int:

A.1

public int myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows;
}

Então uso:

A.2

public void myOtherMethod() {
    ...
    int affectedRows = myLowerLevelMethod(id)

    if(affectedRows > 0) {
        // Success
    } else {
        // Fail
    }
}

Compare com o uso de booleano:

B.1

public boolean myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows > 0;
}

Então uso:

B.2

public void myOtherMethod() {
    ...
    boolean isSuccess = myLowerLevelMethod(id)

    if(isSuccess) {
        // Success
    } else {
        // Fail
    }
}

Qual (A ou B) é melhor? Ou prós / contras de cada um?

Hoang Tran
fonte
No seu "A.2". Se zero linhas são afetadas, por que isso ocorre uma falha se zero linhas precisam ser afetadas? Em outras palavras, se não houver um erro no banco de dados, por que ocorre uma falha?
21413 Jaydee
5
Existe uma diferença semântica entre "sem êxito" e "zero linhas afetadas"? Por exemplo, ao excluir todos os pedidos de um cliente, há uma diferença entre "o cliente não existe" e "o cliente não tem pedidos".
Cefalópode
Você considera uma exclusão com zero linhas inesperada? Nesse caso, jogue.
usr
@ Arian Acho que essa é a verdadeira questão para mim. Eu acho que eu escolhi B, porque com A, o meu código agora contém a verificação de 0 em alguns lugares e para -1 em outros
Hoang Tran

Respostas:

18

Outra opção é retornar um objeto de resultado em vez de tipos básicos. Por exemplo:

OperationResult deleteResult = myOrm.deleteById(id);

if (deleteResult.isSuccess()) {
    // ....
}

Com isso, se, por algum motivo, você precisar retornar o número de linhas afetadas, basta adicionar um método em OperationResult:

if (deleteResult.isSuccess()) {
    System.out.println("rows deleted: " + deleteResult.rowsAffected() );
}

Esse design permite que seu sistema cresça e inclua novas funcionalidades (conhecimento sobre as linhas afetadas) sem modificar o código existente.

AlfredoCasado
fonte
2
+1 "Esse design permite que seu sistema cresça e inclua novas funcionalidades (conhecendo as linhas afetadas) sem modificar o código existente" Esta é a maneira correta de pensar sobre esta categoria de perguntas.
InformedA
Eu fiz coisa semelhante no meu projeto antigo. Eu tenho o objeto Wrapper Request e Result. Ambos usam Composições para incluir mais detalhes. e ambos possuem dados básicos também. Nesse caso, o objeto Result possui um código de status e um campo msg.
InformedA
Especialmente para um sistema que usa um ORM, eu chamaria isso de melhor prática. O ORM em questão também já pode incluir esse tipo de objeto de resultado!
Brian S
Apenas olhando o exemplo dos OPs, tudo o que consigo pensar é que deleteById retornará 2 em algum momento :) então, definitivamente, um tipo personalizado, sim.
deadsven
Eu gosto dessa abordagem. Eu acho que é sem bloqueio, abrange as duas abordagens e modificável / expansível (no futuro, se necessário). A única desvantagem em que consigo pensar é que é um pouco mais de código, especialmente quando aparece no meio do meu projeto. Vou marcar isso como a resposta. Obrigado.
Hoang Tran
12

Retornar o número de linhas afetadas é melhor porque fornece informações adicionais sobre como a operação prosseguiu.

Nenhum programador o culpará porque ele / ela deve escrever isso para verificar se houve algumas alterações durante a operação:

if(affectedRows > 0) {
    // success
} else {
    // fail
}

mas eles o culparão quando precisarem saber o número de linhas afetadas e perceberem que não há método para obter esse número.

BTW: se por "falha" você quer dizer um erro de consulta sintática (nesse caso, o número de linhas afetadas é obviamente 0), lançar a exceção seria mais apropriado.

Bobby
fonte
4
-1 por causa do motivo que você deu. Fornecer informações adicionais nem sempre é uma boa ideia. Muitas vezes, um design melhor levava a comunicar ao chamador exatamente o que ele precisava e nada mais.
Arseni Mourzenko
1
O @MaainMa nada impede a criação de métodos sobrecarregados. Além disso, em idiomas nativos (família C, por exemplo), o número de linhas pode ser usado diretamente na lógica (0 é falso, qualquer outra coisa é verdadeira).
PTwr
@PTwr: para lidar com o fato de o método estar retornando muitas informações, você sugere criar uma sobrecarga? Isso não parece certo. Quanto a "zero é falso, diferente de zero é verdadeiro", esse não é o objetivo do meu comentário e é uma prática ruim em alguns idiomas.
Arseni Mourzenko
1
Provavelmente, sou um dos programadores mimados, pois não acho que usar truques possa ser considerado uma boa prática. De fato, sempre que tenho que lidar com o código de outra pessoa, sou grato a quem o escreveu e não empregou nenhum truque.
proskor
4
Você sempre deve retornar o número de linhas afetadas aqui !!! Como você não pode saber se o número de linhas = 0 é uma falha ou se o número de linhas = 3 é um sucesso? Se alguém quiser inserir 3 linhas e apenas 2 forem inseridas, você retornará true, mas não está certo! E se alguém quiser update t set category = 'default' where category IS NULLaté 0 linhas afetadas seria um sucesso, porque agora não há item sem categoria, mesmo que nenhuma linha tenha sido afetada!
Falco
10

Eu não recomendaria nenhum deles. Em vez disso, não retorne nada (nulo) ao sucesso e lance uma exceção ao falhar.

É exatamente pelo mesmo motivo que escolhi declarar certos membros da classe como particulares. Também torna a função mais fácil de usar. Contexto mais operacional nem sempre significa melhor, mas certamente significa mais complexo. Quanto menos você promete, quanto mais abstrai, mais fácil é para o cliente entender e mais liberdade você tem para escolher como implementá-lo.

A questão é como indicar sucesso / erro. Nesse caso, basta sinalizar a falha lançando uma exceção e não retornando nada com êxito. Por que tenho que fornecer mais do que o usuário precisa?

Falhas / situações excepcionais podem acontecer e você terá que lidar com elas. Se você usa try / catch para fazer isso ou examina os códigos de retorno, é uma questão de estilo / preferência pessoal. As idéias por trás do try / catch são: separar o fluxo normal do fluxo excepcional e permitir que as exceções borbulhem até a camada em que possam ser tratadas da maneira mais adequada. Portanto, como muitos já apontaram, depende se a falha é realmente excepcional ou não.

proskor
fonte
4
-1 Por que você não retornaria nada em que pudesse retornar algo, sem efeitos colaterais negativos, que forneça contexto operacional extra?
FreeAsInBeer
7
Pelo mesmo motivo, escolhi declarar particulares membros da classe. Também torna a função mais fácil de usar. Contexto mais operacional nem sempre significa melhor, mas certamente significa mais complexo. Quanto menos você promete, quanto mais abstrai, mais fácil é para o cliente entender e mais liberdade você tem para escolher como implementá-lo.
proskor
1
Bem, quais dados o usuário realmente precisa obter? A questão é como indicar sucesso / erro. Nesse caso, basta sinalizar a falha lançando uma exceção e não retornando nada com êxito. Por que tenho que fornecer mais do que o usuário precisa?
proskor
4
@ Proskor: Exceções são para casos excepcionais. "Falha" neste cenário pode ser um resultado esperado. De qualquer forma, coloque isso como uma alternativa em potencial, mas não há informações suficientes aqui para fazer uma recomendação.
22414 Nick Barnes
1
-1 Exceções não devem fazer parte do fluxo normal do programa. Não está claro o que "falha" significa no contexto do seu erro, mas uma exceção no contexto de uma chamada ao banco de dados deve ser devido a uma exceção que ocorre no banco de dados. Afetar zero linhas não deve ser uma exceção. Uma consulta desconfigurada que não pode ser analisada, faz referência a uma tabela inexistente etc. seria uma exceção, porque o mecanismo de banco de dados se engasgaria e lançaria.
2

"Isso é melhor que isso?" Não é uma pergunta útil quando as duas alternativas não fazem a mesma coisa.

Se você precisar conhecer a contagem de linhas afetadas, deverá usar a versão A. Se não precisar, poderá usar a versão B - mas qualquer vantagem que possa obter em termos de menos esforço para escrever código já desapareceu desde você teve o trabalho de publicar as duas versões em um fórum online!

O que quero dizer é: qual solução é melhor depende inteiramente de quais são seus requisitos especificamente para este aplicativo e você conhece essas circunstâncias muito melhor do que nós. Não existe uma opção de mercado, prática segura, prática recomendada, que não seja demitida sobre ela que seja melhor em geral ; você tem que pensar sobre isso sozinho. E para uma decisão tão facilmente revisada quanto esta, você também não precisa gastar tanto tempo pensando.

Kilian Foth
fonte
Concordo que isso depende muito dos requisitos do aplicativo. No entanto, parece que esse tipo de situação não é muito singular, e eu não estou procurando uma bala de prata, mas apenas a experiência de outras pessoas lidando com a mesma coisa / similar (talvez a pergunta seja um pouco enganadora, eu gosto de sugestões diferentes de A / B)
Hoang Tran
1

Dois dos princípios mais importantes no design de software sustentável são o KISS e o YAGNI .

  • BEIJO : Mantenha-o simples, estúpido
  • YAGNI : Você não precisa disso

Quase nunca é uma boa idéia colocar na lógica que você não precisa imediatamente no momento . Entre muitas outras pessoas, Jeff Atwood (co-fundador do StackExchange) escreveu sobre isso e, na minha experiência, ele e outros defensores desses conceitos estão completamente certos.

Qualquer complexidade que você adicionar a um programa tem um custo, pago por um longo período de tempo. O programa se torna mais difícil de ler, mais complexo para alterar e mais fácil para os bugs aparecerem. Não caia na armadilha de adicionar coisas "apenas por precaução". É uma falsa sensação de segurança.

Você raramente consegue obter um código correto da primeira vez. Mudanças são inevitáveis; adicionar lógica especulativa para se preparar defensivamente para contingências futuras desconhecidas não o protegerá de refatorar seu código quando o futuro for diferente do esperado. A manutenção de lógicas desnecessárias / contingentes é mais um problema de manutenção do que refatorar posteriormente para adicionar a funcionalidade ausente.

Portanto, como parece que tudo o que seu programa precisa agora é saber se a operação foi bem-sucedida ou falhou, a solução proposta B (retornar um único booleano) é a abordagem correta. Você sempre pode refatorá-lo mais tarde se o requisito for alterado. Esta solução é a mais simples e tem a menor complexidade (KISS) e faz exatamente o que você precisa e nada mais (YAGNI).

Ben Lee
fonte
-1

As linhas inteiras ou um status de erro

Considere retornar as linhas inteiras, pelo menos como uma opção de tempo de execução. Nas inserções de banco de dados, talvez seja necessário inspecionar os dados que foram inseridos - pois geralmente serão diferentes do que você enviou para o banco de dados; exemplos comuns incluem IDs de linha gerados automaticamente (que o aplicativo provavelmente precisará imediatamente), valores padrão determinados pelo DB e resultados de acionadores, se você os usar.

Por outro lado, se você não precisar dos dados retornados, também não precisará da contagem de linhas afetadas, pois não é útil para o resultado de 0. Se houver erros, será necessário retornar que tipo de ocorreu um erro, de maneira consistente com os princípios de tratamento de erros do seu projeto (exceções, códigos de erro numéricos, qualquer que seja); mas há consultas válidas que afetarão corretamente 0 linhas (por exemplo, "excluir todos os pedidos expirados", se realmente não houver).

Peter é
fonte