É bom estilo retornar explicitamente em Ruby?


155

Vindo de um background em Python, onde sempre existe uma "maneira certa de fazer" (uma maneira "Pythonic") quando se trata de estilo, estou me perguntando se o mesmo existe para Ruby. Eu tenho usado minhas próprias diretrizes de estilo, mas estou pensando em liberar meu código-fonte e gostaria que ele seguisse as regras não escritas que possam existir.

É "The Ruby Way" digitar explicitamente returnmétodos? Eu já vi isso com e sem, mas existe uma maneira certa de fazê-lo? Existe talvez um momento certo para fazê-lo? Por exemplo:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end

3
Essa é uma pergunta muito antiga, mas para aqueles que têm perguntas semelhantes, o livro Eloquent Ruby é altamente recomendado. Não é um guia de estilo, mas uma introdução à escrita Ruby idiomática. Eu gostaria que mais pessoas o leiam!
Brian Dear

Como alguém que herdou um grande aplicativo herdado que foi desenvolvido em Ruby, eu aprecio muito o fato de você ter feito essa pergunta. Isso merece um voto positivo. Isso está espalhado por todo o nosso aplicativo herdado. Um dos meus trabalhos nesta equipe é melhorar a base de código (difícil quando é novo no Ruby).
Stevers 16/03/19

Respostas:


226

Pergunta antiga (e "respondida"), mas colocarei meus dois centavos como resposta.

TL; DR - Você não precisa, mas pode tornar seu código muito mais claro em alguns casos.

Embora não usar um retorno explícito possa ser "o caminho do Ruby", é confuso para programadores que trabalham com código desconhecido ou não familiarizado com esse recurso do Ruby.

É um exemplo um tanto artificial, mas imagine ter uma pequena função como essa, que adiciona uma ao número passado e a atribui a uma variável de instância.

def plus_one_to_y(x)
    @y = x + 1
end

Isso deveria ser uma função que retornou um valor ou não? É realmente difícil dizer o que o desenvolvedor quis dizer, pois atribui a variável de instância E retorna o valor atribuído também.

Suponha que, muito mais tarde, outro programador (talvez não familiarizado com o modo como o Ruby retorna com base na última linha de código executada) apareça e deseje colocar algumas instruções de impressão para registro, e a função se torne essa ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
end

Agora a função está quebrada se algo espera um valor retornado . Se nada espera um valor retornado, tudo bem. Claramente, se em algum lugar mais abaixo da cadeia de código, algo chamando isso de espera de um valor retornado, ele falhará, pois não receberá de volta o que espera.

A verdadeira questão agora é esta: algo realmente esperava um valor retornado? Isso quebrou alguma coisa ou não? Será que vai quebrar algo no futuro? Quem sabe! Somente uma revisão completa do código de todas as chamadas informará você.

Portanto, para mim, pelo menos, a abordagem da melhor prática é ser muito explícito que você está retornando algo, se for o caso, ou não devolve nada quando isso não acontece.

Portanto, no caso de nossa pequena função de demonstração, supondo que desejássemos retornar um valor, seria escrito assim ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    return @y
end

E seria muito claro para qualquer programador que ele retornasse um valor, e muito mais difícil para eles quebrá-lo sem perceber.

Como alternativa, pode-se escrever assim e deixar de fora a declaração de retorno ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    @y
end

Mas por que se preocupar em deixar de fora a palavra retorno? Por que não colocá-lo lá e deixar 100% claro o que está acontecendo? Literalmente, não terá impacto na capacidade de execução do seu código.


6
Ponto interessante. Eu acho que realmente tive uma situação semelhante quando fiz essa pergunta, onde o desenvolvedor original usava o valor implícito de retorno em seu código, e era muito difícil entender o que estava acontecendo. Obrigado pela sua resposta!
Sasha Chedygov 30/11/2012

6
Eu encontrei a pergunta enquanto pesquisava no Google sobre retornos implícitos, pois acabara de me queimar com isso. Eu adicionei alguma lógica ao final de uma função que estava implicitamente retornando algo. A maioria das chamadas para ele não se importava (ou verificava) o valor retornado, mas uma era - e falhou. Felizmente, pelo menos, apareceu em alguns testes funcionais.
Tim Holt

16
Embora seja verdade, é importante que o código seja legível, evitando um recurso conhecido de uma linguagem de programação em favor da verbosidade é, na minha opinião, uma prática ruim. Se um desenvolvedor não estiver familiarizado com Ruby, talvez ele deva se familiarizar antes de tocar no código. Acho um pouco tolo ter que aumentar a bagunça do meu código apenas para o evento futuro em que algum desenvolvedor que não seja Ruby precise fazer algo mais tarde. Não devemos reduzir um idioma ao menor denominador comum. Parte da beleza de Ruby é que não precisamos usar 5 linhas de código para dizer algo que deve levar apenas 3.
Brian Dear

25
Se a palavra returnadiciona significado semântico ao código, por que não? Dificilmente é muito detalhado - não estamos falando de 5 linhas x 3, apenas mais uma palavra. Eu preferiria ver returnpelo menos em funções (talvez não em blocos) para expressar o que o código realmente faz. Às vezes, você precisa retornar a função intermediária - não deixe de fora a última returnpalavra-chave na última linha apenas porque pode. Não sou a favor da verbosidade, mas sou a favor da consistência.
mindplay.dk

6
Esta é uma ótima resposta. Mas ainda deixa o risco de você escrever um método Ruby com a intenção de ser um procedimento (também conhecido como unidade de retorno de função), por exemplo, side_effect()e outro desenvolvedor decidir (ab) usar o valor implícito de retorno do seu procedimento. Para corrigir isso, você pode fazer seus procedimentos explicitamente return nil.
Alex Dean

66

Não. O bom estilo Ruby geralmente usaria apenas retornos explícitos para um retorno antecipado . Ruby é especialista em minimalismo de código / magia implícita.

Dito isto, se um retorno explícito tornar as coisas mais claras ou mais fáceis de ler, não prejudicará nada.


116
Qual é o objetivo do minimalismo de código se resultar em maximalismo de confusão?
Jononomo

@ JonCrowell Em vez disso, deve resultar em maximalismo na documentação.
jazzpi

29
@jazzpi Se você precisa documentar fortemente seu código para que seja bem compreendido, você não pratica minimalismo, pratica golfe com código .
Schwern

2
Não é confuso excluir o retorno final, é confuso INCLUÍ-LO - como desenvolvedor Ruby, returnjá tem um significado semântico significativo como uma SAÍDA ANTECIPADA da função.
Devon Parsons

14
@DevonParsons Como diabos alguém poderia confundir um returnna última linha como uma saída precoce de qualquer maneira?
DarthPaghius

31

Pessoalmente, uso a returnpalavra-chave para distinguir entre o que chamo de métodos funcionais , ou seja, métodos executados principalmente por seu valor de retorno e métodos processuais executados principalmente por seus efeitos colaterais. Portanto, métodos nos quais o valor de retorno é importante, obtenha uma returnpalavra-chave extra para chamar a atenção para o valor de retorno.

Uso a mesma distinção ao chamar métodos: métodos funcionais obtêm parênteses, métodos processuais não.

E por último, mas não menos importante, também uso essa distinção com blocos: blocos funcionais recebem chaves, blocos de procedimentos (ou seja, blocos que "fazem" alguma coisa) recebem do/ end.

No entanto, tento não ser religioso: com blocos, chaves e do/ / endtem precedência diferente e, em vez de adicionar parênteses explícitos para desambiguar uma expressão, apenas mudo para o outro estilo. O mesmo vale para a chamada de método: se adicionar parênteses em torno da lista de parâmetros torna o código mais legível, eu o faço, mesmo que o método em questão seja de natureza processual.


1
No momento, omito um retorno em qualquer "one-liners", que é semelhante ao que você está fazendo, e faço tudo o mesmo. É bom saber que não estou completamente de fora do meu estilo. :)
Sasha Chedygov

@Jorg: Você usa retornos antecipados no seu código? Isso causa alguma confusão com seu esquema processual / funcional?
Andrew Grimm

1
@ Andrew Grimm: Sim e sim. Estou pensando em invertê-lo. A grande maioria dos meus métodos é referencialmente transparente / pura / funcional / como você quiser chamá-lo de qualquer maneira, por isso faz sentido usar a forma mais curta lá. E métodos escritos em estilo funcional tendem a não ter retornos precoces, pois isso implica a noção de "etapa", que não é muito funcional. Embora eu não goste de retornos no meio. Eu os uso no início como guardas ou no final para simplificar o fluxo de controle.
Jörg W Mittag

Grande resposta, mas é realmente melhor prática para chamar métodos processuais com () e métodos funcionais sem - veja minha resposta abaixo para o porquê
Alex Dean

13

Na verdade, o importante é distinguir entre:

  1. Funções - métodos executados pelo valor de retorno
  2. Procedimentos - métodos executados por seus efeitos colaterais

O Ruby não tem uma maneira nativa de diferenciá-los - o que o deixa vulnerável a escrever um procedimento side_effect()e outro desenvolvedor que decide abusar do valor implícito de retorno do seu procedimento (basicamente tratando-o como uma função impura).

Para resolver isso, retire uma folha do livro de Scala e Haskell e faça com que seus procedimentos retornem explicitamente nil(aka Unitou ()em outros idiomas).

Se você seguir isso, o uso de returnsintaxe explícita ou não se tornará apenas uma questão de estilo pessoal.

Para distinguir ainda mais entre funções e procedimentos:

  1. Copie a boa idéia de Jörg W Mittag de escrever blocos funcionais com chaves, e blocos procedurais com do/end
  2. Ao invocar procedimentos, use (), enquanto que ao invocar funções, não

Observe que Jörg W Mittag realmente defendeu o contrário - evitando ()s para procedimentos -, mas isso não é aconselhável, porque você deseja que as invocações de métodos com efeito colateral sejam claramente distinguíveis das variáveis, principalmente quando a aridade é 0. Consulte o guia de estilo Scala sobre invocação de métodos para detalhes.


Se eu precisar do valor retornado de function_ae function_afoi gravado para chamar (e só pode operar chamando) procedure_b, então é function_arealmente uma função? Ele retorna um valor, cumprindo seus requisitos de funções, mas tem um efeito colateral, cumprindo os requisitos de procedimentos.
Devon Parsons

2
Você está certo, Devon - function_apoderia conter uma árvore de procedure_...chamadas, como de fato procedure_apoderia conter uma árvore de function_...chamadas. Como Scala, mas diferentemente de Haskell, não há garantia no Ruby de que uma função não tenha efeito colateral.
Alex Dean

8

O guia de estilo declara que você não deve usar returnsua última declaração. Você ainda pode usá-lo se não for o último . Essa é uma das convenções que a comunidade segue estritamente, e você também deve fazer isso se planejar colaborar com alguém que usa Ruby.


Dito isto, o principal argumento para o uso de returns explícito é que é confuso para pessoas vindas de outras línguas.

  • Em primeiro lugar, isso não é totalmente exclusivo para Ruby. Por exemplo, Perl também possui retornos implícitos.
  • Em segundo lugar, a maioria das pessoas a quem isso se aplica é proveniente de idiomas algol. A maioria deles é "nível mais baixo" que o Ruby, portanto, você precisa escrever mais código para fazer alguma coisa.

Uma heurística comum para o comprimento do método (excluindo getters / setters) em java é uma tela . Nesse caso, você pode não estar vendo a definição do método e / ou já se esqueceu de onde estava retornando.

Por outro lado, no Ruby é melhor seguir métodos com menos de 10 linhas . Dado isso, alguém poderia se perguntar por que ele tem que escrever ~ 10% mais declarações, quando elas estão claramente implícitas.


Como Ruby não possui métodos nulos e tudo é muito mais conciso, você estará adicionando sobrecarga para nenhum dos benefícios se usar returns explícitos .


1
"Esta é uma das convenções que a comunidade segue estritamente" Minha experiência é que não, as pessoas não seguem isso estritamente, a menos que sejam desenvolvedores Ruby muito experientes.
Tim Holt

1
@ TimHolt Eu devo ter tido muita sorte então. (:
ndnenkov 4/16

1
@ndn concordo completamente. O importante é que, quando um método (ou classe) começa a exceder 5/100 linhas, é uma boa ideia reconsiderar o que você está tentando realizar. Realmente, as regras de Sandi são uma estrutura cognitiva para pensar sobre código em oposição a um dogma arbitrário. A maioria do código que encontro não é problemática porque é artificialmente restrita - geralmente é o oposto - classes com múltiplas responsabilidades, métodos que são mais longos que muitas classes. Essencialmente, muitas pessoas "pulam no tubarão" - misturando responsabilidades.
Brian Dear

1
Às vezes, a convenção ainda é uma má ideia. É uma convenção estilística que depende de magia, dificulta a adivinhação dos tipos de retorno. não oferece outra vantagem além de salvar 7 pressionamentos de teclas e é incongruente com quase todos os outros idiomas. Se Ruby alguma vez aprovar uma consolidação e simplificação no estilo Python 3, espero que implícito que qualquer coisa seja a primeira no momento.
precisa

2
Exceto claramente, isso não facilita a leitura das coisas! Magia e compreensibilidade não são a mesma coisa.
Shayne

4

Eu concordo com Ben Hughes e discordo de Tim Holt, porque a pergunta menciona a maneira definitiva como o Python faz e pergunta se Ruby tem um padrão semelhante.

Faz.

Esse é um recurso tão conhecido da linguagem que qualquer pessoa que depure um problema no ruby ​​deve razoavelmente saber disso.


3
Eu concordo com você na teoria, mas na prática, os programadores cometem erros e bagunçam. Caso em questão, todos sabemos que você usa "==" de forma condicional e não "=", mas TODOS já cometemos esse erro antes e não o percebemos até mais tarde.
Tim Holt

1
@ TimHolt Não estou abordando nenhum caso de uso específico, apenas estou respondendo à pergunta. É explicitamente mau estilo, conforme escolhido pela comunidade, usar a returnpalavra - chave quando desnecessária.
Devon Parsons

3
Justo. Estou dizendo que o estilo mencionado pode levar a erros se você não for cuidadoso. Com base na minha experiência, eu pessoalmente defendo um estilo diferente.
Tim Holt

1
@ TimHolt A propósito, essa pergunta foi feita muito antes da redação do guia de estilo; portanto, as respostas que discordam não são necessariamente erradas, apenas desatualizadas. Eu tropecei aqui de alguma forma e não vi o link que forneci, então achei que alguém poderia acabar aqui também.
Devon Parsons

1
Não é um "padrão" oficial. O guia de estilo ruby ​​usado pelo rubocop e vinculado acima com a reivindicação que existe por Devon Parsons, é criado por um hacker ruby ​​não essencial. Portanto, o comentário de Devon é simplesmente errado. Claro que você ainda pode usá-lo, mas não é um padrão.
shevy

1

É uma questão de qual estilo você prefere na maior parte. Você precisa usar a palavra-chave return se desejar retornar de algum lugar no meio de um método.


0

Como Ben disse. O fato de 'o valor de retorno de um método ruby ​​ser o valor de retorno da última instrução no corpo da função' faz com que a palavra-chave return raramente seja usada na maioria dos métodos ruby.

def some_func_which_returns_a_list( x, y, z)
  return nil if failed_some_early_check


  # function code 

  @list     # returns the list
end

4
Você também pode escrever "resultado nulo se failed_some_early_check" se a sua intenção é ainda claro
Mike Woodhouse

@ Gishu Na verdade, eu estava reclamando da instrução if, não do nome do método.
Andrew Grimm

@ Andrew .. oh o fim faltando :). Entendi. corrigido para pacificar o outro comentário também de Mike.
Gishu 11/08/19

Por que não if failed_check then nil else @list end? Isso é realmente funcional .
cdunn2001

@ cdunn2001 Não está claro por que motivo, se então o item está funcionando .. o motivo desse estilo é que ele reduz o aninhamento por saídas antecipadas. IMHO também mais legível.
Gishu
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.