Ter aulas 'Util' é motivo de preocupação? [fechadas]

14

Às vezes, crio classes 'Util' que servem principalmente para armazenar métodos e valores que realmente não parecem pertencer a outro lugar. Mas toda vez que crio uma dessas classes, penso "uh-oh, vou me arrepender disso mais tarde ...", porque li em algum lugar que é ruim.

Mas, por outro lado, parece haver dois casos convincentes (pelo menos para mim) para eles:

  1. segredos de implementação que são usados ​​em várias classes dentro de um pacote
  2. fornecendo funcionalidade útil para aumentar uma classe, sem sobrecarregar sua interface

Estou a caminho da destruição? O que você diz !! Devo refatorar?


fonte
11
Pare de usar Utilnos nomes das suas aulas. Problema resolvido.
11115 yannis
3
@MattFenwick Yannis tem um bom argumento. Nomear uma classe SomethingUtilé um pouco preguiçoso e apenas obscurece o verdadeiro objetivo da classe - o mesmo com as classes nomeadas SomethingManagerou SomethingService. Se essa classe tiver uma única responsabilidade, deve ser fácil dar um nome significativo. Se não, esse é o problema real para lidar com ...
MattDavey
1
@MDavey ponto bom, que provavelmente era uma escolha má palavra para uso Util, embora, obviamente, eu não esperava que isso iria ficar fixado em eo resto da questão ignorada ...
1
Se você criar várias classes * Util separadas por qual tipo de objeto elas manipulam, acho que tudo bem. Não vejo nenhum problema em ter um StringUtil, ListUtil etc. A menos que você esteja em C #, deve usar métodos de extensão.
marco-Fiset
4
Costumo ter conjuntos de funções que são usadas para fins relacionados. Eles realmente não mapeiam bem para uma aula. Não preciso de uma classe ImageResizer, só preciso da função ResizeImage. Então, colocarei funções relacionadas em uma classe estática como o ImageTools. Para funções que ainda não estão em nenhum tipo de agrupamento, eu tenho uma classe estática Util para mantê-las, mas uma vez que eu tenho algumas que são relacionadas o suficiente uma para a outra para caber em uma classe, eu as moverei. Não vejo melhor maneira de lidar com isso.
Philip

Respostas:

26

O design moderno do OO aceita que nem tudo é um objeto. Algumas coisas são comportamentos ou fórmulas e outras não têm estado. É bom modelar essas coisas como funções puras para obter o benefício desse design.

Java e C # (e outros) exigem que você crie uma classe util e pule esse aro para fazê-lo. Irritante, mas não o fim do mundo; e não é realmente problemático do ponto de vista do design.

Telastyn
fonte
Sim, isso é o que eu estava pensando. Obrigado pela resposta!
Concordou, embora deva ser mencionado que o .NET agora oferece métodos de extensão e classes parciais como uma alternativa a essa abordagem. Se você levar esse item ao extremo, você terminará com Ruby mixins - uma linguagem que tem pouca necessidade de classes de utilitário.
neontapir
2
@neontapir - Exceto a implementação de métodos de extensão basicamente obriga a fazer uma util classe com os métodos de extensão ...
Telastyn
Verdade. Existem dois lugares onde as classes Util impedem, uma é na própria classe e a outra é o local da chamada. Fazendo com que os métodos pareçam existir no objeto aprimorado, os métodos de extensão solucionam o problema do site de chamada. Concordo, ainda existe a questão da existência da classe Util.
neontapir
16

Nunca diga nunca"

Eu não acho que seja necessariamente ruim, só é ruim se você fizer mal e abusar.

Todos nós precisamos de ferramentas e utilitários

Para começar, todos nós usamos algumas bibliotecas que às vezes são consideradas quase onipresentes e obrigatórias. Por exemplo, no mundo Java, no Google Guava ou em alguns Apache Commons ( Apache Commons Lang , Apache Commons Collections , etc ...).

Portanto, claramente há uma necessidade disso.

Evite erros de palavras duras, duplicação e introdução de erros

Se você pensar sobre estes são praticamente apenas um grande monte dessas Utilclasses que você descrever, exceto que alguém passou por grandes distâncias para obtê-los (relativamente) bem, e eles têm sido tempo - testado e fortemente olho-enrolado por outros.

Então, eu diria que a primeira regra prática quando sentir vontade de escrever uma Utilclasse é verificar se ela Utilrealmente não existe.

O único contra-argumento que eu vi para isso é quando você deseja limitar suas dependências porque:

  • você deseja limitar o espaço de memória de suas dependências,
  • ou você deseja controlar rigidamente o que os desenvolvedores têm permissão para usar (acontece em equipes grandes obsessivas ou quando uma estrutura específica é conhecida por ter a classe super-ruim de evitar absolutamente em algum lugar).

Mas ambos podem ser resolvidos re-empacotando a lib usando o ProGuard ou equivalente, ou desmontando-a (para usuários do Maven , o maven-shade-plugin oferece alguns padrões de filtragem para integrar isso como parte de sua compilação).

Portanto, se estiver em uma biblioteca e corresponder ao seu caso de uso, e nenhum benchmark indicar o contrário, use-o. Se variar um pouco do que você, estenda-o (se possível) ou estenda-o ou, em último caso, reescreva-o.

Convenções de nomenclatura

No entanto, até agora nesta resposta eu os chamei de Utils como você. Não os nomeie assim.

Dê-lhes nomes significativos. Tome o Google Guava como um (muito, muito) bom exemplo do que fazer e imagine que o com.google.guavanamespace seja realmente a sua utilraiz.

Ligue para o seu pacote util, na pior das hipóteses, mas não para as aulas. Se lida com Stringobjetos e manipulação de construções de strings, chame-o de Stringsnão StringUtils(desculpe, Apache Commons Lang - eu ainda gosto e uso você!). Se fizer algo específico, escolha um nome de classe específico (como Splitterou Joiner).

Teste de unidade

Se você precisar recorrer à gravação desses utilitários, faça um teste de unidade. O bom dos utilitários é que eles geralmente são componentes independentes, que recebem entradas específicas e retornam saídas específicas. Esse é o conceito. Portanto, não há desculpa para não testá-los unitariamente.

Além disso, o teste de unidade permitirá definir e documentar o contrato da API. Se os testes forem interrompidos, você alterou algo da maneira errada ou significa que está tentando alterar o contrato da sua API (ou que seus testes originais eram uma porcaria - aprenda com ele e não faça isso de novo) .

Design da API

As decisões de design que você tomará para essas APIs o seguirão por muito tempo, possivelmente. Portanto, embora não gaste horas escrevendo um Splitterclone, tenha cuidado com a maneira como aborda o problema.

Faça a si mesmo algumas perguntas:

  • Seu método utilitário justifica uma classe por si só ou é um método estático bom o suficiente, se faz sentido fazer parte de um grupo de métodos igualmente úteis?
  • Você precisa de métodos de fábrica para criar objetos e tornar suas APIs mais legíveis?
  • Falando em legibilidade, você precisa de uma API fluente , construtores , etc ...?

Você deseja que esses utilitários cubram uma grande variedade de casos de uso, sejam robustos, estáveis, bem documentados, seguindo o princípio da menor surpresa e sejam independentes. Idealmente, cada subpacote de seus utils, ou todo o seu pacote de utilitários, deve ser exportado para um pacote para facilitar a reutilização.

Como sempre, aprenda com os gigantes aqui:

Sim, muitos deles enfatizam coleções e estruturas de dados, mas não me diga que não é onde ou para quê você costuma implementar a maioria de seus utilitários, direta ou indiretamente.

haylem
fonte
+1 paraIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova
+1 Para o design da API em .Net, leia o livro dos arquitetos do .Net. Framework Design Guidelines: Convenções, expressões idiomáticas e padrões para reutilizáveis .NET Bibliotecas
MarkJ
Por que você o chamaria de Strings em vez de StringUtil (s)? Cordas para mim soa como um objeto de coleção.
bdrx
@ bdrx: para mim, um objeto de coleção seria StringsCollectionTypeHerese você quiser uma implementação concreta. Ou um nome mais específico, se essas sequências tiverem um significado específico no contexto do seu aplicativo. Nesse caso específico, o Guava usa Stringspara seus auxiliares relacionados a cadeias, em oposição ao StringUtilsCommons Lang. Acho perfeitamente aceitável, apenas significa para mim que as classes manipulam Stringobjetos ou são uma classe de propósito geral para gerenciar Strings.
haylem 12/09
@bdrx: Em geral, as NameUtilscoisas me incomodam sem fim, porque se estiver sob um nome de pacote claramente rotulado, eu já saberia que é uma classe de utilidade (e, se não, descobriria rapidamente olhando a API). É tão chato para mim como pessoas declarando coisas como SomethingInterface, ISomethingou SomethingImpl. Eu estava bem com esses artefatos ao codificar em C e não usar IDEs. Hoje eu geralmente não precisaria de tais coisas.
haylem 12/09
8

As classes Util não são coesas e geralmente apresentam um design ruim porque uma classe precisa ter um único motivo para mudar (Princípio de Responsabilidade Única).

No entanto, eu já vi "util" classes no muito Java API, como: Math, Collectionse Arrays.

Essas são, de fato, classes de utilidade, mas todos os seus métodos estão relacionados a um único tema, um possui operações matemáticas, um possui métodos para manipular coleções e outro para manipular matrizes.

Tente não ter métodos totalmente não relacionados em uma classe de utilitário. Se for esse o caso, é provável que você também possa colocá-los onde eles realmente pertencem.

Se deve ter util aulas, em seguida, testá-los para ser separados por tema como Java de Math, Collectionse Arrays. Pelo menos, mostra alguma intenção de design, mesmo quando são apenas namespaces.

Eu, por exemplo, sempre evito classes de utilidade e nunca tive a necessidade de criar uma.

Tulains Córdova
fonte
Obrigado pela resposta. Então, mesmo que as classes util sejam privadas de pacotes, elas ainda são um cheiro de design?
nem tudo pertence a uma classe. se você está preso a um idioma que insiste em fazê-lo, as classes utils acontecerão.
jk.
1
Bem, as classes de utilitário não têm um princípio de design para informar quantos métodos são demais, já que não há coesão para começar. Como você pode saber como parar? Após 30 métodos? 40? 100? Os princípios de POO, por outro lado, dão uma idéia de quando um método não pertence a uma classe específica e quando a classe está fazendo muito. Isso se chama coesão. Mas, como eu disse na minha resposta, as pessoas que criaram o Java usavam classes de utilidade. Você não pode fazer isso também. Não crio classes de utilidade e não estive em uma situação em que algo não pertence a uma classe normal.
Tulains Córdova
1
As classes Utils geralmente não são classes, são apenas namespaces contendo várias funções, geralmente puras. As funções puras são tão coesas quanto você pode obter.
jk.
1
@jk eu quis dizer Utils ... A propósito, aparecer com nomes como Mathou Arraysmostra pelo menos alguma intenção de design.
Tulains Córdova
3

É perfeitamente aceitável ter classes util , embora eu prefira o termo ClassNameHelper. O .NET BCL ainda possui classes auxiliares. A coisa mais importante a lembrar é documentar completamente o objetivo das classes, bem como cada método auxiliar individual, e torná-lo um código sustentável de alta qualidade.

E não se deixe levar pelas aulas de ajuda.

David Anderson
fonte
0

Eu uso uma abordagem em duas camadas. Uma classe "Globals" em um pacote "util" (pasta). Para que algo entre na classe "Globals" ou no pacote "util", deve ser:

  1. Simples,
  2. Imutável,
  3. Não depende de mais nada em seu aplicativo

Exemplos que passam nesses testes:

  • Formato de data e decimal em todo o sistema
  • Dados globais imutáveis, como EARLIEST_YEAR (que o sistema suporta) ou IS_TEST_MODE ou algum valor verdadeiramente global e imutável (enquanto o sistema está sendo executado).
  • Métodos auxiliares muito pequenos que solucionam lacunas no seu idioma, estrutura ou kit de ferramentas

Aqui está um exemplo de um método auxiliar muito pequeno, completamente independente do restante do aplicativo:

public static String ordinal(final int i) {
    return (i == 1) ? "1st" :
           (i == 2) ? "2nd" :
           (i == 3) ? "3rd" :
           new StringBuilder().append(i).append("th").toString();
}

Olhando para isso, vejo um bug que 21 deve ser "21", 22 deve ser "22", etc., mas isso não vem ao caso.

Se um desses métodos auxiliares crescer ou se tornar complicado, ele deverá ser movido para sua própria classe no pacote util. Se duas ou mais classes auxiliares no pacote util estiverem relacionadas, elas deverão ser movidas para o próprio pacote. Se um método constante ou auxiliar estiver relacionado a uma parte específica do seu aplicativo, ele deverá ser movido para lá.

Você deve ser capaz de justificar por que não existe um lugar melhor para tudo o que você coloca em uma classe Globals ou pacote de utilitários e deve limpá-lo periodicamente usando os testes acima. Caso contrário, você está apenas criando uma bagunça.

GlenPeterson
fonte