Classes utilitárias são más? [fechadas]

96

Eu vi esse tópico

Se uma classe "Utilities" for má, onde coloco meu código genérico?

e pensei por que as classes utilitárias são más?

Digamos que eu tenha um modelo de domínio com dezenas de classes de profundidade. Eu preciso ser capaz de xml-ify instâncias. Devo fazer um método toXml no pai? Devo criar uma classe auxiliar MyDomainXmlUtility.toXml? Este é um caso em que a necessidade do negócio abrange todo o modelo de domínio - ele realmente pertence a um método de instância? E se houver vários métodos auxiliares na funcionalidade xml do aplicativo?

hvgotcodes
fonte
27
A desvalorização do termo "mal" é mal!
Matthew Lock
1
@matthew eu preservei os termos da postagem na qual minha pergunta se baseia ...;)
hvgotcodes
As classes utilitárias são uma má ideia pelos mesmos motivos que os singletons.
CurtainDog
1
O debate sobre se deve haver um método toXML é centrado em modelos de domínio rico versus anêmico. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.
@james, o toXML é apenas um exemplo ... que tal alguma funcionalidade regex que é usada em todo lugar? Por exemplo, você precisa fazer algumas coisas com as strings em seu modelo de domínio, mas não poderia usar subclasses devido a outra preocupação primordial que usava sua única superclasse (em java)
hvgotcodes

Respostas:

128

As classes utilitárias não são exatamente más, mas podem violar os princípios que compõem um bom design orientado a objetos. Em um bom projeto orientado a objetos, a maioria das classes deve representar uma única coisa e todos os seus atributos e operações. Se você está operando em algo, esse método provavelmente deve ser um membro dessa coisa.

No entanto, há momentos em que você pode usar classes de utilitários para agrupar vários métodos - um exemplo é a java.util.Collectionsclasse que fornece vários utilitários que podem ser usados ​​em qualquer coleção Java. Eles não são específicos para um tipo particular de coleção, mas implementam algoritmos que podem ser usados ​​em qualquer coleção.

Na verdade, o que você precisa fazer é pensar sobre seu projeto e determinar onde faz mais sentido colocar os métodos. Normalmente, é como operações dentro de uma classe. No entanto, às vezes, é de fato uma classe de utilitário. Quando você usa uma classe de utilitário, no entanto, não apenas lance métodos aleatórios nela; em vez disso, organize os métodos por propósito e funcionalidade.

Thomas Owens
fonte
laços bem com este artigo sobre a verificação de classe de utilitário / ajudante contra princípio oo SOLID bem readability.com/articles/rk1vvcqy
melaos
8
Se a linguagem não oferece nenhum mecanismo de namespace além de classes, você não tem escolha a não ser abusar de uma classe onde um namespace serviria. Em C ++, você pode colocar uma função independente, não relacionada a outras funções, em um namespace. Em Java, você deve colocá-lo em uma classe como um membro estático (classe).
Unslander Monica de
1
O agrupamento como métodos pode ser canônico do ponto de vista OOP. No entanto, OOP raramente é a solução (entre as exceções estão a arquitetura de alto nível e os kits de ferramentas de widget como uma das instâncias em que a semântica incrivelmente quebrada de reutilização de código por herança realmente se encaixa) e geralmente os métodos aumentam o acoplamento e as dependências. Exemplo trivial: se você fornecer métodos de impressora / scanner para sua classe como métodos, você acopla a classe a bibliotecas de impressora / scanner. Além disso, você fixa o número de implementações possíveis para efetivamente um. A menos que você inclua uma interface e aumente ainda mais as dependências.
Jo So
Por outro lado, concordo com o seu sentimento "agrupar por finalidade e funcionalidade". Vamos revisitar o exemplo da impressora / scanner novamente e começar com um conjunto de aulas de concertação, design para um propósito comum. Você pode querer escrever algum código de depuração e projetar uma representação textual para suas classes. Você pode implementar suas impressoras de depuração em um único arquivo que depende de todas essas classes e de uma biblioteca de impressoras. O código de não depuração não será sobrecarregado com as dependências desta implementação.
Jo So
1
As classes Util e Helper são um artefato infeliz de tais restrições, e aqueles que não entendem OOP ou não entendem o domínio do problema continuaram a escrever código procedural.
bilelovitch
57

Acho que o consenso geral é que as classes utilitárias não são más per se . Você só precisa usá-los criteriosamente:

  • Projete os métodos de utilitário estáticos para serem gerais e reutilizáveis. Certifique-se de que eles não têm estado; ou seja, sem variáveis ​​estáticas.

  • Se você tiver muitos métodos utilitários, particione-os em classes de forma que seja fácil para os desenvolvedores localizá-los.

  • Não use classes utilitárias onde métodos estáticos ou de instância em uma classe de domínio seriam uma solução melhor. Por exemplo, considere se os métodos em uma classe base abstrata ou em uma classe auxiliar instanciável seriam uma solução melhor.

  • Para o Java 8 em diante, "métodos padrão" em uma interface podem ser uma opção melhor do que classes de utilitário. (Consulte Interface com métodos padrão vs classe abstrata em Java 8, por exemplo.)


A outra maneira de examinar esta questão é observar que na questão citada, "Se as classes de utilidade são" más "" é um argumento de espantalho. É como eu perguntando:

"Se os porcos podem voar, devo levar um guarda-chuva?".

Na pergunta acima, não estou realmente dizendo que os porcos podem voar ... ou que concordo com a proposição de que eles podem voar.

Afirmações típicas do tipo "xyz é mau" são artifícios retóricos com o objetivo de fazer você pensar, colocando um ponto de vista extremo. Eles raramente (ou nunca) pretendem ser declarações de fatos literais.

Stephen C
fonte
16

As classes de utilitários são problemáticas porque falham em agrupar responsabilidades com os dados que as suportam.

No entanto, eles são extremamente úteis e eu os construo o tempo todo como estruturas permanentes ou como etapas durante uma refatoração mais completa.

De uma perspectiva de Código Limpo , as classes de utilitários violam a Responsabilidade Única e o Princípio Aberto-Fechado. Eles têm muitos motivos para mudar e não são extensíveis por design. Eles realmente deveriam existir apenas durante a refatoração como fragmentos intermediários.

Alain O'Dea
fonte
2
Eu diria que é um problema prático, pois, por exemplo, em Java, você não pode criar funções que não sejam métodos em uma classe. Em tal linguagem, a "classe sem variáveis ​​e apenas com métodos estáticos" é um idioma que significa um namespace de funções utilitárias ... Quando confrontado com tal classe em Java, é IMHO inválido e inútil falar de uma classe , mesmo que a classpalavra-chave seja usada. Ele grasna como um namespace, anda como um namespace - é um ...
Unslander Monica
2
Sinto ser franco, mas "métodos estáticos sem estado [...] estranhos em um sistema OOP [...] problema filosófico" é extremamente equivocado. É ÓTIMO se você pode evitar o estado, porque isso torna mais fácil escrever o código correto. É QUEBRADO quando tenho que escrever x.equals(y)porque 1) o fato de que isso realmente não deveria modificar nenhum estado não é transmitido (e eu não posso confiar nisso sem saber a implementação) 2) Eu nunca tive a intenção de incluir xum " posição favorecida "ou" atuada "em comparação com y3) Não quero considerar as" identidades "de xou, yestou apenas interessado em seus valores.
Jo So
4
Na verdade, a maior parte das funcionalidades do mundo real é melhor expressa como funções matemáticas estáticas, sem estado. Math.PI é um objeto que deve sofrer mutação? Eu realmente preciso instanciar um AbstractSineCalculator que implementa IAbstractRealOperation apenas para obter o seno de um número?
Jo So
1
@JoSo Concordo totalmente com o valor da apatridia e da computação direta. O OO ingênuo é filosoficamente oposto a isso e acho que isso o torna profundamente falho. Ele incentiva a abstração de coisas que não precisam (como você demonstrou) e falha em fornecer abstrações significativas para computação real. No entanto, uma abordagem equilibrada para usar uma linguagem OO tende à imutabilidade e à ausência de estado, pois promovem a modularidade e a manutenção.
Alain O'Dea
2
@ AlainO'Dea: Percebo que interpretei mal seu comentário como uma posição contra a funcionalidade sem estado. Peço desculpas pelo meu tom - estou atualmente envolvido em meu primeiro projeto Java (depois de evitar OOP na maior parte dos meus 10 anos de programação) e estou enterrado sob camadas de coisas abstratas com estado e significados confusos. E eu só preciso de um pouco de ar fresco :-).
Jo So
8

Acho que começa a se tornar mal quando

1) Fica muito grande (apenas agrupe-os em categorias significativas neste caso).
2) Métodos que não devem ser métodos estáticos estão presentes

Mas, enquanto essas condições não forem atendidas, acho que são muito úteis.

Enno Shioji
fonte
"Métodos que não deveriam ser métodos estáticos estão presentes." Como isso é possível?
Koray Tugay
@KorayTugay Considere o não estático Widget.compareTo(Widget widget)versus o estático WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). A comparação é um exemplo de algo que não deve ser feito estaticamente.
ndm13
5

Regra de ouro

Você pode olhar para este problema de duas perspectivas:

  • No geral *UtilMétodos geralmente são uma sugestão de projeto de código incorreto ou convenção de nomenclatura preguiçosa.
  • É uma solução de design legítima para funcionalidades sem estado reutilizáveis ​​de domínio cruzado. Observe que, para quase todos os problemas comuns, existem soluções.

Exemplo 1. Uso correto de utilclasses / módulos. Exemplo de biblioteca externa

Vamos supor que você esteja escrevendo um aplicativo que gerencia empréstimos e cartões de crédito. Os dados desses módulos são expostos por meio de serviços da web em jsonformato. Em teoria, você pode converter objetos manualmente em cordas que estarão dentro json, mas isso reinventaria a roda. A solução correta seria incluir em ambos os módulos a biblioteca externa usada para converter objetos java no formato desejado. (na imagem de exemplo que mostrei gson )

insira a descrição da imagem aqui


Exemplo 2. Uso correto de utilclasses / módulos. Escrever o seu próprio utilsem desculpas para outros membros da equipe

Como caso de uso, suponha que precisamos realizar alguns cálculos em dois módulos do aplicativo, mas ambos precisam saber quando há feriados na Polônia. Em teoria, você pode fazer esses cálculos dentro dos módulos, mas seria melhor extrair essa funcionalidade para separar o módulo.

Aqui estão alguns detalhes pequenos, mas importantes. A classe / módulo que você escreveu não é chamado HolidayUtil, mas PolishHolidayCalculator. Funcionalmente é uma utilclasse, mas conseguimos evitar a palavra genérica.

insira a descrição da imagem aqui

Marcin Szymczak
fonte
1
Um fã antigo, eu vejo;) Aplicativo incrível.
Sridhar Sarnobat
3

Aulas de utilitários são ruins porque significam que você estava com preguiça de pensar em um nome melhor para a classe :)

Dito isso, sou preguiçoso. Às vezes, você só precisa fazer o trabalho e sua mente fica em branco ... é aí que as aulas de "Utilidades" começam a surgir.

Larry Watanabe
fonte
3

Olhando para esta questão agora, eu diria que os métodos de extensão C # destroem completamente a necessidade de classes de utilitário. Mas nem todas as linguagens têm essa construção genial.

Você também tem JavaScript, onde pode simplesmente adicionar uma nova função diretamente ao objeto existente.

Mas não tenho certeza se realmente existe uma maneira elegante de resolver esse problema em uma linguagem mais antiga como C ++ ...

Um bom código OO é meio difícil de escrever e é difícil de encontrar, pois escrever um bom OO requer muito mais tempo / conhecimento do que escrever um código funcional decente.

E quando você está com um orçamento apertado, seu chefe nem sempre fica feliz em ver que você passou o dia inteiro escrevendo um monte de aulas ...

HumbleWebDev
fonte
3

Não concordo inteiramente que as classes utilitárias sejam más.

Embora uma classe de utilitário possa violar os princípios OO de algumas maneiras, nem sempre são ruins.

Por exemplo, imagine que você deseja uma função que limpará uma string de todas as substrings correspondentes ao valor x .

stl c ++ (a partir de agora) não suporta isso diretamente.

Você pode criar uma extensão polimórfica de std::string .

Mas o problema é: você realmente deseja que CADA string que usar em seu projeto seja sua classe de string estendida?

Há momentos em que OO realmente não faz sentido, e este é um deles. Queremos que nosso programa seja compatível com outros programas, então vamos nos ater a std::stringe criar uma classeStringUtil_ (ou algo assim).

Eu diria que é melhor se você ficar com um utilitário por aula. Eu diria que é bobagem ter um utilitário para todas as classes ou muitos utils para uma classe.

HumbleWebDev
fonte
2

É muito fácil marcar algo como utilitário simplesmente porque o designer não conseguiu pensar em um local apropriado para colocar o código. Freqüentemente, existem poucos "utilitários" verdadeiros.

Como regra geral, geralmente mantenho o código no pacote em que é usado pela primeira vez e só refatoro para um local mais genérico se descobrir que mais tarde ele realmente é necessário em outro lugar. A única exceção é se eu já tiver um pacote que executa funcionalidade semelhante / relacionada e o código se encaixa melhor nele.

mdma
fonte
2

As classes utilitárias que contêm métodos estáticos sem estado podem ser úteis. Geralmente, esses testes são muito fáceis de realizar.

Seand
fonte
1

Com o Java 8 você pode usar métodos estáticos em interfaces ... problema resolvido.

Marc T
fonte
Não aborda os problemas declarados em stackoverflow.com/a/3340037/2859065
Luchostein
Como isso é diferente de colocá-los em uma classe e importar?
Wilmol
1

A maioria das classes Util são ruins porque:

  1. Eles ampliam o escopo dos métodos.Eles tornam o código público que, de outra forma, seria privado. Se o método util for necessário para vários chamadores em classes separadas e for estável (ou seja, não precisa de atualização), é melhor, em minha opinião, copiar e colar métodos auxiliares privados na classe de chamada. Depois de expô-lo como uma API, fica mais difícil entender qual é o ponto de entrada público para um componente jar (você mantém uma árvore estruturada chamada hierarquia com um pai por método. Isso é mais fácil de segregar mentalmente em componentes, o que é mais difícil quando você tem métodos chamados de vários métodos pai).
  2. Eles resultam em código morto. Os métodos utilitários com o tempo deixam de ser usados ​​conforme o aplicativo evolui e você acaba com o código não usado poluindo sua base de código. Se ele tivesse permanecido privado, seu compilador diria que o método não é usado e você poderia simplesmente removê-lo (o melhor código é nenhum código). Depois de tornar esse método não privado, seu computador ficará sem poder para ajudá-lo a remover o código não utilizado. Ele pode ser chamado de um arquivo jar diferente para tudo que o computador conhece.

Existem algumas analogias com bibliotecas estáticas e dinâmicas.

Sridhar Sarnobat
fonte
0

Quando não consigo adicionar um método a uma classe (digamos, Accountestá bloqueado contra alterações de desenvolvedores Jr.), apenas adiciono alguns métodos estáticos à minha classe Utilities, como:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  
Sr. White
fonte
1
Para mim, parece que você é o Jr ...: P A maneira menos perversa de fazer isso é pedir educadamente ao seu "Desenvolvedor Jr" para desbloquear o Accountarquivo. Ou melhor, vá com sistemas de controle de origem sem travamento.
Sudeep,
1
Suponho que ele quis dizer que o Account arquivo foi bloqueado de forma que os desenvolvedores Jr. não podem fazer alterações nele. Como um Desenvolvedor Jr., para contornar isso, ele fez como acima. Dito isso, ainda é melhor obter acesso de gravação ao arquivo e fazê-lo corretamente.
Doc
Adicionar +1 a isso porque os votos negativos parecem duros quando ninguém tem o contexto completo do motivo pelo qual você forneceu esta resposta
joelc,
0

As classes utilitárias nem sempre são más. Mas eles devem conter apenas os métodos comuns em uma ampla gama de funcionalidades. Se houver métodos que só podem ser usados ​​em um número limitado de classes, considere a criação de uma classe abstrata como um pai comum e coloque os métodos nela.

Sid J
fonte