Refatoração e princípio Aberto / Fechado

12

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 Ie uma classe de implementação correspondente A. Quando a classe Ase torna estável (implementada e testada), normalmente não a modifico muito (possivelmente nem mesmo), ou seja,

  1. 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 Be continuo usando Aenquanto Bnão estiver maduro. Quando Bmaduro, tudo o que é necessário é mudar a forma como Ié instanciado.
  2. Se os novos requisitos também sugerem uma alteração na interface, defino uma nova interface I'e uma nova implementação A'. Então I, Asão congelados e continuam a implementação do sistema de produção, desde que I'e A'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?

Giorgio
fonte

Respostas:

9

Penso no princípio Aberto-Fechado como um objetivo de design . Se você tiver que violá-lo, isso significa que seu design inicial falhou, o que é certamente possível e até provável.

Refatorar significa que você está alterando o design sem alterar a funcionalidade. É provável que você esteja alterando seu design porque há um problema com ele. Talvez o problema seja que é difícil seguir o princípio de aberto-fechado ao fazer modificações no código existente, e você está tentando corrigi-lo.

Você pode estar fazendo uma refatoração para possibilitar a implementação do seu próximo recurso sem violar o OCP ao fazê-lo.

Scott Whitlock
fonte
Você certamente não deve pensar em nenhum princípio como um objetivo de design . São ferramentas - você não está tornando o software bonito e teoricamente correto por dentro, mas tentando produzir valor para o seu cliente. É uma diretriz , nada mais.
24417 Sar T. T.
@ T.Sar Um princípio é uma diretriz, algo pelo qual você se esforça, eles são orientados para manutenção e escalabilidade. Parece-me um objetivo de design. Não consigo ver um princípio como uma ferramenta da maneira como vejo um padrão de design ou uma estrutura como uma ferramenta.
Tulains Córdova
@ TulainsCórdova Manutenção, desempenho, correção, escalabilidade - esses são os objetivos. O princípio Aberto-Fechado é um meio para eles - apenas um dentre muitos. Você não precisa empurrar algo em direção ao princípio de aberto-fechado, se não for aplicável a ele ou prejudicar os objetivos reais do projeto. Você não vende "abertura fechada" a um cliente. Como mera orientação , não é melhor do que uma regra geral que pode ser descartada se você acabar encontrando uma maneira de fazer suas coisas de maneira mais legível e clara. As diretrizes são ferramentas, afinal, nada mais.
557
@ T.Sar Há tantas coisas que você não pode vender para um cliente ... Por outro lado, eu concordo com você, pois não se deve fazer coisas que prejudiquem os objetivos do projeto.
Tulains Córdova
9

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.

radarbob
fonte
2
"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.": Até onde eu entendo, a adição de novos métodos não viola o princípio do OC (aberto para extensão). . O problema é alterar os métodos existentes que implementam uma interface bem definida e, portanto, já possuem semânticas bem definidas (fechadas para modificação). Em princípio, a refatoração não altera a semântica, portanto, o único risco que posso ver é a introdução de bugs em códigos já estáveis ​​e bem testados.
Giorgio
1
Aqui está a resposta do CodeReview que ilustra a extensão aberta . Esse design de classe é extensível. Por outro lado, adicionar um método está modificando a classe.
Radarbob
A adição de novos métodos viola o LSP, não o OCP.
Tulains Córdova
1
A adição de novos métodos não viola o LSP. Se você adicionar um método, você introduziu uma nova interface @ TulainsCórdova
RubberDuck
6

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.

opsb
fonte
1
Eu não sou um defensor do princípio aberto-fechado nem do TDD (no sentido em que não os inventei). O que me surpreendeu foi que alguém propôs o princípio aberto-fechado E o uso de refatoração e TDD ao mesmo tempo. Isso me pareceu contraditório e, portanto, eu estava tentando descobrir como reunir todas essas diretrizes em um processo coerente.
Giorgio
"A ideia é que é arriscado refatorar o código, porque você pode quebrar algo, então é mais seguro deixar o código existente como está e simplesmente adicioná-lo.": Na verdade, eu não o vejo dessa maneira. A idéia é ter unidades pequenas e independentes que você possa substituir ou estender (permitindo que o software evolua), mas você não deve tocar em cada unidade depois que ela for exaustivamente testada.
Giorgio
Você deve pensar que a classe não será usada apenas na sua base de código. A biblioteca que você escreve pode ser usada em outros projetos. Então o OCP é importante. Além disso, um novo programador que não conhece uma classe estendida com a funcionalidade de que precisa é um problema de comunicação / documentação, não um problema de design.
Tulains Córdova
@ TulainsCórdova no código do aplicativo não é relevante. Para o código da biblioteca, eu argumentaria que o controle de versão semântico era mais adequado para comunicar alterações de última hora.
Opsb
1
@ TulainsCórdova com a estabilidade da API do código da biblioteca é muito mais importante porque não é possível testar o código do cliente. Com o código do aplicativo, sua cobertura de teste informará você imediatamente sobre eventuais quebras. Dito de outra forma, o código do aplicativo é capaz de fazer alterações significativas sem risco enquanto o código da biblioteca tem de gerir o risco, mantendo uma API estável e sinalização quebras usando, por exemplo semântica versionamento
opSB
6

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:

  • Deve haver muito poucas razões para fazê-lo (ponto B), tendendo a zero ao longo do tempo.
  • (Ponto C) sempre será possível, embora seja menos frequente.
  • Toda nova funcionalidade deve ser uma especialização, o que significa que as classes devem ser estendidas (herdadas de) (ponto A).
Tulains Córdova
fonte
O princípio aberto / fechado é muito mal compreendido. Seus pontos A e B acertam exatamente.
gnasher729
1

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.

David Hammen
fonte
0

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.

Patkos Csaba
fonte
1
"Então, quando você refatorar, você não adiciona novos recursos.": Mas eu poderia introduzir bugs em um software testado.
Giorgio
"À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 Open-Close, não respeita outros princípios ou requisitos de software.": É nesse momento que eu faria comece a escrever uma nova implementação Be, quando estiver pronto, substitua a implementação antiga Apela nova B(esse é um uso de interfaces). AO código de pode servir como base para Bo código e, em seguida, eu posso usar a refatoração no Bcódigo durante seu desenvolvimento, mas acho que o Acódigo já testado deve permanecer congelado.
Giorgio
@Giorgio Quando você refatorar, pode introduzir bugs, é por isso que você escreve testes (ou melhor ainda, TDD). A maneira mais segura de refatorar é alterar o código quando você souber que está funcionando. Você sabe disso tendo um conjunto de testes que estão sendo aprovados. Depois de alterar seu código de produção, os testes ainda precisam passar, para que você saiba que não introduziu um bug. E lembre-se, os testes são tão importantes quanto o código de produção; portanto, você aplica a mesma regra a eles e ao código de produção, mantendo-os limpos e os refatorando periodicamente e com frequência.
Patkos Csaba 19/10/12
@Giorgio Se o código Bé construído sobre o código Acomo uma evolução de A, então, quando Bé lançado, Adeve ser removido e nunca usado novamente. Clientes anteriormente utilizando Asó vai usar Bsem saber sobre a mudança, desde Interface Inão foi alterada (talvez um pouco de substituição de Liskov aqui ... a? L de SOLID)
Patkós Csaba
Sim, é isso que eu tinha em mente: não jogue fora o código de trabalho até que você tenha uma substituição válida (bem testada).
Giorgio
-1

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.

Narender Parmar
fonte