Coder proprement

From Base de connaissances eggdrops & TCL
Jump to: navigation, search

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

Travaux Préliminaires

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

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

Conventions de nommage

Une chose essentielle lorsqu'on développe, c'est que le code soit lisible et auto-suffisant. Cela passe par la mise en page (indentations propres), les commentaires judicieux (ni trop, ni trop peu) et un bon nommage de toutes les procédures et variables.

Il n'y a pas de règles définies, c'est à vous de vous les créer et de vous y tenir. Et surtout, soyez cohérents. J'ai vu des codes du genre :

proc verifuser { nick args } {
   set arg1 [lindex $arg 0]
   set arg2 [lindex $arg 1]
   if { $arg2 == "" } {
      putserv "PRIVMSG $nick :[::msgcat::mc m9]"
      return 0
   }
   return 1
}

Donc, on ne sait pas comment fonctionne cette procédure, est-ce que m9 est un message d'erreur, pourquoi $arg2 ne peut pas être vide, ...

Quitte à faire un code aussi peut parlant, pourquoi ne pas faire:

proc p19 {n a} {set b1 [lindex $a 0]; set b2 [lindex $a 1]; if {$b2==""} {putserv "PRIVMSG $n ::[::msgcat::mc m9]"; return 0;} return 1}

C'est vraiment le pire, non ?

Pour ma part, voici ce que je ferais:

proc verifUser { nick args } {
   set uIdent [lindex $arg 0]
   set uPass [lindex $arg 1]
   if { $uPass == "" } {
      putserv "PRIVMSG $nick :[::msgcat::mc errNoPass]"
      return 0
   }
   return 1
}

Ce n'est pas parfait, mais connaissant mes conventions de nommage, je sais que:

  • uIdent et uPass sont des informations utilisateur (préfixe u) qui correspondent donc à un identifiant et un mot de passe
  • Si le mot de passe est vide, j'utilise un message d'erreur (préfixe err) qui signale l'absence du password.

Les namespaces ça explose tout

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 cool

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.

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 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 !

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

80 colonnes - 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 lignes : 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 fort

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)
		}
}