Sou desenvolvedor de longa data (tenho 49 anos), mas sou novo no desenvolvimento orientado a objetos. Eu tenho lido sobre OO desde Eiffel de Bertrand Meyer, mas fiz muito pouca programação OO.
A questão é que todo livro sobre design de OO começa com um exemplo de barco, carro ou qualquer objeto comum que usamos com muita frequência, e eles começam a adicionar atributos e métodos e a explicar como modelam o estado do objeto e o que pode ser feito com ele. isto.
Então eles costumam dizer algo como "quanto melhor o modelo, melhor ele representa o objeto no aplicativo e melhor ele sai".
Até aqui tudo bem, mas, por outro lado, encontrei vários autores que fornecem receitas como “uma classe deve caber em apenas uma página” (acrescentaria “em que tamanho de monitor?” Agora que tentamos não para imprimir código!).
Tomemos, por exemplo, uma PurchaseOrder
classe que tenha uma máquina de estados finitos controlando seu comportamento e uma coleção de PurchaseOrderItem
, um dos argumentos aqui no trabalho é que devemos usar uma PurchaseOrder
classe simples, com alguns métodos (pouco mais que uma classe de dados) e ter uma PurchaseOrderFSM
"classe especialista" que lida com a máquina de estados finitos para o PurchaseOrder
.
Eu diria que se enquadra na classificação “Feature Envy” ou “In Intimate Intimity” do post Code Smells de Jeff Atwood em Coding Horror. Eu chamaria isso de bom senso. Se eu puder emitir, aprovar ou cancelar o meu pedido de compra real, então a PurchaseOrder
classe deve ter issuePO
, approvePO
e cancelPO
métodos.
Isso não se aplica aos princípios da velhice “maximizar a coesão” e “minimizar o acoplamento” que entendo como pedras angulares da OO?
Além disso, isso não ajuda na manutenção da classe?
fonte
PurchaseOrder
, você poderia apenas nomear seus métodosissue
,approve
ecancel
. OPO
sufixo adiciona apenas valor negativo.Respostas:
Uma classe deve usar o Princípio de Responsabilidade Única. A maioria das classes muito grandes que eu já vi fazer muitas coisas, e é por isso que elas são muito grandes. Observe cada método e o código decida que deve estar nessa classe ou se é um código duplicado separado. Você pode ter um método issuePO, mas ele contém 10 linhas de código de acesso a dados, por exemplo? Esse código provavelmente não deveria estar lá.
fonte
Na minha opinião, o tamanho da classe não importa, desde que as variáveis, funções e métodos sejam relevantes para a classe em questão.
fonte
O maior problema com grandes classes é quando se trata de testar essas classes ou de sua interação com outras classes. O tamanho da turma grande, por si só, não é um problema, mas, como todos os cheiros, indica um problema em potencial . De um modo geral, quando a turma é grande, isso é uma indicação de que a turma está fazendo mais do que uma única turma deveria.
Para a analogia do seu carro, se a classe
car
é muito grande, provavelmente é uma indicação de que ela precisa ser dividida em classeengine
, classedoor
e classewindshield
, etc.fonte
Eu não acho que exista uma definição específica de uma classe ser muito grande. No entanto, eu usaria as seguintes diretrizes para determinar se devo refatorar minha classe ou redefinir o escopo da classe:
Em relação a 1, imagine que você define o tamanho do pára-brisa, o tamanho da roda, o modelo da lâmpada traseira e outros 100 detalhes da sua classe
car
. Embora todos sejam "relevantes" para o carro, é muito difícil rastrear o comportamento dessa classecar
. E quando algum dia seu colega acidentalmente (ou, em alguns casos, intencionalmente) alterar o tamanho do pára-brisa com base no modelo da lâmpada traseira, você leva uma eternidade para descobrir por que o pára-brisa não se encaixa mais no seu carro.Em relação a 2, imagine que você tente definir todos os comportamentos que um carro pode ter na classe
car
, mascar::open_roof()
estará disponível apenas para conversíveis ecar::open_rear_door()
não se aplica a esses carros de 2 portas. Embora os conversíveis e os carros de 2 portas sejam "carros" por definição, implementá-los na classecar
complica a classe. A turma se torna mais difícil de usar e mais difícil de manter.Além dessas duas diretrizes, eu sugeriria uma definição clara do escopo da classe. Depois de definir o escopo, implemente a classe estritamente com base na definição. Na maioria das vezes, as pessoas obtêm uma classe "muito grande" por causa da definição deficiente do escopo ou por causa dos recursos arbitrários adicionados à classe
fonte
Diga o que você precisa em 300 linhas
Nota: esta regra geral pressupõe que você esteja seguindo a abordagem 'uma classe por arquivo', que é uma boa ideia de manutenção
Não é uma regra absoluta, mas se eu ver mais de 200 linhas de código em uma classe, desconfio. 300 linhas e sinos de aviso acendem. Quando abro o código de outra pessoa e encontro 2000 linhas, sei que é hora de refatorar, mesmo sem ler a primeira página.
Principalmente isso se resume à manutenção. Se seu objeto é tão complexo que você não pode expressar seu comportamento em 300 linhas, será muito difícil para alguém entender.
Estou descobrindo que estou dobrando essa regra no mundo Model -> ViewModel -> View no qual estou criando um 'objeto de comportamento' para uma página da interface do usuário, porque geralmente leva mais de 300 linhas para descrever a série completa de comportamentos em uma página. No entanto, ainda sou novo nesse modelo de programação e encontrando boas maneiras de refatorar / reutilizar comportamentos entre páginas / modelos de exibição, o que está gradualmente reduzindo o tamanho dos meus modelos de exibição.
fonte
Vamos ao contrário:
Na verdade, é uma pedra angular da programação, da qual o OO é apenas um paradigma.
Vou deixar Antoine de Saint-Exupéry responder à sua pergunta (porque sou preguiçoso;)):
Quanto mais simples a classe, mais confiante você estará, mais fácil será testá-la completamente .
fonte
Estou com o OP na classificação Feature Envy. Nada me irrita mais do que ter um monte de classes com vários métodos que funcionam nos internos de outras classes. OO sugere que você modele dados e as operações nesses dados. Isso não significa que você coloca as operações em um local diferente dos dados! Ele está tentando fazer com que você junte os dados e esses métodos .
Com os modelos
car
eperson
tão predominantes, seria como colocar o código para fazer a conexão elétrica, ou mesmo girar o motor de partida, naperson
classe. Eu espero que isso seja obviamente errado para qualquer programador experiente.Eu realmente não tenho um tamanho ideal de classe ou método, mas prefiro manter meus métodos e funções adequados em uma tela para que eu possa ver a totalidade em um só lugar. (Eu tenho o Vim configurado para abrir uma janela de 60 linhas, o que acontece em torno do número de linhas em uma página impressa.) Há lugares em que isso não faz muito sentido, mas funciona para mim. Eu gosto de seguir a mesma diretriz para definições de classe e tentar manter meus arquivos em poucas "páginas". Qualquer coisa acima de 300, 400 linhas começa a parecer pesada (principalmente porque a classe começou a assumir muitas responsabilidades diretas).
fonte
Quando uma definição de classe tem algumas centenas de métodos, é muito grande.
fonte
Concordo plenamente com o uso do princípio de responsabilidade única para as classes, mas se isso for seguido e resultar em classes grandes, que assim seja. O foco real deve ser que métodos e propriedades individuais não sejam muito grandes; a complexidade ciclomática deve ser o guia para seguir e resultar em muitos métodos particulares que aumentarão o tamanho geral da classe, mas que a deixarão legível e testável em um nível granular. Se o código permanecer legível, o tamanho será irrelevante.
fonte
A emissão de um pedido de compra geralmente é um processo complicado que envolve mais do que apenas o pedido. Nesse caso, manter a lógica de negócios em uma classe separada e simplificar o pedido de compra é provavelmente a coisa certa a fazer. Em geral, porém, você está certo - não deseja classes de "estrutura de dados". Por exemplo, o
issue()
método da classe de negócios "POManager" ' provavelmente deve chamar os PO'sissue()
método . O pedido de compra pode definir seu status como "Emitido" e anotar a data de emissão, enquanto o POManager pode enviar uma notificação para as contas a pagar para que eles esperem uma fatura e outra notificação para inventário, para que saibam que algo está chegando e a data prevista.Qualquer pessoa que adote "regras" para o tamanho do método / classe obviamente não passou tempo suficiente no mundo real. Como outros já mencionaram, geralmente é um indicador de refatoração, mas às vezes (especialmente no trabalho do tipo "processamento de dados") você realmente precisa escrever uma classe com um único método que abrange mais de 500 linhas. Não se desligue.
fonte
Em termos do tamanho físico de uma classe, ver uma classe grande é uma indicação de um cheiro de código, mas não significa necessariamente que sempre há cheiros. (Geralmente eles são). Como os piratas dos Piratas do Caribe diriam, isso é mais o que você chamaria de "diretrizes" do que regras reais. Eu tentei seguir rigorosamente o "não mais que cem LOC em uma classe" uma vez e o resultado foi MUITO desnecessário em excesso de engenharia.
Eu normalmente começo meus projetos com isso. O que essa classe faz no mundo real? Como minha classe deve refletir esse objeto conforme declarado em seus requisitos? Nós adicionamos um pouco de estado aqui, um campo ali, um método para trabalhar nesses campos, adicione um pouco mais e pronto! Temos uma classe trabalhadora. Agora preencha as assinaturas com as implementações apropriadas e estamos prontos, ou você pensa. Agora temos uma ótima aula com todas as funcionalidades de que precisamos. Mas o problema disso é que não leva em consideração o modo como o mundo real funciona. E com isso quero dizer que não leva em conta "CHANGE".
A razão pela qual as classes são preferencialmente divididas em partes menores é que é difícil mudar grandes classes posteriormente, sem afetar a lógica existente ou introduzir um novo bug ou dois sem querer, ou apenas dificultar a reutilização. É aqui que a refatoração, os padrões de design, o SOLID etc. entram em cena e o resultado final geralmente é uma classe pequena trabalhando em outras subclasses menores e mais granulares.
Além disso, no seu exemplo, a adição de IssuePO, ApprovePO e Cancel PO à classe PurchaseOrder parece ilógica. Os pedidos de compra não emitem, aprovam ou cancelam a si mesmos. Se a PO tiver métodos, os métodos devem estar trabalhando em seu estado, não na classe como um todo. Eles realmente são apenas classes de dados / registros nas quais outras classes trabalham.
fonte
Grande o suficiente para conter toda a lógica necessária para realizar seu trabalho.
Pequeno o suficiente para ser sustentável e seguir o Princípio de Responsabilidade Única .
fonte