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 ⧉.