Em este artigo por Alex Papadimoulis, você pode ver este trecho:
private void attachSupplementalDocuments()
{
if (stateCode == "AZ" || stateCode == "TX") {
//SR008-04X/I are always required in these states
attachDocument("SR008-04X");
attachDocument("SR008-04XI");
}
if (ledgerAmnt >= 500000) {
//Ledger of 500K or more requires AUTHLDG-1A
attachDocument("AUTHLDG-1A");
}
if (coInsuredCount >= 5 && orgStatusCode != "CORP") {
//Non-CORP orgs with 5 or more co-ins require AUTHCNS-1A
attachDocument("AUTHCNS-1A");
}
}
Eu realmente não entendo este artigo.
Eu cito:
Se cada constante de regras de negócios foi armazenado em algum arquivo de configuração, a vida seria muito [mais ( sic )] difícil para todos manter o software: haveria um monte de arquivos de código que compartilhou um, grande arquivo (ou, o contrário, um monte de pequenos arquivos de configuração); a implantação de alterações nas regras de negócios não exige um novo código, mas a alteração manual dos arquivos de configuração; e depuração é muito mais difícil.
Este é um argumento contra o número inteiro constante "500000" em um arquivo de configuração ou o "AUTHCNS-1A" e outras constantes de seqüência de caracteres.
Como isso pode ser uma má prática?
Nesse snippet, "500000" não é um número. Não é, por exemplo, o mesmo que:
int doubleMe(int a) { return a * 2;}
onde 2, é um número que não precisa ser abstraído. Seu uso é óbvio e não representa algo que possa ser reutilizado posteriormente.
Pelo contrário, "500000" não é simplesmente um número. É um valor significativo, que representa a ideia de um ponto de interrupção na funcionalidade. Esse número pode ser usado em mais de um local, mas não é o número que você está usando; é a ideia do limite / limite, abaixo do qual uma regra se aplica e acima do qual outra regra.
Como está se referindo a ele a partir de um arquivo de configuração, ou mesmo a #define
, const
ou qualquer que seja o idioma fornecido, pior do que incluir seu valor? Se mais tarde no programa, ou algum outro programador, também exigir esse limite, para que o software faça outra escolha, você estará ferrado (porque quando ele muda, nada garante que ele será alterado nos dois arquivos). Isso é claramente pior para a depuração.
Além disso, se amanhã, o governo exigir "De 5/3/2050, você precisará adicionar AUTHLDG-122B em vez de AUTHLDG-1A", essa constante de sequência não será uma constante de sequência simples. É aquele que representa uma ideia; é apenas o valor atual dessa ideia (que é "a coisa que você adiciona se o razão estiver acima de 500k").
Deixe-me esclarecer. Não estou dizendo que o artigo está errado; Eu simplesmente não entendo; talvez não esteja muito bem explicado (pelo menos para o meu pensamento).
Entendo que a substituição de cada valor literal ou numérico de string possível por uma variável constante, definida ou de configuração não é apenas não necessária, mas complicada demais, mas esse exemplo em particular não parece se encaixar nessa categoria. Como você sabe que não precisará mais tarde? Ou alguém mais para esse assunto?
Respostas:
O autor está alertando contra a abstração prematura.
A linha se
if (ledgerAmt > 500000)
parece com o tipo de regra de negócios que você esperaria ver em grandes sistemas de negócios complexos, cujos requisitos são incrivelmente complexos, mas precisos e bem documentados.Normalmente, esses tipos de requisitos são casos excepcionais / de ponta, em vez de lógica útil reutilizável. Esses requisitos geralmente são de propriedade e mantidos por analistas de negócios e especialistas no assunto, e não por engenheiros
(Observe que a 'propriedade' de requisitos por analistas de negócios / especialistas nesses casos geralmente ocorre em que desenvolvedores que trabalham em áreas especializadas não têm experiência suficiente em domínio; embora eu ainda esperasse uma comunicação / cooperação completa entre desenvolvedores e especialistas em domínio para proteger contra requisitos ambíguos ou mal escritos.)
Ao manter sistemas cujos requisitos estão repletos de casos extremos e lógica altamente complexa, geralmente não há como abstrair utilmente essa lógica ou torná-la mais sustentável; tentativas de criar abstrações podem facilmente sair pela culatra - não apenas resultando em perda de tempo, mas também em código menos sustentável.
Esse tipo de código tende a ser protegido pelo fato de que o próprio código provavelmente possui um mapeamento individual para os requisitos; ou seja, quando um desenvolvedor sabe que a
500000
figura aparece duas vezes nos requisitos, ele também sabe que aparece duas vezes no código.Considere o outro cenário (igualmente provável) em que
500000
aparece em vários locais no documento de requisitos, mas os especialistas no assunto decidem alterar apenas um deles; lá você tem um risco ainda maior de que alguém que altere oconst
valor não perceba que500000
é usado para significar coisas diferentes - então o desenvolvedor o altera no único local em que ele encontra no código e acaba quebrando algo que não sabia que eles haviam mudado.Esse cenário ocorre muito no software jurídico / financeiro sob medida (por exemplo, lógica de cotação de seguro) - as pessoas que escrevem esses documentos não são engenheiros e não têm problema em copiar + colar partes inteiras da especificação, modificando algumas palavras / números, mas deixando a maior parte do mesmo.
Nesses cenários, a melhor maneira de lidar com os requisitos de copiar e colar é escrever o código de copiar e colar e fazer com que o código pareça o mais semelhante possível aos requisitos (incluindo codificar todos os dados).
A realidade de tais requisitos é que eles geralmente não ficam copiados e colados por muito tempo, e os valores às vezes mudam regularmente, mas geralmente não mudam em conjunto, portanto, tentando racionalizar ou abstrair esses requisitos ou simplificar eles acabam criando mais dor de cabeça para manutenção do que apenas traduzir requisitos literalmente em código.
fonte
Those requirements are typically owned and maintained by business analysts and subject matter experts, rather than by engineers
o que nem sempre é uma boa ideia. Às vezes, o ato de transformar esses requisitos em código revelará casos extremos onde os requisitos não são bem definidos ou são definidos de maneira a contrariar o interesse comercial. Se os analistas de negócios e desenvolvedores puderem cooperar para alcançar um objetivo comum, muitos problemas poderão ser evitados.O artigo tem um bom argumento. Como pode ser uma má prática extrair constantes para um arquivo de configuração? Pode ser uma prática ruim se complicar o código desnecessariamente. Ter um valor diretamente no código é muito mais simples do que ter que lê-lo em um arquivo de configuração, e o código, como está escrito, é fácil de seguir.
Sim, então você muda o código. O objetivo do artigo é que não é mais complicado alterar o código do que alterar um arquivo de configuração.
A abordagem descrita no artigo não é escalável se você obtiver uma lógica mais complexa, mas o ponto é que você precisa fazer um julgamento e, às vezes, a solução mais simples simplesmente é a melhor.
Este é o ponto do princípio YAGNI. Não projete para um futuro desconhecido, que pode ser completamente diferente, projete para o presente. Você está certo de que, se o valor 500000 for usado em vários locais do programa, é claro que ele deve ser extraído para uma constante. Mas esse não é o caso no código em questão.
Softcoding é realmente uma questão de separação de preocupações . Você codifica as informações que você sabe que podem mudar independentemente da lógica do aplicativo principal. Você nunca codificaria uma string de conexão com um banco de dados, porque sabe que ela pode mudar independentemente da lógica do aplicativo e precisará diferenciá-la para diferentes ambientes. Em um aplicativo da web, gostamos de separar a lógica de negócios dos modelos html e das folhas de estilos, porque eles podem mudar de forma independente e até serem alterados por pessoas diferentes.
Porém, no caso do exemplo de código, as seqüências codificadas e os números codificados são parte integrante da lógica do aplicativo. É concebível que um arquivo possa mudar seu nome devido a alguma alteração de política fora do seu controle, mas é igualmente concebível que precisamos adicionar uma nova verificação if-branch para uma condição diferente. A extração dos nomes e números dos arquivos realmente quebra a coesão nesse caso.
fonte
if
é baseado em uma variável diferente! Se a variável necessária não estiver acessível na configuração, você precisará modificar o software de qualquer maneira.O artigo continua falando sobre o 'Enterprise Rule Engine', que provavelmente é um exemplo melhor do que ele está argumentando.
A lógica é que você pode generalizar até o ponto em que sua configuração se torna tão complicada que contém sua própria linguagem de programação.
Por exemplo, o código de estado para mapear o documento no exemplo pode ser movido para um arquivo de configuração. Mas você precisaria expressar um relacionamento complexo.
Talvez você também coloque o valor do razão?
Em breve, você descobrirá que está programando em um novo idioma que você inventou e salvando esse código em arquivos de configuração que não têm controle de origem ou alteração.
Note-se que este artigo é de 2007, quando esse tipo de coisa era uma abordagem comum.
Atualmente, provavelmente resolveríamos o problema com injeção de dependência (DI). Ou seja, você teria um 'codificado'
que você substituiria por um código rígido ou mais configurável
quando os requisitos legais ou comerciais mudaram.
fonte
if
instruções para fornecer valores diferentes para cada cliente? Parece algo que deve estar em um arquivo de configuração. Estar em um tipo de arquivo ou outro, sendo o resto igual, não é um motivo para não controlar / rastrear / fazer backup do arquivo. O @ewan parece estar dizendo que um arquivo de DSL não pode ser salvo como parte do projeto por algum motivo, quando até mesmo ativos que não sejam de código, como imagens, arquivos de som e documentação, certamente o são .E isso é expresso por ter (e eu poderia argumentar que até o comentário é redundante):
Isso está apenas repetindo o que o código está fazendo:
Observe que o autor assume que o significado de 500000 esteja vinculado a esta regra; não é um valor que é ou é provável que seja reutilizado em outro lugar:
O ponto principal do artigo, na minha opinião, é que, às vezes, um número é apenas um número: ele não tem um significado extra além do que é transmitido no código e provavelmente não será usado em outro lugar. Portanto, resumir desajeitadamente o que o código está fazendo (agora) em um nome de variável apenas para evitar valores codificados é repetição desnecessária, na melhor das hipóteses.
fonte
LEDGER_AMOUNT_REQUIRING_AUTHLDG1A
, não escreveria mais o comentário no código. Os comentários não são mantidos bem pelos programadores. Se a quantia for alterada, aif
condição e o comentário ficarão fora de sincronia. Pelo contrário, a constanteLEDGER_AMOUNT_REQUIRING_AUTHLDG1A
nunca sai de sincronia consigo mesma e explica seu objetivo sem o comentário desnecessário.attachDocument("AUTHLDG-2B");
linha não atualize o nome da constante ao mesmo tempo. Nesse caso, acho que o código é bastante claro, sem um comentário nem uma variável explicativa. (Embora possa fazer sentido ter uma convenção de indicar a seção apropriada do documento de requisitos de negócios por meio de comentários de código. De acordo com essa convenção, um comentário de código que faça isso seria apropriado aqui.)LEDGER_AMOUNT_REQUIRING_ADDITIONAL_DOCUMENTS
(o que eu provavelmente deveria ter feito em primeiro lugar). Eu também tenho o hábito de colocar os IDs de requisitos de negócios nas mensagens de confirmação do Git, não no código-fonte.attachIfNecessary()
método e apenas repetiria todos eles.As outras respostas estão corretas e atenciosas. Mas aqui está a minha resposta curta e doce.
Se as regras e os valores especiais aparecerem em um local no código e não forem alterados durante o tempo de execução, codifique como mostrado na pergunta.
Se as regras ou valores especiais aparecerem em mais de um local no código e não forem alterados durante o tempo de execução, faça o soft-code. A codificação suave para uma regra pode definir uma classe / método específico ou usar o padrão Builder . Para valores, codificação eletrônica pode significar definir uma única constante ou enumeração para o valor a ser usado em seu código.
Se as regras ou valores especiais puderem mudar durante o tempo de execução, você deverá externalizá-los. Isso geralmente é feito atualizando valores em um banco de dados. Ou atualize os valores na memória manualmente por um usuário digitando dados. Isso também é feito armazenando valores em um arquivo de texto (XML, JSON, texto sem formatação, o que for) que é varrido repetidamente para a modificação da data e hora da modificação do arquivo.
fonte
Esta é a armadilha caímos em quando usamos um problema brinquedo e, em seguida, colocar apenas Strawman soluções, quando estamos a tentar ilustrar um problema real.
No exemplo dado, não faz diferença se os valores fornecidos são codificados como valores inline ou definidos como consts.
É o código circundante que tornaria o exemplo um horror de manutenção e codificação. Se não é nenhum código circundante, em seguida, o trecho é bom, pelo menos em um ambiente de refatoração constante. Em um ambiente em que a refatoração tende a não acontecer, os mantenedores desse código já estão mortos, por razões que em breve se tornarão óbvias.
Veja, se houver código em torno dele, coisas ruins acontecem claramente.
A primeira coisa ruim é que o valor 50000 é usado para outro valor em algum lugar, digamos, o valor contábil sobre o qual a taxa de imposto muda em alguns estados ... então, quando a mudança acontece, o mantenedor não tem como saber, quando encontra esses valores. duas instâncias de 50000 no código, se elas significam os mesmos 50k ou 50ks totalmente não relacionados. E você também deve procurar por 49999 e 50001, caso alguém as use também como constantes? Não se trata de chamar essas variáveis em um arquivo de configuração de um serviço separado: mas codificá-las em linha também está claramente errado. Em vez disso, eles devem ser constantes, definidas e com escopo definido na classe ou no arquivo em que são usados. Se as duas instâncias de 50k usam a mesma constante, elas provavelmente representam a mesma restrição legislativa; se não, provavelmente não; e de qualquer forma, eles terão um nome,
Os nomes de arquivos estão sendo passados para uma função - attachDocument () - que aceita nomes de arquivos base como string, sem caminho ou extensão. Os nomes de arquivo são, essencialmente, chaves estrangeiras para algum sistema de arquivos, banco de dados ou para onde o attachDocument () obtém os arquivos. Mas as strings não dizem nada sobre isso - quantos arquivos existem? Quais são os tipos de arquivo? Como você sabe, ao abrir um novo mercado, se precisa atualizar esta função? A que tipos de coisas eles podem ser anexados? O mantenedor é deixado inteiramente no escuro, e tudo o que ele tem é uma string, que pode aparecer várias vezes no código e significar coisas diferentes toda vez que aparece. Em um lugar, "SR008-04X" é um código de trapaça. Noutro, é um comando encomendar quatro foguetes de reforço SR008. Aqui está um nome de arquivo? Eles estão relacionados? Alguém acabou de alterar essa função para mencionar outro arquivo, "CLIENT". Então, você, mantenedor pobre, foi informado que o arquivo "CLIENT" precisa ser renomeado para "CLIENTE". Mas a string "CLIENT" aparece 937 vezes no código ... onde você começa a procurar?
O problema do brinquedo é que os valores são todos incomuns e podem ser razoavelmente garantidos como únicos no código. Não "1" ou "10", mas "50.000". Não é "cliente" ou "relatório", mas "SR008-04X".
O argumento é que a única outra maneira de resolver o problema de constantes impenetrávelmente opacas é inseri-las no arquivo de configuração de algum serviço não relacionado.
Juntos, você pode usar essas duas falácias para provar que qualquer argumento é verdadeiro.
fonte
if (...) { attachDocument(...); }
.Existem vários problemas nisso.
Um problema é se um mecanismo de regras deve ser criado para tornar todas as regras facilmente configuráveis fora do próprio programa. A resposta em casos semelhantes a esse geralmente é não. As regras mudarão de maneiras estranhas, difíceis de prever, o que significa que o mecanismo de regras deve ser estendido sempre que houver uma alteração.
Outra questão é como lidar com essas regras e suas alterações no seu controle de versão. A melhor solução aqui é dividir as regras em uma classe para cada regra.
Isso permite que cada regra tenha sua própria validade, algumas regras mudam a cada ano, algumas mudam dependendo da concessão de uma permissão ou da emissão de uma fatura. A própria regra que contém a verificação para qual versão deve ser aplicada.
Além disso, como a constante é privada, ela não pode ser mal utilizada em nenhum outro lugar do código.
Depois, tenha uma lista de todas as regras e aplique a lista.
Uma questão adicional é como lidar com constantes. 500000 pode parecer discreto, mas é preciso muito cuidado para garantir que ela seja convertida corretamente. Se qualquer aritmética de ponto flutuante for aplicada, ela poderá ser convertida em 500.000,00001, portanto, uma comparação com 500.000,00000 poderá falhar. Ou ainda pior, 500000 sempre funciona conforme o esperado, mas, de alguma forma, 565000 falha quando convertido. Verifique se a conversão é explícita e feita por você, não pelo palpite do compilador. Geralmente, isso é feito convertendo-o em algum BigInteger ou BigDecimal antes de ser usado.
fonte
Embora não seja mencionado diretamente na pergunta, gostaria de observar que o importante é não enterrar a lógica de negócios no código.
Código, como no exemplo acima, que codifica os requisitos de negócio especificadas externamente deve realmente vivem em uma parte distinta da árvore de origem, talvez chamado
businesslogic
ou algo semelhante, e os cuidados devem ser tomados para garantir que ele única codifica os requisitos de negócio como forma simples, legível e de forma concisa possível, com um mínimo de clichê e com comentários claros e informativos.Ele deve não ser misturado com o código "infra-estrutura", que implementa a funcionalidade necessária para realizar a lógica de negócios, como, por exemplo, a implementação do
attachDocument()
método no exemplo, ou por exemplo UI, registro ou código de banco de dados em geral. Embora uma maneira de impor essa separação seja "codificar" toda a lógica de negócios em um arquivo de configuração, isso está longe de ser o único (ou o melhor) método.Esse código de lógica de negócios também deve ser escrito com clareza suficiente para que, se você o mostrasse a um especialista em domínio de negócios sem habilidades de codificação, ele seria capaz de entendê-lo. No mínimo, se e quando os requisitos de negócios forem alterados, o código que os codifica deve ser claro o suficiente para que mesmo um novo programador sem família anterior com a base de código possa localizar, revisar e atualizar facilmente a lógica de negócios, assumindo que nenhuma funcionalidade qualitativamente nova é necessária.
Idealmente, esse código também seria escrito em uma linguagem específica de domínio para impor a separação entre a lógica de negócios e a infraestrutura subjacente, mas isso pode ser desnecessariamente complicado para um aplicativo interno básico. Dito isto, se você estiver, por exemplo, vendendo o software para vários clientes, cada um precisando de um conjunto personalizado de regras de negócios, uma linguagem de script simples e específica de domínio (talvez, por exemplo, com base em um sandbox Lua ) pode ser a coisa certa .
fonte