Por que muitos desenvolvedores de software violam o princípio aberto / fechado?

71

Por que muitos desenvolvedores de software violam o princípio de abertura / fechamento , modificando muitas coisas, como renomear funções que quebram o aplicativo após a atualização?

Esta pergunta pula para minha cabeça depois das versões rápida e contínua na biblioteca React .

A cada curto período, noto muitas alterações na sintaxe, nos nomes dos componentes, etc.

Exemplo em a próxima versão do React :

New Deprecation Warnings

The biggest change is that we've extracted React.PropTypes and React.createClass into their own packages. Both are still accessible via the main React object, but using either will log a one-time deprecation warning to the console when in development mode. This will enable future code size optimizations.

These warnings will not affect the behavior of your application. However, we realize they may cause some frustration, particularly if you use a testing framework that treats console.error as a failure.

  • Essas mudanças são consideradas uma violação desse princípio?
  • Como iniciante em algo como React , como faço para aprender com esses mudanças rápidas na biblioteca (é tão frustrante)?
por Anyname Donotcare 30.04.2017 / 16:54
fonte

4 respostas

143

A resposta de IMHO JacquesB, embora contenha muita verdade, mostra um mal-entendido fundamental do OCP. Para ser justo, sua pergunta já expressa esse equívoco também - renomear funções quebra compatibilidade com versões anteriores , mas não o OCP. Se quebrar a compatibilidade parecer necessário (ou manter duas versões do mesmo componente para não quebrar a compatibilidade), o OCP já foi 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 eles estão 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 no ponto em que todos os pontos de extensão naturais estão lá por padrão". IMHO seguindo 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 do @DerekElkin abaixo:

The OCP is advice on how to write a module [...], not about implementing a change management process that never allows modules to change.

Bons programadores usam sua experiência para projetar componentes com os pontos de extensão "certos" em mente (ou - melhor ainda - de maneira que não são necessários pontos de extensão artificiais). No entanto, para fazer isso corretamente e sem overengineering desnecessário, você precisa saber de antemão como futuros casos de uso do seu componente podem parecer. Mesmo programadores experientes não podem olhar para o futuro e conhecer todos os requisitos futuros de antemão. 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 determinados tipos de requisitos, sempre haverá um requisito que não pode ser implementado facilmente sem modificar o componente.

    
por 30.04.2017 / 23:35
fonte
66

O princípio de abertura / fechamento tem benefícios, mas também apresenta alguns inconvenientes sérios.

Em teoria o princípio resolve o problema de compatibilidade com versões anteriores, criando código que é "aberto para extensão, mas fechado para modificação". Se uma classe tiver alguns novos requisitos, você nunca modificará o código-fonte da própria classe, mas criará uma subclasse que substituirá apenas os membros apropriados necessários para alterar o comportamento. Todo o código escrito contra a versão original da classe, portanto, 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 excesso de código e uma bagunça confusa de classes obsoletas. Se não for possível modificar algum comportamento de um componente por meio de extensão, será necessário 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 de design fundamental em uma classe base da qual muitas classes são herdadas. Digamos que o erro seja devido a um campo particular ser do tipo errado. Você não pode consertar isso substituindo um membro. Basicamente você tem que sobrescrever toda a classe, o que significa que você acaba estendendo Object para fornecer uma classe base alternativa - e agora você também tem que fornecer alternativas para todas as subclasses, terminando com uma hierarquia duplicada de objetos, uma hierarquia defeituosa , um melhorado. Mas você não pode remover a hierarquia defeituosa (já que a exclusão do código é uma modificação), todos os clientes futuros serão expostos a ambas as hierarquias.

Agora, a resposta teórica para este problema é "apenas projetá-lo corretamente na primeira vez". Se o código estiver perfeitamente decomposto, sem falhas ou erros, e projetado com pontos de extensão preparados para todas as possíveis mudanças futuras, então evite a bagunça. Mas, na realidade, todos cometem erros e ninguém consegue prever o futuro perfeitamente.

Tome algo como o .NET framework - ele ainda carrega o conjunto de classes de coleção que foram projetadas antes dos genéricos serem introduzidos há mais de uma década. Este é certamente um benefício para a compatibilidade com versões anteriores (você pode atualizar o framework sem ter que reescrever nada), mas também incha o framework e apresenta aos desenvolvedores um grande conjunto de opções onde muitos são simplesmente obsoletos.

Aparentemente, os desenvolvedores do React acharam que não valia a pena o custo em termos de complexidade e o code-bloat seguiria estritamente o princípio aberto / fechado.

A alternativa pragmática para abrir / fechar é a reprovação controlada. Em vez de quebrar a compatibilidade retroativa em um único lançamento, os componentes antigos são mantidos por um ciclo de lançamento, 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. Esta parece ser a abordagem de Reagir neste caso.

(Minha interpretação do princípio é baseada no Princípio do Aberto / Fechado por Robert C. Martin)

    
por 30.04.2017 / 17:12
fonte
20

Eu chamaria o princípio de abertura / fechamento de um ideal. Como todos os ideais, dá pouca consideração às realidades do desenvolvimento de software. Também como todos os ideais, é impossível realmente alcançá-lo na prática - um só se esforça para abordar esse ideal da melhor forma possível.

O outro lado da história é conhecido como as Algemas Douradas. Algemas Douradas são o que você ganha quando se escraviza demais com o princípio de abrir / fechar. As Algemas Douradas são o que ocorre quando o seu 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 para o Windows 95, foi declarado que todos os aplicativos do Windows 3.1 funcionariam no Windows 95. A Microsoft realmente adquiriu licenças para milhares de programas para testá-las no Windows 95. Um dos casos problemáticos foi o Sim City. O Sim City realmente tinha um bug que fazia com que ele escrevesse para a memória não alocada. No Windows 3.1, sem um gerenciador de memória "adequado", esse era um faux pas menor. No entanto, no Windows 95, o gerenciador de memória iria pegar isso e causar uma falha de segmentação. A solução? No Windows 95, se o nome do aplicativo for simcity.exe , o SO realmente relaxará as restrições do gerenciador de memória para evitar a falha de segmentação!

A verdadeira questão por trás deste ideal são os conceitos de produtos e serviços. Ninguém realmente faz um ou outro. Tudo se alinha em algum lugar na região cinza entre os dois. Se você pensa em uma abordagem orientada a produtos, abrir / fechar parece 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 aberto / fechado, a quantidade de funcionalidade que sua equipe deve suportar deve aproximar-se assintoticamente do infinito, porque você nunca pode limpar a funcionalidade antiga. Isso significa que sua equipe de desenvolvimento deve suportar mais e mais códigos todos os anos. Eventualmente você chega a um ponto de ruptura.

A maioria dos softwares atuais, especialmente os de código aberto, segue uma versão comum e relaxada do princípio aberto / fechado. É muito comum ver aberto / fechado, seguido por versões secundárias, mas abandonado para grandes lançamentos. Por exemplo, o Python 2.7 contém muitas "más escolhas" do Python 2.0 e 2.1 dias, 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çaram o Windows 2000 quebrou todos os tipos de coisas, mas fez significa que nunca precisamos lidar com um gerenciador de memória verificando o nome do aplicativo para decidir o comportamento!)

    
por 30.04.2017 / 21:37
fonte
11

A resposta de Doc Brown está mais próxima da precisão, as outras respostas ilustram os mal-entendidos do Princípio do Aberto Fechado.

Para articular explicitamente o mal-entendido, parece haver uma crença de que o OCP significa que você não deve fazer alterações incompatíveis com versões anteriores (ou mesmo quaisquer alterações ou algo assim.) O OCP é sobre projetar componentes para que você não precise fazer alterações neles 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 de adicionar funcionalidade que você pode fazer alterações em um componente, sejam 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 funcionalidades). 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ê esteja violando o OCP).

Realmente, não é sobre código-fonte. Uma declaração mais abstrata e relevante do OCP é: "um componente deve permitir a extensão sem necessidade de violar seus limites de abstração". Eu iria mais longe e diria que uma versão mais moderna é: "um componente deve reforçar seus limites de abstração, mas permitir extensão". Mesmo no artigo sobre OCP de Bob Martin enquanto ele "descreve" "fechado à modificação" como "o código-fonte é inviolável", ele mais tarde começa a falar sobre encapsulamento que não tem nada a ver com modificação de código fonte e tudo a ver com abstração limites.

Assim, a premissa falha na questão é que o OCP é (pretendido como) uma diretriz sobre as evoluções de uma base de código. O OCP é tipicamente sloganizado como "um componente deve estar aberto a extensões e fechado a modificações pelos consumidores". Basicamente, se um consumidor de um componente quiser adicionar funcionalidade ao componente, ele deve ser capaz de estender o componente antigo para um novo com a funcionalidade adicional, mas eles devem Não é possível alterar o componente antigo.

O OCP não diz nada sobre a funcionalidade criador de um componente alterando ou removendo . O OCP não está defendendo a manutenção da compatibilidade de bugs para todo o sempre. Você, como criador, não está violando o OCP alterando ou até mesmo removendo um componente. Você, ou melhor, os componentes que você escreveu, estão violando o OCP, se a única maneira pela qual os consumidores podem adicionar funcionalidade a seus componentes é por meio da alteração, por exemplo. por monkey patching ou ter acesso ao código-fonte e recompilação. Em muitos casos, nenhuma delas é uma opção para o consumidor, o que significa que, se o seu componente não estiver "aberto para extensão", estará sem sorte. Eles simplesmente não podem usar seu componente para suas necessidades. O OCP alega não colocar os consumidores de sua biblioteca nessa posição, pelo menos no que diz respeito a alguma classe identificável de "extensões". Mesmo quando modificações podem serem feitas no código-fonte ou até mesmo na cópia primária do código-fonte, é melhor "fingir" que você não pode modificá-lo, pois há muitas conseqüências negativas em fazê-lo .

Então, para responder às suas perguntas: Não, essas não são violações do OCP. Nenhuma alteração feita por um autor pode ser uma violação do OCP porque o OCP não é uma proporção de alterações. 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 uma parte específica do código, não da história evolutiva de uma base de código.

Por contraste, a compatibilidade retroativa é uma propriedade de uma alteração do código. Não faz sentido dizer que alguma parte do código é ou não compatível com versões anteriores. Faz sentido apenas falar sobre a compatibilidade retroativa de algum código em relação a algum código antigo. Portanto, nunca faz sentido falar sobre o primeiro corte de algum código sendo compatível com versões anteriores ou não. O primeiro corte de código pode satisfazer ou não satisfazer o OCP e, em geral, podemos determinar se algum código satisfaz o OCP sem referir-se a nenhuma versão histórica do código.

Quanto à sua última pergunta, é indiscutivelmente fora do tópico para o StackExchange em geral como sendo basicamente baseado em opinião, mas o que falta é bem-vindo à tecnologia e particularmente JavaScript onde nos últimos anos o fenômeno que você descreve tem sido chamado fadiga JavaScript . (Sinta-se à vontade para google encontrar uma variedade de outros artigos, alguns satíricos, falando sobre isso de múltiplas perspectivas.)

    
por 01.05.2017 / 00:38
fonte