Coder proprement
Je vous propose ce soir un petit tuto, car en relisant certains scripts je me dis que parfois ce serait pas du luxe. Je suis, bien sur, ouvert à toute remarque constructive, sachez cependant que je lyncherai personnellement toute personne qui remettrait en cause se que j'écris (et les fautes d'orthographe que vous pourriez penser voir ne sont que révélatrices de votre ignorance des subtiles exceptions de notre si belle langue) -- Mareo
N.B. La consommation excessive de café peut nuire à votre santé et à celle des nerfs de votre entourage.
Sommaire |
Travaux Préliminaires
(note : ne pas retirer "Travaux")
Bon cette partie recense tout ce qu'il va vous falloir faire avant de commencer à penser à comment vous allez hypothétiquement initier un processus d'activation des zones de votre cerveau impliqué dans ce que nous appelons communément la programmation. Vous êtes bien sur libre de ne pas faire ce qui y est indiqué à condition de suivre très précisément ce que j'y énonce (vous voyez hein, je vous laisse le choix ^^)
Avant de commencer à coder (pensez à préparer du café)
Choisissez un nom évocateur pour votre script c'est toujours utile. Faites une petit cadre tendance de commentaires en haut de votre script afin d'indiquer le nom du script, son ou ses auteurs (et leurs emails), son utilité, éventuellement sa version et un petit changelog, bugs connus, à faire, etc. Le tout ne dépassant idéalement pas les 80 colonnes, j'y reviendrai un peu plus tard.
Un bon exemple serait :
################################################################################
# script.tcl v18.0 par Mareo (mareoh@gmail.com) #
# LICENSE : GNU GPLv3 (lien vers le texte de la license qui passe pas) #
# #
# Encodage : UTF-8 Taille des tabulations : 6 colonnes #
# #
#------------------------------------------------------------------------------#
# Description #
#------------------------------------------------------------------------------#
# Ce script est juste là pour servir d'exemple à mon tuto... #
# #
#------------------------------------------------------------------------------#
# /!\ Attention /!\ #
#------------------------------------------------------------------------------#
# Une version de TCL supérieure ou égale à 8.5 est requise #
# pour que ce script puisse fonctionner correctement. #
# #
# (c'est bien d'indiquer la raison ici) #
#------------------------------------------------------------------------------#
# Installation #
#------------------------------------------------------------------------------#
# Ajoutez à la fin du fichier 'eggdrop.conf' l'instruction suivante : #
# source -encoding utf-8 "chemin/vers/script.tcl" #
# #
# Vous pouvez également modifier quelques options plus bas #
#------------------------------------------------------------------------------#
# #
# [ TODO ] #
# - Faire un vrai script #
# - Faire du café #
# - Taper Artix #
# #
# [ CHANGES ] #
# * 24/03/2010 v18.0 #
# Correction d'un bug qui empêchait de voter. #
# Ajout d'une fonction pour remplir sa fiche d'imposition. #
# #
# [...] (plein de truc mais on s'en fiche) #
# #
# * 24/03/1992 v1.0 #
# Première version stable. #
# #
# * 24/06/1991 v0.1 #
# Début du developpement. #
# #
################################################################################
Bon vous êtes pas obliger de raconter votre vie comme je le fais, mais si votre script est conséquent c'est toujours bien de faire un truc informatif, la longueur des tabulation et l'encodage ça aide vraiment les gens qui vous relisent, alors si vous connaissez ces infos hésitez pas à les mettre.
Bon ceci fait passons à un autre incontournable, j'ai nommé le namespace
Avant de commencer à penser à coder (en préparant le café)
La namespace ça explose tout (sauf les tasses de café)
Certaines personnes aiment bien nommer leurs procédures "script:action", outre le fait que ça fait un nom à rallonge, c'est pas très judicieux, en effet autant utiliser correctement les outils que nous avons à notre disposition. Pour rappel, voici la page du manuel sur les namespace
Pour ça c'est très simple, il suffit de mettre son script entre :
namespace eval "script" {
(ton script qui déchire grave)
}
Et magie tout fonctionne ! Ça va être très pratique quand nous aborderont la désinstallation du script :)
En attendant, il faut juste penser à inclure le chemin complet des commandes dans les bind, exemple :
namespace eval "script" {
proc irc_uneputaindeproc { s_nick s_uhost s_hand s_chan s_msg } {
(un putain de code)
}
bind PUBM - "*" [namespace current]::irc_uneputaindeproc
(ou)
bind PUBM - "*" script::irc_uneputaindeproc
}
Si vous oubliez le [namespace current]:: ou le script:: ça risque pas de marcher, vous pouvez choisir l'un ou l'autre. Ma préférence va pour le [namespace current]:: ça facilite la vie si vous modifiez le nom du namespace.
!! Attention subtilité !!
Si vous voulez accéder à des variable globale, il ne faudra plus utiliser global mais variable qui s'emploie un peu différemment :
Sans namespace :
set s_boisson "cafééééé"
set s_supplement "au lait !"
proc boire { } {
global s_boisson s_supplement
return [concat $s_boisson $s_supplement]
}
Avec un namespace :
namespace eval "::bar" {
set s_boisson "cafééééé"
set s_supplement "au lait !"
proc boire { } {
variable s_boisson
variable s_supplement
return [concat $s_boisson $s_supplement]
}
}
Eh oui, variable ne s'utilise qu'une fois par variable, contrairement à global. Si vous vous dites que ça vous fait trop de ligne au début de votre procédure, c'est que vous utilisez trop de variables globales, et ça c'est mal !
Un script qui se désinstalle tout seul c'est de bon gout (mais moins que celui du café)
Ah vous n'en avez pas rêvé ? Supprimer un petit "source" de votre eggdrop.conf et faire ainsi disparaitre toute trace du script correspondant au rehash. Et bien c'est possible et sans trop d'effort ! C'est par ailleurs l'une des première chose à faire après avoir construit votre namespace. Il existe plusieurs façons de procéder, une très simple et une un peu plus complexe, comptez une ou deux TdC (Tasse de Café, unité de temps de tout codeur qui se respecte) supplémentaire tout au plus.
L'astuce consiste à utiliser l'évènement prerehash qui est généré juste avant un rehash couplée à namespace delete qui supprime un namespace et tout son contenu (variables, procédures). Cependant, tout ce qui n'est pas lié au namespace est conservé, ce qui sur un eggdrop inclut binds et timers qu'il faut penser à supprimer.
namespace eval "script" {
proc irc_uneputaindeproc { s_nick s_uhost s_hand s_chan s_msg } {
(un putain de code)
}
proc uninstall { args } {
unbind PUBM - "*" [namespace current]::irc_uneputaindeproc
unbind EVNT - "prerehash" [namespace current]::uninstall
namespace delete [namespace current]
}
bind PUBM - "*" [namespace current]::irc_uneputaindeproc
bind EVNT - "prerehash" [namespace current]::uninstall
}
Tellement simple qu'on se demande parfois pourquoi on ne le retrouve pas partout. Cette technique à néanmoins un petit désavantage, si le script est un jeu par exemple, toutes les informations à propos dudit jeu seront supprimées à chaque rehash, ce qui, on le conçoit, peut sérieusement perturber certaines parties (ce qui est d'autant plus important lorsque l'enjeu est une cafetière). Et bien soit ! Qu'à cela ne tienne trouvons une solution à cet épineux problèmes :
On va ici utiliser une variable pour savoir si le script à été rechargé ainsi que l'évènement "rehash" qui lui est appelé après un rehash.
namespace eval "script" {
# Variable témoin, pour savoir si le script à été rechargé,
# elle est remise à 1 à chaque chargement du script.
set b_loaded 1
proc irc_uneputaindeproc { s_nick s_uhost s_hand s_chan s_msg } {
(un putain de code)
}
proc evnt_prerehash { s_event } {
variable b_loaded
# On remet la variable à 0 avant chaque rehash.
set b_loaded 0
}
proc evnt_rehash { s_event } {
variable b_loaded
if { [string is false -strict $b_loaded] } {
uninstall
}
}
proc uninstall { } {
unbind PUBM - "*" [namespace current]::irc_uneputaindeproc
bind EVNT - "prerehash" [namespace current]::evnt_prerehash
bind EVNT - "rehash" [namespace current]::evnt_rehash
namespace delete [namespace current]
}
bind PUBM - "*" [namespace current]::irc_uneputaindeproc
bind EVNT - "prerehash" [namespace current]::evnt_prerehash
bind EVNT - "rehash" [namespace current]::evnt_rehash
}
Bon ça demande plus de code et un peu plus de TdC mais le résultat est là , et c'est un peu adaptés dans certaines situations, remarquez que si votre script se contente de vous rappeler d'aller faire du café dès que vous entrez en PL alors la première méthode suffit amplement.
En plein dans le feu de l'action !
(note : avec tout le café que vous allez ingurgiter, vous pourrez tenir toute la nuit !)
Bon là ça va plutôt être des petites astuces, des conseils, des règles arbitraires et dépassées qu'il vous faudra impérativement suivre, ce genre de joyeusetés !
Pendant que vous commencez à coder (en buvant le café)
80 (cafés) - Une limite à ne pas dépasser
Vous avez peut être déjà entendu parler de la fameuse limite des 80 colonnes ? Cette très ancienne règle stipule que dans un fichier texte, il ne doit pas y avoir plus de 80 caractère par ligne. Cette limitation provient des cartes perforées du début du siècle dernier dont le format le plus répandu était celui ayant 80 caractères de largeur. Inutile de dire que c'est l'une des plus anciennes règle arbitraire jamais énoncée en programmation. Vous avez tout a fait le droit de ne pas vous y conformer, ceci ne vous expose juste à être maudit jusqu'à la 42ème génération par tout intégriste barbu qui code encore sur un TRS-80. Le seul moyen de conjurer le sort étant de boire 1337 tasses de café un soir de pleine lune à cheval sur un buste d'Ada Lovelace tout en récitant la définition d'une machine de Turing. Pour éviter de vous retrouver dans cette très fâcheuse situation, je vous donne ici quelques astuces.
Poursuivre une instruction sur plusieurs ligne : On utilise le caractère \ pour "échaper" le saut de ligne.
if { [::tcl::mathop::< $i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] \
&& $i ne "" } {
}
Poursuivre une instructions sur plusieurs ligne en coupant en plein milieu d'une chaîne :
On utilise concat afin de concaténer plusieurs chaîne ensemble, attention cependant à faire la coupure au niveau d'un espace.
set s_msg [concat "blablalbalablablalbala blablalbala blablalbala blablalbala" \
"blablalbala blablalbala"]
$ concat "a" "b"
-> "a b" # Notez l'espace !
$ concat "a " "b"
-> "a b" # Ici aucun espace n'a été ajouté.
Lorsque vous initialisez une liste avec des constantes :
set l_list {
"aaaaaa" "bbbbb" "ccccc" "ddddd" "eeeee" "fffff"
"ggggg" "hhhhh" "jjjjj" "kkkkk" "lllll" "mmmmm"
}
Notez que dans ce cas précis vous n'avez pas besoin du caractère \
Une autre façon de faire aurait été :
set l_list [list "aaaaaa" "bbbbb" "ccccc" "ddddd" "eeeee" "fffff" "ggggg" \
"hhhhh" "jjjjj" "kkkkk" "lllll" "mmmmm"]
Vous pouvez prolonger une instruction sur autant de ligne que vous le souhaitez :
putlog [concat "une information très longe qui prend tout un tas de place," \
"tellement que l'on est obligé de la prolonger sur un certain nombre de" \
"lignes afin de respecter la règle des 80 colonnes, et d'éviter tout un" \
"tas de petits désagrément mettant en scène un buste, une pleine lune" \
"et une certaine malédiction"]
Voilà vous avez entre les mains toute les connaissances nécessaires pour vous foutre méchamment de la gueule de ceux qui dépassent les 80 colonnes :)
TCL en langage qui ne possède pas de typage (ça c'est) fort (de café)
Bon, vous aurrez remarqué que mes variable sont très étrangement préfixées d'un "s_" énigmatique. J'en arrive à une deuxième règle très importante à mes yeux puisqu'elle vous permet de remarque tout de suite l'erreur typique du débutant, à savoir appliquer des fonction de string sur des list et inversement. (MenzAgitat me signale que dans certains cas c'est possible de façon légitime, ce en quoi il a toute a fait raison, aussi veuillez ne pas tenir compte de sa remarque avant que je n'ai modifié le tuto pour en parler plus en détail, et sauver les apparences, tout ça...). Le principe est simple, si votre variable est destinée à contenir une chaîne de caractère (string) préfixez là d'un "s_", si elle doit contenir une liste d'un "l_", un tableau (array) "a_" un entier (integer) "i_" ou un dictionnaire "d_", rien de très sorcier et ça améliore vraiment la lisibilité de vos sources, c'est toujours bien d'en parler quelque part au début de votre script, afin que les lecteurs comprennent l'astuce et puisse en bénéficier eux aussi :) Exemple :
################################################################################
# ATTENTION #
#------------------------------------------------------------------------------#
# Vous n'avez pas besoin de modifier ce qui suit, Ã moins que vous ne #
# souhaitiez changer le comportement de ce script. #
#------------------------------------------------------------------------------#
# Tcl étant un langage non typé, il a été choisi de préfixer chaque variable #
# par le type de la valeur qu'elle contient : #
# "s_" pour une chaîne de caractère (string) #
# "i_" pour un entier (integer) #
# "l_" pour une liste (list) #
# "d_" pour un dictionnaire (dict) #
# "a_" pour un tableau (array) #
################################################################################
namespace eval "script" {
# C'est toujours bien de mettre ceci pour être sur que la version de TCL
# soit bien égale ou supérieure à 8.5
package require Tcl 8.5
proc irc_uneputaindeproc { s_nick s_uhost s_hand s_chan s_msg } {
set l_argument [split $s_msg " "]
switch -exact -nocase -- [lindex $l_argument 0] {
(des trucs pas interessants)
}
}
namespace eval "script" {
proc irc_uneputaindeproc { s_nick s_uhost s_hand s_chan s_msg } {
switch -exact -nocase -- [lindex $s_msg 0] {
# vous la voyez la grosse erreur sur la ligne juste au dessus ?
# si non, voyez un opticien, une fonction dont le nom commence par
# un "l" appliquée à une chaîne franchement c'est moche !
(des trucs pas interessants)
}
}
N.B. J'aime pas le café en vrai, et puis sapusaypalibre