Uso do pretérito (voz passiva) para conotar imutabilidade? (Por exemplo, Array.Transposed vs. Array.Transpose)


8

[Nota: O abaixo é um pouco pesado em C # em sua sintaxe e convenções, como citar regras FxCop, porque esse é o meu histórico, mas o objetivo desta discussão é ser independente de idioma e os exemplos de código usados ​​geralmente devem ser considerados como pseudo-código. - Mike]

Acho que todos concordamos que as classes geralmente devem ser projetadas para que suas instâncias de objetos sejam imutáveis ​​sempre que possível, e os resultados de chamar membros da classe devem estar livres de efeitos colaterais, tanto quanto possível.

As convenções de nomenclatura histórica, no entanto, tendem a usar a voz ativa: 'GetSomething', 'DoSomehing' etc. etc., e isso é bom porque indica claramente que ação o aluno deve executar.

Agora estou começando a pensar, no entanto, que os membros às vezes (ou possivelmente com freqüência) se beneficiam sendo nomeados no pretérito para conotar que um valor é retornado sem afetar o objeto referenciado.

Por exemplo, para um método que transpõe uma matriz, os estilos de nomenclatura atuais provavelmente o chamariam 'Array.Transpose', mas se esse membro retornar uma nova matriz, sem transpor o objeto referenciado, acho que chamando de 'Array.Transposed 'seria mais preciso e tornaria mais claro para o programador.

Ao trabalhar com um método 'Array.Transpose ()', pode-se tentar acidentalmente usar código como o seguinte:

Array myArray = new Array(); 
myArray.Transpose();

Mas se o método 'Transpose' retornar uma nova matriz sem afetar o objeto referenciado, esse código não fará nada - ou seja, 'myArray' silenciosamente não seria transposto. (Suponho que, se 'Transpose' fosse uma propriedade, o compilador perceberia que o acima não é legal, mas as diretrizes da FxCop determinam que um membro que realiza uma conversão deve ser um método.)

Mas se o método fosse nomeado no passado como 'Array.Transposed', seria mais claro para o usuário que a seguinte sintaxe é necessária:

Array myArray = new Array(); 
Array transposedArray = myArray.Transposed();

Outros nomes que eu acho que se encaixariam nessa categoria seriam os membros denominados 'Redimensionado' vs. 'Redimensionar', 'Deslocado' vs. 'Deslocamento' etc. O tempo presente seria usado para membros que afetassem o objeto referenciado, enquanto o o pretérito seria usado para membros que retornassem um novo valor sem afetar o objeto referenciado.

Outra abordagem que é mais comum atualmente é prefixar cada nome com "Get" ou "To", mas acho que isso adiciona peso desnecessário, enquanto a alteração no pretérito comunica o significado de maneira eficaz.

Isso faz sentido para outros programadores aqui, ou essa ideia de pretérito soa estranho demais ao lê-la?

Editar:

Ótimas discussões abaixo, agradeço muito a todos. Gostaria de resumir alguns pontos com base nos seus comentários e no meu próprio pensamento revisado a seguir.

Método de encadeamento de sintaxes "fluentes"

Com base nas discussões abaixo, o uso do tempo passado é provavelmente de algum valor quando usado em uma classe mutável, a fim de deixar claro quais membros afetam, ou não, o estado interno do objeto referenciado.

No entanto, para classes imutáveis, que deveriam ser a maioria dos casos (seria de esperar), acho que o uso do tempo passado pode tornar a sintaxe desnecessariamente estranha.

Por exemplo, com "sintaxes fluentes", como as criadas pelo encadeamento de métodos, o uso do tempo passado tornaria o código menos legível. Por exemplo, ao usar o LINQ via C #, uma cadeia de métodos típica pode ter a seguinte aparência:

dataSource.Sort().Take(10).Select(x => x.ToString);

Se alterado para o passado, isso se tornaria estranho:

dataSource.Sorted().Taken(10).Selected(x => x.ToString);

Lembre-se de que dataSource.Sorted () tem um significado mais claro do que dataSource.Sort (), porque as expressões LINQ sempre criam uma nova enumeração sem afetar a fonte. No entanto, como estamos plenamente conscientes disso ao usar esse paradigma, o uso do tempo passado é redundante e torna a "fluência" de lê-lo desajeitada, sem ganho real.

Uso de propriedades vs. métodos

Um problema importante abordado abaixo diz respeito às diretrizes do FxCop para o uso dos métodos "Get" versus propriedades somente leitura. Por exemplo, regra FxCop CA1024 .

Embora não mencionado explicitamente nessa regra do FxCop, o uso dos métodos "To" também parece se aplicar a esse raciocínio, pois os métodos ToXXXXXX () são métodos de conversão. E no caso de ToArray (), o método é um método de conversão e é um método que retorna uma matriz.

Uso do pretérito para propriedades booleanas

Alguns sugeriram que o tempo passado pode conotar um valor booleano, por exemplo, uma propriedade 'Succeeded' ou 'Connected'. Acredito que as réplicas abaixo enfatizando o uso de um 'É', 'Tem' ou 'É' para propriedades e campos booleanos estão corretas. O pretérito também pode ajudar nesse sentido, mas não tanto quanto usar o prefixo 'É'. Sinta-se à vontade para usar o tempo passado para propriedades e campos booleanos, isso ajuda, mas acho que é ainda mais importante prefixar o nome com 'Is', 'Has' ou 'Are'.

Conclusões Gerais?

Para a questão de Tranpose () vs. Transposed (), acho que 'skizzy' acertou, sugerindo que deveria ser Transpose () para uma ação mutante vs. GetTransposed () para um resultado não mutante. Isso torna 100% claro. Mas, novamente, acho que isso pode se aplicar apenas a uma classe mutável, como uma matriz.

Para classes imutáveis, especialmente onde uma sintaxe fluente de encadeamento de métodos pode ser usada, acho que essa sintaxe se tornaria desnecessariamente estranha. Por exemplo, compare:

var v = myObject.Transpose().Resize(3,5).Shift(2);

para:

var v = myObject.GetTransposed().GetResized(3,5).GetShifted(2);

Hmmm ... na verdade, mesmo aqui parece deixar 100% claro que o resultado não afeta a fonte, mas não tenho certeza.

Eu acho que para um paradigma como o LINQ, onde é bem conhecido, o uso de "Get" e / ou o tempo passado não é necessário, mas para uma nova API, ela pode ter algum valor - a princípio! Se a API contiver apenas objetos imutáveis, uma vez que o usuário entenda isso, o uso de "Get" e / ou o tempo passado apenas reduzirão a legibilidade.

Então, acho que não há respostas fáceis aqui. Acho que é preciso pensar em toda a sua API e escolher com cuidado!

-- Mike


Espero que você simplesmente esquecer os parênteses para invocação de método (ou aqueles são opcionais na língua os exemplos estão em, embora isso seja raro) e você não tinha a intenção estes ser propriedades ...

Oi delnan, sim, é uma propriedade e é intensional. Por ser uma propriedade, fica ainda mais claro que a propriedade 'Transpose' (ou 'Transposed') retorna um valor e não deve ter um efeito colateral. De fato, o compilador deve captar o uso incorreto do código acima, mas a idéia aqui é tentar ser ainda mais claro para o usuário - antes que o compilador o compreenda.
precisa

Eu acho que é uma péssima idéia usar propriedades para tudo, como criar um objeto totalmente novo. E para fazer o backup, o MSDN diz que um método é preferível a uma propriedade se "O método executa uma operação demorada. O método é notavelmente mais lento do que o tempo necessário para definir ou obter o valor de um campo".

Fonte agradável, muito obrigado por isso. Portanto, meu exemplo 'Transposto' pode ter sido ideal, pois ele tropeça na regra CA1024 do FxCop porque esse membro (a) realiza uma conversão e (b) retorna uma matriz. Dito isto, a discussão sobre o presente versus o pretérito ainda se aplica, mas definitivamente vou usar os métodos ToXXXXX () ou GetXXXX () em vez de propriedades ao retornar um resultado de conversão ou matriz!
Mike Rosenblum

Eu acho que você quer dizer que objetos devem ser imutáveis, não classes. As aulas geralmente são sempre imutáveis, pelo menos em idiomas estaticamente tipados.
Nemanja Trifunovic

Respostas:


7

O tempo passado parece meio estranho. Eu acho que você pretendia que a palavra fosse um adjetivo.

Aqui está minha sugestão para o problema que você pediu: em vez de Transpose()chamá-lo GetTransposed(). Dessa forma, você sabe que está recebendo algo e não está modificando o objeto.


1
Eu acho que, no final, essa é realmente a resposta "certa". Não vou marcá-lo como "correto" porque (1) alguns outros, particularmente 'qes' e 'Developer Art', fizeram alguns comentários realmente bem pensados ​​e (2) eu realmente não acho que exista um "certo" responda a isso - essa é mais uma pergunta do "Community Wiki". (Does p.se têm tal conceito um?)
Mike Rosenblum

1
+1 resposta concisa agradável! Dessa maneira, Transpose()ainda pode ser interpretado como o ajuste do objeto especificado. Talvez isso deva ser uma construção da linguagem. :) object.Transpose()vs object:Transpose().
91111 Steven Jeuris

Eu prefiro AsTransposeddo que GetTransposednos casos em que chamadas repetidas consecutivas, sem efeitos colaterais, produzem resultados semanticamente idênticos (que podem ou não ser o mesmo objeto, mas não podem ser mostrados como objetos diferentes sem usar coisas como ReferenceEqualsou equivalente) .
Supercat

6

Isso realmente faz sentido.

O que você está falando, na minha opinião, deve ser tomado como regra geral ao projetar uma API ou uma biblioteca. Somente então ele ofereceria um benefício tangível se o sistema for consistente em todas as classes.

Outra questão é que, em muitos casos, os usuários das interfaces não se importam com a maneira como o resultado é alcançado.

Transpose () transporá o objeto atual? Transpose () criará uma nova instância transposta e retornará a referência a ela? Ou talvez transponha uma vez, armazene em cache os resultados em uma cópia e retorne-a nas chamadas subseqüentes sem afetar o objeto atual?

Os usuários geralmente querem apenas obter o resultado transposto, e é por isso que muitos não se importam com a convenção de nomenclatura que você está propondo.

Mas, por si só, é uma proposta válida.


4

Eu discordo fortemente. O tempo passado é estranho e não é comumente usado (o que já é razão suficiente para não preferir).

Se o método modificou o objeto em que foi chamado, ele não deve retornar sozinho - isso deixa claro que modifica o objeto.

Da mesma forma, se o método não modificar o objeto em que foi chamado e construir um novo objeto, o método retornará um objeto do mesmo tipo. Esta é uma convenção clara e já comum.

A assinatura do método é, na minha opinião, uma convenção mais clara e muito mais usada para indicar se o método retorna um novo objeto ou modifica o chamado.


Oi, sim, sim, a abordagem do tempo passado não é comumente usada, e foi por isso que fiz a pergunta aqui. (Eu acho que faz sentido, mas o precedente conta muito.) Seu ponto de vista sobre a assinatura do método é bom, mas, infelizmente, é extremamente sutil, porque os compiladores geralmente não impõem que o resultado de um método seja usado. . Portanto, um compilador não pode pegar o erro de chamar myObject.Resize () e não usar o valor de retorno. Minha proposta é que nomear o método "Redimensionado" tornaria mais claro para o programador que result = myObject.Resized () é necessário.
25811 Mike Rosenblum

1
Os compiladores certamente podem avisar que o resultado de um método não é usado, o que é mais do que eles podem fazer por qualquer comportamento baseado no tempo de um verbo no idioma inglês usado em um identificador, portanto esse é um ponto inválido. Estou afirmando que nomear no passado não tornará mais claro porque: 1. Não é uma convenção estabelecida. 2. Existem outras convenções mais estabelecidas. 3. Haverá muitas palavras que serão excepcionalmente desajeitadas ou totalmente inválidas no passado, enquanto as assinaturas de métodos não sofrerão essa confusão.
você

Certamente os compiladores podem alertar para isso, mas geralmente o fazem apenas por propriedades. Os valores de retorno dos métodos que não são usados ​​geralmente são ignorados por quase todos os compiladores nas configurações padrão.
Mike Rosenblum

Dito isso, quanto mais eu penso nos seus comentários e na regra CA1024 do FxCop (msdn.microsoft.com/en-us/library/ms182181(VS.80).aspx), meus exemplos são todas situações em que uma conversão está ocorrendo e / ou onde uma matriz está sendo retornada. O resultado é que eles devem usar uma nomeação de método GetXXXXX () ou ToXXXXX (), e acho que isso está correto. (Especialmente porque é a convenção estabelecida.) Eu acho que nomear o método no tempo passado, como em "GetTransposed ()", ainda é útil aqui, mas apenas marginalmente.
Mike Rosenblum

3

Parece razoável e existem precedentes - o Python possui ambos list.sort()e sorted(seq). O primeiro é um método, o outro é uma função que atua em qualquer iterável e retorna uma lista.


list.sort () retorna alguma coisa?
você

Não, não faz.
precisa saber é o seguinte

2

Na biblioteca de coleções Scala, existem alguns métodos no passado:

  • groupedretorna um iterador que itera sobre os elementos de uma sequência em partes de nelementos
  • sorted retorna uma versão classificada da coleção
  • updated retorna uma nova versão da coleção com um elemento alterado

No entanto, não tenho certeza se essa foi uma decisão consciente ou se eles simplesmente ficaram sem nomes, porque a biblioteca de coleções do Scala é muito rica. Por exemplo, há também groupBye sortBymétodos, e o updatemétodo é especial, porque o compilador reescreve atribuição

foo(bar) = baz

para dentro

foo.update(bar, baz)

Versões mais antigas do Scala de fato fez uso updatepara coleções imutáveis, mas ter updateretorno uma nova versão em vez de mutação é muito estranho, porque obviamente você tem que atribuir o valor de retorno para alguma coisa, então por exemplo, "atualizar" uma entrada em um mapa imutável seria algo gostar

val newMap = oldMap(key) = value // HUH ??!!??

Agora é

val newMap = oldMap.updated(key -> value)

Em uma linguagem com um bom sistema de tipos, os tipos geralmente informam se a rotina muda ou não seus argumentos. Em Haskell, por exemplo, o tipo da versão imutável seria algo como

transpose :: Array -> Array

enquanto uma versão mutável seria como

transpose :: State Array

1

Quando vejo um método / propriedade no tempo passado, imediatamente presumo que ele retorne um valor booleano. Sua proposta pode fazer sentido logicamente, mas parece estranha e estranha. Se eu tiver que explicar a alguém por que faz sentido, provavelmente não é uma boa convenção.


2
É por isso que é uma boa convenção prefixar valores booleanos com "is". ; p Além disso, a razão pela qual ele explica isso é porque não é uma convenção. Algumas respostas a esta pergunta dependem muito das convenções existentes. Você precisa se afastar e julgá-lo sem uma convenção conhecida.
91111 Steven Jeuris

0

Eu não sou um grande fã de nomes de variáveis ​​no tempo passado para imutabilidade - é uma convenção um tanto incomum e acho que causaria confusão, especialmente para aqueles indivíduos que não são particularmente fortes na gramática inglesa (e estou apenas brincando) há...)

Além disso, essas convenções eliminam a oportunidade de usar o pretérito para denotar outra coisa que eu acho mais importante: uma descrição do estado do objeto descrito, por exemplo, "itens processados" informa claramente que os itens de uma coleção já foram processado, que é uma informação lógica importante a ser transmitida.

Uma ideia melhor: tornar tudo imutável . Então você não precisa de nenhuma distinção lingüística sofisticada. Você sabe que você quer :-)


1
É por isso que é uma boa convenção prefixar valores booleanos com "is" e "has".
91111 Steven Jeuris
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.