O que é “Soft Coding”, realmente?

87

Em este artigo de 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.

cito:

If every business rule constant was stored in some configuration file, life would be much [more (sic)] difficult for everyone maintaining the software: there’d be a lot of code files that shared one, big file (or, the converse, a whole lot of tiny configuration files); deploying changes to the business rules require not new code, but manually changing the configuration files; and debugging is that much more difficult.

Este é um argumento contra ter o inteiro constante "500000" em um arquivo de configuração, ou o "AUTHCNS-1A" e outras constantes de string.

Como isso pode ser uma prática ruim?

Neste 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 lugar, mas não é o número que você está usando. é a ideia do limite / limite, abaixo da qual uma regra se aplica, e acima da qual outra.

Como está se referindo a ele a partir de um arquivo de configuração, ou mesmo de um #define , const ou o que quer que seu idioma forneça, pior do que incluir seu valor? Se mais tarde o programa, ou algum outro programador, também exigir que o borderline, para que o software faça outra escolha, você está ferrado (porque quando ele muda, nada garante que ele irá mudar em ambos arquivos). Isso é claramente pior para a depuração.

Além disso, se amanhã, o governo exigir "A partir de 5/3/2050, você precisa adicionar AUTHLDG-122B em vez de AUTHLDG-1A", essa constante de string não é uma constante de string simples. É aquele que representa uma ideia; é apenas o valor atual dessa ideia (que é "a coisa que você adiciona se o ledger estiver acima de 500k").

Deixe-me esclarecer. Não estou dizendo que o artigo está errado; Eu simplesmente não entendo; talvez não seja muito bem explicado (pelo menos para o meu pensamento).

Eu entendo que a substituição de todos os valores literais ou numéricos possíveis por uma constante, definição ou variável de configuração, não só não é necessária, como também supercomplica as coisas, mas este exemplo em particular não parece pertencer a esta categoria. Como você sabe que não precisará mais tarde? Ou outra pessoa para esse assunto?

    
por K. Gkinis 08.04.2016 / 11:14
fonte

8 respostas

100

O autor está alertando contra a abstração prematura.

A linha if (ledgerAmt > 500000) se parece com o tipo de regra de negócios que você esperaria ver para sistemas de negócios complexos e grandes, cujos requisitos são incrivelmente complexos, mas precisos e bem documentados.

Normalmente, esses tipos de requisitos são excepcionais / casos de borda, em vez de lógica útil reutilizável. Esses requisitos são geralmente 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 normalmente ocorre quando os desenvolvedores que trabalham em áreas especializadas não possuem conhecimento suficiente no domínio; embora eu ainda espere uma comunicação / cooperação completa entre desenvolvedores e especialistas no domínio para proteger contra requisitos ambíguos ou mal escritos.)

Ao manter sistemas cujos requisitos são repletos de casos de ponta e lógica altamente complexa, geralmente não há como abstrair essa lógica ou torná-la mais sustentável; tentativas de tentar construir abstrações podem facilmente sair pela culatra - não apenas resultando em perda de tempo, mas também resultando em código menos passível de manutenção.

How is referring to it from a config file, or even a #define, const or whatever your language provides, worse than including its value? If later on the program, or some other programmer, also requires that borderline, so that the software makes another choice, you're screwed (because when it changes, nothing guarantees you that it will change in both files). That's clearly worse for debugging.

Esse tipo de código tende a ser guardado pelo fato de que o próprio código provavelmente tem um mapeamento de um para um para os requisitos; Ou seja, quando um desenvolvedor sabe que a figura 500000 aparece duas vezes nos requisitos, esse desenvolvedor 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 lugares no documento de requisitos, mas os especialistas no assunto decidem alterar apenas um deles; aí você tem um risco ainda maior de que alguém alterando o valor const possa não perceber que 500000 é usado para significar coisas diferentes - então o desenvolvedor o altera no único lugar em que ele o encontra no código, e acaba quebrando algo que eles não perceberam que haviam mudado.

Esse cenário acontece muito em software legal / financeiro personalizado (por exemplo, lógica de cotação de seguro) - pessoas que escrevem tais documentos não são engenheiros, e não têm cópia de problema + colando 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 é gravar código copiar e colar e tornar o código semelhante aos requisitos (incluindo a codificação de todos os dados) possível.

A realidade de tais requisitos é que eles normalmente não ficam copiados e colados por muito tempo, e os valores às vezes mudam regularmente, mas eles geralmente não mudam em conjunto, então tentando racionalizar ou abstrair esses requisitos ou simplificá-los de qualquer maneira acaba gerando mais problemas de manutenção do que apenas traduzir os requisitos literalmente em código.

    
por 08.04.2016 / 11:58
fonte
44

O artigo tem um bom ponto. Como pode ser uma prática ruim 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 escrito é fácil de seguir.

In addition, tomorrow, the government goes "From 5/3/2050, you need to add AUTHLDG-122B instead of AUTHLDG-1A".

Sim, você muda o código. O ponto 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 escala 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.

How do you know that you will not need it later on? Or someone else for that matter?

Este é o ponto do princípio YAGNI. Não projetar para um futuro desconhecido, que pode revelar-se completamente diferente, design para o presente. Você está correto que se o valor 500000 for usado em vários lugares do programa, é claro que ele deve ser extraído para uma constante. Mas este não é o caso no código em questão.

Softcoding é realmente uma questão de separação de preocupações . Você softcode informações que você sabe podem mudar de forma independente da lógica do aplicativo principal. Você nunca iria codificar uma string de conexão para um banco de dados, porque você sabe que ela pode mudar independentemente da lógica do aplicativo e você precisará diferenciá-la para diferentes ambientes. Em um aplicativo da web, gostamos de separar a lógica de negócios dos modelos de HTML e das folhas de estilo, porque eles podem ser alterados independentemente e até mesmo alterados por pessoas diferentes.

Mas, no caso do exemplo de código, as cadeias de caracteres e números codificados são parte integrante da lógica da aplicação. É concebível que um arquivo possa mudar seu nome devido a alguma mudança de política fora de seu controle, mas é tão concebível que precisamos adicionar uma nova verificação if-branch para uma condição diferente. Extrair os nomes e números de arquivos realmente quebra a coesão neste caso.

    
por 08.04.2016 / 12:27
fonte
26

O artigo continua falando sobre o 'Enterprise Rule Engine', que é provavelmente um melhor exemplo do que ele está argumentando contra.

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 o mapeamento de documentos no exemplo pode ser movido para um arquivo de configuração. Mas você precisaria então expressar um relacionamento complexo.

<statecode id="AZ">
    <document id="SR008-04X"/>
    <document id="SR008-04XI"/>
</statecode>

Talvez você também coloque o valor do ledger em

<statecode id="ALL">
    <document id="AUTHLDG-1A" rule="ledgerAmt >= 50000"/>
</statecode>

Logo você descobre 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 código fonte nem controle de alteração.

Deve-se notar que este artigo é de 2007, quando esse tipo de coisa era uma abordagem comum.

Hoje em dia provavelmente resolveríamos o problema com injeção de dependência (DI). Ou seja, você teria um "código fixo"

InvoiceRules_America2007 : InvoiceRules

que você substituiria por um codificado ou mais configurável

InvoiceRules_America2008 : InvoiceRules

quando a lei ou os requisitos de negócios foram alterados.

    
por 08.04.2016 / 13:26
fonte
17

On the contrary, "500000" is not simply a number. It's a significant value, one that represents the idea of a breakpoint in functionality. This number could be used in more than one places, but it's not the number that you're using, it's the idea of the limit/borderline, below which one rule applies, and above which another.

E isso é expresso por ter (e eu poderia argumentar que até mesmo o comentário é redundante):

 if (ledgerAmnt >= 500000) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
  }

Isso é apenas repetir o que o código está fazendo:

LEDGER_AMOUNT_REQUIRING_AUTHLDG1A=500000
if (ledgerAmnt >= LEDGER_AMOUNT_REQUIRING_AUTHLDG1A) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
}

Observe que o autor assume que o significado de 500000 está vinculado a essa regra; não é um valor que é ou provavelmente será reutilizado em outro lugar:

The one and only business rule change that this preceding Soft Coding could ever account for is a change in the ledger amount that required a form AUTHLDG-1A. Any other business rule change would require even more work – configuration, documentation, code, etc

O ponto principal do artigo, a meu ver, é que às vezes um número é apenas um número: não tem nenhum significado extra além do que é transmitido no código e não é provável que seja usado em outro lugar. Portanto, resumindo desajeitadamente o que o código está fazendo (agora) em um nome de variável apenas para evitar valores codificados permanentemente, é uma repetição desnecessária na melhor das hipóteses.

    
por 08.04.2016 / 12:22
fonte
8

As outras respostas estão corretas e pensativas. Mas aqui está minha resposta curta e doce.

  Rule/value          |      At Runtime, rule/value…
  appears in code:    |   …Is fixed          …Changes
----------------------|------------------------------------
                      |                 |
  Once                |   Hard-code     |   Externalize
                      |                 |   (soft-code)
                      |                 |
                      |------------------------------------
                      |                 |
  More than once      |   Soft-code     |   Externalize
                      |   (internal)    |   (soft-code)
                      |                 |
                      |------------------------------------

Se as regras e os valores especiais aparecerem em um lugar no código e não forem alterados durante o tempo de execução, use o código como mostrado na pergunta.

Se as regras ou valores especiais aparecerem em mais de um lugar no código e não forem alterados durante o tempo de execução, use o código virtual. Soft-coding para uma regra pode me definir uma classe / método específico ou usar o padrão Builder . Para valores, soft-coding pode significar definir uma única constante ou enum para o valor a ser usado no seu código.

Se as regras ou valores especiais podem mudar durante o tempo de execução, você deve externalizá-los. É comumente feito pela atualização de valores em um banco de dados. Ou atualize os valores na memória manualmente por um usuário inserindo dados. Também é feito armazenando valores em um arquivo de texto (XML, JSON, texto simples, qualquer que seja) que seja repetidamente varrido para alteração de data e hora de modificação de arquivo.

    
por 08.04.2016 / 21:57
fonte
7

Esta é a armadilha em que caímos quando usamos um problema de brinquedo e, em seguida, colocamos apenas soluções strawman , quando estamos tentando ilustrar um problema real.

No exemplo dado, não faz diferença alguma se os valores fornecidos são codificados como valores sequenciais ou definidos como consts.

É o código circundante que tornaria o exemplo um horror de manutenção e codificação. Se houver nenhum código ao redor, o snippet é bom, pelo menos em um ambiente de constante refatoração. Em um ambiente onde 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 há código em torno dele, então coisas ruins acontecem claramente.

A primeira coisa ruim é que o valor 50000 é usado para outro valor em algum lugar, digamos, o valor do livro no qual a taxa de imposto muda em alguns estados ... então quando a mudança acontece, o mantenedor não tem como saber, quando ele encontra essas duas instâncias de 50000 no código, se elas significam os mesmos 50k, ou 50k inteiramente não relacionados. E você também deve procurar por 49999 e 50001, no caso de alguém usar esses dados como constantes também? Esta não é uma chamada para plongar 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, definidos e com escopo dentro da classe ou 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 o fazem; e de qualquer forma, eles terão um nome, que será menos opaco do que um número in-line.

Os nomes de arquivos estão sendo passados para uma função - attachDocument () - que aceita nomes de arquivos básicos como string, sem caminho ou extensão. Os nomes dos arquivos são, essencialmente, chaves estrangeiras para algum sistema de arquivos ou banco de dados, ou onde quer que attachDocument () obtenha os arquivos. Mas as strings não dizem nada sobre isso - quantos arquivos existem? Que tipos de arquivos são eles? Como você sabe, ao abrir um novo mercado, se precisa atualizar essa função? Para que tipos de coisa eles podem ser ligados? O mantenedor fica totalmente 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 aparecer. Em um lugar, "SR008-04X" é um código de fraude. Em outro, é um comando para encomendar quatro foguetes de propulsão SR008. Aqui está um nome de arquivo? Estão relacionados? Alguém acabou de alterar essa função para mencionar outro arquivo, "CLIENT". Então você, mantenedor pobre, foi informado de que o arquivo "CLIENT" precisa ser renomeado para "CUSTOMER". 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 strawman é que a única outra maneira de resolver o problema de constantes impenetrávelmente opacas é transferi-las para o arquivo de configuração de algum serviço não relacionado.

Juntos, você pode usar essas duas falácias para provar qualquer argumento verdadeiro.

    
por 10.04.2016 / 01:15
fonte
2

Existem vários problemas nisso.

Uma questão é se um mecanismo de regras deve ser construído para tornar todas as regras facilmente configuráveis fora do próprio programa. A resposta em casos semelhantes a isso é mais frequentemente não. As regras serão alteradas de formas estranhas que são 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 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 alterações são alteradas quando uma permissão é concedida ou uma fatura é emitida. A própria regra contém a verificação de qual versão ela deve aplicar.

Além disso, como a constante é privada, ela não pode ser usada em qualquer outro lugar do código.

Em seguida, tenha uma lista de todas as regras e aplique a lista.

Outra questão é como lidar com constantes. 500000 pode parecer inconspícuo, mas muito cuidado deve ser tomado para garantir que ele seja convertido corretamente. Se alguma 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 como planejado, mas de alguma forma o 565000 falha quando convertido. Certifique-se de que a conversão seja explícita e feita por você, não pelo adivinhador do compilador. Geralmente isso é feito convertendo-o em BigInteger ou BigDecimal antes de ser usado.

    
por 10.04.2016 / 20:01
fonte
2

Embora não seja mencionado diretamente na pergunta, gostaríamos de observar que o importante não é enterrar a lógica de negócios no código

.

O código, como o exemplo acima, que codifica requisitos de negócios especificados externamente deve realmente estar em uma parte distinta da árvore de origem, talvez denominada businesslogic ou algo semelhante, e deve-se tomar cuidado para certifique-se de que apenas codifique os requisitos de negócios da forma mais simples, legível e 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, digamos, a implementação do attachDocument() método no exemplo, ou por exemplo UI, registro ou código do banco de dados em geral. Embora o um meio de impor essa separação seja para "soft code" 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 lógico de negócios também deve ser escrito com clareza suficiente para que, se você o mostrar a um especialista em domínio de negócios sem nenhuma habilidade de codificação, ele seja capaz de entendê-lo. No mínimo, se e quando os requisitos do negócio mudarem, o código que os codifica deve ser claro o suficiente para que até mesmo um novo programador sem família prévia com o codebase possa localizar, revisar e atualizar facilmente a lógica de negócios, supondo que nenhuma funcionalidade qualitativamente nova é necessária.

Idealmente, esse código também seria escrito em uma linguagem específica do 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ê é, por exemplo, vendendo o software para vários clientes que precisam de seu próprio conjunto personalizado de regras de negócios, uma linguagem de script simples específica do domínio (talvez, por exemplo, com base em um sandbox Lua ) pode ser apenas a coisa.

    
por 11.04.2016 / 00:08
fonte

Tags