Quão grande é aceitável para uma classe?

29

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 PurchaseOrderclasse 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 PurchaseOrderclasse 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 PurchaseOrderclasse deve ter issuePO, approvePOe cancelPOmé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?

Miguel Veloso
fonte
17
Não há motivo para codificar o nome do tipo no nome do método. Ou seja, se você tem PurchaseOrder, você poderia apenas nomear seus métodos issue, approvee cancel. O POsufixo adiciona apenas valor negativo.
dash-tom-bang
2
Contanto que você fique longe dos métodos dos buracos negros , deve ficar bem. :)
Marque C
1
Com a minha resolução de tela atual, eu diria 145 caracteres.
DavRob60
Obrigado a todos por seus comentários, eles realmente me ajudaram com esse problema. Se esse site tivesse permitido, eu também teria escolhido a resposta da TMN e do Lázaro, mas ela só permite uma, então foi para Craig. Cumprimentos.
Miguel Veloso

Respostas:

27

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á.

Craig
fonte
1
Craig, eu não estava ciente do Princípio da Responsabilidade Única e acabei de ler sobre ele. A primeira coisa que me veio à mente foi: qual é o escopo de uma responsabilidade? para um pedido, eu diria que está gerenciando seu estado, e esse é o motivo dos métodos issuePO, approvePO e cancelPO, mas também inclui a interação com a classe PurchaseOrderItem, que lida com o estado do item do pedido. Portanto, não estou claro se essa abordagem é consistente com o SRP. o que você diria?
Miguel Veloso
1
+1 para uma resposta muito agradável e sucinta. Realisticamente, não pode haver uma medida definitiva para o tamanho da classe. A aplicação do SRP garante que a classe seja tão grande quanto precisa .
31512 S.Robins
1
@MiguelVeloso - A aprovação de um pedido provavelmente possui lógica e regras diferentes associadas a ele. Nesse caso, faz sentido separar as responsabilidades em um POApprover e um POIssuer. A idéia é que, se as regras mudam para um, por que você quer mexer com a classe que contém o outro.
22612 Matthew
12

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.

Mike42
fonte
1
Por outro lado, tamanho grande indica que esses métodos provavelmente não são relevantes para a classe em questão.
Billy ONeal
5
@ Billy: Eu diria que grandes classes são um cheiro de código, mas não são automaticamente ruins.
David Thornley
@ David: É por isso que há a palavra "provavelmente" lá dentro - é importante :)
Billy ONeal
Eu teria que discordar ... Eu trabalho em um projeto que tem uma convenção de nomenclatura bastante consistente e as coisas estão bem definidas ... mas ainda tenho uma classe com 50 mil linhas de código. É simplesmente muito grande :) #
217 Jay Jay
2
Essa resposta mostra exatamente como a "estrada para o inferno" vai - se não restringir os responsiblities de uma classe, haverá muitas variáveis, funções e métodos tornando-se relevantes para a classe - e isso leva facilmente ter demasiado muitos deles.
Doc Brown
7

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 classe engine, classe doore classe windshield, etc.

Billy ONeal
fonte
8
E não vamos esquecer que um carro deve implementar a interface do veículo. :-)
Chris
7

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:

  1. Existem muitas variáveis ​​que são independentes uma da outra? (Minha tolerância pessoal é de cerca de 10 ~ 20 na maioria dos casos)
  2. Existem muitos métodos projetados para caixas de canto? (Minha tolerância pessoal é ... bem, depende muito do meu humor, eu acho)

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 classe car. 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, mas car::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 classe carcomplica 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

YYC
fonte
7

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.

Jay Beavers
fonte
Minha orientação pessoal também é de cerca de 300 linhas. 1
Marcie
Eu acho que o OP está procurando uma heurística, e essa é boa.
neontapir
Obrigado, é valioso usar números precisos como orientação.
Will Sheppard
6

Vamos ao contrário:

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?

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;)):

A perfeição é alcançada, não quando não há mais nada a acrescentar, mas quando não há mais nada a ser levado.

Quanto mais simples a classe, mais confiante você estará, mais fácil será testá-la completamente .

Matthieu M.
fonte
3

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 care persontão predominantes, seria como colocar o código para fazer a conexão elétrica, ou mesmo girar o motor de partida, na personclasse. 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).

traço-tom-bang
fonte
+1 - boas regras práticas, mas não autoritário. Eu diria, no entanto, que apenas porque uma classe é dividida, não significa que as diferentes classes devem tocar os membros privados umas das outras.
Billy ONeal
Não acho que classes diferentes devam tocar as partes íntimas umas das outras. Embora o acesso de 'amigo' seja conveniente para colocar algo em funcionamento, (para mim) é um trampolim para um design melhor. Em geral, evito acessadores também, pois, dependendo dos componentes internos de outra classe, há outro código de cheiro .
traço-tom-bang
1

Quando uma definição de classe tem algumas centenas de métodos, é muito grande.

C Johnson
fonte
1

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.

Lázaro
fonte
1

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.

TMN
fonte
Eu acho que o POManager seria o "controlador" e o PurchaseOrder seria o "modelo" no padrão MVC, certo?
Miguel Veloso
0

Encontrei vários autores que fornecem receitas como "uma classe deve caber em apenas uma página"

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.

Então eles costumam dizer algo como "quanto melhor o modelo, melhor ele representa o objeto no aplicativo e melhor ele sai".

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.

Jonn
fonte
0

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 .

RMalke
fonte