Estou em uma situação difícil. Estou trabalhando em um repositório que faz muitas alterações no banco de dados em suas funções.
A função com a qual estou lidando retorna responseids (mas de uma transformação, não de qualquer ação de banco de dados). No entanto, como efeito colateral, ele adiciona um objeto contendo esses responseIds ao banco de dados.
Então, devo nomeá-lo:
-
getResponseIds
: Isso destaca os valores de retorno. É uma maneira muito funcional de pensar, mas obviamente, se eu tiver a funçãopostToDB
, não faz sentido usargetStatusOfPost
-
addResponseIdToDB
: Isso destaca o efeito colateral, embora eu realmente pense que muitas das minhas funções operam apenas no banco de dados (e tendem a não retornar nada) -
getAndAddResponseIdsToDB
: Muito informativo, mas muito longo.
Quais são os prós e os contras das sugestões acima? Ou você mesmo pode fazer uma sugestão melhor?
Comentários
Resposta
Um nome de função que contém and
está no nível incorreto de abstração .
Eu me inclino para addResponseIdToDB()
porque, caso contrário, o “efeito colateral” é uma surpresa completa. No entanto:
responseIds = addResponseIdToDB();
não deixa ninguém surpreso.
O princípio de segregação de responsabilidade de consulta de comando argumenta que essa não deve ser a única maneira de obter o objeto responseId. Também deve haver uma consulta que não altere o banco de dados que pode obter este objeto.
Ao contrário de Bertrand Meyer , não acredito que isso signifique que o add deva retornar void. Significa apenas que deve existir uma consulta pura equivalente e ser fácil de encontrar, para que o banco de dados não seja abusado desnecessariamente pelo uso de consultas de alteração de estado.
Dado que getResponseIds()
deve existir e não se comunicar com o banco de dados, o melhor nome para um método que faz as duas coisas é addToDB(getResponseId())
. Mas isso é só se você quiser obter toda a composição funcional sobre isso.
Comentários
- Adicionando ao que foi dito, mas adicionando uma palavra que não ‘ foi usada, eu diria que ” e ” é apropriado quando uma função faz algo que é atômico . Se houver um benefício a ser obtido por duas coisas acontecendo atomicamente / juntas / ao mesmo tempo, ser explícito com o que essas duas coisas são na função nome é bom. Portanto, concordo com sua primeira afirmação em geral, mas discordo sem qualquer ressalva.
- ” … não deixa ninguém surpreso. “. Discordo, ‘ fico surpreso que uma variável booleana se chame
responseIds
e eu então tenho que fazer uma mudança mental para perceber que adiciona e obtém tudo em um. Mas eu discordo com @tintintong quegetAndAddResponseIdsToDB
é muito longo; ele ‘ tem cerca de metade do comprimento de muitos dos meus nomes de função. Se uma função faz duas coisas, diga em seu nome. - @Luaan OK, para o que você vai renomear
getAndIncrement
então?otherIncrement
? - @Luaan Então, qual valor
Increment
retorna, o valor pré-incremento ou pós-incremento? Não ‘ não está claro o que seria sem mergulhar nos documentos.Acho queincrementAndGet
egetAndIncrement
são nomes de método muito melhores , o que acredito ser um bom caso para ” E ” em nomes de métodos, pelo menos no caso específico de métodos que têm efeitos colaterais que podem afetar o valor de retorno. - @Luaan Quando você diz ” Incremento retorna o valor incrementado “, você quer dizer o valor pré-incremento? Entendi, não entendi você a princípio. que é um ótimo exemplo de por que
getAndIncrement
eincrementAndGet
são nomes melhores do que apenasincrement
, mesmo quando considerado individualmente
Resposta
Como outros mencionaram, usar and
em um nome de função implica automaticamente que uma função está fazendo pelo menos duas coisas, o que geralmente indica que ela está fazendo muito ou está fazendo algo no lugar errado.
Usar a cláusula and
em um nome de função, no entanto, em minha experiência, pode fazer sentido quando há algumas etapas em um determinado fluxo que precisa ser executado em sequência ou quando todo o cálculo se tornar significativo.
Na computação numérica, isso pode fazer muito sentido:
normalizeAndInvert(Matrix m)
Quero dizer, quem sabe, certo? Se você precisar calcular algumas … digamos, trajetórias de iluminação em computação gráfica e em um certo no estágio, você precisa normalizar e inverter uma certa matriz, e você se vê escrevendo constantemente:
m = normalize(m)
, seguido por invert(m)
, apenas como um exemplo simples, apresentando a abstração de normalizar e inverter, pode ser bom do ponto de vista da legibilidade.
Falando semanticamente, coisas como divideAndConquer()
etc, provavelmente é desencorajado a ser escrito explicitamente, mas, bem, o And
lá é basicamente necessário.
Comentários
- Funções regularmente fazem mais de uma coisa, mas essas pequenas coisas devem se somar a uma grande coisa que ‘ é significativo em um nível superior.
normalizeAndInvert
poderia sercomputeLighting
edivideAndConquer
poderia serapplyMachiavelli
- Obviamente 🙂 Apenas afirmando que primeiro a criação de uma função com ” e ” no nome pode vir como uma abstração natural baseada em um contexto particular. A refatoração por meio do ” método de renomeação ” deve vir imediatamente depois;)
- @BrunoOliveira Exceto que às vezes as duas etapas são eles próprios um componente de muitas coisas, renomear não é ‘ a abordagem certa. E deve ser raro, mas nunca usá-lo significa que você ‘ está se repetindo.
Resposta
Eu diria que “está bem em geral, mas não é aceitável em todos os casos.
Por exemplo, pode-se argumentar contra and
em um nome de função baseado no princípio de responsabilidade única , também conhecido como S em SOLID – porque and
pode implicar responsabilidades múltiplas. Se and
realmente significa que a função está fazendo duas coisas, então você provavelmente vai querer pensar cuidadosamente sobre o que está acontecendo.
Um exemplo de uma biblioteca que utiliza and
em nomes de função que podem ser encontrados em Java “s simultaneidade e aqui and
é, na verdade, uma parte muito importante do que está acontecendo e corresponde exatamente ao que você descreve em sua postagem, onde o estado é alterado e o estado é retornado. Portanto, é claro que há algumas pessoas ( e alguns casos de uso) onde “é considerado aceitável.
Comentários
- ” o
and
pode implicar múltiplas responsabilidades “. E, neste caso, de fato indica isso. A função obtém alguns IDs de resposta de algum lugar, os grava em um banco de dados e os retorna. A necessidade de ” e ” deve, pelo menos, levantar a ideia com o OP de que duas funções são necessárias: uma para obter os IDs e um para gravá-los no banco de dados. - Embora eu concorde que
and
é um cheiro de código, o comportamento básico parece legítimo: geralmente não é ruim idéia de retornar alguns valores adicionais de um método que por acaso sejam resultados (intermediários) necessários para realizar sua função principal. Se a função principal desse método é adicionar os IDs de resposta ao banco de dados, retornar adicionalmente os IDs que ele adicionou ao chamador é apenas um recurso de usabilidade simples e sem esforço do método. - Não ‘ acho que isso necessariamente viola o SRP. Você sempre precisará de funções que façam várias coisas. A parte importante é fazer com que essas funções chamem outra função para cada coisa que desejam fazer, em vez de conter o código necessário diretamente.
- Se a função faz duas coisas, o nome deve refleti-la.
Resposta
Na verdade, essa é uma pergunta difícil de responder, como vários comentários podem atestar. As pessoas parecem ter opiniões e conselhos contraditórios sobre o que é um bom nome.
Eu gostaria de adicionar meus 2 centavos a este tópico de 2 meses porque acho que posso adicionar mais cores ao que já foi sugerido.
Nomear é um processo
Tudo isso me lembra o excelente guia de 6 etapas: Nomear como um processo .
Um nome de função ruim é aquele que é surpreendente e você percebe que não pode confiar. Uma boa nomenclatura é difícil de acertar no primeiro tiro. Torna-se mais fácil com a experiência.
6 etapas iterativas para construir um bom nome
- Substitua o nome surpreendente por um absurdo óbvio como
appleSauce()
. Parece bobo, mas é temporário e torna óbvio que o nome não é confiável. - Obtenha um nome honesto , com base no que você entende do que a função faz. Digamos que você ainda não tenha entendido a inserção na parte DB,
getResponseIdsAndProbablyUseDb
seria uma opção. - Get para ser completamente honesto , então o nome da função diz tudo o que a função faz (
getAndAddResponseIdsToDB
ougetResponsesButAlsoAddCacheOfTheResponsesToTheDb
de @Fattie são ótimos exemplos) - Get to “faz a coisa certa” que é basicamente a parte em que você divide sua função ao longo do “AND”. Então, você realmente tem
getResponseIds
eaddResponseIdsToDb
. - Obtenha um nome de “revelação de intenção” que resolve o ponto de “Eu sempre quero obter o ID de resposta I inserido no banco de dados depois de fazer isso “. Pare de pensar nos detalhes de implementação e construa uma abstração que use as 2 funções menores para construir outra coisa. Este é o nível de abstração superior mencionado por @candied_orange. xample
createResponseIds
poderia fazer isso e será a composição degetResponseIds
eaddResponseIdsToDb
. - Obtenha a abstração de seu domínio . Isso é difícil, depende do seu negócio. Leva tempo para ouvir sua linguagem de negócios para acertar. Eventualmente, você pode acabar com o conceito de
Response
eResponse.createIds()
faria sentido. Ou talvezResponseId
seja algo valioso e você teria uma fábrica para criar muitos. A inserção no DB seria um detalhe de implementação de um Repositório, etc. Sim, o design seria mais abstrato. Seria mais rico e permitiria que você expressasse mais. Ninguém fora de sua equipe pode dizer qual deve ser a abstração de Domínio correta em sua situação. Depende .
No seu caso, você já calculou 1 e 2, então você provavelmente deve ir pelo menos para 3 (use “E”) ou mais. Mas ir além não é apenas uma questão de nomenclatura, você realmente dividiria as responsabilidades.
Assim, as diferentes sugestões aqui são válidas
Essencialmente:
- Sim, nomes de função surpreendentes são terríveis e ninguém quer lidar com isso
- Sim, “E” é aceitável em um nome de função porque é melhor do que um nome enganoso
- Sim, o nível de abstração é um conceito relacionado e é importante
Você não precisa ir para o estágio 6 imediatamente, é bom ficar em estágio 2, 3 ou 4 até que você saiba melhor!
Espero que seja útil para dar uma perspectiva diferente sobre o que parece ser um conselho contraditório. Acredito que todos têm o mesmo objetivo ( nomes não surpreendentes), mas param em estágios diferentes, com base em suas próprias experiências.
Fico feliz em responder se você tiver alguma dúvida 🙂
Resposta
Sua função faz duas coisas:
- Obtém um conjunto de IDs por meio de uma transformação e os retorna ao chamador,
- Grava esse conjunto de IDs em um banco de dados.
Lembre-se do princípio de responsabilidade única aqui: você deve ter como objetivo uma função ter uma responsabilidade, não duas. Você tem duas responsabilidades, portanto, duas funções:
getResponseIds
– Obtém um conjunto de IDs por meio de uma transformação e os retorna ao chamador
addResponseIdToDB
– Pega um conjunto de IDs e grava em um banco de dados.
E toda a questão de como chamar uma única função com múltiplas responsabilidades vai embora e a tentação de colocar and
nos nomes das funções também vai embora.
Como um bônus adicional, porque a mesma função não é mais responsável por duas ações não relacionadas, getResponseIds
poderia ser removido de seu código de repo (onde não pertence já que não está fazendo nenhuma atividade relacionada ao banco de dados), em uma classe / módulo de nível de negócios separado, etc.
Comentários
- Isso ‘ certamente é verdade, mas você se pega repetindo um trecho usando esses dois métodos SRP muitas vezes, então você provavelmente deve escrever um método trivial fazendo isso para que você SECA, não ‘ não é?
- @maaartinus, se você se pegar chamando esses dois métodos em muitos lugares, provavelmente terá um problema com a falta de coesão em seu código. Criar uma função ” DRY ” e apenas mascarar essa falta de coesão.
Resposta
Ter um nome de função composto é aceitável, portanto, o esquema de nomenclatura que você usa depende do seu contexto. Existem puristas que vão dizer para você separar, mas eu argumentarei o contrário.
Existem duas razões para tentar evitar tais coisas:
- Pureza – Uma função que faz duas coisas, deve ser convertido em duas funções que fazem uma coisa cada.
- Tamanho lexical – a
bigUglyCompositeFunctionName
fica difícil de ler.
O argumento da pureza é tipicamente aquele em que o foco. Como uma heurística geral vaga, dividir funções é uma coisa boa. Ajuda a compartimentar o que está acontecendo, tornando mais fácil de entender. É menos provável que você faça truques “inteligentes” com o compartilhamento de variáveis que levam ao acoplamento entre os dois comportamentos.
Portanto, pergunte-se “devo fazer isso de qualquer maneira?” Eu digo que quebrar as coisas é uma heurística vaga, então você realmente só precisa de um argumento decente para ir contra ela.
Uma das principais razões é que é benéfico pensar nas duas operações como uma. O exemplo definitivo disso é encontrado em operações atômicas: compare_and_set
. compare_and_set
é uma pedra angular fundamental da atômica (que é uma maneira de fazer multithreading sem bloqueios) que é bem-sucedida o suficiente para que muitos estejam dispostos a pensar nela como o Pilar. Há “um” e “nesse nome. Há um bom motivo para isso. O objetivo dessa operação é que ela faz a comparação e a configuração como uma operação indivisível. Se você o dividisse em compare()
e set()
, você anularia todo o motivo pelo qual a função existia em primeiro lugar, porque dois compares()
pode ocorrer consecutivamente antes dos set()
s.
Também vemos isso no desempenho. Meu exemplo favorito é fastInvSqrt . Este é um algoritmo famoso de Quake que calcula 1 / sqrt (x). Combinamos as operações “inversa” e “raiz quadrada” porque isso resulta nos um grande aumento de desempenho. Eles fazem uma aproximação de Newton que faz ambas as operações em uma única etapa (e acontece de fazer isso em matemática inteira, em vez de ponto flutuante, o que era importante na época). Se você fizesse inverse(sqrt(x))
, seria muito mais lento!
Há momentos em que o resultado é mais claro. Eu escrevi várias APIs envolvendo threading, nas quais é preciso ter muito cuidado com coisas como deadlocks. Não queria que meu usuário soubesse dos detalhes de implementação interna (especialmente porque posso alterá-los), então escrevi a API com algumas funções “e” que evitavam que o usuário tivesse que segurar um bloqueio para mais de uma chamada de função. Isso significa que eles nunca precisaram saber como eu estava lidando com a sincronização multithread nos bastidores. Na verdade, a maioria dos meus usuários nem sabia que estava em um ambiente multithread!
Portanto, embora a regra geral seja separar as coisas, sempre pode haver um motivo para juntá-los. Por exemplo , seu usuário pode quebrar sua arquitetura adicionando a um banco de dados várias vezes sem o “get” entre? Se cada uma das respostas registradas no banco de dados precisa ter um identificador exclusivo, você pode ter problemas se o usuário fizer isso à vontade ou nada. Da mesma forma, você gostaria de deixar um usuário “obter” sem registrar os resultados no banco de dados? Se a resposta for “sim”, divida-os para que o usuário possa acessar a função “obter”. No entanto, se o a segurança da sua aplicação é quebrada se o usuário puder “obter” sem o resultado ser registrado no banco de dados, você realmente deve manter as funções juntas.
Agora, quanto aos problemas lexicais, o nome da sua função deve descrever o que o usuário precisa saber sobre isso. Vamos começar com a parte “obter”.É muito fácil remover o “get” de um nome de função, porque todos os seus exemplos mostrarão uma atribuição: int id = addResponseIdToDB()
“. Em todos os lugares onde a função é usada, você terminará documentando o fato de que a função retornou um valor.
Da mesma forma, “adicionar” pode ser opcional. Usamos o termo “efeito colateral” como um termo abrangente, mas há diferentes nuances dele. Se as entradas do banco de dados forem meramente um log, pode não haver razão para destacá-lo. Nunca vemos funções como playMusicAndSendPersonalInformationToOurCorporateServers
. É apenas playMusic
e, se você tiver sorte, a documentação pode mencionar algo sobre uma conexão de soquete pela Internet. Por outro lado, se espera-se que os usuários chamem essa função com o propósito de adicionar coisas ao BD, então “adicionar” é essencial. Não o tire.
Agora, escrevi muitos motivos para fazer todas as combinações possíveis do que você está pedindo. Não respondi à pergunta intencionalmente porque há uma escolha a ser feita. Não é uma regra a ser seguida. Você está elaborando uma API.
Dito isso, meu instinto é que addResponseIdToDB é provavelmente a melhor resposta. Na maioria dos casos, a parte “get” é um efeito colateral óbvio o suficiente para não gerar o ruído extra causado ao digitá-la em todos os lugares. No entanto, há alguns lugares onde posso considerá-la importante:
- Se o “get” for caro – se você tiver que fazer um “get” que envolva buscar algo na Internet e, em seguida, adicioná-lo a um banco de dados de cache local, por suposto o “get” é importante. É o que o usuário está tentando fazer.
- Se você precisar deixar claro que o usuário precisa prestar atenção ao valor. Se o usuário quiser usar a variável, ele perceberá que a API a retornará e a usará. No entanto, você deseja estar atento ao usuário que não sabe que precisa. Por exemplo, se você tiver que “fechar” esta operação por ID posteriormente para liberar memória, convém chamar a atenção para o fato de que você “está fazendo algo. Nesses casos, você pode querer olhar para um verbo diferente de “get”. “get” geralmente implica funções idempotentes (chamá-lo novamente não faz nada). Se ele retornar recursos, outros verbos como “criar” ou “adquirir” são bons. Esse mecanismo de falha específico é um grande problema em C ao fazer o tratamento de exceções .C não tem o mecanismo catch / throw de C ++, então ele depende de códigos de retorno. Os desenvolvedores são famosos por simplesmente não verificarem esses códigos de retorno e se envolverem em situações realmente ruins, como buffer overflows por causa disso.
- Simetria – às vezes, você projeta uma API para ter uma simetria lexical. Você configura as palavras para que venham em pares ou outros padrões e cria os comandos de forma que sejam fáceis de identificar visualmente se o padrão foi seguido ou não. Eu diria que isso é raro, mas não é inédito. Há um motivo para fechar as tags XML para repetir o nome da tag (como < foo > e < / foo >).
Comentários
- Para pessoas que não ‘ t conheço a matemática: tanto 1 / x quanto sqrt (x) são funções bastante lentas. Usando algumas matemáticas inteligentes, podemos calcular 1 / sqrt (x) mais rápido do que uma divisão ou uma raiz quadrada. Na verdade, muito mais rápido que a maneira mais rápida de calcular sqrt (x) é calcular 1 / sqrt (x) com uma implementação inteligente e multiplicar o resultado por x.
- Como na física, onde as unidades elementares não são mais metro e segundo, mas segundo e velocidade da luz, porque podemos medir a velocidade da luz com mais precisão do que o comprimento de um metro.
Resposta
Qual é o máximo intenção deste meth od? Por que adicionar esses ids transformados ao banco de dados, é para fins de cache? Vou trabalhar sob essa suposição.
Parece que a intenção do método é realmente apenas obter os ids de resposta transformados (fazendo a transformação ou obtendo-os de um cache), e então há o nome do seu método:
getTransformedResponseIds
ou menos detalhadamente getResponseIds()
Um método com este nome pode armazenar em cache ou talvez não, e isso pode ser documentado, mas o nome do seu método não deve amarrá-lo a uma implementação específica. E se você decidir parar de usar um banco de dados e, em vez disso, armazená-los em outro lugar, como apenas temporariamente na memória?
Os efeitos colaterais devem ser apenas isso, efeitos colaterais, provavelmente devem ser documentados, mas não devem realmente afetar a intenção central (ou sucesso) ou o nome da função. Se a obtenção do valor do cache falhar (ou se você configurar para pular o cache), isso não deve ser um problema crítico, o método deve apenas recalcular, armazenar em cache (ou não) de forma transparente e retornar o novo valor.
Às vezes, usar um “AND” é útil para transmitir a intenção, como com os métodos Java getAndIncrement
e incrementAndGet
. isso “certamente não está fora de questão.
Resposta
Você diz que a função retorna algo” de uma transformação, não de any db action “.
Isso sugere que a função se comporta mais como um construtor do que como um getter. No entanto, construtores também não devem” causar efeitos colaterais (obrigado pelo link, @ MechMK1 ).
Os métodos de fábrica, por outro lado, já pressupõem algum nível de confusão . Por exemplo, uma das motivações para usar métodos de fábrica é que um método de fábrica:
Permite um código mais legível em casos onde existem vários construtores, cada um para uma razão diferente. – wikipedia
Da mesma forma,
Se você precisar trabalhar com efeitos colaterais, um método de fábrica pode ser nomeado de forma que fique mais claro quais são esses efeitos colaterais. – ruakh
Considere tornar a função um método de fábrica, usando o termo “registrado” para alertar os programadores sobre o armazenamento do banco de dados:
- Declaração:
createLoggedResponseid(...) {...} // log object to db
- Ligue para:
responseid = createLoggedResponseid(...);
Comentários
- Os construtores não devem causar efeitos colaterais
- @ MechMK1 Eu ‘ editei para remover a sugestão do construtor e para fornecer mais suporte para um método de fábrica com nome apropriado. Informe-me pense.
persistResponseIds
?getStatusOfPost
🙂responseIds = createResponseIds()
parece claro e conciso. Vocêget()
algo que não ‘ não existia antes, entãocreate
é o verbo apropriado. O material recém-criado é o que você ‘ espera que uma funçãocreate()
retorne. Ou talvez eu ‘ esteja faltando algo óbvio?