O princípio da responsabilidade única é fantástico… mas você ainda precisa de usabilidade!

O princípio da responsabilidade única é um grande princípio do design de software. Basicamente, ele afirma que toda classe ou módulo deve possuir responsabilidade por apenas uma única parte do sistema.

Essa é uma ideia realmente poderosa, pois quanto menor a responsabilidade de uma classe ou módulo, menor a chance de ocorrer alterações nessas classes ou módulos.

Introdução

Conforme os sistemas vão sendo desenvolvidos e começam a ganhar uma grande proporção, eles costumam ficar “rígidos”. Isso significa que eles não aceitam alterações, mesmo que pequenas, sem que outras partes do sistema sejam afetadas.

Costumo dizer que sistemas, em seu relacionamento entre os elementos que constituem seu corpo, são como teias de aranha. Não há como “mexer” em uma parte da teia sem que toda a teia sofra os impactos. Isto, é claro, para os sistema com alto acoplamento, ou seja, aqueles sistemas onde existe uma alta dependência entre os módulos – e módulos aqui significam classes, units, packages, etc.

A engenharia de software, ao longo do tempo, nos ensinou que existem certas práticas e formas de pensar que ajudam muito a desenvolver sistemas com qualidade, que sejam flexíveis às mudanças. Além do princípio da responsabilidade única, temos diversos outros que nos ajudam a desenvolver sistemas melhores, como o princípio do aberto e fechado, e o princípio de inversão das dependências.

Com o princípio da responsabilidade única, temos a divisão do nosso sistema em diversos micro-elementos, o que garante que, caso haja uma alteração – e acredite, ela vai existir – iremos alterar apenas o elemento necessário para atingir o objetivo da alteração, sendo que o restante permanecerá intacto. Isso é  ótimo porque cada nova alteração gera um impacto e possíveis problemas podem aparecer em virtude disso. Assim, quanto menos alterações, menos “bugs”.

O princípio da responsabilidade única é uma dessas abordagens que melhoram a forma como desenvolvemos sistemas. Ela faz com que criemos “granularidade”, ou seja, tornemos os elementos do nosso sistema tão pequenos que o design do software seja favorecido com os benefícios da “reutilização”, “baixo acoplamento”, ou baixa dependência e fornece uma abertura para “extendermos esses elementos” ao invés de alterá-los.

Um exemplo prático encontra-se abaixo:

Essa é a forma que a maioria dos sistemas são desenvolvidos. A classe TArquivo possui todas as funcionalidades envolvendo a manipulação desse arquivo. E qual o problema disso? Afinal de contas, não está, de fato, de forma organizada, sendo que o objeto agrupa todas as suas funcionalidades? Não é esse um dos principais objetivos da orientação à objetos?

De fato, possui uma organização, o que é bom, mas possui mais do que isso. A classe TArquivo ficou com diversas responsabilidades. Ela é responsável por carregar o arquivo, limpar o arquivo, enviar o arquivo e validar o arquivo. Qualquer alteração em qualquer uma dessas funcionalidades pode comprometer as outras funcionalidades.

Uma outra forma de fazermos a mesmo coisa seria:

Perceba que criamos uma classe para cada funcionalidade. O que fizemos aqui foi distribuir a responsabilidade que antes estava apenas com a classe TArquivo para diversas outras classes. Assim, caso precise alterar a forma que os arquivos são validados, a alteração irá ocorrer apenas no código responsável por isso, sem interferir no código restante. Isolamos a alteração para impedir qualquer impacto negativo sobre o restante do código.

Caso precisemos criar outras funcionalidades, elas terão sua própria classe, impedindo que essas classes atuais sejam modificadas (opa! seguimos o princípio do aberto/fechado naturalmente!)

Há como melhorar ainda mais o código acima, utilizando interfaces e tornando as classes ainda mais genéricas e reutilizáveis, mas o importante aqui é a visualização de como podemos alterar nossa forma de pensar sistemas e separar cada “responsabilidade” em seu devido lugar. Ao fazer isso, o design da sua aplicação assume características de uma boa implementação, porque espontaneamente leva à utilização de outros bons princípios de design.

Responsabilidade Única vs Usabilidade

Conforme formos aplicando o princípio da responsabilidade única, estaremos criando milhares de classes, todas minúsculas. É esse mesmo o objetivo. Alguns irão reclamar que toda essa multidão de classes é desnecessária. Eu penso diferente. Acho que ter 10 ou 100 classes não é problema. O compilador ou o interpretador não liga para isso. Claro que quanto mais classes, mais difícil fica a organização delas dentro do projeto, mas os benefícios dessa prática são inúmeras vezes maiores do que esse problema.

Contudo, existe um aspécto inerente ao princípio da responsabilidade única que é importante. Chama-se usabilidade. E aqui refiro-me à usabilidade do código-fonte pelos desenvolvedores. A não ser que você desenvolva sozinho  e tenha ótima memória, quando desenvolvemos um conjunto de classes, desenvolvemos em um ambiente com a coparticipação de diversas pessoas. Desenvolvemos pensando na manutenção futura que esse sistema terá. Não há como negligenciar o fato de que uma enorme quantidade de classe exige um esforço muito maior para o aprendizado do código, um esforço para a memorização de toda a estrutura.

Assim, conforme o número de classes vai aumentado, a usabilidade vai diminuíndo.

Conhecendo o padrão de projetos Facade (ou Fachada)

Facade é um padrão de projeto há muito já conhecido. O seu objetivo é fornecer uma interface simplificada para um sub-conjunto de interfaces, ou seja, ele agrupa em uma única interface as funcionalidades distribuídas em outras interfaces.

Quando você utiliza sua máquina de lavar roupas, você está utilizando um Facade. O simples fato de escolher o nível de água, a programação da roupa – roupa de cama, toalha, ciclo rápido, etc – e apertar o botão para iniciar a lavagem dispara internamente, uma série de execuções dentro do equipamento. A máquina irá abrir o mecanismo de entrada de água, utilizar o dispenser de sabão e amaciante quando necessário, iniciar a rotação, etc. Todas essas operações ficam acessíveis através da interface com o usuário: botão de escolha dos níveis de água, tipo de lavagem e botão iniciar.

Essa interface é a fachada da máquina de lavar. É através dessa fachada (facade) que é possível ter acesso às reais funcionalidades da máquina de lavar. A fachada em sí, muitas vezes não faz nada, mas ela centraliza a ação do usuário em uma única interface. Ela facilita o uso do subconjunto de funcionalidades.

Quando falamos de facade em software, falamos de uma classe que servirá como a fachada, a interface para a utilização de outras classes, essas as quais serão a verdadeiras responsáveis pelo funcionamento do sistema.

Unindo o design com a usabilidade

É possível unir um bom design mantendo a facilidade de uso. Como vimos, o padrão facade nos ajuda a unir em uma única interface toda a granularidade da prática do princípio da responsabilidade única.

Podemos refatorar nosso código dos exemplos anteriores para que fique assim:

Passamos todas as funcionalidades para a classe facade. Não estamos ferindo o princípio da responsabilidade única porque a responsabilidade da classe facade é justamente servir de fachada, de uma “interface” que facilite a utilização de nossas outras classes. Mantivemos o princípio da responsabilidade única e ainda facilitamos a utilização de todo esse mecanismo, visto que os “clientes dessas classes”, ou seja, os outros desenvolvedores envolvidos, e até o criador das classes depois de certo tempo, terão estrema facilidade em utilizar os recursos de manipulação de TArquivo pois todas as funcionalidades desejáveis estão disponíveis na classe facade.

Novamente aqui, preferí dar o foco nas responsabilidades e não na melhor forma de implementar o desacoplamento. O melhor nesse caso seria utilizarmos interfaces que nos ajudassem a diminuir as dependências de classes.

Uma implementação com as interfaces ficaria assim:

As interfaces garantem que eu posso trocar as classes que manipulam TArquivo sem alterar a estrutura já existente. É uma das formas de codificar para abstrações. Mas isso, já é um outro assunto.

Conclusão

É possível desenvolver sistemas com designs altamente maleáveis e que ao mesmo tempo sejam simples e fáceis de se utilizar. A preocupação com a manutenção e a simplicidade do código é uma das chaves para o sucesso no desenvolvimento de sistema.

2 respostas para “O princípio da responsabilidade única é fantástico… mas você ainda precisa de usabilidade!”

  1. Parabéns por seus artigos, mas já li em vários outros artigos de outros autores que falam da responsabilidade única, mas mesmo com o método de fachada(facade) eu não consegui entender algo do tipo como eu faço então por exemplo se eu preciso descomprimir um arquivo e preciso obter o tamanho desse arquivo eu usaria uma função para isso e depois mudar algo nesse arquivo imaginando um arquivo do tipo texto onde eu preciso ler linha por linha e no caso de haver duplicação remove-la onde eu usaria outra função e ainda como exemplo onde cada linha é preenchida com uma sequencia de números e que estão fora de ordem eu preciso de outra função para organizar esses números em ordem crescente, imaginando isso de uma forma procedural eu simplesmente abriria este arquivo faria um loop “while not eof(arq) do” e dentro dele usaria as funções já mencionadas, mas utilizando a responsabilidade única essas funções seriam trocadas por classes e na classe onde é feita o loop eu precisaria chamar essas outras classes dai eu não estaria
    tendo o problema de encapsulamento já que para a realização da correção do arquivo estaria varias outras classes dentro dessa classe? Espero que consiga entender minha dúvida. Mas desde já agradeço sua atenção.

    1. João,

      eu questionei muito a questão da responsabilidade única, mas hoje não imagino uma boa arquitetura de software sem ela. Basicamente porque se você for para TDD, não tem forma melhor do que o princípio da responsabilidade única. Mas isso é outra história. Vamos lá.

      Primeiro, entenda que todo o código de manipulação do arquivo, no seu exemplo, ainda irá existir. O fato de uma classe ter responsabilidade única, não significa que você não tenha que implementar as funcionalidades. O que ocorre, é que você terá muito mais classes.

      No seu exemplo, em um modelo tradicional, você teria algo como:

      TArquivo = class
      public
      procedure TratarArquivo;
      end;

      ou ainda,

      TArquivo = class
      public
      procedure DescomprimirArquivo;
      function ObterTamanhoArquivo: Integer;
      procedure RemoverLinhaArquivo;
      procedure OrdenarLinhasArquivo;
      end;

      com o princípio da responsabilidade única, você realmente teria uma classe para cada funcionalidade, como por exemplo:

      TDescompactadorDeArquivos = class
      public
      procedure DescomprimirArquivo;
      end;

      TObtenedorDeTamanhoDeUmArquivo = class
      public
      function ObterTamanhoArquivo: Integer;
      end;

      TRemovedorDeLinhaDeArquivo = class
      procedure RemoverLinhaArquivo;
      end;

      TOrdenadorDeLinhasDeArquivos = class
      procedure OrdenarLinhasArquivo;
      end;

      Com isso, existe uma pulverização de classes, sem dúvida, e elas se inter-relacionam, gerando uma dependência. Mas entenda uma coisa: a dependência já existe. O fato de estar separado em classes, não é o que gera a dependência. O processo de manipulação do arquivo é que causa a dependência.

      Outra coisa legal na separação, é que a dependência fica clara. Quando você tem um amontoado de coisas fazendo tudo, as relações entre os elementos do código ficam invisíveis. Com a separação, você pode facilmente definir “o que depende do que”.

      A grandeza da separação das responsabilidades reside na criação de uma melhor arquitetura onde você ganha reusabilidade. Você pode utilizar a mesma classe com diferentes tipos de arquivos, por exemplo, se for o caso (desde que aplicável) sem ter que reescrevê-lo. Quanto menor a estrutura, mais fácil de reutillizá-la. Pense no brinquedo “lego”. É fantástico porque é feito de minúsculas unidades de montagem, o que proporciona uma infinidade de formas possíveis de serem montadas. Se a peça fosse um carro, por exemplo, você não conseguiria construir uma parede, por exemplo, usando a mesma peça (exemplo esdrúxulo, mas acho que pegou a ideia).

      E por fim, imagine o cenário: você precisa alterar o código que remove a linha do arquivo. Com a separação das responsabilidade, onde você iria? Na classe que responde por apenas isso, certo? Não é fantástico? O que você alterar ali, não interfere em nada mais, apenas ali. Assim você não precisa se preocupar com o restante do código, porque vai funcionar. Preocuparia-se apenas com o método que remove a linha do arquivo, com a certeza de não estar inserindo novos bugs.

      Espero que tenha deixado um pouco mais claro essa forma de pensar, e quais os benefícios que ela traz.

      Abraços!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *