Recentemente, li um site sobre desenvolvimento de código limpo (não coloquei um link aqui porque não está em inglês).
Um dos princípios anunciados por este site é o Princípio Aberto Fechado : cada componente de software deve estar aberto para extensão e fechado para modificação. Por exemplo, quando implementamos e testamos uma classe, devemos modificá-la apenas para corrigir erros ou adicionar novas funcionalidades (por exemplo, novos métodos que não influenciam os existentes). A funcionalidade e implementação existentes não devem ser alteradas.
Normalmente aplico esse princípio definindo uma interface I
e uma classe de implementação correspondente A
. Quando a classe A
se torna estável (implementada e testada), normalmente não a modifico muito (possivelmente nem mesmo), ou seja,
- Se novos requisitos chegarem (por exemplo, desempenho ou uma implementação totalmente nova da interface) que exijam grandes alterações no código, escrevo uma nova implementação
B
e continuo usandoA
enquantoB
não estiver maduro. QuandoB
maduro, tudo o que é necessário é mudar a forma comoI
é instanciado. - Se os novos requisitos também sugerem uma alteração na interface, defino uma nova interface
I'
e uma nova implementaçãoA'
. EntãoI
,A
são congelados e continuam a implementação do sistema de produção, desde queI'
eA'
não são suficientes estável para substituí-los.
Portanto, em vista dessas observações, fiquei um pouco surpreso que a página da Web sugerisse o uso de refatorações complexas , "... porque não é possível escrever código diretamente em sua forma final".
Não existe contradição / conflito entre aplicar o Princípio Aberto / Fechado e sugerir o uso de refatorações complexas como uma prática recomendada? Ou a idéia aqui é que se pode usar refatorações complexas durante o desenvolvimento de uma classe A
, mas quando essa classe foi testada com sucesso, deve ser congelada?
O princípio Aberto-Fechado é mais um indicador de quão bem seu software foi projetado ; não é um princípio a seguir literalmente. Também é um princípio que ajuda a impedir a alteração acidental de interfaces existentes (classes e métodos que você chama e como espera que funcionem).
O objetivo é escrever software de qualidade. Uma dessas qualidades é extensibilidade. Isso significa que é fácil adicionar, remover e alterar o código, pois essas alterações tendem a ser limitadas ao menor número possível de classes existentes. Adicionar novo código é menos arriscado do que alterar o código existente, portanto, a esse respeito, Abrir-Fechado é uma boa coisa a se fazer. Mas de que código estamos falando exatamente? O crime de violar o OC é muito menor quando você pode adicionar novos métodos a uma classe em vez de precisar alterar os existentes.
OC é fractal . É maçãs em todas as profundezas do seu design. Todo mundo assume que é aplicado apenas no nível da classe. Mas é igualmente aplicável no nível do método ou no nível da montagem.
A violação muito frequente do CO no nível apropriado sugere que talvez seja hora de refatorar . "Nível apropriado" é uma chamada de julgamento que tem tudo a ver com seu design geral.
Após Abrir-Fechado significa literalmente que o número de classes explodirá. Você criará (maiúsculas "I") interfaces desnecessariamente. Você terminará com algumas funcionalidades espalhadas pelas classes e precisará escrever muito mais código para conectá-lo. Em algum momento, você perceberá que mudar a classe original teria sido melhor.
fonte
O princípio Aberto-Fechado parece ser um princípio que apareceu antes do TDD ser mais prevalente. A ideia é que é arriscado refatorar o código, porque você pode quebrar algo, por isso é mais seguro deixar o código existente como está e simplesmente adicioná-lo. Na ausência de testes, isso faz sentido. A desvantagem dessa abordagem é a atrofia do código. Cada vez que você estende uma classe, em vez de refatorá-la, acaba com uma camada extra. Você está simplesmente colocando o código em cima. Toda vez que você aplica mais código, aumenta a chance de duplicação. Imagine; há um serviço na minha base de código que eu quero usar, acho que ele não tem o que eu quero, então crio uma nova classe para estendê-lo e incluir minha nova funcionalidade. Outro desenvolvedor aparece mais tarde e também deseja usar o mesmo serviço. Infelizmente, eles não não percebo que minha versão estendida existe. Eles codificam contra a implementação original, mas também precisam de um dos recursos que eu codifiquei. Em vez de usar minha versão, eles agora também estendem a implementação e adicionam o novo recurso. Agora, temos três classes, a original e duas novas versões com algumas funcionalidades duplicadas. Siga o princípio de aberto / fechado e essa duplicação continuará aumentando ao longo da vida útil do projeto, levando a uma base de código desnecessariamente complexa.
Com um sistema bem testado, não há necessidade de sofrer essa atrofia do código, você pode refatorar com segurança o código, permitindo que seu design assimile novos requisitos, em vez de ter que se apegar continuamente ao novo código. Esse estilo de desenvolvimento é chamado de design emergente e leva a bases de código capazes de permanecer em boa forma durante toda a vida útil, em vez de gradualmente coletar lixo.
fonte
Nas palavras do leigo:
A. O princípio O / C significa que a especialização deve ser feita estendendo, não modificando uma classe para acomodar necessidades especializadas.
B. Adicionar funcionalidades ausentes (não especializadas) significa que o design não foi concluído e você deve adicioná-las à classe base, obviamente sem violar o contrato. Eu acho que isso não está violando o princípio.
C. A refatoração não viola o princípio.
Quando um design amadurece , digamos, depois de algum tempo na produção:
fonte
Para mim, o Princípio Aberto-Fechado é uma diretriz, não uma regra rígida e rápida.
Com relação à parte aberta do princípio, as classes finais em Java e as classes em C ++ com todos os construtores declarados como privados violam a parte aberta do princípio de aberto-fechado. Existem bons casos de uso sólido (nota: sólido, não SOLID) para as classes finais. Projetar para extensibilidade é importante. No entanto, isso exige muita perspicácia e esforço, e você está sempre contornando a linha de violar o YAGNI (você não precisará disso) e injetando o cheiro do código da generalidade especulativa. Os principais componentes de software devem estar abertos para extensão? Sim. Todos? Não. Isso por si só é uma generalidade especulativa.
No que diz respeito à parte fechada, ao passar da versão 2.0 para 2.1 para 2.2 para 2.3 de algum produto, não é recomendável modificar o comportamento. Os usuários realmente não gostam quando cada versão menor quebra seu próprio código. No entanto, ao longo do caminho, geralmente se descobre que a implementação inicial na versão 2.0 foi fundamentalmente interrompida ou que restrições externas que limitavam o design inicial não se aplicavam mais. Você sorri, aguenta e mantém esse design no release 3.0, ou você torna o 3.0 não compatível com versões anteriores em alguns aspectos? A compatibilidade com versões anteriores pode ser uma grande restrição. Os principais limites de versão são o local em que a compatibilidade com versões anteriores é aceitável. Você precisa ter cuidado, pois isso pode deixar seus usuários chateados. Deve haver um bom argumento para o porquê dessa ruptura com o passado ser necessária.
fonte
A refatoração, por definição, está alterando a estrutura do código sem alterar o comportamento. Portanto, ao refatorar, você não adiciona novos recursos.
O que você fez como exemplo para o princípio Abrir e fechar parece bom. Esse princípio trata da extensão do código existente com novos recursos.
No entanto, não entenda mal esta resposta. Não insisto que você deva fazer apenas recursos ou refatorar apenas grandes pedaços de dados. A maneira mais comum de programação é fazer um pouco de um recurso do que fazer imediatamente um pouco de refatoração (combinada com testes, é claro, para garantir que você não altere nenhum comportamento). Refatoração complexa não significa refatoração "grande", significa aplicação de técnicas de refatoração complicadas e bem pensadas.
Sobre os princípios do SOLID. São realmente boas diretrizes para o desenvolvimento de software, mas não existem regras religiosas a serem seguidas cegamente. Às vezes, muitas vezes, depois de adicionar um segundo e um terceiro e um nono recurso, você percebe que seu design inicial, mesmo que respeite o Aberto-Fechado, não respeita outros princípios ou requisitos de software. Há pontos na evolução de um design e de um software quando mudanças mais complexas precisam ser feitas. O objetivo principal é encontrar e solucionar esses problemas o mais rápido possível e aplicar as técnicas de refatoração da melhor maneira possível.
Não existe design perfeito. Não existe um projeto que possa e deva respeitar todos os princípios ou padrões existentes. Isso está codificando a utopia.
Espero que esta resposta tenha ajudado em seu dilema. Sinta-se livre para pedir esclarecimentos, se necessário.
fonte
B
e, quando estiver pronto, substitua a implementação antigaA
pela novaB
(esse é um uso de interfaces).A
O código de pode servir como base paraB
o código e, em seguida, eu posso usar a refatoração noB
código durante seu desenvolvimento, mas acho que oA
código já testado deve permanecer congelado.B
é construído sobre o códigoA
como uma evolução deA
, então, quandoB
é lançado,A
deve ser removido e nunca usado novamente. Clientes anteriormente utilizandoA
só vai usarB
sem saber sobre a mudança, desde InterfaceI
não foi alterada (talvez um pouco de substituição de Liskov aqui ... a? L de SOLID)De acordo com meu entendimento - se você adicionar novos métodos à classe existente, ela não quebrará o OCP. no entanto, estou um pouco confuso com a adição de novas variáveis na classe. Mas se você alterar o método e os parâmetros existentes no método existente, ele certamente quebrará o OCP, porque o código já foi testado e passado se alterarmos intencionalmente o método [Quando a alteração de requisitos] será um problema.
fonte