Arquitetura de Informação

10 abr, 2020

SOLID de verdade – Interface Segregation Principle (ISP)

Publicidade

De todos os princípios SOLID, o Interface Segregation Principle (ISP) parece ser um dos mais simples de ser compreendido e aplicado. Contudo, há algumas camadas de percepção desse princípio que geralmente passam despercebidas pelos leitores e alguns equívocos bastante comuns. Quero abordar alguns deles a seguir.

O corolário do Interface Segregation Principle é:

“Nenhum cliente deveria ser forçado a depender de métodos que ele não usa”

O entendimento comum é que uma classe deve manter a sua interface simples. Deve compô-la apenas com os métodos necessários para garantir seu tipo e que estejam relacionados com sua responsabilidade. Isso não está errado, mas não é suficiente. Para compreender melhor o ISP, é preciso estudar um pouco mais sobre os princípios para construção de componentes coesos.

Cito novamente o livro Clean Architecture, do Bob Martin, que dedicou um capítulo exclusivamente para tratar da construção de componentes coesos. São três princípios: Reuse/Release equivalence principle, Common Closure principle e Common reuse principle. Desses, os dois últimos têm forte ligação com o Single Responsibility Principle e Interface Segregation Principle respectivamente.

Pense comigo: O que define o tipo em uma classe? Você pode pensar que é o nome que dá para ela. Mas pense outra vez: Se você modificasse esse nome, a classe não continuaria agindo da mesma forma? Você pode dizer, então, que são os dados. No entanto, dados representam apenas uma instância. E se o objeto for mutável, representam ainda apenas o estado temporal de uma instância de objeto.

 James Riley propôs uma forma de reconhecer o tipo de uma classe através da seguinte frase:

“Quando eu vejo um pássaro que caminha como um pato, nada como um pato e grasna como um pato, eu chamo aquele pássaro de pato”.

Duck Typing é como ficou conhecido esse estilo de programação em que a definição do tipo é feita a partir dos métodos presentes na classe e não pela sua cadeia de herança. O que me permite trocar estratégias e automatizar tarefas do código sem saber a implementação que estou recebendo, apenas o seu tipo.

É importante, todavia, perceber que quando falamos interface não estamos nos referindo ao statement interface, que está presente na maior parte das linguagens orientadas a objeto. Interface, nesse caso, é a forma como a sua classe permite a interação com outras classes. É a lista de propriedades e métodos públicos. Não compreender essa definição pode resultar em tremendas confusões na compreensão do ISP.

Um dos primeiros exemplos que vi abordando a temática do ISP me fez acender a luz amarela. Tratava-se de um exemplo no qual havia sido definida uma interface para provedores de nuvem. Além dos dois métodos esperados – storeFile e getFile – também havia métodos específicos para registro na nuvem – createServer, por exemplo. O exemplo seguia com uma classe que abstraia o acesso à nuvem da Amazon. Até aí, perfeito. Depois, implementando a mesma interface, temos uma abstração do Dropbox. Tudo errado, afinal não é possível criar um servidor no Dropbox.

Com certeza esse design está errado. Mas não é de Interface Segregation Principle que estamos falando aqui. Se for aplicado o princípio da Single Responsibility Principle, já seria o suficiente para refatorar esse código a contento e melhorar o projeto da classe. Em tempo, se observarmos o exemplo proposto, ele não é sobre a instância A utilizando uma instância B. Como veremos adiante, o ISP é sobre a relação de consumo das classes e não sobre implementação de interfaces.

Outro erro que já vi em exemplos de ISP foi em relação a métodos abstratos, que não têm sentido de existir nas classes filhas, recebendo apenas um return como codificação. Com certeza este é outro erro de projeto. Talvez a metáfora escolhida não está sendo a mais adequada para a abstração que está sendo construída. No entanto, mais uma vez, este não é o tipo de erro que a ISP quer tratar.

Qual problema, afinal, ISP veio resolver?

Vamos supor que você esteja escrevendo o sistema S1. O sistema S1 depende de uma implementação que está presente no componente C1. Outro time também está escrevendo um sistema S2 que depende de uma outra implementação no componente C1. Dado esse cenário, me responda: Caso eu precise recompilar o componente C1, ou pior, o componente C1 apresente uma falha, quantos sistemas serão impactados? Todos.

É exatamente esse tipo de comportamento que o Interface Segregation Principle deseja evitar. Recompilações desnecessárias causam um enorme esforço de recompilação, revalidação e reimplantação, que poderiam ser evitados. Vai dizer que você nunca teve de alterar um código em um dos módulos principais e depois teve de recompilar tudo? Em linguagens de tipagem estática, isso é bastante comum – especialmente se o projeto inicial não foi bem desenhado.

Tanto o Single Responsibility Principle quanto o Interface Segregation Principle colabora para formação de classes coesas, e por consequência, componentes coesos. Eles encontram seus similares em Common Closure Principle e Common Reuse Principle, que são também princípios arquiteturais que nos ajudam a decidir quais classes devem ou não entrar em um componente.

Como é possível inferir, o buraco está mais embaixo. Em muitos casos, apenas a refatoração já será o suficiente. Pensando em um contexto de mesmo componente/pacote, refatorar para interfaces menores propicia a reutilização de código, e de certa forma até mesmo para a automação do código (algo que veremos em outro momento).

No entanto, essa dependência pode ser externa. Você pode estar escrevendo um componente para acessar um banco de dados, ou a um sistema de terceiros, por exemplo. Em casos como esse, não há muito o que fazer. Você não tem controle das dependências e nem tem como diminuí-las na fonte. Neste caso, convém adotar outros padrões de projeto a ponto de deixar esse acoplamento o mais fraco possível e invisível para quem consome. Caso o componente externo precise ser trocado, o seu adapter deverá sofrer alterações.

É preciso ter em mente que erros acontecem. Quanto maior a sucessão de componentes desde o início até o fim do processamento, maior as chances de dar errado. Por isso, é preciso ter um código fácil de manter e que dê feedbacks rápidos de onde está o erro. Isso é saber lidar com uma exceção.

Como você pode perceber, o Interface Segregation Principle é muito mais do que refatorar algumas interfaces. Os impactos de seguir ou violar esse princípio poderá custar caro para o projeto, podendo até comprometê-lo. Sair desesperado repartindo as interfaces em somente um método também não vai resolver. Por isso, a dica final para que você possa seguir o ISP é: ao olhar para o seu projeto, não pergunte se as classes estão com as interfaces inchadas. Pergunte se as interfaces estão coesas. Se estiverem, ótimo! Do contrário, refatore o quanto antes.