Quelques règles de programmation
Dans PSDK, il y a certaines règles de programmation à suivre. Nous utilisons rubocop
mais avec quelques options spéciales (car certaines règles sont ennuyeuses et ne correspondent pas à l'usage de PSDK). Vous pouvez trouver la configuration de rubocop ici : .rubocop.yml
rubocop
n'était' pas du tout utilisé, nous n'avons pas essayé de passer les vieux scripts à la norme rubocop
. Nous modifions l'aspect de programmation lorsque nous avons besoin de modifier un script.
Les itérateurs
Dans PSDK nous décourageons qui que ce soit d'utiliser les boucles for
. Les boucles Ruby for
sont créées juste pour appeler .each
sans l'écrire explicitement. A la place vous pouvez utiliser :
number.times
si vous avez besoin d'itérer de 0 ànumber - 1
.number.upto(final)
si vous avez besoin de boucler denumber
àfinal
(inclus, incrémenté de + 1).number.downto(final)
si vous avez besoin de boucler denumber
àfinal
(inclus, décrémenté de - 1).number.step(step_size, final)
si vous avez besoin de boucler denumber
àfinal
avec un pas de step_size (final
n'est pas toujours la dernière valeur itérée).collection.each
si vous avez besoin d'itérer chaque objet d'une collection ou d'un array par exemple.array.each_with_index
si vous avez besoin d'itérer chaque objet d'un array et d'obtenir leur index.array.index
si vous avez besoin de trouver l'index d'un élément (qui répond à des critères) dans un Array.enumerable.find
si vous avez besoin de trouver une valeur spécifique (correspondant aux critères) dans un Array ou un Enumerator.enumerable.any?
Si vous avez besoin de savoir si quelque chose dans un Array/Enumerator répond aux critères.
ℹNe pas donner de block permet de vérifier qu'un Array n'est pas vide!enumerable.all?
si vous avez besoin de savoir si tous les éléments d'un Array/Enumarator correspondent aux critères.enumerable.none?
si vous avez besoin de savoir si aucun des éléments d'un Array/Enumarator ne correspond aux critères.enumerable.select
si vous avez besoin de sélectionner les éléments qui correspondent aux critères.array.select!
si vous avez besoin de sélectionner les éléments qui correspondent aux critères et n'y garder que ces éléments.enumerable.reject
si vous avez besoin de sélectionner les éléments qui ne correspondent pas aux critères.array.reject!
si vous avez besoin de sélectionner les éléments qui ne correspondent pas aux critères et n'y garder que ces éléments.enumerable.collect
ouenumerable.map
si vous avez besoin de transformer un Enumator en Array où la valeur est transformée par le block.
Exemple :ruby ten_to_nineteen = 10.times.collect { |i| i + 10 } # => [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] hex_digits = 10.upto(15).collect { |i| i.to_s(16).upcase } # => ["A", "B", "C", "D", "E", "F"]
array.collect!
ouarray.map!
pour transformer tous les éléments d'un Array en utilisant un block.
Ici, l'utilisation d'itérations à la place d'une boucle for
:
def find_height(img) first_y = img.height.times.find do |y| img.width.times.any? { |x| img.get_pixel_alpha(x, y) != 0 } end return img.height - first_y.to_i end
Ici, la version for :
def find_height(img) first_y = nil for y in 0...img.height for x in 0...img.width break first_y = y if img.get_pixel_alpha(x, y) != 0 end break if first_y end return img.height - first_y.to_i end
Les valeurs nils
Dans Ruby les variables d'instances et les variables globales contiennent nil
Si elles ne sont pas assignées. Cela cause souvent des problèmes comme NoMethodError (undefined method 'meth' for nil:NilClass)
pour éviter cela, il y a d'autres façons de les utiliser.
Assignement des valeurs par défaut
Pour assigner une valeur par défaut à une variable (quand la valeur n'est pas censée être nil
et que nous ne sommes pas dans #initialize
) vous avez deux solutions legit :
@var = value if @var.nil?
ou
@var = value unless @var
La première n'est pas optimale si la variable n'est pas supposée être un booléen (Ruby appelera nil?
avant d'appliquer la valeur). La seconde est plus performante mais n'est pas recommandée par rubocop
.
Si vous avez besoin d'assigner une valeur à une variable contenant nil, utilisez l'opérateur ||=
:
@var ||= value
unless @var
, c'est en effet plus rapide que ||= mais vous devez vous attendre à ce que rubocop
soulève une erreur.
Navigation sécurisée
La navigation sécurisée est quelque chose qui a été introduit dans Ruby 2.3. Cela permet d'appeler des méthodes ou des méthodes d'appel en châines sans de nombreuses conditions grâce au mot-clé &.
.
Voici quelques exemples :
@system.update if @system
peut être écrit :
@system&.update
if @system && @system.things && @system.things.other
peut être écrit :
if @system&.things&.other
&.
est spécial car il vérifie uniquement si la valeur à sa gauche vaut nil
. Si elle vaut nil, la chaîne de &.
est rompue et le résultat renvoyé est nil
qui ne valide pas la condition.
&.
n'est pas adapté à une comparaison puisqu'il peut retourner nil
. Sauf si vous savez ce que vous faites, utilisez la navigation sécurisée pour appeler vos méthodes. Notez qu'en Ruby expr.> value
est une syntaxe valide ainsi expr&.> value
ne causera pas d'erreur si expr vaut nil mais c'est laid.
Créations d'Arrays et fonctionnement
Si vous venez d'Essentials, vous utilisez probablement la méthode push
d'un Array vide pour le remplir (en raison de la boucle for
ou juste parce que vous avez vu ça dans d'autres scripts).
Dans PSDK nous utilisons rarement la méthode push
. Nous préférons utiliser <<
à la place (Cela marche pour les Arrays et les Strings) et dans Ruby 2.5 il a son propre opcode
(opt_ltlt).
Pour la création d'Arrays nous préférons pré-remplir l'array plutôt que d'ajouter des valeurs une par une.
Exemple :
ary = [first_thing, second_thing, third_thing]
à la place de :
ary = [] ary.push first_thing ary.push second_thing ary.push third_thing
Dans Ruby les deux méthodes donnent le même résultat mais ne sont pas exécutées de la même façon. (La première utilise un opcode
spécifique qui est un peu plus rapide que d'utiliser push
).
Pour des Arrays qui contiennent des valeurs plus complexes comme des Pokemon_Info_Box dans une équipe Pokémon, nous utiliserons Array.new(size) { |index| thing_to_push }
. Cette manière de créer un array nous permet d'exécuter du code avant d'ajouter chaque valeur.
Exemple :
party_boxes = Array.new(6) { |index| create_info_box(index) }
à la place de :
party_boxes = [] for index in 0...6 party_boxes.push create_info_box(index) end
next(value)
pour dire explicitement ce qui est stocké.
<<
pour ajouter une valeur à un Array (surtout quand les conditions qui confirme l'existence de la valeur dans l'Array sont complexes).
Explicitation
Comme vous avez pu l'observer dans Créations d'Arrays et fonctionnement nous préférons utiliser du code explicite. C'est pourquoi vous verrez dans le nouveau contenu un tas de méthodes comme create_things
à la place de dizaine de lignes de code dans les blocks.
Quand vous le pouvez, essayez de factoriser votre code dans des méthodes plus petites. Cela permet plusieurs choses : Nous savons ce que votre code est censé faire grâce à son nom. Nous pouvons cutomiser le tout en utilisant le monkey patch/specialization sans avoir besoin de tout réécrire.
Dans les règles de rubocop
utilisées par PSDK, nous avons désactivé les règles à propos des return
/next
implicits. Si une méthode ou un block doit retourner une valeur à ce qui l'a appelé et est sur plus d'une ligne, mettez le mot-clef return
/next
(selon s'il s'agit d'une méthode ou d'un block). Cela aide beaucoup pour lire le code.
Factorisation
Dans un nouveau script, nous tentons de factoriser le code à chaque fois que cela est possible. La factorisation est le processus de découpage d'une seule méthode ou d'une partie de méthode en différentes méthodes. Le meilleur exemple de factorisation est celui de la méthode create_graphics
dans les scènes GamePlay :
def create_graphics @viewport = Viewport.create(:main, 500) @base_ui = UI::GenericBase.new(@viewport, button_texts) @mouse_button_cancel = @base_ui.ctrl.last @sub_background = Sprite.new(@viewport).set_bitmap('tcard/background', :interface) @trainer_sprite = Sprite.new(@viewport) .set_bitmap(PLAYER_SPRITE_NAME[$trainer.playing_girl], :interface) # Adjust the origin of the sprite since the TCard has a smaller surface for the sprite @trainer_sprite.set_origin((@trainer_sprite.width - PLAYER_SURFACE.first) / 2, (@trainer_sprite.height - PLAYER_SURFACE.last) / 2) @trainer_sprite.set_position(*PLAYER_COORDINATES) end
Sera factorisé en :
def create_graphics create_viewport create_base_ui create_sub_background create_trainer_sprite end def create_viewport @viewport = Viewport.create(:main, 500) end def create_base_ui @base_ui = UI::GenericBase.new(@viewport, button_texts) @mouse_button_cancel = @base_ui.ctrl.last end def create_sub_background @sub_background = Sprite.new(@viewport).set_bitmap('tcard/background', :interface) end def create_trainer_sprite @trainer_sprite = Sprite.new(@viewport) .set_bitmap(PLAYER_SPRITE_NAME[$trainer.playing_girl], :interface) # Adjust the origin of the sprite since the TCard has a smaller surface for the sprite @trainer_sprite.set_origin((@trainer_sprite.width - PLAYER_SURFACE.first) / 2, (@trainer_sprite.height - PLAYER_SURFACE.last) / 2) @trainer_sprite.set_position(*PLAYER_COORDINATES) end
La factorisation prendra plus de lignes (et sera probablement un peu plus lente) mais donne plussieurs avantages. Cela permet des monkey-patch plus précis et rend le processus de spécialisation plus facile.
Changer des paramètres possibles en méthodes
Même si la factorisation donne un peu d'aide pour la spécialisation, ce n'est pas une solution complète. Il y a une chose que l'on peut ajouter à cela. Dans la méthode précédente, nous avions factorisé un bloc de code dans la méthode create_trainer_sprite
, mais dans cette méthode nous avons un paramètre très important : l'image utilisée. La version actuelle ne peut pas montrer les modifications du joueur (les cheveux coupés par exemple) ou d'autres dresseurs parce que nous utilisons une constante pour le nom de l'image du dresseur. Voici la version qui est un peu plus spécifique et qui permet de créer des variations d'apparence du dresseur :
def create_trainer_sprite @trainer_sprite = Sprite.new(@viewport).set_bitmap(trainer_image, :interface) # [...] end def trainer_image PLAYER_SPRITE_NAME[$trainer.playing_girl] end
Cette version prend aussi plus de lignes que la précédente mais vous pouvez maintenant changer trainer_image
pour utiliser la bonne image de dresseur selon vos propres règles (au lieu d'uniquement une différence masculin/féminin).
trainer_image
dans PFM::Trainer
pour créer une Carte de Dresseur un peu plus dynamique. Vous devriez vous poser cette question Suis-je en train de mettre cette méthode dans la bonne classe ?
avant de créer simplement la même méthode dans la même class avec quelques paramètres.