Por que muitos desenvolvedores de software violam o princípio de aberto / fechado , modificando muitas coisas, como renomear funções, que interromperão o aplicativo após a atualização?
Essa pergunta me vem à cabeça depois das versões rápida e contínua na biblioteca React .
A cada curto período, noto muitas mudanças na sintaxe, nos nomes dos componentes, etc.
Exemplo na próxima versão do React :
Novos avisos de descontinuação
A maior mudança é que extraímos React.PropTypes e React.createClass em seus próprios pacotes. Ambos ainda podem ser acessados pelo objeto React principal, mas o uso de ambos registrará um aviso de descontinuação único no console quando estiver no modo de desenvolvimento. Isso permitirá futuras otimizações de tamanho de código.
Esses avisos não afetarão o comportamento do seu aplicativo. No entanto, percebemos que eles podem causar alguma frustração, principalmente se você usar uma estrutura de teste que trate console.error como uma falha.
- Essas mudanças são consideradas uma violação desse princípio?
- Como iniciante em algo como React , como o aprendo com essas rápidas mudanças na biblioteca (é tão frustrante)?
fonte
Respostas:
A resposta do IMHO JacquesB, embora contenha muita verdade, mostra um mal-entendido fundamental do OCP. Para ser justo, sua pergunta também expressa esse mal-entendido - renomear funções quebra a compatibilidade com versões anteriores , mas não o OCP. Se a quebra de compatibilidade parecer necessária (ou a manutenção de duas versões do mesmo componente para não quebrar a compatibilidade), o OCP já estava quebrado antes!
Como Jörg W Mittag já mencionou em seus comentários, o princípio não diz "você não pode modificar o comportamento de um componente" - diz: deve-se tentar projetar componentes de uma maneira que eles estejam abertos para serem reutilizados (ou estendidos) de várias maneiras, sem a necessidade de modificação. Isso pode ser feito fornecendo os "pontos de extensão" corretos ou, como mencionado por @AntP, "decompondo uma estrutura de classe / função para o ponto em que todos os pontos de extensão naturais estão por padrão". O IMHO após o OCP não tem nada em comum com "manter a versão antiga inalterada para compatibilidade com versões anteriores" ! Ou, citando o comentário de @ DerekElkin abaixo:
Bons programadores usam sua experiência para projetar componentes com os pontos de extensão "certos" em mente (ou - melhor ainda - de maneira que nenhum ponto de extensão artificial seja necessário). No entanto, para fazer isso corretamente e sem uma engenharia desnecessária, é necessário saber de antemão como serão os futuros casos de uso do seu componente. Mesmo programadores experientes não podem olhar para o futuro e conhecer todos os requisitos futuros com antecedência. E é por isso que às vezes a compatibilidade com versões anteriores precisa ser violada - não importa quantos pontos de extensão seu componente tenha, ou quão bem ele siga o OCP em relação a certos tipos de requisitos, sempre haverá um requisito que não pode ser implementado facilmente sem modificar o componente.
fonte
O princípio aberto / fechado tem benefícios, mas também apresenta algumas desvantagens sérias.
Em teoria, o princípio resolve o problema de compatibilidade com versões anteriores, criando código "aberto para extensão, mas fechado para modificação". Se uma classe tem alguns novos requisitos, você nunca modifica o código fonte da própria classe, mas cria uma subclasse que substitui apenas os membros apropriados necessários para alterar o comportamento. Todo o código gravado na versão original da classe não é afetado, portanto, você pode ter certeza de que sua alteração não quebrou o código existente.
Na realidade, você acaba facilmente com o inchaço do código e uma confusão confusa de classes obsoletas. Se não for possível modificar algum comportamento de um componente por meio de extensão, você deverá fornecer uma nova variante do componente com o comportamento desejado e manter a versão antiga inalterada para compatibilidade com versões anteriores.
Digamos que você descubra uma falha fundamental de design em uma classe base da qual muitas classes herdam. Digamos que o erro seja devido a um campo privado do tipo errado. Você não pode corrigir isso substituindo um membro. Basicamente, você precisa substituir toda a classe, o que significa que você acaba estendendo
Object
para fornecer uma classe base alternativa - e agora você também precisa fornecer alternativas para todas as subclasses, terminando com uma hierarquia de objetos duplicada, uma hierarquia defeituosa e uma melhorada . Mas você não pode remover a hierarquia defeituosa (já que a exclusão do código é uma modificação), todos os futuros clientes serão expostos às duas hierarquias.Agora, a resposta teórica para esse problema é "apenas projete-o corretamente da primeira vez". Se o código for perfeitamente decomposto, sem falhas ou erros e projetado com pontos de extensão preparados para todas as possíveis alterações futuras de requisitos, evite a confusão. Mas, na realidade, todo mundo comete erros e ninguém pode prever o futuro perfeitamente.
Pegue algo como o framework .NET - ele ainda carrega o conjunto de classes de coleção que foram projetadas antes da introdução dos genéricos há mais de uma década. Isso certamente é um benefício para a compatibilidade com versões anteriores (você pode atualizar a estrutura sem precisar reescrever nada), mas também incha a estrutura e apresenta aos desenvolvedores um grande conjunto de opções, onde muitas são simplesmente obsoletas.
Aparentemente, os desenvolvedores do React sentiram que não valia a pena o custo em complexidade e inchaço do código para seguir rigorosamente o princípio de abrir / fechar.
A alternativa pragmática ao aberto / fechado é a depreciação controlada. Em vez de quebrar a compatibilidade com versões anteriores em um único release, os componentes antigos são mantidos por um ciclo de release, mas os clientes são informados por meio de avisos do compilador de que a abordagem antiga será removida em um release posterior. Isso dá aos clientes tempo para modificar o código. Essa parece ser a abordagem do React neste caso.
(Minha interpretação do princípio é baseada no Princípio Aberto-Fechado de Robert C. Martin)
fonte
Eu chamaria o princípio aberto / fechado de ideal. Como todos os ideais, ele dá pouca consideração às realidades do desenvolvimento de software. Também como todos os ideais, é impossível alcançá-lo na prática - apenas se esforça para abordar esse ideal da melhor maneira possível.
O outro lado da história é conhecido como Algemas de Ouro. Algemas de Ouro são o que você ganha quando se escrava demais do princípio de abrir / fechar. Algemas douradas são o que ocorre quando o produto que nunca quebra a compatibilidade com versões anteriores não pode crescer porque muitos erros do passado foram cometidos.
Um exemplo famoso disso é encontrado no gerenciador de memória do Windows 95. Como parte do marketing do Windows 95, foi declarado que todos os aplicativos do Windows 3.1 funcionariam no Windows 95. A Microsoft adquiriu licenças para milhares de programas para testá-los no Windows 95. Um dos casos de problemas foi o Sim City. Na verdade, o Sim City tinha um bug que fazia com que ele gravasse na memória não alocada. No Windows 3.1, sem um gerenciador de memória "adequado", esse foi um pequeno passo em falso. No entanto, no Windows 95, o gerenciador de memória detectaria isso e causaria uma falha de segmentação. A solução? No Windows 95, se o nome do seu aplicativo for
simcity.exe
, o sistema operacional relaxará as restrições do gerenciador de memória para evitar a falha de segmentação!A verdadeira questão por trás desse ideal são os conceitos simplificados de produtos e serviços. Ninguém realmente faz um ou o outro. Tudo se alinha em algum lugar na região cinzenta entre os dois. Se você pensa em uma abordagem orientada ao produto, abrir / fechar soa como um ótimo ideal. Seus produtos são confiáveis. No entanto, quando se trata de serviços, a história muda. É fácil mostrar que, com o princípio de aberto / fechado, a quantidade de funcionalidade que sua equipe deve suportar deve abordar assintoticamente o infinito, porque você nunca pode limpar as funcionalidades antigas. Isso significa que sua equipe de desenvolvimento deve oferecer suporte a mais e mais códigos a cada ano. Eventualmente, você chega a um ponto de ruptura.
Atualmente, a maioria dos softwares, especialmente de código aberto, segue uma versão relaxada comum do princípio de aberto / fechado. É muito comum ver aberto / fechado seguido servilmente para lançamentos menores, mas abandonado para lançamentos principais. Por exemplo, o Python 2.7 contém muitas "más escolhas" dos dias Python 2.0 e 2.1, mas o Python 3.0 varreu todas elas. (Além disso, a mudança da base de código do Windows 95 para a base de código do Windows NT quando lançou o Windows 2000 quebrou todos os tipos de coisas, mas que significa que nunca tem que lidar com um gerenciador de memória verificando o nome do aplicativo para decidir comportamento!)
fonte
A resposta do Doc Brown é a mais exata possível, as outras respostas ilustram mal-entendidos do Princípio Aberto Fechado.
Articular explicitamente o mal-entendido, parece haver uma crença de que o OCP significa que você não deve fazer alterações para trás incompatíveis (ou mesmo quaisquer alterações ou algo nesse sentido.) O OCP é sobre a criação de componentes de modo que você não precisa de faça alterações para estender sua funcionalidade, independentemente de essas alterações serem compatíveis com versões anteriores ou não. Há muitas outras razões além da adição de funcionalidades, que você pode fazer alterações em um componente, seja elas compatíveis com versões anteriores (por exemplo, refatoração ou otimização) ou incompatíveis com versões anteriores (por exemplo, descontinuando e removendo a funcionalidade). O fato de você poder fazer essas alterações não significa que seu componente violou o OCP (e definitivamente não significa que você estão violando o OCP).
Realmente, não se trata de código fonte. Uma declaração mais abstrata e relevante do OCP é: "um componente deve permitir a extensão sem a necessidade de violar seus limites de abstração". Eu iria além e diria que uma versão mais moderna é: "um componente deve impor seus limites de abstração, mas permitir a extensão". Mesmo no artigo sobre OCP de Bob Martin, enquanto ele "descreve" "fechado para modificação" como "o código-fonte é inviolável", mais tarde ele começa a falar sobre encapsulamento que nada tem a ver com a modificação do código-fonte e tudo a ver com abstração limites.
Portanto, a premissa defeituosa na pergunta é que o OCP é (pretendido como) uma diretriz sobre evoluções de uma base de código. O OCP é tipicamente slogan como "um componente deve ser aberto a extensões e fechado a modificações pelos consumidores". Basicamente, se um consumidor de um componente desejar adicionar funcionalidade ao componente, ele poderá estender o componente antigo para um novo com a funcionalidade adicional, mas não poderá alterar o componente antigo.
O OCP não diz nada sobre o criador de um componente alterando ou removendo a funcionalidade. O OCP não está defendendo a manutenção da compatibilidade de bugs para sempre. Você, como criador, não está violando o OCP alterando ou mesmo removendo um componente. Você, ou melhor, os componentes que você escreveu, está violando o OCP se a única maneira de os consumidores adicionarem funcionalidade aos seus componentes é alterando-a, por exemplo, através de patches de macacosou ter acesso ao código fonte e recompilar. Em muitos casos, nenhuma dessas opções é para o consumidor, o que significa que, se seu componente não estiver "aberto para extensão", eles terão azar. Eles simplesmente não podem usar seu componente para as necessidades deles. O OCP argumenta para não colocar os consumidores da sua biblioteca nessa posição, pelo menos no que diz respeito a alguma classe identificável de "extensões". Mesmo quando modificações podem ser feitas no código-fonte ou até mesmo na cópia principal do código-fonte, é melhor "fingir" que você não pode modificá-lo, pois há muitas conseqüências negativas em potencial.
Portanto, para responder às suas perguntas: Não, essas não são violações do OCP. Nenhuma mudança feita por um autor pode ser uma violação do OCP, porque o OCP não é uma série de mudanças. As alterações, no entanto, podem criar violações do OCP e podem ser motivadas por falhas do OCP em versões anteriores da base de código. O OCP é uma propriedade de um pedaço de código específico, não a história evolutiva de uma base de código.
Por outro lado, a compatibilidade com versões anteriores é uma propriedade de uma alteração de código. Não faz sentido dizer que algum código é ou não é compatível com versões anteriores. Só faz sentido falar sobre a compatibilidade com versões anteriores de algum código com relação a algum código mais antigo. Portanto, nunca faz sentido falar sobre o primeiro corte de algum código ser compatível com versões anteriores ou não. O primeiro corte de código pode satisfazer ou deixar de satisfazer o OCP e, em geral, podemos determinar se algum código satisfaz o OCP sem se referir a nenhuma versão histórica do código.
Quanto à sua última pergunta, é indiscutivelmente fora de tópico para o StackExchange, em geral, ser principalmente baseado em opiniões, mas a falta dela é bem-vinda à tecnologia e, principalmente, ao JavaScript, onde nos últimos anos o fenômeno que você descreve foi chamado fadiga do JavaScript . (Fique à vontade no Google para encontrar uma variedade de outros artigos, alguns satíricos, falando sobre isso de várias perspectivas.)
fonte
private
ou não. Se um autor faz umprivate
métodopublic
mais tarde, isso não significa que eles violaram o controle de acesso, (1/2)private
antes. "A remoção de um componente publicado é claramente uma alteração", não é uma sequência. Os componentes da nova versão satisfazem o OCP ou não, não é necessário o histórico da base de código para determinar isso. Pela sua lógica, eu nunca poderia escrever código que satisfaça o OCP. Você está conflitando com a compatibilidade com versões anteriores, uma propriedade do código muda, com o OCP, uma propriedade do código. Seu comentário faz tanto sentido quanto dizer que o quicksort não é compatível com versões anteriores. (2/2)