web-dev-qa-db-fra.com

Comment attribuer une variable avec le résultat d'un bloc if..else?

J'ai discuté avec un collègue de la meilleure façon d'attribuer une variable dans un bloc if..else. Son code d'origine était:

@products = if params[:category]
  Category.find(params[:category]).products
else
  Product.all
end

Je l'ai réécrit de cette façon:

if params[:category]
  @products = Category.find(params[:category]).products
else
  @products = Product.all
end

Cela pourrait également être réécrit avec un one-line utilisant un opérateur de ternery (? :), mais supposons que l'affectation de produit soit plus longue que 100 caractères et ne puisse pas contenir une ligne.

Lequel des deux est le plus clair pour vous? La première solution prend un peu moins de place, mais je pensais que déclarer une variable et l’affecter à trois lignes après pouvait être plus sujet aux erreurs. J'aime aussi voir mon if et else alignés, il est plus facile pour mon cerveau de l'analyser!

31

Comme alternative à la syntaxe de réponse de badp , je voudrais proposer:

@products = 
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end

J'affirme que cela présente deux avantages:

  1. Indentation uniforme: chaque niveau d'imbrication logique est indenté de exactement deux espaces (OK, c'est peut-être une question de goût)
  2. Compacité horizontale: Un nom de variable plus long ne poussera pas le code en retrait au-delà de la marque de colonne 80 (ou autre)

Il faut une ligne de code supplémentaire, ce que je n’aimerais normalement pas, mais dans ce cas, il semble utile de remplacer le minimalisme vertical par le minimalisme horizontal.

Avertissement: C’est ma propre approche idiosyncratique et je ne sais pas dans quelle mesure elle est utilisée ailleurs dans la communauté Ruby.

Edit: Je devrais mentionner que la réponse de matsadler est également similaire à celle-ci. Je pense qu’avoir une certaine indentation est utile. J'espère que cela suffit pour justifier une réponse distincte.

40
antinome

En tant que programmeur Ruby, je trouve le premier plus clair. Cela indique clairement que l'expression entière est une assignation dont la chose assignée est déterminée en fonction d'une logique, et cela réduit les doubles emplois. Cela semblera étrange aux personnes qui ne sont pas habituées aux langues où tout est une expression, mais écrire votre code pour des personnes qui ne la connaissent pas n'est pas un objectif important de l'OMI, à moins que ce ne soit spécifiquement votre cible. Autrement, on devrait s’attendre à ce que les gens s’y familiarisent avec le temps.

Je suis également d'accord avec la suggestion de bp que vous puissiez la lire plus clairement en mettant en retrait l'intégralité de l'expression if, de sorte que tout se trouve visuellement à droite de la cession. C'est totalement esthétique, mais je pense que cela le rend plus facile à parcourir et devrait être plus clair, même pour une personne non familiarisée avec la langue.

Juste en passant: Cette sorte de if n'est pas unique en son genre à Ruby. Il existe dans tous les Lisps (Common LISP, Scheme, Clojure, etc.), Scala, tous les ML (F #, OCaml, SML), Haskell, Erlang et même le prédécesseur direct de Ruby, Smalltalk. Ce n'est tout simplement pas courant dans les langages basés sur C (C++, Java, C #, Objective-C), ce que la plupart des gens utilisent.

21
Chuck

encapsulation ...

@products = get_products

def get_products
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end
12
Derick Bailey

Juste une autre approche:

category = Category.find(params[:category]) if params[:category]
@products = category ? category.products : Product.all
6
Matías Flores

Une autre approche consisterait à utiliser un bloc pour conclure.

@products = begin
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

qui résout le problème de la cession. Bien que ce soit trop de lignes pour ce code "complexe". Cette approche serait utile dans le cas où nous voudrions initialiser la variable une seule fois:

@products ||= begin
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

C'est quelque chose que vous ne pouvez pas faire avec le code réécrit et il est correctement aligné.

5
Tombart
@products =
if params[:category]
  Category.find(params[:category]).products
else
  Product.all
end

Une autre option est d’éviter la répétition de @products et de maintenir la if alignée sur else.

2
matsadler

En supposant que vos modèles ressemblent à ceci:

class Category < ActiveRecord::Base
  has_many :products
end
class Product < ActiveRecord::Base
  belongs_to :category
end

vous pourriez faire quelque chose d'encore plus fou, comme ceci:

#assuming params[:category] is an id
@products = Product.all( params[:category] ? {:conditions => { :category_id => params[:category]}} : {})

Ou, vous pouvez utiliser la fonctionnalité sexy named_scope , paresseusement chargée:

class Product < ActiveRecord::Base
  ...

  #again assuming category_id exists
  named_scope :all_by_category, lambda do |cat_id|
    if cat_id
      {:conditions => {:category_id => cat_id}}
    end
  end

  #if params[:category] is a name, and there is a has and belongs to many
  named_scope :all_by_category, lambda do |cat_name|
    if cat_name
      {:joins => :categories, :conditions => ["categories.name = ?",cat_name]}
    end
  end
  ...
end

utilisé comme

@products = Product.all_by_category params[:category]
2
BaroqueBobcat

D'abord si vous utilisez ternaire, ensuite si ce n'est pas le cas. 

Le premier est presque impossible à lire.

1
Nate Noonen

Je ne suis pas non plus un Ruby, mais Alarm Bells sonne immédiatement pour indiquer la portée de la deuxième commande. Cette variable sera-t-elle même disponible après la fin de votre bloc si?

1
blissapp

Je dirais que cette deuxième version est plus lisible par les personnes non familiarisées avec cette structure dans Ruby Donc + pour ça! De l'autre côté, la première construction est plus sèche.

Comme je regarde un peu plus longtemps, je trouve la première solution plus attrayante. Je suis un programmeur Ruby, mais je ne l'ai pas utilisé plus tôt. Bien sûr, je vais commencer à!

1
klew

Je pense que le meilleur code est:

@products = Category.find(params[:category])&.products.presence || Product.all

Les "&" après la découverte garantissent que la méthode "produits" n'évaluera pas si la catégorie est nulle.

1
Julien 'JS'

Il me semble que le second serait plus lisible par le programmeur typique. Je ne suis pas un gars de Ruby, alors je ne savais pas que si/autre retourne une valeur ... Donc, pour me prendre pour exemple (et oui, c’est mon point de vue: D), le second semble bon choix.

0
joeslice

Je n'aime pas votre utilisation des parenthèses dans votre premier bloc. Oui, je suis un utilisateur de LISP, mais je crois que je me fais un devoir de dire que le premier pourrait sembler déroutant au milieu d'un autre code, peut-être autour d'autres blocs if.

Que diriez-vous...

@products = (if (params[:category])
        ((Category.find params[:category]).
            products)
    else
        (Product all)
    end
)

(^ La langue dans la joue, pour exacerber les problèmes avec la réponse de @ badp )

0
Slipp D. Thompson

Si vous parcourez le code, je dirais que le deuxième bloc de code (le vôtre) est certainement celui que je trouve le plus facile à comprendre rapidement. 

Le code de votre ami est correct, mais l'indentation, comme l'a souligné bp, fait toute la différence en ce sens.

0
Andres