DEV Community

Wesley Egberto
Wesley Egberto

Posted on • Edited on

Resumo do paper 'On The Criteria To Be Used In Decomposing Systems Into Modules' do Parnas

Hoje resumirei um paper que foi citado no podcast sobre Academia e Universidade vs Mercado e Indústria do Hipster.Tech e que também foi resumido e comentado neste video do Alberto de Souza no seu canal Dev Eficiente.

Publicado em 1971, disponível em On The Criteria To Be Used In Decomposing Systems Into Modules (D.L. Parnas).

Disclaimer:
Não assuma nada aqui como verdade, foram notas que tomei direto do paper a partir do meu entendimento.
Recomendo que faça a leitura logo em seguida e valide o que você entendeu.

Problema

  • programas com uma grande quantidade de funcionalidades são grandes e complicados por natureza
  • é necessário um critério para decomposição do programa
  • a efetividade da modularização depende do critério utilizado para dividir os módulos

Módulos

Definição de modularização seguindo Gouthier e Pont:

  • uma segmentação bem definida do projeto garante modularidade do sistema
  • cada "tarefa" forma um módulo distinto e separado do programa
    • ex.: módulo de pagamentos que envolve validação dados, cobrança do cartão, persistência da cobrança e tratamentos.
  • no momento da implementação, cada módulo com suas entradas e saídas são bem definidas, não há confusão sobre o conexão com outros módulos do sistema
  • a integridade do sistema pode ser testada de forma independente
  • erros de sistema e deficiências podem ser rastreados até o módulo específico ao limitar o escopo da busca de detalhes do erro

Pense em um módulo como uma unidade de trabalho.

Benefícios dos módulos:

  • gerencial:
    • tempo de desenvolvimento pode ser encurtado
    • grupos separados podem trabalhar em cada módulo com pouca comunicação (Nota pessoal: era o que se esperava mas hoje sabemos que precisamos de uma boa comunicação)
  • flexibilidade:
    • mudanças podem ser feitas em um módulo sem afetar outro
  • compreensibilidade:
    • é possível estudar o sistema um módulo de cada vez

Para maximizar tais benefícios é preciso dividir os módulos de forma apropriada.

Contribuição

Paper compara duas formas de dividir os módulos:

  • Decomposição 1: usa abordagem de fluxograma onde cada módulo é um grande passo de um processo.
  • Decomposição 2: usa ocultamento de informação como critério:
    • cada módulo é caracterizado por ocultar as decisões tomadas na implementação;
    • ao esconder as decisões, cada usuário do módulo não precisa saber o funcionamento interno associado com a decisão, e assim ao mudar essa decisão não deve haver impactos.

Exemplo analisado no paper:

Decomposição 1:

  • Módulo 1: Input
  • Módulo 2: Circular Shift
  • Módulo 3: Alphabetizing
  • Módulo 4: Output
  • Módulo 5: Master Control

Decomposição 2:

  • Módulo 1: Line Storage
  • Módulo 2: Input
  • Módulo 3: Circular Shifter
  • Módulo 4: Alphabetizer
  • Módulo 5: Output
  • Módulo 6: Master Control

Nota pessoal: as decomposições parecem iguais mas existe uma diferença sutil no seu uso em alguns módulos, onde um expõe detalhes da implementação (função que receber uma lista de palavras com uma divisão específica) enquanto a outra não expõe essa regra (apenas armazena a linha para que possam operar sobre ela). E ainda, a decomposição 1 tem um jeito bem estruturado em que passa a ideia de ser um fluxo direto, e a decomposição 2 não (normalmente é o que acontece, temos alguns módulos se comunicando através de todo o processo para resolver um problema).

Comparação

Apesar das decomposições serem parecidas, apenas são na representação executável. As representações para manutenção, documentação, entendimento e etc podem não ser parecidas.

  • Geral:
    • ambos produziram programas que funcionam
    • podem ser iguais após a compilação
  • Manutenção:
    • se o formato da mensagem mudar, em ambas decomposições apenas um módulo será alterado
    • se o formato do armazenamento ou caso não armazene mais, a decomposição 1 terá alteração em todos os módulos (vazou detalhes da implementação ao propagar a mensagem formatada) enquanto que a decomposição 2 apenas um módulo será alterado
  • Desenvolvimento independente:
    • as interfaces entre os módulos da decomposição 1 são mais complexas que na decomposição 2 pois detalhes do formato da mensagem, armazenamento e etc vazou entre os módulos (pois são utilizados na entrada e saída)
  • Compreensibilidade:
    • o autor acha o decomposição 2 mais fácil de entender:
    • na decomposição 1 apenas é possível saber o que o sistema faz ao olhar todos os módulos e como eles funcionam em conjunto, algumas coisas em um módulo só farão sentido após entender o que o outro módulo faz
    • Nota pessoal: nos cenários exemplificados, tenho tendência a concordar caso as funções expostas nos módulos tenham interface voltada ao problema (nome dos métodos, parâmetros de entrada e saída). Ex.: módulo de alphabetizer ordena uma lista de palavras, não me importa qual algoritmo usa ou porque, importa apenas que me retorna a lista ordenada. Aqui podemos ter uma assinatura sort(words: List<string>): SortedList<string>.

Hierarquia das Estruturas

Decomposição 1:

  • mapeia o programa em forma de árvore.
  • as funções só podem ser utilizadas por outras funções acima dela:
    • Function 1 -> Function 2 -> Function 3

Decomposição 2:

  • o sistema tem uma hierarquia do programa com um todo.
    • ex.: os módulos de output e alphabetizer precisam do shifter
  • alguns módulos mais baixo nível (sem dependências para iniciar) podem ser extraídos para utilizar em outro lugar
    • ex.: line storage

Benefícios da hierarquia:

  • existe uma estrutura hierárquica se existe uma relação entre os módulos que pode ser parcialmente ordenada
    • módulo A depende ou usa módulo B
    • módulo A -> módulo B
  • partes do sistemas são simplificadas porque elas usam serviços de níveis inferiores
  • os níveis superiores podem ser retirados do programa e ainda podemos ter um programa utilizável (em outro problema talvez)

Independência:

  • estrutura hierárquica e decomposição limpa são propriedades independentes
  • embora decomposição 2 produza um programa hierárquico, é possível obter um resultado parecido através da abordagem da decomposição 1 - talvez com mais iterações

Método Proposto

  1. começar pela lista de decisões de design que são prováveis de sofrerem mudanças e então construir os módulos de modo que essas decisões sejam ocultas
  2. todo o conhecimento sobre o contexto do módulo precisa estar contido nele mesmo
    • estrutura de dados, o procedimento de acesso e modificação
  3. os passos necessários para chamar uma rotina e a própria rotina são partes do mesmo módulo
    • precisamos simplificar os pontos de entrada do módulo (rotinas chamadas) de modo a evitar o zig-zag entre os módulos o que vai fazer com que fazer informações do funcionamento interno
    • ex.: uma função que recebe apenas string mas a entrada atual é um inteiro, o módulo pode fornecer uma rotina para converter o o inteiro em string
    • Nota pessoal: caso o módulo seja nível mais baixo e simples, onde performa apenas uma tarefa (converter, formatar, etc), pode valer a pena; em um módulo mais complexo e mais utilizado em que não sabemos o cliente (ex.: módulo de pagamento) talvez não vale a pena converter todo formato possível (vazando modelo do cliente), melhor fornecer um builder para facilitar a construção.

Reforços:

Ao aplicar a decomposição de módulos usando o ocultamento de informações nas regras contextualizadas possívelmente teremos um design com alta coesão e baixo acomplamento ao limitar o conhecimento entre os módulos apenas através dos pontos necessários.

Para analisar o acoplamento e coesão de um módulo basta olharmos seu motivo para alteração e os efeitos colaterais de fazê-la.

Próximos Passos

Ler as referências:

Top comments (0)