Nunca use Strings em Java? [fechadas]

72

Eu tropecei em uma entrada de blog desencorajando o uso de Strings em Java para fazer com que seu código não tenha semântica, sugerindo que você deve usar classes de wrapper thin em seu lugar. Estes são os exemplos de antes e depois que a dita entrada fornece para ilustrar o assunto:

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

Na minha experiência de ler blogs de programação, cheguei à conclusão de que 90% é um disparate, mas fico me perguntando se esse é um ponto válido. De alguma forma, não parece certo para mim, mas eu não poderia exatamente identificar o que está errado com o estilo de programação.

    
por DPM 25.01.2012 / 23:04
fonte

10 respostas

87

O encapsulamento existe para proteger seu programa contra alteração . A representação de um nome vai mudar? Se não, então você está desperdiçando seu tempo e YAGNI se aplica.

Edit: Eu li o post do blog e ele tem uma idéia fundamentalmente boa. O problema é que ele escalou muito longe. Algo como String orderId é muito ruim, porque presumivelmente "!"£$%^&*())ADAFVF não é um orderId válido. Isso significa que String representa muito mais valores possíveis do que os orderId s válidos. No entanto, para algo como name , não é possível prever o que pode ou não ser um nome válido e qualquer String é um name válido.

No primeiro caso, você está (corretamente) reduzindo as entradas possíveis para apenas as válidas. No segundo caso, você não alcançou o estreitamento de possíveis entradas válidas.

Edite novamente: considere o caso de entrada inválida. Se você escrever "Gareth Gobulcoque" como seu nome, vai parecer bobo, não vai ser o fim do mundo. Se você colocar um OrderID inválido, é provável que simplesmente não funcione.

    
por 25.01.2012 / 23:10
fonte
46

Isso é uma loucura:)

por 25.01.2012 / 23:09
fonte
23

Concordo com o autor, principalmente. Se houver algum comportamento peculiar a um campo, como a validação de um ID de pedido, eu criaria uma classe para representar esse tipo. Seu segundo ponto é ainda mais importante: se você tem um conjunto de campos que representam algum conceito como um endereço, crie uma classe para esse conceito. Se você estiver programando em Java, estará pagando um preço alto pela tipagem estática. Você também pode obter todo o valor que puder.

    
por 26.01.2012 / 00:28
fonte
16

Não faça isso; isso vai complicar demais as coisas e você não vai precisar disso

... é a resposta que eu teria escrito aqui há dois anos. Agora, porém, não tenho tanta certeza; Na verdade, nos últimos meses, comecei a migrar código antigo para esse formato, não porque não tenha nada melhor a fazer, mas porque realmente precisava disso para implementar novos recursos ou alterar os existentes. Eu entendo as aversões automáticas que outros aqui têm visto esse código, mas eu acho que isso é algo que merece um pensamento sério.

Benefícios

O principal benefício é a capacidade de modificar e estender o código. Se você usa

class Point {
    int x,y;
    // other point operations
}

ao invés de apenas passar um par de inteiros ao redor - o que é algo que, infelizmente, muitas interfaces fazem - então fica muito mais fácil adicionar outra dimensão. Ou altere o tipo para double . Se você usar List<Author> authors ou List<Person> authors em vez de List<String> authors , mais tarde será mais fácil adicionar mais informações ao que um autor representa. Escrevendo assim, parece que estou afirmando o óbvio, mas na prática eu tenho sido culpado de usar strings dessa maneira muitas vezes, especialmente em casos em que não era óbvio no início, então eu precisaria de mais do que isso. uma string.

Atualmente, estou tentando refatorar algumas listas de strings que estão interligadas em todo o meu código porque preciso de mais informações e sinto a dor: \

Além disso, eu concordo com o autor do blog de que ele carrega mais informações semânticas , facilitando o entendimento do leitor. Embora os parâmetros geralmente recebam nomes significativos e obtenham uma linha de documentação dedicada, isso geralmente não acontece com campos ou locais.

O benefício final é o tipo de segurança , por razões óbvias, mas aos meus olhos é uma pequena coisa aqui.

Desvantagens

Demora mais tempo a escrever . Escrever uma pequena turma é rápido e fácil, mas não sem esforço, especialmente se você precisar de muitas dessas classes. Se você parar a cada 3 minutos para escrever uma nova classe de wrappers, isso pode ser um verdadeiro prejuízo para a sua concentração também. Eu gostaria de pensar, no entanto, que esse estado de esforço geralmente só acontecerá no primeiro estágio de escrever qualquer pedaço de código; Eu geralmente consigo uma boa ideia rapidamente sobre quais entidades precisarão estar envolvidas.

Pode envolver muitos setters (ou construções) e getters redundantes . O autor do blog dá o exemplo realmente desagradável de new Point(x(10), y(10)) em vez de new Point(10, 10) , e gostaria de acrescentar que um uso também pode envolver coisas como Math.max(p.x.get(), p.y.get()) em vez de Math.max(p.x, p.y) . E o código longo é muitas vezes considerado mais difícil de ler e com justiça. Mas com toda honestidade, tenho a sensação de que um lote de código movimenta objetos, e somente métodos selecionados o criam, e menos ainda precisam acessar seus detalhes corajosos (o que não é OOPy de qualquer maneira). / p>

Debatable

Eu diria se isso ajuda ou não com a legibilidade do código é discutível. Sim, mais informação semântica, mas mais código. Sim, é mais fácil entender o papel de cada local, mas é mais difícil entender o que você pode fazer com ele, a menos que você leia a documentação dele.

Como acontece com a maioria das outras escolas de pensamento de programação, acho que não é saudável levar isso ao extremo. Eu não me vejo separando as coordenadas xey para ser de um tipo diferente. Eu não acho que Count é necessário quando um int deve ser suficiente. Eu não gosto do uso de unsigned int em C - apesar de teoricamente bom, ele simplesmente não fornece informações suficientes, e proíbe estender seu código mais tarde para suportar esse -1 mágico. Às vezes você precisa de simplicidade.

Acho que o post do blog é um pouco extremo. Mas, no geral, aprendi com a dolorosa experiência que a ideia básica por trás disso é feita a partir das coisas certas.

Tenho uma profunda aversão ao código superengenharia. Eu realmente faço. Mas acertou, não acho que isso seja engenharia excessiva.

    
por 26.01.2012 / 22:30
fonte
5

Embora seja um pouco exagerado, muitas vezes acho que a maioria das coisas que vi é pouco engenhosa.

Não se trata apenas de "Segurança", uma das coisas muito legais sobre Java é que ele ajuda muito a lembrar / descobrir exatamente o que uma determinada chamada de método de biblioteca precisa / espera.

A MAIOR (por enquanto) biblioteca Java com a qual trabalhei foi escrita por alguém que gostava muito do Smalltalk e modelou a biblioteca GUI depois dela para fazer com que ela funcionasse mais como o smalltalk - o problema sendo que cada método tomava o mesmo Objeto base, mas não podia realmente usar tudo o que o objeto base poderia ser convertido para, então você estava de volta para adivinhar o que passar em métodos e não saber se você tinha falhado até runtime (algo que eu costumava ter que lidar com cada tempo em que eu estava trabalhando em C).

Outro problema - Se você passar strings, ints, coleções e matrizes sem objetos, tudo que você tem são bolas de dados sem significado. Isso parece natural quando você pensa em termos de bibliotecas que "alguns aplicativos" usarão, mas ao projetar um aplicativo inteiro é muito mais útil atribuir significado (código) a todos os seus dados no local em que os dados estão definidos e pensar apenas termos de como esses objetos de alto nível interagem. Se você está passando em torno de primitivos em vez de objetos, então você está - por definição - alterando dados em um lugar diferente de onde está definido (É também por isso que eu realmente não gosto de Setters e Getters - mesmo conceito, você está operando em dados que não são seus).

Finalmente, se você definir objetos separados para tudo, você sempre terá um ótimo lugar para validar tudo - por exemplo, se você criar um objeto para CEP e depois descobrir que precisa garantir que o CEP sempre inclua a extensão de 4 dígitos você tem um lugar perfeito para colocá-lo.

Não é realmente uma má ideia. Pensando nisso eu nem tenho certeza se diria que foi super engenharia, é mais fácil trabalhar com praticamente todos os aspectos - a única exceção é a proliferação de classes minúsculas, mas as classes Java são tão leves e fáceis para escrever que isso dificilmente é um custo (eles podem até ser gerados).

Eu estaria realmente interessado em ver um projeto java bem escrito que realmente tivesse muitas classes definidas (onde isso tornava mais difícil a programação), estou começando a pensar que não é possível ter muitas classes.

    
por 26.01.2012 / 02:11
fonte
3

Eu acho que você tem que olhar para este conceito de outro ponto de partida. Dê uma olhada na perspectiva de um designer de banco de dados: os tipos passados no primeiro exemplo não definem seus parâmetros de uma maneira única, muito menos de uma maneira útil.

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

Dois parâmetros são necessários para especificar o patrono atual que é reservar tíquetes, você pode ter dois filmes diferentes com nomes idênticos (por exemplo, remakes). Você pode ter o mesmo filme com nomes diferentes (por exemplo, traduções). Uma certa cadeia de cinemas pode ter filiais diferentes, então como você vai lidar com isso em uma seqüência de caracteres e de forma consistente (por exemplo, você está usando $chain ($city) ou $chain in $city ou até mesmo outra coisa e como você está indo? Para ter certeza de que isso é usado consistentemente, o pior é especificar seu usuário por meio de dois parâmetros, o fato de que tanto o nome como o sobrenome não garantem um cliente válido (e você não pode distinguir dois John Doe s) .

A resposta para isso é declarar tipos, mas esses raramente serão invólucros finos, conforme mostrado acima. Muito provavelmente, eles funcionarão como seu armazenamento de dados ou serão acoplados a algum tipo de banco de dados. Então, um objeto Cinema provavelmente terá um nome, localização, ... e assim você se livra dessas ambiguidades. Se eles são invólucros finos, eles são por coincidência.

Então, IMHO o post do blog está dizendo "certifique-se de passar os tipos corretos", seu autor acabou de fazer a escolha excessivamente restrita para escolher tipos de dados básicos em particular (que é a mensagem errada).

A alternativa proposta é melhor:

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

Por outro lado, acho que o post do blog vai longe demais com tudo. Count é muito genérico, eu poderia contar maçãs ou laranjas com isso, adicioná-las e ainda ter uma situação em minhas mãos onde o sistema de tipos me permite fazer operações sem sentido. É claro que você pode aplicar a mesma lógica do blog e definir os tipos CountOfOranges etc., mas isso também é bobagem.

Por que vale a pena, eu realmente escrever algo como

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

Longa história: você não deve passar variáveis sem sentido; as únicas vezes em que você realmente especificou um objeto com um valor que não determina o objeto real é quando você executa uma consulta (por exemplo, public Collection<Film> findFilmsWithTitle(String title) ) ou quando está reunindo uma prova de conceito. Mantenha seu sistema de tipos limpo, portanto, não use um tipo muito geral (por exemplo, um filme representado por String ) ou muito restritivo / específico / planejado (por exemplo, Count em vez de int ). Use um tipo que defina seu objeto de maneira única e inequívoca sempre que possível e viável.

edit : um resumo ainda mais curto. Para pequenas aplicações (por exemplo, prova de conceitos): por que se preocupar com um projeto complicado? Basta usar String ou int e seguir em frente.

Para aplicativos grandes: é provável que você tenha muitas classes que consistam em um único campo com um tipo de dados básico? Se você tem poucas classes, você só tem objetos "normais", nada de especial acontecendo lá.

Eu sinto a idéia de encapsular strings, ... é apenas um design que é incompleto: excessivamente complicado para aplicações pequenas, não completo o suficiente para aplicações grandes.

    
por 28.01.2012 / 17:46
fonte
2

Para mim, isso é o mesmo que usar regiões em C #. Geralmente, se você acha que isso é necessário para tornar seu código legível, então você tem problemas maiores com os quais você deve gastar seu tempo.

    
por 25.01.2012 / 23:21
fonte
2

Eu diria que é realmente uma boa idéia em um idioma com um tipo de typedef strongmente tipado.

Em Java, você não tem isso, portanto, criar uma classe totalmente nova para essas coisas significa que o custo provavelmente supera o benefício. Você também pode obter 80% do benefício tomando cuidado com a nomeação de variáveis / parâmetros.

    
por 26.01.2012 / 09:12
fonte
0

Seria bom, IF String (fim Integer, e ... falando apenas de String) não era final, então essas classes poderiam ser alguma (restrita) String com significado e ainda poderiam ser enviadas para algum objeto independednt que sabe como lidar com o tipo de base (sem conversar para trás e para frente).

E "goodnes" dele aumentam quando existem, por exemplo. restrições em todos os nomes.

Mas ao criar algum aplicativo (não biblioteca), é sempre possível refatorá-lo. Então prefarei começar sem isso.

    
por 26.01.2012 / 16:38
fonte
0

Para dar um exemplo: no nosso sistema atualmente desenvolvido, existem muitas entidades diferentes que podem ser identificadas por vários tipos de identificadores (devido ao uso de sistemas externos), às vezes até o mesmo tipo de entidade. Todos os identificadores são Strings - então, se alguém mixar o tipo de id que deve ser passado como parâmetro, nenhum erro de tempo de compilação é mostrado, mas o programa explodirá em tempo de execução. Isso acontece com bastante frequência. Portanto, devo dizer que o propósito primordial deste princípio não é proteger-se contra a mudança (embora também sirva para isso), mas sim proteger-se contra os erros. E, de qualquer forma, se alguém cria uma API, ela precisa refletir os conceitos do domínio, por isso é conceitualmente útil definir classes específicas de domínio - tudo depende se há ordem na mente dos desenvolvedores, e isso pode ser muito facilitado pelo sistema de tipos!

    
por 20.04.2012 / 14:20
fonte