Sobre C ++ e Programação Orientada a Objetos

Olá, Habr!



Gostaríamos de chamar sua atenção para um artigo, o autor do qual não aprova a abordagem puramente orientada a objetos ao trabalhar com a linguagem C ++. Pedimos que avalie, se possível, não só a argumentação do autor, mas também a lógica e o estilo.



Ultimamente tem havido muito escrito sobre C ++ e para onde a linguagem está indo e quanto do que é chamado de "C ++ moderno" simplesmente não é uma opção para desenvolvedores de jogos.



Embora eu compartilhe totalmente esse ponto de vista, tendo a ver a evolução do C ++ como o resultado de idéias penetrantes que a maioria dos desenvolvedores é guiada. Neste artigo, tentarei organizar algumas dessas ideias junto com meus próprios pensamentos - e talvez eu consiga algo pequeno.



Programação orientada a objetos (OOP) como ferramenta



Embora C ++ seja descrito como uma linguagem de programação multiparadigma , na prática a maioria dos programadores usa C ++ puramente como uma linguagem orientada a objetos (a programação genérica é usada para "complementar" OOP).



OOP deve ser uma ferramenta, um dos muitos paradigmas que um programador pode usar para resolver problemas em código. No entanto, em minha experiência, OOP é aceito pela maioria dos profissionais como o padrão ouro para desenvolvimento de software. Basicamente, o desenvolvimento de uma solução começa com a determinação de quais objetos precisamos. A solução para um problema específico começa depois que o código foi distribuído entre os objetos. Com a transição para esse tipo de pensamento orientado a objetos, a POO passa de uma ferramenta para uma caixa de ferramentas inteira.



Sobre a entropia como a força secreta que alimenta o desenvolvimento de software



Gosto de pensar em uma solução OOP como uma constelação: é um grupo de objetos com linhas desenhadas aleatoriamente entre eles. Tal solução também pode ser considerada como um gráfico em que os objetos são nós e as relações entre eles são arestas, mas o fenômeno de um grupo / cluster, que é transmitido pela metáfora da constelação, está mais perto de mim (em comparação com ele, o gráfico é muito abstrato).



Mas não gosto da forma como essas "constelações de objetos" são compostas. No meu entendimento, cada uma dessas constelações nada mais é do que um instantâneo da imagem que se formou na cabeça do programador e reflete como o espaço da solução se parece em um determinado momento. Mesmo levando em consideração todas as promessas que são dadas no design orientado a objetos sobre extensibilidade, reusabilidade, encapsulamento, etc ... o futuro é imprevisível, então em cada caso podemos oferecer uma solução exatamente para o problema que enfrentamos agora.



Devemos ser encorajados que estamos "apenas" resolvendo o problema que está diretamente diante de nós, mas na minha experiência, um programador usando os princípios de design no espírito da POO cria uma solução, embora seja limitado pela suposição de que o problema em si não mudará significativamente e , portanto, a solução pode ser considerada permanente. Quero dizer que a partir daqui, as pessoas começam a falar sobre a solução em termos dos objetos que formam a referida constelação, e não em termos de dados e algoritmos; o próprio problema é abstraído.

No entanto, o programa está sujeito à entropia não menos do que qualquer outro sistema e, portanto, todos sabemos que o código vai mudar. Além disso, de forma imprevisível. Mas para mim, neste caso, é absolutamente claro que o código se degradará em qualquer caso, escorregando para o caos e a desordem, se você não lutar conscientemente.



Eu vi esse manifesto de muitas maneiras diferentes nas soluções OOP:



  • Novos níveis intermediários aparecem na hierarquia, embora não fosse originalmente a intenção de introduzi-los.
  • Novas funções virtuais são adicionadas com implementações vazias na maior parte da hierarquia.
  • Um dos objetos da constelação requer mais processamento do que o planejado, por isso as conexões entre os outros objetos começam a escorregar.
  • , , , .
  • .…


Todos esses são exemplos de extensibilidade mal organizada. Além disso, o resultado é sempre o mesmo, pode vir daqui a alguns meses, ou talvez daqui a alguns anos. Com a ajuda da refatoração, eles estão tentando eliminar as violações dos princípios de design OOP, feitas quando novos objetos foram adicionados à constelação, e eles foram adicionados devido à reformulação do próprio problema. Às vezes, a refatoração ajuda. Por um tempo. A entropia é constante, e os programadores não têm tempo para refatorar cada constelação OOP para superá-la, então qualquer projeto se encontra regularmente na mesma situação, cujo nome é caos.



No ciclo de vida de qualquer projeto OOP, mais cedo ou mais tarde chega um ponto a partir do qual é impossível mantê-lo. Normalmente, neste ponto, uma de duas ações deve ser realizada:



  • « »: - . , , , , , .
  • : -, , , .


Observação: a opção com uma caixa preta ainda exigirá reescrita caso o desenvolvimento de novos recursos precise continuar e / ou a necessidade de eliminar bugs permaneça.



A situação com a reescrita da solução nos traz de volta ao fenômeno de um instantâneo do espaço de solução disponível em um determinado momento. Então, o que mudou entre o Projeto OOP # 1 e a situação atual? Basicamente, é isso. O problema mudou, portanto, uma solução diferente é necessária.



Enquanto escrevíamos a solução, seguindo os princípios do design OOP, abstraímos o problema e, assim que mudou, nossa solução se desfez como um castelo de cartas.

Acho que é nesse momento que começamos a nos perguntar o que deu errado, tentamos fazer o contrário e atualizar as estratégias de resolução do problema com base nos resultados do post mortem (debriefing). Porém, cada vez que me deparo com esse cenário de "hora de reescrever", nada muda: os princípios OOP são usados ​​novamente, de acordo com os quais um novo instantâneo é implementado, correspondendo ao estado atual do espaço do problema. Todo o ciclo se repete.



Facilidade de remoção de código como princípio de design



Em qualquer sistema construído com base no princípio de OOP, são os objetos na "constelação" que recebem a atenção principal. Mas acredito que as relações entre os objetos são tão importantes, senão mais, do que os próprios objetos.



Eu prefiro soluções simples em que o gráfico de dependência do código consiste em um número mínimo de nós e arestas. Quanto mais simples for a solução, mais fácil será não apenas alterá-la, mas também removê-la. Também descobri que quanto mais fácil é remover o código, mais rápido você pode reorientar a solução e adaptá-la às condições do problema em mudança. Ao mesmo tempo, o código torna-se mais resistente à entropia, pois exige muito menos esforço para mantê-lo em ordem e evitar que caia no caos.



Sobre desempenho por definição



Mas uma das principais considerações para evitar o design OOP é o desempenho. Quanto mais código você precisar executar, pior será o desempenho.



Também é impossível não notar que os recursos OOP, por definição, não brilham com o desempenho. Implementei uma hierarquia OOP simples com uma interface e duas classes derivadas que substituem uma única chamada de função virtual pura no Compiler Explorer .



O código neste exemplo imprime “Hello, World!” Ou não, dependendo do número de argumentos transmitidos ao programa. Em vez de programar diretamente tudo que acabei de descrever, um dos padrões de projeto OOP padrão, herança, será usado para resolver esse problema no código.



Nesse caso, o mais surpreendente é a quantidade de código que os compiladores geram, mesmo após a otimização. Em seguida, tomar um olhar mais atento, você pode ver como caro e, ao mesmo tempo inútil tal manutenção: quando um programa é passado um número diferente de zero de argumentos, o código ainda aloca memória (chamada new), carrega os endereços de vtableambos os objetos, carrega o endereço da função Work()de ImplBe salta para ele, para que, em seguida, imediatamente voltar, uma vez que não há nada para fazer lá. Finalmente, ele é chamado deletepara liberar a memória alocada.



Nenhuma dessas operações foi necessária, mas o processador as executou corretamente.



Assim, se um dos principais objetivos de seu produto é a obtenção de alto desempenho (estranho se fosse o contrário), então no código você deve evitar operações dispendiosas desnecessárias, preferindo as simples, que são fáceis de julgar, e usar construções que ajudam a atingir esse objetivo.



Veja o Unity , por exemplo . Como parte de sua prática recente, desempenho é correção usando C #, uma linguagem orientada a objetos, uma vez que essa linguagem já é usada no próprio motor. No entanto, eles se estabeleceram em um subconjunto de C # , além disso, um que não está rigidamente vinculado à OOP e, com base nisso, criaram construções aprimoradas para alto desempenho.



Dado que o trabalho de um programador é resolver problemas usando um computador, é impensável que nosso negócio dedique tão pouca atenção à escrita de código que realmente faz o processador fazer o trabalho em que o processador é particularmente bom.



Sobre lutar contra estereótipos



No artigo de Angelo Pesce "A supercomplicação é a raiz de todo o mal " , o autor vai direto ao ponto (veja a última seção: Pessoas) ao admitir que a maioria dos problemas de software são na verdade fatores humanos.



As pessoas da equipe precisam interagir e desenvolver um entendimento comum de qual é o objetivo geral e qual é o caminho para alcançá-lo. Se houver divergência na equipe, por exemplo, sobre o caminho para a meta, então, para maiores avanços, é necessário desenvolver um consenso. Isso geralmente não é difícil se as diferenças de opinião forem pequenas, mas é muito mais difícil de tolerar se as opções diferirem fundamentalmente, diga "OOP ou não OOP".

Mudar de ideia não é fácil. Duvidar do seu ponto de vista, perceber o quão errado você estava e ajustar seu curso é difícil e doloroso. Mas é muito mais difícil mudar a opinião de outra pessoa!



Tive muitas conversas com pessoas diferentes sobre OOP e seus problemas inerentes, e embora eu acredite que sempre fui capaz de explicar porque penso dessa forma e não de outra forma, não acho que consegui afastar ninguém de OOP.



É verdade que, ao longo dos anos de trabalho, identifiquei três argumentos principais para mim, por causa dos quais as pessoas não estão prontas para dar uma chance ao outro lado:



  • « ». « ». « » . , , ( , - ). « …».
  • « , , , ». «» , , . , « ».
  • "Todo mundo conhece OOP, é muito conveniente falar com as pessoas em uma língua comum, tendo conhecimentos gerais." Este é um erro lógico denominado "argumento para o povo", ou seja, se quase todos os programadores usam os princípios de OOP, então essa ideia não pode ser inadequada.


Estou bem ciente de que revelar erros lógicos na argumentação não é suficiente para desmascará-los. No entanto, acredito que ao ver as falhas em seus próprios julgamentos, você pode chegar ao fundo da verdade e descobrir a profunda razão pela qual rejeita uma ideia incomum.



All Articles