Nunca use Strings em Java? [fechadas]

73

Eu me deparei com 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ê deveria usar classes de wrapper finas. Estes são os exemplos anteriores e posteriores que a referida 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 absurdo, mas fico me perguntando se esse é um ponto válido. De alguma forma, isso não parece certo para mim, mas eu não conseguia identificar exatamente o que está errado com o estilo de programação.

DPM
fonte
Discussão relevante no Wiki original: "o que é esse toString () de?"
Christoffer Hammarström
5
Oh wow, que post me fez sentir fisicamente doente
Rob
5
Li pelo menos um livro (que pode ter sido a primeira edição do Code Complete) que recomendava a digitação de todos os seus tipos primitivos para que tivessem nomes do domínio do problema. A mesma ideia.
usar o seguinte comando
9
uma declaração que começa com "nunca" é "sempre" falsa!
Florents Tselai
11
Esse cara é um CTO ?!
Hyangelo 15/06/12

Respostas:

88

O encapsulamento existe para proteger seu programa contra alterações . A representação de um nome vai mudar? Caso contrário, 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 foi escalado demais. Algo como String orderId é muito ruim, porque presumivelmente "!"£$%^&*())ADAFVFnão é válido orderId. Isso significa que Stringrepresenta muito mais valores possíveis do que existem orderIds válidos . No entanto, para algo como a name, você não pode prever o que pode ou não ser um nome válido, e qualquer Stringum é válido name.

Na primeira instância, você está (corretamente) reduzindo as entradas possíveis para apenas as válidas. Na segunda instância, você não conseguiu restringir possíveis entradas válidas.

Editar novamente: considere o caso de entrada inválida. Se você escrever "Gareth Gobulcoque" como seu nome, parecerá bobo, não será o fim do mundo. Se você inserir um Código de Pedido inválido, é provável que ele simplesmente não funcione.

DeadMG
fonte
5
Com os IDs dos pedidos, eu provavelmente preferiria adicionar um cheque no código, aceitando os IDs e deixando a String. As classes devem fornecer métodos para operar com dados. Se alguma classe verifica apenas a validade de alguns dados e não faz nada, isso não me parece apropriado. OOP é bom, mas você não deve exagerar.
Malcolm
13
@ Malcolm: Mas então você não tem como saber quais Strings são validadas, exceto para validá-las novamente.
DeadMG
7
"[...] você não pode prever o que pode ou não ser um nome válido, e qualquer String é um nome válido." Acho que um dos pontos fortes dessa técnica é que, se seu parâmetro for do tipo Name, você não poderá passar acidentalmente outro valor String não relacionado. Onde você espera um Name, apenas um Nameserá compilado e a String "Eu devo $ 5" não pode ser acidentalmente aceita. (Note que este não está relacionado com qualquer tipo de validação de nome!)
Andres F.
6
Criar tipos específicos para um uso específico adiciona riqueza semântica e ajuda a adicionar segurança. Além disso, a criação de tipos para representar valores específicos ajuda muito ao usar um contêiner de IoC para conectar automaticamente suas classes - é fácil resolver o bean certo a ser usado quando é o único bean registrado para uma classe específica. É necessário muito mais esforço quando é apenas uma das muitas seqüências registradas.
Datan
3
@DeadMG Isso é argumentativo, e não algo que possamos resolver nos comentários. Eu diria que extraviar valores de tipos primitivos acontece, e proteger suas interfaces é uma maneira de melhorar a situação.
Andres F.
46

Isso é loucura :)

Makach
fonte
2
Essa também é minha opinião, mas lembro-me dessa sugestão em "Código completo". É claro que nem tudo nesse livro é indiscutível, mas pelo menos isso me faz pensar duas vezes antes de rejeitar uma ideia.
DPM
8
Você acabou de mencionar alguns slogans sem qualquer justificativa real. Você pode elaborar sua opinião? Alguns padrões e linguagem recursos aceitos, por exemplo, pode ser parecido com complexidade adicional, ainda oferecer algo de valor em troca (por exemplo: tipagem estática)
Andres F.
12
O senso comum é tudo menos comum.
Kaz Dragon
11
+1, acrescentaria que quando uma linguagem suporta parâmetros nomeados e o IDE é bom, como é o caso do C # e do VS2010, não é necessário compensar a falta de recursos na linguagem com os padrões malucos. . Não há necessidade de uma classe chamada X e uma classe chamada Y, se alguém puder escrever. var p = new Point(x: 10, y:20);Além disso, não é como o Cinema é tão diferente de uma string. Eu entenderia se alguém lidasse com quantidades físicas, como pressão, temperatura, energia, onde as unidades diferem e algumas não podem ser negativas. O autor do blog precisa tentar funcional.
Job
11
+1 para "Não exagere na engenharia!"
Ivan
23

Eu 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ê tiver um conjunto de campos que representam algum conceito como um endereço, crie uma classe para esse conceito. Se você está programando em Java, está pagando um preço alto pela digitação estática. Você também pode obter todo o valor possível.

Kevin Cline
fonte
2
O downvoter pode comentar?
kevin Cline
Qual é o preço alto que estamos pagando pela digitação estática?
Richard Tingle
@RichardTingle: o tempo para recompilar o código e reiniciar a JVM para cada alteração de código. O tempo perdido quando você faz uma alteração compatível com a fonte, mas incompatível com binários, e as coisas que precisam ser recompiladas não são e você recebe "MissingMethodException".
kevin Cline
Eu nunca teve um problema com aplicações java (o que com a compilação contínua é normalmente já compilado pelo tempo que eu bati prazo), mas é um ponto justo com as guerras da web para que realocá parece um pouco mais lento
Richard Tingle
16

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

... é a resposta que eu teria escrito aqui há 2 anos. Agora, porém, não tenho tanta certeza; de fato, nos últimos meses, comecei a migrar códigos antigos para esse formato, não porque não tenho nada melhor para fazer, mas porque realmente precisava dele para implementar novos recursos ou alterar os existentes. Entendo as aversões automáticas que outras pessoas veem nesse código, mas acho que isso é algo que merece uma reflexão séria.


Benefícios

O principal entre os benefícios é a capacidade de modificar e estender o código. Se você usar

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

em vez de apenas passar alguns números inteiros - o que, infelizmente, muitas interfaces fazem -, fica muito mais fácil adicionar outra dimensão posteriormente. Ou mude o tipo para double. Se você usar List<Author> authorsou em List<Person> authorsvez List<String> authorsdisso mais tarde, será muito mais fácil adicionar mais informações ao que o autor representa. Anotá-lo dessa maneira, parece que estou dizendo o óbvio, mas, na prática, eu tenho sido culpado de usar strings dessa maneira muitas vezes, especialmente nos casos em que não era óbvio no começo, então eu precisaria de mais do que uma linha.

No momento, estou tentando refatorar uma lista de cadeias de caracteres que está entrelaçada em todo o meu código, porque preciso de mais informações e sinto a dor: \

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

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

Desvantagens

Leva mais tempo para escrever . Escrever uma turma pequena é rápido e fácil, mas não exige esforço, especialmente se você precisar de muitas dessas aulas. Se você parar a cada 3 minutos para escrever uma nova classe de wrapper, também poderá ser um prejuízo real para sua concentração. Eu gostaria de pensar, no entanto, que esse estado de esforço geralmente só acontece no primeiro estágio de escrever qualquer parte do código; Normalmente, eu posso ter uma boa idéia rapidamente de quais entidades precisarão estar envolvidas.

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

Discutível

Eu diria se isso ajuda ou não a legibilidade do código é discutível. Sim, mais informações semânticas, mas código mais longo. 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 leia a documentação.


Como na maioria das outras escolas de programação, acho que não é saudável levar essa ao extremo. Eu nunca me vejo separando as coordenadas xey para ser de um tipo diferente. Eu não acho que Counté necessário quando um intdeve ser suficiente. Não gosto do unsigned intuso em C - embora teoricamente bom, ele não fornece informações suficientes e proíbe estender seu código posteriormente para suportar esse -1 mágico. Às vezes você precisa de simplicidade.

Eu acho que o post do blog é um pouco extremo. Mas, no geral, aprendi por experiência dolorosa que a idéia básica por trás disso é feita com as coisas certas.

Tenho uma aversão profunda ao código de engenharia excessiva. Eu realmente faço. Mas, usado corretamente, não acho que isso exagere na engenharia.

Carvalho
fonte
5

Embora seja um exagero, muitas vezes acho que a maioria das coisas que vi são pouco projetadas.

Não é apenas "Segurança". Uma das coisas realmente boas sobre Java é que ele ajuda muito a lembrar / descobrir exatamente o que qualquer método de biblioteca chamado / precisa / espera.

A PIOR (de longe) biblioteca Java com a qual trabalhei foi escrita por alguém que gostava muito de Smalltalk e modelou a biblioteca GUI para fazer com que ela parecesse mais com smalltalk - o problema é que todo método tem o mesmo objeto base, mas na verdade não podia USAR tudo o que o objeto base poderia ser convertido, então você voltou a adivinhar o que passar nos métodos e sem saber se havia falhado até o tempo de execução (algo que eu costumava lidar todas as vezes que eu estava trabalhando em C).

Outra questão - se você passar strings, ints, coleções e matrizes sem objetos, tudo o que você tem são bolas de dados sem significado. Isso parece natural quando você pensa em termos de bibliotecas que "algum aplicativo" usará, mas ao projetar um aplicativo inteiro, é muito mais útil atribuir significado (código) a todos os seus dados no local em que os dados são definidos e pensar apenas em termos de como esses objetos de alto nível interagem. Se você está passando primitivas em vez de objetos, está alterando, por definição, os dados em um local diferente do local 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).

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

Na verdade, não é uma má ideia. Pensando nisso, eu nem tenho certeza se diria que foi superprojetado, é mais fácil trabalhar com quase todos os aspectos - a única exceção é a proliferação de pequenas classes, mas as classes Java são muito leves e fáceis escrever que isso dificilmente é um custo (eles podem até ser gerados).

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

Bill K
fonte
3

Eu acho que você tem que olhar para esse 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);

São necessários dois parâmetros para especificar o consumidor real que está registrando os ingressos. Você pode ter dois filmes diferentes com nomes idênticos (por exemplo, remakes), ou 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 de uma maneira consistente e consistente (por exemplo, você está usando $chain ($city)ou $chain in $cityou mesmo outra coisa e como garantir que isso seja Na verdade, o pior é especificar seu usuário por meio de dois parâmetros, o fato de que o nome e o sobrenome sejam fornecidos não garante um cliente válido (e você não pode distinguir dois John Doe).

A resposta para isso é declarar tipos, mas esses raramente serão invólucros finos, como mostramos acima. Provavelmente, eles funcionarão como seu armazenamento de dados ou serão acoplados a algum tipo de banco de dados. Portanto, um Cinemaobjeto provavelmente terá um nome, local ... e assim você se livra dessas ambiguidades. Se são embalagens finas, são por coincidência.

Então, IMHO, o post do blog está apenas dizendo "certifique-se de passar os tipos corretos", seu autor acabou de fazer a escolha excessivamente restrita de 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 em 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 em que o sistema de tipos me permita realizar operações sem sentido. É claro que você pode aplicar a mesma lógica do blog e definir tipos CountOfOrangesetc., mas isso também é bobagem.

Pelo que vale a pena, eu escreveria algo como

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

Para encurtar a 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á montando uma prova de conceito. Mantenha seu sistema de tipos limpo, portanto, não use um tipo que seja muito geral (por exemplo, um filme representado por a String) ou muito restritivo / específico / artificial (por exemplo, em Countvez de int). Use um tipo que defina seu objeto de maneira única e inequívoca sempre que possível e viável.

editar : um resumo ainda mais curto. Para pequenas aplicações (por exemplo, prova de conceitos): por que se preocupar com um design complicado? Basta usar Stringou inte continuar com isso.

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

Sinto que a idéia de encapsular seqüências de caracteres ... é apenas um projeto incompleto: excessivamente complicado para aplicativos pequenos, não completo o suficiente para aplicativos grandes.

Egon
fonte
Entendo o seu argumento, mas eu diria que você está assumindo mais do que o autor está afirmando. Pelo que sabemos, seu modelo só tem uma String para um cliente, uma String para um filme e assim por diante. Essa funcionalidade extra hipotética é realmente o cerne do problema, de modo que ele estava muito "distraído" para omitir isso ao apresentar seu argumento ou acha que deveríamos fornecer mais poder semântico apenas porque. Novamente, pelo que sabemos, é o último que ele tinha em mente.
DPM
@Jubbat: na verdade, assumo mais do que o que o autor está afirmando. Mas o que quero dizer é que você tem uma aplicação simples, caso em que as duas maneiras são excessivamente complicadas. Para essa escala, a manutenção não é um problema e a distinção semântica impede sua velocidade de codificação. Se, por outro lado, seu aplicativo for grande, vale a pena definir corretamente seus tipos (mas é improvável que sejam invólucros simples). IMHO, seus exemplos não são convincentes ou têm grandes falhas de design além do que ele está tentando fazer.
Egon
2

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

Tom Squires
fonte
2
+1 por implicar o tratamento dos sintomas e não a causa.
Job
2

Eu diria que é realmente uma boa idéia em um idioma com um recurso tipo typedef fortemente 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.

jk.
fonte
0

Seria bom se IF String (end Integer e ... falando apenas sobre String) não fosse final, portanto essas classes poderiam ser uma String (restrita) com significado e ainda poderem ser enviadas para algum objeto independente que sabe lidar com isso. o tipo de base (sem conversar para frente e para trás).

E "goodnes" 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 prefiro começar sem ele.

user470365
fonte
0

Para dar um exemplo: em nosso sistema desenvolvido atualmente, 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 - portanto, se alguém misturar qual tipo de identificação deve ser passado como parâmetro, nenhum erro de tempo de compilação será mostrado, mas o programa explodirá em tempo de execução. Isso acontece com bastante frequência. Portanto, devo dizer que o objetivo principal desse princípio não é proteger contra mudanças (embora isso também sirva), mas proteger-nos de bugs. De qualquer forma, se alguém cria uma API, ela deve refletir os conceitos do domínio, por isso é conceitualmente útil definir classes específicas do domínio - tudo depende da existência de ordem na mente dos desenvolvedores,

thSoft
fonte
@ downvoter: você pode dar uma explicação?
thSoft