Pular para conteúdo

Regras de Negócio, jargões e outros acrônimos

Existem alguns chavões na área de desenvolvimento de sistemas. Alguns deles parecem ser óbvios e são passados entre gerações de programadores. Aparecem frequentemente nas discussões sobre práticas e qualidade do código, rapidamente sendo incorporados graças aos efeitos de cultura comunitária. Uma vez incorporados, estes conceitos passam a criar dificuldades na administração do código-fonte. Algumas dificuldades oriundas dessa incorporação:

  • Quais classes implementam regras de negócio?
  • A mixagem de código-fonte "funcional" e código-fonte "não funcional"?

Nos próximos tópicos examinaremos alguns desses termos chave. O propósito deste exame é apresentar a necessária separação entre o que é efetivamente programável e a interpretação das ideias que explicam esta ou aquela maneira de organizar os artefatos de código fonte de um software.

Lógica de negócio

Chama-se de lógica de negócio ao conjunto de regras operacionais (computáveis, não contraditórias, não ambíguas) que o software deve executar para que tenha serventia para usuários e para o cliente patrocinador.

Lógica de negócio só é completamente implementada em um único artefato de código quando a regra de funcionamento (independentemente da complexidade das expressões) é um conjunto independente de expressões que são computáveis in-process, que dependem unicamente dos parâmetros de entrada e de algum estado interno dos atributos previamente gravados no contexto da rotina computadora.

Alguns exemplos triviais:

  • Cálculo de validade de CPF, CNPJ, CEP. Dependem unicamente do texto informado para validação. Nenhuma dependência
  • Checagem de validade estrutural de um e-mail. Em geral pode ser validado com expressões regulares e alguns filtros aplicáveis sobre o texto de entrada.
  • Quantidade de cédulas para atender ao valor de um saque. Versões mais simplistas dependem de um máximo divisor comum e mínimo múltiplo comum, algum vetor de priorização e algum vetor de estoque de cédulas no momento do cálculo.
  • Cálculo de próximas datas e dias úteis. Em geral, as rotinas dependem de um cálculo trivial de dias por mês, ano bissexto e um vetor de entrada de feriados.

No entanto, tomando os mesmos exemplos:

  • Verificação de unicidade de um CPF, um CNPJ ou um e-mail. Dependem de verificação parametrizada contra uma base de dados.
  • Quantidade de cédulas para atender ao valor de um saque. Se o estoque de cédulas participa do cálculo de priorização, indiretamente, o resultado do cálculo depende - operação a operação - de consulta parametrizada ao estoque de cédulas.

Pode-se sempre argumentar que a verificação de base de dados e consulta de estoques não é uma operação tão relevante ou complexa quanto calcular um conjunto de expressões algébricas, probabilísticas, vetoriais.

Não se discorda desse argumento!

O ponto de atenção, entretanto, é observar que o artefato que computa as expressões de interesse, os artefatos que obtém os dados para cálculo, os artefatos que permitem ao utilizador a parametrização devida e os artefatos que disponibilizam o resultado para o consultante participam, cada um operando de acordo com o próprio escopo de atuação, da consecução da regra de funcionamento que o software executa.

Perceba-se ainda que para a execução de cada lógica proposta acima, ao menos três artefatos de código são necessários. Como exemplo trivial, para cálculo de CPF, observe-se o pequeno programa abaixo.

const lerTextoEntrada = function(config){

  let textoEntrada = "";

  /* código de leitura de algum input */  

  let rTexto = {
    valor: textoEntrada,
    estaValido: true,
  };


  if (config.validacaoFrontEnd){
     rTexto = config.validacaoFrontEnd(rTexto);
  }

  return rTexto;

};

/* front end -> output */
const apresentarResultado = function(resultado){
  /* código para apresentação do resultado */ 
};

/* back end -> validação do texto como um CPF */
const validarTextoComoCPF = function(candidato){

  let valido = false;
  let msg = "";

/* código para validação de CPF  */ 

  let rCalculo = {
    valor: candidato,
    mensagem: msg,
    estaValido: valido,
  };

  return rCalculo;

};

/* back end -> verificação de unicidade */
const verificarUnicidade = function(candidato){
  let valido = false;
  let msg = "";

/* código para consulta às tabelas e bases de dados */ 

  let rCalculo = {
    valor: candidato,
    mensagem: msg,
    estaValido: valido,
  };

  return rCalculo;

};

/* back end -> rotina de execução da lógica: "validar CPF" */
const validarCpf(){

   const rTextoCPF  = lerTextoEntrada(
      { aceitarNulos: false, 
        validacaoFrontEnd : validarSequenciasInvalidas(),
      }
    );

   if (rTextoCPF.estaValido == false){
    apresentarResultado(rTextoCPF);
    return;
   }

   const rValidaCpf = validarTextoComoCPF(rTextoCPF);

   if (rValidaCpf.estaValido == false){
    apresentarResultado(rValidaCpf);
    return;
   }

   const rCpfUnico = verificarUnicidade(rValidaCpf);

   apresentarResultado(rCpfUnico);
}

Para o utilizador, a execução da "regra de negócio" está disponibilizada pela rotina validarCpf. Para o desenvolvedor entretanto, existem as divisões:

  • As rotinas lerTextoEntrada, apresentarResultado podem ser agrupadas como front-end.
  • As rotinas validarTextoComoCPF , verificarUnicidade podem ser agrupadas como back-end.
  • A rotina orquestradora validarCpf que pode ser invocada a partir do front-end (API, Console, página Web, Smartphone)
  • A rotina orquestradora representa a codificação da "operação de negócio", conforme foi entendida, codificada e eventualmente aceita pelo contratante.

Perceba-se ainda que:

  • Os artefatos de código produzidos para tal realização, são apenas uma das possibilidades de codificação.
  • A codificação de todas as etapas dentro de um único artefato, na rotina validarCpf, também é possível e computará o resultado desejado do mesmo jeito.
  • As disciplinas de práticas de códigos e arquiteturas de softwares nos aconselham, entretanto, uma separação das etapas em blocos menores de código (sub-rotinas, classes, módulos) a fim de que o roteiro orquestrador permaneça inteligível.
  • Em arquiteturas reativas, orientadas a eventos ou distribuídas, é mais complexo de manter o roteiro orquestrador em um único artefato de código. Nestes casos, a compreensão do roteiro orquestrado só é "visível" através de diagramas, documentos ou outros artefatos de documentação.

Não existe, por óbvio, um encerramento desta discussão.

As contribuições pretendidas nestas reflexões são:

  • Agregar operações fortemente relacionadas a uma mesma "lógica de negócio" e mantê-los em poucos artefatos de código é uma prática desejável e que ajuda a manter a compreensão do funcionamento do código sob controle.
  • Estabelecer boas separações de responsabilidades para cada grupo de artefatos de código é uma maneira efetiva de manter a complexidade sob controle.

Os tópicos abaixo exploram estas separações.

Entretanto, é importante estabelecer boas separações de assuntos durante o desenvolvimento de um ou mais módulos de software. Por isso, valem algumas observações sobre princípios de codificação bem conhecidos: KISS ⧉, SOLID ⧉, DRY ⧉, ACID ⧉:

KISS - Uma ética de trabalho

(Keep it Simple, Stupid)

A busca por simplicidade é uma ética de trabalho importante, mas não um objetivo em si mesmo alcançável! Dito de outra forma, nunca se pode dizer que se atingiu de maneira definitiva a simplicidade.

Também não é para estúpidos ou sortudos, mas para estudiosos que precisam lidar com as complexidades intrínsecas e com as complexidades acidentais do trabalho em software; que vão, aos poucos, obtendo e burilando práticas de criação, sustentação, configuração, documentação, comunicação e transformação de softwares. E que, eventualmente, divulgam seus achados para a comunidade a fim de fazer andar o estado da arte.

Simplicidade e facilidade não são sinônimos!

Aprendemos a programar a partir do nosso aprendizado anterior da leitura: lemos de cima para baixo, da esquerda para direita. O raciocínio é desenvolvido pelo autor uma página depois da outra. Os assuntos extensos são organizados em capítulos.

Nos livros técnicos, raciocínios, teorias, demonstrações e exercícios são apresentados utilizando o esquema acima a fim de que o leitor possa compreender e praticar os assuntos que está se esforçando para apreender. Dependendo da abordagem de cada seção, assuntos já apresentados podem ser revisitados - ampliações, detalhamentos - mas não são inteiramente reapresentados.

Como escrevedor, também se aprende a escrever uma letra por vez, uma palavra após a outra. Colocar muitas palavras juntas em frases é o trabalho da sintaxe, com as regras gramaticais para estruturar substantivos, verbos, adjetivos, advérbios e assim por diante. A semântica cuida de fazer com que uma ou mais frases comuniquem uma ideia que o interlocutor possa compreender. Há ainda a estilística, que cuida em ensinar ao escrevedor que uma mesma mensagem pode ser expressa de várias formas.

Vencidas estas fases iniciais, o estudante escrevedor consegue escrever um texto inteligível para um leitor.

A escrita de uma apostila, um manual e mesmo um livro técnico envolve muitos textos, imagens, diagramas. Para este novo desafio é necessário ajuntar mais elementos de linguagem e comunicação visual: formato da publicação, texto introdutório, índice com assunto e página, glossário, apresentação visual do texto (layout, fonte, tamanho, espaçamento, margem). É também necessário planejamento para organizar tantos elementos novos: leiautes de texto utilizados em cada seção, divisão dos assuntos por seção e capítulos, número de revisões, encomenda de elementos gráficos, entre outros.

O trabalho de escrita de código em software tem similaridades, aqui também se começa aos poucos até se chegar ao primeiro programa cuja sintaxe é aceita pelo compilador/interpretador. A etapa da semântica pode ser aferida por uma outra pessoa ou por um ou mais esquemas de testagem. A estilística, cada indivíduo vai descobrindo à medida que lê o trabalho de outros e outras, adota esta ou aquela técnica para nomenclatura, declaração de variáveis, quantidade de código em cada rotina, composição de rotinas, organização em módulos, diretórios, enfim...

Dissemos que a escrita de código tem similaridades ao trabalho de escrita em linguagem natural. Entretanto, existem peculiaridades! A principal delas é que o que o conjunto de códigos redigido em vários arquivos distintos tem que funcionar! Funcionar implica não em ser legível para outros seres humanos, mas que um computador consiga executar o conjunto de instruções que está descrito num programa. Considerando unicamente este objetivo básico:

  • É possível escrever um conjunto completo instruções numa abordagem top-down, sem modularização ou sub-rotinas, e este conjunto executar de acordo com o esperado.
  • O computador processará qualquer conjunto de instruções que esteja sintaticamente válido, à revelia das repetições de códigos, dezenas de condições lógicas aninhadas, configurações e senhas de acesso hard-coded.
  • Instruções como GOTO são eficazes.
  • Uma vez escrito e colocado para funcionar, à revelia da quantidade de esforço necessária, pode-se não precisar mais revisitar este código-fonte (Comum em POC, testes de técnicas, etc.)

Entretanto, caso o software em questão venha a ser utilizado regularmente, terá manutenções, correções, melhorias, ampliações e poderá servir de base para escrita de outros softwares. Neste caso, quanto mais legível e inteligível for o código e a estruturação de código-fonte, tanto melhor será a consecução de quaisquer dessas atividades.

Conceitos como modularização em rotinas e módulos, POO, mixins, testes, diagramas e documentação existem tanto para auxiliar a criação rápida de novos softwares. Mas também existe para que:

  • Mantenham inteligíveis e manuteníveis os conhecimento codificado em instruções de programação.
  • Facilitem as tarefas de compreensão e codificação (para aprimoramento ou sustentação do código já realizado) na linguagem em que o software está escrito.

Um software cuja complexidade seja administrável por uma única pessoa, geralmente demanda a escrita de algumas dezenas de programas. Estes, por sua vez, podem se utilizar de operações e recursos codificados em dezenas de outros componentes - também códigos escritos e mantidos por outras pessoas. Organizar de maneira sistemática os artefatos, mitigar as dificuldades técnicas decorrentes dos limites da linguagem de programação, da compreensão e codificação dos "conceitos de negócio", fazer prototipação, manter mais de uma versão em execução e ainda manter a base de código dinamicamente coerente não são desafios fáceis.

A gestão de cada tarefa acima e das suas combinações às vezes se torna simples! Seja pela introdução de novas técnicas, novos produtos, novos entendimentos.

Quando não é o caso, a gestão é feita utilizando a educação e conhecimentos pré-existentes e com as ferramentas e técnicas com as quais já se aprendeu a trabalhar. Mesmo que assim fazendo, os resultados sejam precários, trabalhosos ou insatisfatórios.

Sugestões

Planejamento do produto, comunicação verbal, mapas, diagramas UML, blueprints documentos de visão, documentos de requisitos, e-mails, dicas do StackOverflow, planilhas para registro de issues e registro de horas são úteis. A inspeção desses insumos - quando existem - mais ainda.

As sugestões expressas logo mais abaixo, podem ser úteis:

  • Compreensão do serviço a fazer em uma frase
  • Compreensão gradual dos muitos detalhes.
  • Revisão e reelaboração de conceitos e funcionalidades conflitantes
  • Aprenda com os melhores
  • Treine programar para não treinar durante o trabalho no código do patrocinador.
  • Treinamentos em tópicos cada vez mais específicos e didaticamente organizados. Sempre te possibilitarão um novo olhar sobre o genérico e sobre os fundamentos.
  • Não tenha medo do SonarQube. Estude os alertas e os erros. É uma base monumental de conhecimento aplicado em situações específicas.
  • Rascunhando códigos incompletos
  • Ajuda a manter o foco no assunto principal
  • Mantenha assinaturas de rotinas com quatro parâmetros no máximo. Mais do que isso é um sinal de que é necessário mais de uma rotina para o mesmo serviço. SonarQube agradece!
  • Roteirização top down do que é processado numa rotina.
  • Algoritmo que justifica a existência da rotina
  • Ajuda a manter o foco e segregar aspectos secundários.
  • Ferramentas de refatoração e análise de código
  • Faz pelo seu código o que os editores de texto fizeram com textos antes produzidos por máquinas de escrever.
  • Aprenda quais são as que estão disponíveis na IDE de preferência. Jetbrains é uma das maiores fornecedoras de ferramentas nesse assunto.
  • Use aos poucos em coisas simples como renomear variáveis, rotinas, classes.
  • Verifique quais aspectos mais avançados a ferramenta oferece.

Nada vem de graça, nem o pão nem a cachaça!

DRY - Eficiência e Economia

Refs https://thevaluable.dev/dry-principle-cost-benefit-example/ ⧉ https://verraes.net/2014/08/dry-is-about-knowledge/ ⧉ https://en.wikipedia.org/wiki/Single_source_of_truth ⧉

Da mesma forma que em outras atividades laborais, o trabalho em software também tem suas muitas atividades repetitivas: configuração de ambiente, preparação de controle de versão, propostas, especificações, cronogramas, modelos de dados, rotinas para tratamento de dados, bibliotecas e "pedaços de códigos" com finalidades específicas...

Embora não seja especificamente desta repetição que o acrônimo trata, a adoção de ideias, arquiteturas, trechos de códigos ou componentes inteiros já utilizados ou praticadas anteriormente é uma dos principais condicionantes à consecução deste princípio, porque:

  • Regras, entidades ou configurações que expressem conhecimentos funcionais só devem existir em um único lugar no software.
  • Repetição de código e duplicação de conhecimento não são sinônimos ⧉.