Devemos definir tipos para tudo?

141

Recentemente, tive um problema com a legibilidade do meu código.
Eu tinha uma função que fazia uma operação e retornava uma string representando o ID dessa operação para referência futura (um pouco como o OpenFile no Windows retornando um identificador). O usuário usaria esse ID posteriormente para iniciar a operação e monitorar seu término.

O ID tinha que ser uma sequência aleatória devido a preocupações de interoperabilidade. Isso criou um método que tinha uma assinatura muito pouco clara:

public string CreateNewThing()

Isso torna clara a intenção do tipo de retorno. Eu pensei em quebrar essa string em outro tipo que torna seu significado mais claro assim:

public OperationIdentifier CreateNewThing()

O tipo conterá apenas a sequência e será usado sempre que essa sequência for usada.

É óbvio que a vantagem desse modo de operação é mais segurança de tipo e intenção mais clara, mas também cria muito mais código e código que não é muito idiomático. Por um lado, gosto da segurança adicional, mas também cria muita confusão.

Você acha que é uma boa prática agrupar tipos simples em uma classe por motivos de segurança?

Ziv
fonte
4
Você pode estar interessado em ler sobre tipos minúsculos. Eu brinquei com ele um pouco e não o recomendaria, mas é uma abordagem divertida de se pensar. darrenhobbs.com/2007/04/11/tiny-types
Jeroen Vannevel
40
Depois de usar algo como Haskell, você verá o quanto o uso de tipos ajuda. Tipos ajudam a garantir um programa correto.
Nate Symer #
3
Mesmo uma linguagem dinâmica como Erlang se beneficia de um sistema de tipos, e é por isso que possui um sistema de tipos de tipos e uma ferramenta de verificação de tipos familiar aos Haskellers, apesar de ser uma linguagem dinâmica. Linguagens como C / C ++, em que o tipo real de qualquer coisa é um número inteiro que concordamos com o compilador para tratar de uma certa maneira, ou Java, onde você quase usa tipos, se decide fingir que classes são tipos, apresentam um problema de sobrecarga semântica no idioma (é uma "classe" ou um "tipo"?) ou ambiguidade de tipo (é um "URL", "String" ou "UPC"?). No final, use as ferramentas que puder para desambiguar.
Zxq9 4/15
7
Não sei de onde vem a idéia de que o C ++ tem uma sobrecarga nos tipos. Com o C ++ 11, nenhum dos fabricantes de compiladores viu um problema ao atribuir a cada lambda seu próprio tipo único. Mas TBH, você realmente não pode esperar encontrar muitas informações em um comentário sobre o "sistema de tipos C / C ++" como se os dois compartilhassem um sistema de tipos.
MSalters
2
@JDB - não, não. Nem parecido.
Davor raldralo 5/05

Respostas:

152

Primitivas, como stringou int, não têm significado em um domínio comercial. Uma conseqüência direta disso é que você pode usar um URL por engano quando é esperado um ID do produto ou usar quantidade ao esperar o preço .

É também por isso que o desafio Object Calisthenics apresenta empacotamento de primitivas como uma de suas regras:

Regra 3: agrupar todas as primitivas e seqüências de caracteres

Na linguagem Java, int é um objeto primitivo, não real, por isso obedece a regras diferentes dos objetos. É usado com uma sintaxe que não é orientada a objetos. Mais importante, um int por si só é apenas um escalar, por isso não tem significado. Quando um método usa int como parâmetro, o nome do método precisa executar todo o trabalho de expressar a intenção. Se o mesmo método demorar uma hora como parâmetro, é muito mais fácil ver o que está acontecendo.

O mesmo documento explica que há um benefício adicional:

Objetos pequenos como Hour ou Money também nos dão um lugar óbvio para colocar comportamentos que, de outra forma, seriam espalhados por outras classes .

De fato, quando as primitivas são usadas, geralmente é extremamente difícil rastrear a localização exata do código relacionado a esses tipos, geralmente levando à duplicação grave do código . Se houver Price: Moneyclasse, é natural encontrar a faixa de verificação dentro. Se, em vez disso, é usado um int(pior, a double) para armazenar os preços dos produtos, quem deve validar o intervalo? O produto? O desconto? O carrinho?

Finalmente, um terceiro benefício não mencionado no documento é a capacidade de alterar relativamente fácil o tipo subjacente. Se hoje meu ProductIdtem shortcomo seu tipo subjacente e, posteriormente, eu preciso usá-lo int, é provável que o código a ser alterado não abranja toda a base de código.

A desvantagem - e o mesmo argumento se aplica a todas as regras do exercício Object Calisthenics - é que, se rapidamente se torna esmagador demais para criar uma classe para tudo . Se Productcontém o ProductPriceque herda do PositivePricequal herda o Pricequal, por sua vez Money, não é uma arquitetura limpa, mas uma bagunça completa em que, para encontrar uma única coisa, um mantenedor deve abrir algumas dezenas de arquivos todas as vezes.

Outro ponto a considerar é o custo (em termos de linhas de código) da criação de classes adicionais. Se os invólucros forem imutáveis ​​(como deveriam ser normalmente), significa que, se usarmos C #, você precisará ter, dentro do invólucro, pelo menos:

  • O getter da propriedade,
  • Seu campo de apoio,
  • Um construtor que atribui o valor ao campo de suporte,
  • Um costume ToString(),
  • Comentários da documentação XML (que cria muitas linhas),
  • A Equalse a GetHashCodesubstitui (também muito LOC).

e eventualmente, quando relevante:

  • Um atributo DebuggerDisplay ,
  • Uma substituição ==e !=operadores,
  • Eventualmente, uma sobrecarga do operador implícito de conversão para converter sem problemas de e para o tipo encapsulado,
  • Contratos de código (incluindo o invariante, que é um método bastante longo, com seus três atributos),
  • Vários conversores que serão usados ​​durante a serialização XML, serialização JSON ou armazenamento / carregamento de um valor para / de um banco de dados.

Cem LOC para um invólucro simples o tornam bastante proibitivo, e é também por isso que você pode ter certeza absoluta da lucratividade a longo prazo desse invólucro. A noção de escopo explicada por Thomas Junk é particularmente relevante aqui. Escrever cem LOCs para representar um ProductIdusado em toda a sua base de código parece bastante útil. Escrever uma classe desse tamanho para um pedaço de código que cria três linhas em um único método é muito mais questionável.

Conclusão:

  • Agrupe primitivas em classes que tenham significado no domínio comercial do aplicativo quando (1) ajudar a reduzir erros, (2) reduzir o risco de duplicação de código ou (3) ajudar a alterar o tipo subjacente posteriormente.

  • Não agrupe automaticamente todos os primitivos encontrados no seu código: há muitos casos em que o uso stringou inté perfeitamente adequado.

Na prática, public string CreateNewThing()retornar uma instância da ThingIdclasse em vez de stringpode ajudar, mas você também pode:

  • Retorne uma instância da Id<string>classe, que é um objeto do tipo genérico, indicando que o tipo subjacente é uma sequência. Você tem o benefício da legibilidade, sem a desvantagem de ter que manter muitos tipos.

  • Retorne uma instância da Thingclasse. Se o usuário precisar apenas do ID, isso pode ser feito facilmente com:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    
Arseni Mourzenko
fonte
33
Eu acho que o poder de mudar livremente o tipo subjacente é importante aqui. Hoje é uma string, mas talvez um GUID ou muito amanhã. A criação do wrapper de tipo oculta essas informações, levando a menos alterações no caminho. ++ Boa resposta aqui.
RubberDuck
4
No caso de dinheiro, verifique se a moeda está incluída no objeto Money ou se todo o programa usa a mesma (que deve ser documentada).
Paŭlo Ebermann
5
@Blrfl: embora existam casos válidos em que a herança profunda é necessária, geralmente vejo essa herança em código superprojetado escrito sem atenção à legibilidade, resultante da atitude de pagamento por LOC compulsivo. Assim, o caráter negativo desta parte da minha resposta.
Arseni Mourzenko
3
@ArTs: Esse é o argumento "vamos condenar a ferramenta porque algumas pessoas a usam da maneira errada".
Blrfl
6
@MainMa Um exemplo de onde o significado pode ter evitado um erro oneroso: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar
60

Eu usaria o escopo como regra geral: quanto mais restrito for o escopo de gerar e consumir values, menor será a probabilidade de você criar um objeto que represente esse valor.

Digamos que você tenha o seguinte pseudocódigo

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

então o escopo é muito limitado e eu não faria sentido em torná-lo um tipo. Mas digamos que você gere esse valor em uma camada e o passe para outra camada (ou até outro objeto), faria sentido criar um tipo para isso.

Thomas Junk
fonte
14
Um ponto muito bom. Tipos explícitos são uma ferramenta importante para comunicar a intenção , especialmente importante através dos limites de objetos e serviços.
Avner Shahar-Kashtan
+1, embora se você ainda não refatorou todo o código de doFancyOtherstuff()uma sub-rotina, você pode pensar que a job.do(id)referência não é local o suficiente para ser mantida como uma sequência simples.
Mark Hurd
Isso é totalmente verdade! É o caso quando você tem um método privado, onde sabe que o mundo exterior nunca usará o método. Depois disso, você pode pensar um pouco mais quando a classe está protegida e depois quando a classe é pública, mas não faz parte de uma API pública. Escopo do escopo e muita arte para posicionar a linha na boa posição entre tipos muito primitivos e personalizados.
Samuel
34

Os sistemas do tipo estático têm tudo a ver com a prevenção de usos incorretos dos dados.

Existem exemplos óbvios de tipos que fazem isso:

  • você não pode obter o mês de um UUID
  • você não pode multiplicar duas cordas.

Existem exemplos mais sutis

  • você não pode pagar por algo usando o comprimento da mesa
  • você não pode fazer uma solicitação HTTP usando o nome de alguém como o URL.

Podemos ficar tentados a usar doubletanto por preço quanto por comprimento ou usar um stringpara um nome e um URL. Mas fazer isso subverte nosso incrível sistema de tipos e permite que esses abusos passem nas verificações estáticas do idioma.

Confundir segundos de libra com segundos de Newton pode ter resultados ruins em tempo de execução .


Isso é particularmente um problema com seqüências de caracteres. Eles freqüentemente se tornam o "tipo de dados universal".

Estamos acostumados principalmente a interfaces de texto com computadores e geralmente estendemos essas interfaces humanas (UIs) para interfaces de programação (APIs). Pensamos em 34.25 como os caracteres 34.25 . Pensamos em uma data como os personagens 05-03-2015 . Pensamos em um UUID como os caracteres 75e945ee-f1e9-11e4-b9b2-1697f925ec7b .

Mas esse modelo mental prejudica a abstração da API.

As palavras ou a linguagem, como são escritas ou faladas, não parecem desempenhar nenhum papel no meu mecanismo de pensamento.

Albert Einstein

Da mesma forma, as representações textuais não devem desempenhar nenhum papel no design de tipos e APIs. Cuidado com o string! (e outros tipos "primitivos" excessivamente genéricos)


Tipos comunicam "quais operações fazem sentido".

Por exemplo, uma vez trabalhei em um cliente para uma API REST HTTP. O REST, feito corretamente, usa entidades hipermídia, que possuem hiperlinks apontando para entidades relacionadas. Nesse cliente, não apenas as entidades foram digitadas (por exemplo, Usuário, Conta, Assinatura), mas também os links para essas entidades (UserLink, AccountLink, SubscriptionLink). Os links eram pouco mais do que invólucros Uri, mas os tipos separados impossibilitavam o uso de um AccountLink para buscar um usuário. Se tudo tivesse sido simples Uri- ou pior ainda string- , esses erros só seriam encontrados em tempo de execução.

Da mesma forma, na sua situação, você tem dados que são usados ​​para apenas uma finalidade: identificar um Operation. Não deve ser usado para mais nada, e não devemos tentar identificar Operations com sequências aleatórias que criamos. Criar uma classe separada adiciona legibilidade e segurança ao seu código.


Obviamente, todas as coisas boas podem ser usadas em excesso. Considerar

  1. quanta clareza acrescenta ao seu código

  2. com que frequência é usado

Se um "tipo" de dados (no sentido abstrato) é usado com frequência, para propósitos distintos e entre interfaces de código, é um candidato muito bom por ser uma classe separada, à custa da verbosidade.

Paul Draper
fonte
21
"seqüências de caracteres geralmente se tornam o tipo de dados 'universal'" - essa é a origem do apelido "linguagens de tipos estritos".
MSalters
@ Salters, ha ha, muito bom.
Paul Draper
4
E, claro, o que 05-03-2015 significa quando interpretado como uma data ?
um CVn
10

Você acha que é uma boa prática agrupar tipos simples em uma classe por motivos de segurança?

As vezes.

Este é um daqueles casos em que você precisa avaliar os problemas que podem surgir ao usar um em stringvez de um mais concreto OperationIdentifier. Qual é a gravidade deles? Qual é a probabilidade deles?

Então você precisa considerar o custo de usar o outro tipo. Quão doloroso é usar? Quanto trabalho é feito?

Em alguns casos, você economiza tempo e esforço com um bom tipo de concreto para trabalhar. Em outros, não valerá a pena.

Em geral, acho que esse tipo de coisa deve ser feito mais do que é hoje. Se você tem uma entidade que significa algo em seu domínio, é bom tê-lo como seu próprio tipo, pois é mais provável que essa entidade mude / cresça com a empresa.

Telastyn
fonte
10

Eu geralmente concordo que muitas vezes você deve criar um tipo para primitivas e seqüências de caracteres, mas como as respostas acima recomendam a criação de um tipo na maioria dos casos, listarei algumas razões pelas quais / quando não:

  • Atuação. Devo me referir a idiomas reais aqui. Em C #, se um ID de cliente é curto e você o agrupa em uma classe - você acabou de criar muita sobrecarga: memória, já que agora tem 8 bytes em um sistema de 64 bits e velocidade, já que agora está alocado no heap.
  • Quando você faz suposições sobre o tipo. Se um ID de cliente é curto e você tem alguma lógica que o compacta de alguma maneira - você geralmente já faz suposições sobre o tipo. Em todos esses lugares, agora você precisa quebrar a abstração. Se é um ponto, não é grande coisa, se está em todo lugar, você pode achar que metade do tempo está usando o primitivo.
  • Porque nem todo idioma tem typedef. E para idiomas que não codificam e que já estão escritos, fazer essa alteração pode ser uma grande tarefa que pode até apresentar bugs (mas todo mundo tem um conjunto de testes com boa cobertura, certo?).
  • Em alguns casos, reduz a legibilidade. Como imprimo isso? Como eu valido isso? Devo verificar se há nulo? São todas as perguntas que exigem que você se aprofundar na definição do tipo.
ytoledano
fonte
3
Certamente, se o tipo de invólucro for uma estrutura e não uma classe, então um short(por exemplo) terá o mesmo custo envolto ou desembrulhado. (?)
MathematicsOrchid
1
Não seria bom design para um tipo encapsular sua própria validação? Ou para criar um tipo auxiliar para validá-lo?
Nick Udell
1
Empacotar ou mover desnecessariamente é um anti-padrão. As informações devem fluir de forma transparente através do código do aplicativo, exceto onde a transformação for explícita e genuinamente necessária . Os tipos são bons, pois geralmente substituem uma pequena quantidade de código bom, localizado em um único local, por grandes quantidades de implementação ruim espalhadas por todo o sistema.
Thomas W
7

Não, você não deve definir tipos (classes) para "tudo".

Mas, como outras respostas indicam, geralmente é útil fazê-lo. Você deve desenvolver - conscientemente, se possível - um sentimento de atrito excessivo devido à ausência de um tipo ou classe adequada, ao escrever, testar e manter seu código. Para mim, o início do atrito excessivo é quando quero consolidar vários valores primitivos como um valor único ou quando preciso validar os valores (isto é, determinar quais de todos os valores possíveis do tipo primitivo correspondem aos valores válidos do 'tipo implícito').

Descobri que considerações como a que você colocou na sua pergunta são responsáveis ​​por muito design no meu código. Eu desenvolvi o hábito de evitar deliberadamente escrever mais código do que o necessário. Espero que você esteja escrevendo bons testes (automatizados) para o seu código - se estiver, poderá refatorá-lo facilmente e adicionar tipos ou classes, se isso fornecer benefícios líquidos para o desenvolvimento e manutenção contínuos do seu código .

A resposta de Telastyn e resposta de Thomas Junk ambos fazem um bom ponto sobre o contexto eo uso do código relevante. Se você estiver usando um valor dentro de um único bloco de código (por exemplo, método, loop, usingbloco), não há problema em usar apenas um tipo primitivo. É bom usar um tipo primitivo se você estiver usando um conjunto de valores repetidamente e em muitos outros lugares. Mas quanto mais frequente e amplamente você estiver usando um conjunto de valores, e quanto menos esse conjunto de valores corresponder aos valores representados pelo tipo primitivo, mais você deverá considerar encapsular os valores em uma classe ou tipo.

Kenny Evitt
fonte
5

Aparentemente, tudo o que você precisa fazer é identificar uma operação.

Além disso, você diz o que uma operação deve fazer:

para iniciar a operação [e] para monitorar seu final

Você diz isso de uma maneira como se fosse "exatamente como a identificação é usada", mas eu diria que essas são propriedades que descrevem uma operação. Isso soa como uma definição de tipo para mim, há até um padrão chamado "padrão de comando" que se encaixa muito bem. .

Diz

o padrão de comando [...] é usado para encapsular todas as informações necessárias para executar uma ação ou acionar um evento posteriormente .

Eu acho que isso é muito semelhante comparado ao que você quer fazer com suas operações. (Compare as frases que coloquei em negrito nas duas aspas). Em vez de retornar uma string, retorne um identificador para um Operationno sentido abstrato, um ponteiro para um objeto dessa classe no oop, por exemplo.

Em relação ao comentário

Seria wtf-y chamar essa classe de comando e depois não ter a lógica dentro dela.

Não, não seria. Lembre-se de que os padrões são muito abstratos , tão abstratos de fato que são um tanto meta. Ou seja, eles geralmente abstraem a própria programação e não algum conceito do mundo real. O padrão de comando é uma abstração de uma chamada de função. (ou método) Como se alguém apertasse o botão de pausa logo após passar os valores dos parâmetros e logo antes da execução, para retomar mais tarde.

A seguir, está considerando oop, mas a motivação por trás disso deve ser verdadeira para qualquer paradigma. Eu gosto de dar algumas razões pelas quais colocar a lógica no comando pode ser considerada uma coisa ruim.

  1. Se você tivesse toda essa lógica no comando, seria inchada e confusa. Você pensaria "oh, bem, toda essa funcionalidade deve estar em classes separadas". Você refatoraria a lógica do comando.
  2. Se você tivesse toda essa lógica no comando, seria difícil testar e atrapalhar durante o teste. "Puta merda, por que eu tenho que usar esse comando sem sentido? Eu só quero testar se essa função gera 1 ou não, não quero chamá-lo mais tarde, quero testá-lo agora!"
  3. Se você tivesse toda essa lógica no comando, não faria muito sentido relatar quando terminar. Se você pensa em uma função a ser executada de forma síncrona por padrão, não faz sentido ser notificado quando concluir a execução. Talvez você esteja iniciando outro encadeamento porque sua operação é realmente renderizar avatar no formato de cinema (pode precisar de encadeamento adicional no raspberry pi), talvez você precise buscar alguns dados de um servidor, ... seja o que for, se houver uma razão para relatando quando terminar, pode ser porque há alguma assíncrona (isso é uma palavra?) acontecendo. Eu acho que executar um thread ou entrar em contato com um servidor é uma lógica que não deve estar sob seu comando. Isso é um argumento meta e talvez controverso. Se você acha que não faz sentido, diga-me nos comentários.

Para recapitular: o padrão de comando permite agrupar a funcionalidade em um objeto, para ser executado posteriormente. Por uma questão de modularidade (a funcionalidade existe independentemente de ser executada via comando ou não), testabilidade (a funcionalidade deve ser testável sem comando) e todas as outras palavras que expressam essencialmente a demanda para escrever um bom código, você não colocaria a lógica real no comando.


Como os padrões são abstratos, pode ser difícil criar boas metáforas do mundo real. Aqui está uma tentativa:

"Ei, vovó, você poderia apertar o botão de gravação na TV às 12, para não perder os simpsons no canal 1?"

Minha avó não sabe o que acontece tecnicamente quando aperta o botão de gravação. A lógica está em outro lugar (na TV). E isso é uma coisa boa. A funcionalidade é encapsulada e as informações são ocultas do comando, é um usuário da API, não necessariamente parte da lógica ... oh caramba, estou balbuciando palavras de novo, é melhor terminar esta edição agora.

nulo
fonte
Você está correto, eu não pensei nisso assim. Mas o padrão de comando não é 100% adequado porque a lógica da operação está encapsulada em outra classe e não posso colocá-lo dentro do objeto que é o Comando. Seria wtf-y chamar essa classe de comando e depois não ter a lógica dentro dela.
Ziv
Isso faz muitosentido. Considere retornar uma operação, não um OperationIdentifier. Isso é semelhante ao C # TPL em que você retorna uma tarefa ou tarefa <T>. @ Ziv: Você diz "a lógica da operação está encapsulada em outra classe". Mas isso realmente significa que você também não pode fornecê-lo daqui?
Moby Disk
@ Ziv Eu imploro para diferir. Veja os motivos que adicionei à minha resposta e veja se eles fazem algum sentido para você. =)
null
5

Idéia por trás do agrupamento de tipos primitivos,

  • Para estabelecer um idioma específico do domínio
  • Limitar erros cometidos pelos usuários transmitindo valores incorretos

Obviamente, seria muito difícil e impraticável fazê-lo em qualquer lugar, mas é importante agrupar os tipos quando necessário,

Por exemplo, se você tiver a classe Order,

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

As propriedades importantes para procurar um Pedido são principalmente OrderId e InvoiceNumber. E o Valor e o Código da Moeda estão intimamente relacionados, onde se alguém alterar o Código da Moeda sem alterar o Valor, o Pedido não poderá mais ser considerado válido.

Portanto, apenas agrupar OrderId, InvoiceNumber e introduzir um composto para moeda faz sentido nesse cenário e provavelmente a descrição do agrupamento não faz sentido. Portanto, o resultado preferido pode parecer,

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

Portanto, não há razão para embrulhar tudo, mas coisas que realmente importam.

Pelicano voador baixo
fonte
4

Opinião impopular:

Você normalmente não deve definir um novo tipo!

Definir um novo tipo para quebrar uma classe primitiva ou básica é algumas vezes chamado de definição de um pseudo-tipo. A IBM descreve isso como uma má prática aqui (eles se concentram especificamente no uso indevido de genéricos nesse caso).

Os pseudo tipos tornam inútil a funcionalidade comum da biblioteca.

As funções matemáticas do Java podem funcionar com todas as primitivas numéricas. Mas se você definir uma nova classe Porcentagem (agrupar um dobro que pode estar no intervalo de 0 a 1), todas essas funções serão inúteis e precisarão ser agrupadas por classes que (ainda pior) precisam saber sobre a representação interna da classe Porcentagem .

Pseudo-tipos se tornam virais

Ao criar várias bibliotecas, você frequentemente descobrirá que esses pseudotipos se tornam virais. Se você usar a classe Porcentagem acima mencionada em uma biblioteca, será necessário convertê-la no limite da biblioteca (perdendo toda a segurança / significado / lógica / outro motivo que você tinha para criar esse tipo) ou terá que fazer essas classes acessível para a outra biblioteca também. Infectando a nova biblioteca com seus tipos, onde uma simples dupla pode ter sido suficiente.

Tirar mensagem

Desde que o tipo que você agrupe não precise de muita lógica de negócios, eu recomendaria não agrupá-lo em uma pseudo classe. Você só deve agrupar uma classe se houver sérias restrições de negócios. Em outros casos, nomear suas variáveis ​​corretamente deve contribuir bastante para transmitir significado.

Um exemplo:

A uintpode representar perfeitamente um UserId, podemos continuar usando operadores incorporados do Java para uints (como ==) e não precisamos de lógica de negócios para proteger o 'estado interno' do ID do usuário.

Roy T.
fonte
Aceita. toda a filosofia de programação é bom, mas na prática você tem que fazê-lo funcionar bem
Ewan
Se você já viu quaisquer anúncios de empréstimos para as pessoas mais pobres do Reino Unido, você vai encontrar essa percentagem para embrulhar um casal no período 0..1 não funciona ...
gnasher729
1
No C / C ++ / Objective C, você pode usar um typedef, que fornece apenas um novo nome para um tipo primitivo existente. Por outro lado, seu código deve funcionar, independentemente do tipo subjacente, por exemplo, se eu alterar OrderId de uint32_t para uint64_t (porque preciso processar mais de 4 bilhões de pedidos).
Gtp
Não vou te rebaixar por sua bravura, mas os pseudotipos e os tipos definidos na resposta mais alta são diferentes. Esses são tipos específicos de negócios. Psuedotypes são tipos que ainda são genéricos.
0fnt
@ gnasher729: O C ++ 11 também possui literais definidos pelo usuário que tornam o processo de empacotamento muito agradável para o usuário: Distância d = 36.0_mi + 42.0_km; . msdn.microsoft.com/pt-BR/library/dn919277.aspx
damix911
3

Como em todas as dicas, saber quando aplicar as regras é a habilidade. Se você criar seus próprios tipos em um idioma controlado por tipo, receberá a verificação de tipo. Então, geralmente, esse será um bom plano.

NamingConvention é a chave para a legibilidade. Os dois combinados podem transmitir intenção claramente.
Mas /// ainda é útil.

Então, sim, eu diria que crie muitos tipos próprios quando a vida deles estiver além de um limite de classe. Considere também o uso de ambos Structe Class, nem sempre, de Classe.

phil soady
fonte
2
O que ///significa isso ?
Gabe
1
/// é o comentário da documentação em C # que permite ao intellisense mostrar a documentação da assinatura no ponto do método de chamada. Considere a melhor maneira de documentar o código. Pode ser cultivado por ferramentas e oferecer comportamento de inteligência de alcance. msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
phil soady