Perl moderne : 2014

Prenez le contrôle de votre programmation avec le Perl moderne.


précédentsommairesuivant

X. Gestion des programmes réels

Un livre peut vous apprendre à écrire de petits programmes pour résoudre des exemples de problèmes. Vous pouvez apprendre beaucoup de syntaxe de cette façon.

Pour écrire des programmes réels afin de résoudre des problèmes réels, vous devez apprendre à gérer le code écrit dans votre langage. Comment organiser le code ? Comment savoir s'il fonctionne ? Comment pouvez-vous le rendre robuste face à des anomalies ? Qu'est-ce qui rend le code concis, clair et maintenable ? Le Perl moderne vous permet de répondre à toutes ces questions.

X-A. Tests

Vous avez déjà testé votre code si vous l'avez déjà exécuté, remarqué quelque chose qui ne fonctionnait pas tout à fait comme il fallait, fait une modification, puis exécuté à nouveau. Le test est le processus de vérification que votre logiciel se comporte comme prévu. Les tests efficaces automatisent ce processus. Plutôt que de compter sur les humains pour effectuer parfaitement des vérifications manuelles répétées, laissez l'ordinateur le faire.

Perl fournit d'excellents outils pour vous aider à écrire les bons tests.

X-A-1. Test::More

Les tests Perl commencent avec le module standard Test::More et sa fonction ok(). ok() prend deux paramètres, une valeur booléenne et une chaîne qui décrit l'objectif du test :

 
Sélectionnez
ok(   1, 'le nombre un devrait être vrai'               );
ok(   0, '... et le zéro ne le devrait pas'             );
ok(  '', 'la chaîne vide devrait être fausse'           );
ok( '!', '... et une chaîne non vide ne le devrait pas' );

done_testing();

Toute condition que vous pouvez tester dans votre programme peut finalement se résumer à une valeur binaire. Chaque assertion de test est une simple question avec une réponse par oui ou par non : est-ce que ce code se comporte comme je voulais ? Un programme complexe peut avoir des milliers de conditions individuelles. Aucun problème. Vous pouvez tester chacune de ces conditions, si vous souhaitez faire cet effort. Isoler des comportements particuliers dans des assertions individuelles vous aide à déboguer les erreurs de codage et de compréhension, surtout lorsque vous modifierez le code dans le futur.

La fonction done_testing() indique à Test::More que le programme a exécuté toutes les assertions demandées. Si le programme a rencontré une exception lors de l'exécution ou s'est arrêté de façon inattendue avant l'appel à done_testing(), le module de test vous informe que quelque chose s'est mal passé. Sans un mécanisme comme done_testing(), comment pourriez-vous le savoir ? Certes, cet exemple de code est trop simple pour échouer, mais le code qui est trop simple pour échouer échoue beaucoup plus souvent que vous ne le voulez.

Test::More vous permet d'établir un plan de test facultatif pour compter le nombre d'assertions individuelles que vous prévoyez d'exécuter :

 
Sélectionnez
use Test::More tests => 4;

ok(   1, 'le nombre un devrait être vrai'               );
ok(   0, '... et le zéro ne le devrait pas'             );
ok(  '', 'la chaîne vide devrait être fausse'           );
ok( '!', '... et une chaîne non vide ne le devrait pas' );

L'argument tests de Test::More définit le plan de test pour le programme. Il s'agit d'un filet de sécurité. Si moins de quatre tests ont été exécutés, quelque chose s'est mal passé. Si plus de quatre tests ont été exécutés, quelque chose s'est mal passé. done_testing() est plus facile, mais parfois un décompte exact peut être utile.

X-A-2. Exécuter des tests

Cet exemple de fichier de test est un programme Perl complet qui produit la sortie :

 
Sélectionnez
ok 1 - le nombre un devrait être vrai
not ok 2 - ... et le zéro ne le devrait pas
#   Failed test '... et le zéro ne le devrait pas'
#   at truth_values.t line 4.
not ok 3 - la chaîne vide devrait être fausse
#   Failed test 'la chaîne vide devrait être fausse'
#   at truth_values.t line 5.
ok 4 - ... et une chaîne non vide ne le devrait pas
# Looks like you failed 2 tests of 4.

Cet affichage utilise un format de sortie de test appelé TAP, Test Anything Protocol (http://testanything.org/). Les tests TAP échoués produisent des messages de diagnostic comme une aide au débogage.

Le résultat affiché par ce petit exemple est facile à comprendre, mais cela peut devenir rapidement compliqué. Dans la plupart des cas, vous voulez savoir si tous les tests ont réussi ou le détail des échecs. Le module standard TAP::Harness interprète le TAP. Son programme prove qui lui est associé exécute des tests et affiche uniquement l'information la plus pertinente :

 
Sélectionnez
$ prove truth_values.t
truth_values.t .. 1/?
#   Failed test '... et le zéro ne le devrait pas'
#   at truth_values.t line 4.

#   Failed test 'la chaîne vide devrait être fausse'
#   at truth_values.t line 5.
# Looks like you failed 2 tests of 4.
truth_values.t .. Dubious, test returned 2
    (wstat 512, 0x200)
Failed 2/4 subtests

Test Summary Report
-------------------
truth_values.t (Wstat: 512 Tests: 4 Failed: 2)
Failed tests:  2-3

Cela fait beaucoup d'information en sortie pour afficher ce qui est déjà évident : le deuxième et le troisième test échouent parce que le zéro et la chaîne vide ont la valeur fausse. Heureusement, il est facile à corriger par coercition booléenne ces tests qui échouent (Coercition booléenneCoercition booléenne) :

 
Sélectionnez
ok(   ! 0, '... et le zéro ne le devrait pas'   );
ok(  ! '', 'la chaîne vide devrait être fausse' );

Avec ces deux modifications, prove affiche maintenant :

 
Sélectionnez
$ prove truth_values.t
truth_values.t .. ok
All tests successful.

Voir perldoc prove pour d'autres options de test, telles que l'exécution de tests en parallèle (-j), l'ajout automatique de lib/ à la liste des répertoires où peuvent se trouver des modules Perl (-l), l'exécution récursive de tous les fichiers de test trouvés sous t/ (-r t) et l'exécution des tests lents en premier (--state=slow,save).

L'alias bash proveall suivant peut s'avérer utile :

 
Sélectionnez
alias proveall='prove -j9 --state=slow,save -lr t'

X-A-3. Meilleures comparaisons

Même si le cœur de tous les tests automatisés est la condition booléenne « est-ce vrai ou faux ? », il est fastidieux de tout réduire à cette condition booléenne et le diagnostic pourrait être meilleur. Test::More offre plusieurs autres fonctions pratiques d'assertion.

La fonction is() compare deux valeurs en utilisant l'opérateur eq de Perl. Si les valeurs sont égales, le test réussit :

 
Sélectionnez
is(       4, 2 + 2, 'l\'addition devrait marcher' );
is( 'crêpe',   100, 'les crêpes sont numériques'  );

Comme vous vous en doutez sans doute, le premier test réussit et le second échoue avec un message de diagnostic :

 
Sélectionnez
t/is_tests.t .. 1/2
#   Failed test 'les crêpes sont numériques'
#   at t/is_tests.t line 8.
#          got: 'crêpe'
#     expected: '100'
# Looks like you failed 1 test of 2.

Là où ok() ne fournit que le numéro de ligne du test échoué, is() affiche les valeurs attendues et reçues.

is() applique un contexte scalaire implicite à ses valeurs (PrototypesPrototypes). Cela signifie, par exemple, que vous pouvez vérifier le nombre d'éléments dans un tableau sans évaluer explicitement le tableau dans un contexte scalaire... et vous pouvez omettre les parenthèses :

 
Sélectionnez
my @cousins = qw( Rick Kristen Alex Kay Eric Corey );
is @cousins, 6, 'Je devrais avoir seulement six cousins';

... bien que certaines personnes préfèrent écrire scalar @cousins par souci de clarté.

La fonction inverse isnt() de Test::More compare deux valeurs en utilisant l'opérateur ne, et retourne la valeur vraie si elles ne sont pas égales. Elle aussi fournit un contexte scalaire à ses opérandes.

is() et isnt() appliquent toutes les deux des comparaisons de chaînes avec les opérateurs eq et ne. C'est presque toujours une bonne chose, mais utilisez la fonction cmp_ok() — qui vous oblige à fournir un opérateur de comparaison spécifique — pour les valeurs complexes telles que des objets avec surcharge (SurchargeSurcharge) ou des variables dualvar (DualvarsDualvars) :

 
Sélectionnez
cmp_ok( 100, $solde_actuel, '<=', 
    'Je devrais avoir au moins 100 ' );

cmp_ok( $singe, $grand_singe, '==', 
    'La conversion en nombre des simiens devrait marcher' );

Les classes et les objets fournissent leurs propres façons intéressantes d'interagir avec les tests. Testez qu'une classe ou un objet hérite d'une autre classe (HéritageHéritage) avec isa_ok() :

 
Sélectionnez
my $singezilla = RobotSinge->new;
isa_ok( $singezilla, 'Robot' );
isa_ok( $singezilla, 'Singe' );

isa_ok() fournit son propre message de diagnostic en cas d'échec.

can_ok() vérifie si une classe ou un objet peuvent invoquer la méthode ou les méthodes demandées :

 
Sélectionnez
can_ok( $singezilla, 'manger_banane' );
can_ok( $singezilla, 'transformer', 'detruire_tokyo' );

La fonction is_deeply() compare deux références pour s'assurer que leur contenu est identique :

 
Sélectionnez
use Clone;

my $nombres   = [ 4, 8, 15, 16, 23, 42 ];
my $clonenbre = Clone::clone( $nombres );

is_deeply( $nombres, $clonenbre,
     'clone() devrait produire des éléments identiques' );

Si la comparaison échoue, Test::More fera de son mieux pour fournir un diagnostic raisonnable indiquant la position de la première inégalité entre les structures. Voir les modules Test::Differences et Test::Deep du CPAN pour des tests plus configurables.

Test::More dispose de plusieurs fonctions de test, mais celles que nous venons de voir sont les plus utiles.

X-A-4. Organiser des tests

Les distributions du CPAN doivent en principe inclure un répertoire t/contenant un ou plusieurs fichiers de test nommés avec l'extension .t. Lorsque vous construisez une distribution avec le module Module::Build ou ExtUtils::MakeMaker, l'étape de test exécute tous les fichiers t/*.t, résume leur sortie et réussit ou échoue sur l'ensemble des résultats de la suite de tests. Il n'y a aucune ligne directrice concrète sur la façon de gérer le contenu des fichiers .t individuels, mais deux stratégies sont populaires :

  • chaque fichier .t doit correspondre à un fichier .pm ;
  • chaque fichier .t doit correspondre à une fonctionnalité.

Une approche hybride est plus flexible ; un test peut vérifier si tous vos modules compilent, tandis que d'autres tests démontrent que chaque module se comporte comme prévu. Lorsque votre projet grandit, la seconde approche est plus facile à gérer. Gardez vos fichiers de test petits et ciblés et ils seront plus faciles à maintenir.

Des fichiers séparés de test peuvent également accélérer le développement. Si vous ajoutez à votre RobotSinge la capacité de souffler du feu, vous voudriez peut-être exécuter uniquement le fichier de test t/souffler_feu.t. Lorsque la fonctionnalité marche comme vous voulez, exécutez toute la série de tests pour vérifier si les changements locaux n'ont aucun effet global indésirable.

X-A-5. Autres modules de test

Test::More s'appuie sur un mécanisme sous-jacent de test connu sous le nom de Test::Builder. Ce dernier module gère le plan de test et coordonne la sortie du test dans le TAP. Cette conception permet aux multiples modules de test de partager le même mécanisme Test::Builder. Par conséquent, le CPAN a des centaines de modules de test disponibles et ils peuvent travailler tous ensemble dans le même programme.

  • Test::Fatal permet de vérifier si votre code signale (ou pas) des exceptions de façon appropriée. Vous pouvez également rencontrer Test::Exception.
  • Test::MockObject et Test::MockModule permettent de tester des interfaces difficiles en les simulant (émulation produisant des résultats différents).
  • Test::WWW::Mechanize permet de tester des applications Web, tandis que Plack::Test, Plack::Test::Agent et la sous-classe Test::WWW::Mechanize::PSGI peuvent le faire sans utiliser un serveur Web externe.
  • Test::Database fournit des fonctions pour tester l'utilisation et les dysfonctionnements des bases de données. DBICx::TestDatabase aide à créer des schémas temporaires de bases de données de test construits avec DBIx::Class.
  • Test::Class offre un mécanisme alternatif pour organiser des suites de tests. Il vous permet de créer des classes dans lesquelles des méthodes spécifiques groupent des tests. Vous pouvez hériter des classes de test tout comme les classes de votre code héritent les unes des autres. C'est un excellent moyen de réduire la duplication dans les suites de tests. Voir l'excellente série Test::Class de Curtis « Ovid » Poe http://www.modernperlbooks.com/mt/2009/03/organizing-test-suites-with-testclass.html. La distribution récente Test::Routine offre des possibilités similaires à travers l'utilisation de Moose (MooseMoose).
  • Test::Differences teste l'égalité des chaînes et structures de données et affiche toute différence dans ses diagnostics. Test::LongString ajoute des assertions similaires.
  • Test::Deep teste l'équivalence des structures de données imbriquées (Structures de données imbriquéesStructures de données imbriquées).
  • Devel::Cover analyse l'exécution de votre suite de tests et fait un rapport sur la quantité de votre code testé effectivement. En général, plus c'est couvert, mieux c'est — bien que 100 % ne soit pas toujours possible, 95 % c'est beaucoup mieux que 80 %.
  • Test::Most regroupe plusieurs modules utiles de test en un seul module parent. Il permet d'économiser temps et effort.

Voir le projet Perl QA (http://qa.perl.org/) pour plus d'informations sur les tests en Perl.

X-B. Gérer les avertissements

Bien qu'il y ait plusieurs façons d'écrire un programme Perl qui marche, certaines de ces façons peuvent être déroutantes, pas claires, et même incorrectes. Le système optionnel d'avertissements de Perl peut vous aider à éviter ces situations.

X-B-1. Produire des avertissements

Utilisez la commande interne warn pour émettre un avertissement :

 
Sélectionnez
warn 'Quelque chose s\'est mal passé !';

warn imprime une liste de valeurs dans le descripteur de fichier STDERR (Entrée et sortieEntrée et sortie). Perl concaténera le nom de fichier et le numéro de la ligne ayant appelé warn sauf si le dernier élément de la liste se termine par un saut de ligne.

Le module standard Carp étend les mécanismes d'alerte de Perl. Sa fonction carp() signale un avertissement dans la perspective du code appelant. Étant donnée une fonction comme :

 
Sélectionnez
use Carp 'carp';

sub seulement_deux_arguments
{
    my ($lop, $rop) = @_;
    carp( 'Trop d\'arguments fournis' ) if @_ > 2;
    ...
}

... l'avertissement d'arité (AritéArité) comprendra le nom et le numéro de ligne du code appelant, pas seulement_deux_arguments(). La méthode cluck() de Carp est similaire, mais elle produit une trace de tous les appels de fonction jusqu'à la fonction en cours.

Le mode verbeux de Carp ajoute les traces de tous les avertissements produits par carp() et croak() (Rapports d'erreursRapports d'erreurs) à travers l'ensemble du programme :

 
Sélectionnez
$ perl -MCarp=verbose my_prog.pl

Utilisez Carp au lieu de warn ou die lors de l'écriture des modules (ModulesModules).

X-B-2. Activer et désactiver des avertissements

Parfois, des programmes plus anciens utilisent l'argument de ligne de commande -w, qui active les avertissements tout au long du programme, même dans les modules externes écrits et maintenus par d'autres personnes. C'est tout ou rien — mais cela peut vous être utile si vous avez le temps et l'énergie d'éliminer les avertissements réels et potentiels tout au long du code source.

L'approche moderne consiste à utiliser le pragma warnings ou un équivalent, comme use Modern::Perl;. Cela active les avertissements dans les portées lexicales. Si vous avez utilisé warnings dans une portée, vous indiquez que le code ne devrait produire normalement aucun avertissement.

Options d'avertissements globaux

L'option -W active les avertissements tout au long du programme de façon unilatérale, indépendamment de toute utilisation de warnings. L'option -X désactive les avertissements tout au long du programme de façon unilatérale. Aucun des deux n'est courant.

-w, -W et -X affectent tous la valeur de la variable globale $^W. Le code écrit avant l'introduction du pragma warnings avec Perl 5.6.0 — au printemps 2000, pour que vous sachiez que c'est vieux — peut localiser $^W pour supprimer certains avertissements à l'intérieur d'une portée donnée.

X-B-3. Désactiver des catégories d'avertissements

Pour désactiver certains avertissements particuliers à l'intérieur d'une portée, utilisez no warnings; avec une liste d'arguments. L'omission de la liste d'arguments désactive tous les avertissements à l'intérieur de cette portée.

perldoc perllexwarn répertorie toutes les catégories d'avertissements comprises par votre version de Perl. La plupart d'entre eux représentent des conditions vraiment intéressantes, mais certains peuvent être inutiles dans vos circonstances particulières. Par exemple, l'avertissement recursion se produira si Perl détecte qu'une fonction s'est appelée elle-même plus de cent fois. Si vous êtes confiant dans votre capacité à écrire des conditions d'arrêt de la récursion, vous pouvez désactiver cet avertissement dans la portée de la récursion (bien que les appels terminaux puissent être préférables ; Appels de fonctions et récursion terminaleAppels de fonctions et récursion terminale).

Si vous générez du code (Génération de codeGénération de code) ou redéfinissez des symboles localement, vous pouvez souhaiter désactiver les avertissements redefine.

Certains développeurs Perl expérimentés désactivent les avertissements de type Use of uninitialized value dans le code qui traite des chaînes et qui concatène des valeurs provenant de plusieurs sources. Si vous êtes prudent sur l'initialisation de vos variables, vous pouvez ne jamais avoir besoin de désactiver cet avertissement, mais parfois l'avertissement gêne l'écriture du code concis dans votre style local.

X-B-4. Rendre les avertissements fatals

Si votre projet considère les avertissements aussi onéreux que les erreurs, vous pouvez les rendre fatals. Afin de transformer tous les avertissements en exceptions au sein d'une portée lexicale :

 
Sélectionnez
use warnings FATAL => 'all';

Vous pouvez également rendre fatales des catégories spécifiques d'avertissements, telles que l'utilisation de constructions obsolètes :

 
Sélectionnez
use warnings FATAL => 'deprecated';

Avec une bonne discipline, cela peut produire un code très robuste — mais soyez prudent. De nombreux avertissements proviennent des conditions d'exécution. Si votre suite de tests ne parvient pas à identifier tous les avertissements que vous pourriez rencontrer, votre programme peut s'arrêter à cause des exceptions d'exécution non interceptées. Gardez à l'esprit que les versions les plus récentes de Perl peuvent ajouter de nouveaux avertissements et les avertissements customisés seront également fatals (Enregistrer vos propres avertissementsEnregistrer vos propres avertissements). Si vous activez les avertissements fatals, faites-le uniquement dans le code que vous contrôlez et jamais dans le code d'une bibliothèque censée être utilisée par d'autres personnes.

X-B-5. Intercepter des avertissements

Si vous êtes prêt à y travailler, vous pouvez intercepter des avertissements comme vous le feriez pour des exceptions. La variable %SIG (voir perldoc perlvar) contient des gestionnaires pour les signaux hors bande lancés par Perl ou par votre système d'exploitation. Affectez à $SIG{__WARN__} une référence de fonction pour intercepter un avertissement :

 
Sélectionnez
{
    my $warning;
    local $SIG{__WARN__} = sub { $warning .= shift };

    # faites quelque chose de risqué
    ...

    say "Intercepté l'avertissement :\n$warning" if $warning;
}

Dans le gestionnaire d'avertissements, le premier argument est le message de l'avertissement. Certes, cette technique est moins utile que la désactivation lexicale des avertissements, mais elle peut convenir à la bonne utilisation de modules de test tels que Test::Warnings du CPAN, où le texte de l'avertissement est important.

Faites attention, car %SIG est globale. localisez-la dans la portée la plus petite possible, mais comprenez qu'elle reste toujours une variable globale.

X-B-6. Enregistrer vos propres avertissements

Le pragma warnings::register vous permet de créer vos propres avertissements afin que les utilisateurs de votre code puissent les activer et désactiver dans les portées lexicales. Dans un module, utilisez le pragma warnings::register ainsi :

 
Sélectionnez
package Singe::Effrayant;

use warnings::register;

Cela va créer une nouvelle catégorie d'avertissements du nom du paquetage Singe::Effrayant. Activez ces avertissements avec use warnings 'Singe::Effrayant' et désactivez-les avec no warnings 'Singe::Effrayant'.

Utilisez warnings::enabled() pour tester si la portée lexicale de l'appelant a activé une catégorie d'avertissements. Utilisez warnings::warnif() pour produire un avertissement uniquement si les avertissements sont en vigueur. Par exemple, pour produire un avertissement dans la catégorie deprecated :

 
Sélectionnez
package Singe::Effrayant;

use warnings::register;

sub import
{
    warnings::warnif( 'deprecated',
        'empty imports from ' . __PACKAGE__ .
        ' are now deprecated' )
    unless @_;
}

Voir perldoc perllexwarn pour plus de détails.

X-C. Fichiers

La majorité des programmes doivent interagir d'une façon ou d'une autre avec le monde réel, souvent en lisant, en écrivant ou en manipulant d'une autre façon des fichiers. L'origine de Perl comme outil pour les administrateurs système a produit un langage bien adapté pour le traitement du texte.

X-C-1. Entrée et sortie

Un descripteur de fichier représente l'état courant d'un canal spécifique d'entrée ou de sortie. Chaque programme Perl a trois descripteurs de fichiers standard disponibles, STDIN (l'entrée au programme), STDOUT (la sortie du programme), et STDERR (la sortie d'erreurs du programme). Par défaut, tout ce que vous affichez en sortie avec print ou say va dans STDOUT, tandis que les erreurs et les avertissements vont dans STDERR. Cette séparation de la sortie vous permet de rediriger la sortie utile et les erreurs vers deux endroits différents — un fichier de sortie et les journaux d'erreurs, par exemple.

Utilisez la commande interne open pour obtenir un descripteur de fichier. Pour ouvrir un fichier en lecture :

 
Sélectionnez
open my $fh, '<', 'nomFichier' 
    or die "Impossible de lire '$nomFichier': $!\n";

Le premier opérande est une variable lexicale qui contiendra le descripteur de fichier résultant. Le deuxième opérande est le mode d'ouverture du fichier, qui détermine le type de l'opération sur le descripteur de fichier (lecture, écriture, ou ajout à la fin, par exemple). L'opérande final est le nom du fichier. Si l'ouverture échoue, la clause die va lancer une exception, avec la raison de l'échec dans la variable magique $!.

Vous pouvez ouvrir des fichiers pour y écrire, ajouter à la fin, lire et écrire, et plus encore. Les modes d'ouverture de fichiers les plus importants sont les suivants :

Symbole

Explication

<

Ouverture en lecture.

>

Ouverture en écriture, effacement du contenu existant si le fichier existe ou création d'un nouveau fichier dans le cas contraire.

>>

Ouverture en écriture, avec ajout à la fin du contenu existant si le fichier existe ou création d'un nouveau fichier dans le cas contraire.

+<

Ouverture pour lecture et écriture.

-|

Ouverture d'un pipe vers un processus externe pour lecture.

|-

Ouverture d'un pipe vers un processus externe pour écriture.

Vous pouvez même créer des descripteurs de fichiers qui lisent ou écrivent dans de simples scalaires Perl, en utilisant n'importe quel mode de fichier existant :

 
Sélectionnez
open my $lire_fh,   '<', \$entree_truquee;
open my $ecrire_fh, '>', \$sortie_capturee;

faire_quelque_chose_de_genial( $lire_fh, $ecrire_fh );

perldoc perlopentut explique en détail les usages plus exotiques de open, y compris sa capacité de lancer et contrôler d'autres processus, ainsi que l'utilisation de sysopen pour contrôle plus fin sur l'entrée et la sortie. perldoc perlfaq5 inclut des exemples de code permettant de réaliser de nombreuses tâches courantes d'entrée/sortie.

Vous rappelez-vous autodie  ?

Considérez que tous les exemples de cette section ont activé use autodie; et peuvent donc gérer les erreurs en toute sécurité. Si vous choisissez de ne pas utiliser autodie, n'oubliez pas de vérifier les valeurs de retour de tous les appels système pour gérer les erreurs de manière appropriée.

X-C-1-a. Unicode, couches E/S et modes de fichier

En plus du mode d'ouverture de fichier, vous pouvez ajouter une couche d'encodage E/S qui permet à Perl d'encoder ou décoder à partir d'un codage Unicode. Par exemple, si vous savez que vous allez lire un fichier écrit dans l'encodage UTF-8 :

 
Sélectionnez
open my $entree_fh,  '>:encoding(UTF-8)', $fichier_en_entree;

... ou écrire dans un fichier en utilisant l'encodage UTF-8 :

 
Sélectionnez
open my $sortie_fh, '<:encoding(UTF-8)', $fichier_en_sortie;

X-C-1-b. Fonction open à deux arguments

Des programmes anciens utilisaient souvent la forme à deux arguments de open(), qui concatène dans le même chaîne le mode d'ouverture du fichier avec le nom du fichier à ouvrir :

 
Sélectionnez
open my $fh, "> $un_fichier"
    or die "Impossible d'écrire dans '$un_fichier': $!\n";

Perl doit extraire le mode du fichier du nom de fichier. C'est un risque ; quand Perl doit deviner ce que vous voulez dire, il peut deviner incorrectement. Pire encore, si $un_fichier provenait d'une entrée d'utilisateur non fiable, vous avez un problème potentiel de sécurité, car tout caractère inattendu pourrait changer le comportement de votre programme.

La fonction open() à trois arguments est un remplacement plus sûr pour ce code.

Les nombreux noms de DATA
Le descripteur de fichier du paquetage spécial global DATA représente le fichier courant. Lorsque Perl termine la compilation du fichier, il laisse DATA ouvert et pointant vers la fin de l'unité de compilation si le fichier comporte une section __DATA__ ou __END__. Tout texte qui survient après ce symbole est disponible en lecture à partir de DATA. L'ensemble du fichier est disponible si vous utilisez seek pour revenir au début du descripteur de fichier. C'est utile pour les programmes courts, autonomes. Voir perldoc perldata pour plus de détails.

X-C-1-c. Lire des fichiers

Pour lire depuis un descripteur de fichier ouvert en entrée, utilisez la commande interne readline, écrite aussi <>. Un idiome commun lit une ligne à la fois dans une boucle while() :

 
Sélectionnez
open my $fh, '<', 'un_fichier';

while (<$fh>)
{
    chomp;
    say "Lire une ligne '$_'";
}

En contexte scalaire, readline lit une seule ligne du fichier et retourne cette ligne, ou undef si elle atteint la fin du fichier (eof()). Chaque itération dans cet exemple retourne la ligne suivante ou undef. Cette syntaxe while vérifie si la variable utilisée pour itération est définie, de sorte que seule la condition de fin de fichier arrête la boucle. Autrement dit, c'est un raccourci pour :

 
Sélectionnez
open my $fh, '<', 'un_fichier';

while (defined($_ = <$fh>))
{
    chomp;
    say "Lire une ligne '$_'";
    last if eof $fh;
}

Pourquoi utiliser while et pas for  ?

for impose le contexte de liste à son opérande. Dans le cas de readline, Perl va lire l’entièreté du fichier avant de traiter son contenu. while exécute les itérations et lit une ligne à la fois. Lorsque l'utilisation de la mémoire est une préoccupation (par exemple avec de gros fichiers), utilisez while.

Chaque ligne lue par readline inclut le ou les caractères qui marquent la fin d'une ligne. Dans la majorité des cas, c'est une séquence spécifique à la plate-forme, consistant en un saut de ligne (\n), un retour chariot (\r), ou une combinaison des deux (\r\n). Utilisez chomp pour l'enlever.

La façon la plus propre de lire un fichier ligne par ligne en Perl est la suivante :

 
Sélectionnez
open my $fh, '<', $nomFichier;

while (my $ligne = <$fh>)
{
    chomp $ligne;
    ...
}

Par défaut, Perl accède aux fichiers en mode texte. Si vous lisez des données binaires — comme un fichier multimédia ou un fichier compressé, utilisez binmode avant d'effectuer toute opération E/S. Cela forcera Perl à traiter les données du fichier comme des donnée pures, sans les modifier en aucune façon. Les modifications comprennent la traduction du \n en la séquence de saut de ligne spécifique à la plate-forme. Même si les plates-formes de type Unix peuvent ne pas avoir toujours besoin de binmode, les programmes portables agissent prudemment (Unicode et chaînes de caractèresUnicode et chaînes de caractères).

X-C-1-d. Écrire dans des fichiers

Pour écrire dans un descripteur de fichier ouvert en sortie, utilisez print ou say :

 
Sélectionnez
open my $out_fh, '>', 'output_file.txt';

print $out_fh "Voici une ligne de text\n";
say   $out_fh "... et en voici une autre";

Notez l'absence de virgule entre le descripteur de fichier et l'opérande qui le suit.

Désambiguïsation de descripteur de fichier

Le livre De l'art de programmer en Perl de Damian Conway conseille de prendre l'habitude de mettre le descripteur de fichier entre des accolades. Cela est nécessaire pour lever l'ambiguïté de l'analyse d'un descripteur de fichier contenu dans une variable composée (tableau ou hachage, par exemple), et cela ne nuira pas dans les cas les plus simples.

Tant print que say prennent une liste d'opérandes. Perl utilise la variable magique globale $, comme séparateur entre les valeurs de la liste. Perl utilise également la valeur courante de $\ comme dernier argument à print or say. Ainsi, ces deux lignes de code produisent le même résultat :

 
Sélectionnez
my @princes = qw( Corwin Eric Random ... );

print @princes;
print join( $,, @princes ) . $\;

X-C-1-e. Fermer des fichiers

Lorsque vous avez fini de travailler avec un fichier, fermez explicitement son descripteur de fichier (avec close) ou arrangez-vous pour sortir de sa portée. Perl le fermera pour vous. L'avantage d'appeler close explicitement est que vous pouvez vérifier et remédier à des erreurs spécifiques, telles que le manque d'espace sur un périphérique de stockage ou une connexion réseau interrompue.

Comme d'habitude, autodie gère ces vérifications pour vous :

 
Sélectionnez
use autodie qw( open close );

open my $fh, '>', $fichier;

...

close $fh;

X-C-1-f. Variables spéciales de gestion des fichiers

Pour chaque ligne lue, Perl incrémente la valeur de la variable $., qui sert de compteur de lignes.

readline utilise le contenu actuel de $/ comme séquence de fin de ligne. La valeur par défaut de cette variable est la séquence de caractères de fin de ligne la plus appropriée pour les fichiers texte sur votre plate-forme courante. Le mot ligne est impropre, cependant. Vous pouvez définir $/ pour contenir n'importe quelle séquence de caractères... mais, malheureusement, pas une expression régulière. Peut-être à partir de Perl 5.22. C'est utile pour des données très structurées que vous souhaitez lire enregistrement par enregistrement.

Pour lire un enregistrement à la fois à partir d'un fichier avec des enregistrements séparés par deux lignes vides, définissez $/ à la valeur \n\n. chomp sur un enregistrement lu supprimera la séquence double-saut de ligne.

Perl met par défaut sa sortie dans un tampon et effectue des opérations d'entrée/sortie seulement lorsque la quantité de données en attente dépasse un seuil. Cela lui permet de grouper les opérations d'E/S coûteuses au lieu d'écrire chaque fois de très petites quantités de données. Pourtant, parfois vous voulez envoyer les données dès que vous les avez sans attendre le remplissage de la mémoire tampon, surtout si vous écrivez un filtre de lignes de commande relié à d'autres programmes ou un service réseau orienté ligne.

La variable $| contrôle la mise en tampon du descripteur de fichier de sortie courant. Lorsqu'elle est définie à une valeur différente de zéro, Perl videra la sortie après chaque écriture dans le descripteur de fichier. Lorsqu'elle est définie à la valeur zéro, Perl utilisera sa stratégie par défaut de mise en mémoire tampon.

Écriture immédiate automatique

Les fichiers possèdent une stratégie par défaut de mise en tampon intégrale. Lorsqu'il est connecté à un terminal actif — mais pas un autre programme — STDOUT utilise une stratégie de mise en tampon de ligne, par laquelle Perl videra STDOUT chaque fois qu'il rencontrera un saut de ligne dans la sortie.

Au lieu d'encombrer votre code avec une variable globale, utilisez la méthode autoflush() sur un descripteur de fichier lexical :

 
Sélectionnez
open my $fh, '>', 'pecan.log';
$fh->autoflush( 1 );

...

Depuis Perl 5.14, vous pouvez utiliser n'importe quelle méthode fournie par IO::File sur un descripteur de fichier. Vous n'avez pas besoin de charger IO::File explicitement. En Perl 5.12, vous devez charger vous-même IO::File.

Les méthodes input_line_number() et input_record_separator() de IO::File font le travail de $. et $/ sur les descripteurs de fichiers individuels. Consultez la documentation de IO::File, IO::Handle et IO::Seekable.

X-C-2. Répertoires et chemins

Travailler avec des répertoires est analogue à travailler avec des fichiers, sauf que vous ne pouvez pas écrire dans des répertoires. Au lieu de cela, vous enregistrez, déplacez, renommez et supprimez des fichiers. Ouvrez un descripteur de répertoire avec la commande interne opendir :

 
Sélectionnez
opendir my $dirh, '/home/singedompteur/taches/';

La commande interne readdir lit un répertoire. Comme avec readline, vous pouvez parcourir le contenu des répertoires un par un ou vous pouvez les affecter à un tableau en une seule fois :

 
Sélectionnez
# itération
while (my $file = readdir $dirh)
{
    ...
}

# aplatissement en une liste, affectation à un tableau
my @files = readdir $autre_dirh;

Perl 5.12 a ajouté une fonctionnalité où readdir définit $_ dans une boucle while :

 
Sélectionnez
use 5.012;

opendir my $dirh, 'taches/cirque/';

while (readdir $dirh)
{
    next if /^\./;
    say "Trouvé une tâche $_!";
}

La curieuse expression régulière dans cet exemple permet d'ignorer ce qu'on appelle des fichiers cachés sur les systèmes de type Unix, où un point en début de leur nom les empêche par défaut d'apparaître dans l'affichage du contenu de répertoires. Elle permet d'ignorer également les deux fichiers spéciaux . et .., qui représentent respectivement le répertoire courant et le répertoire parent.

Les noms retournés par readdir sont relatifs au répertoire même. Autrement dit, si le répertoire tâches/ contient trois fichiers nommés manger, boire, et sois_singe, readdir retournera manger, boire et sois_singe et non tâches/manger, tâches/boire, et tâches/sois_singe. En revanche, un chemin absolu est un chemin d'accès complet à son système de fichiers.

Fermez un descripteur de répertoire en le laissant sortir de la portée ou avec la commande interne closedir.

X-C-2-a. Manipuler des chemins de fichier

Perl offre une vue de type Unix de votre système de fichiers et interprétera de façon appropriée les chemins de fichier du style Unix pour votre système d'exploitation et système de fichiers. Si vous utilisez Microsoft Windows, vous pouvez utiliser le chemin C:/Mes documents/Robots/Bender/ aussi bien que le chemin C:\Mes Documents\Robots\Caprica Six\.

Même si Perl utilise constamment la sémantique des fichiers Unix, la manipulation des fichiers sur des environnements variés est beaucoup plus facile avec un module. La famille de modules standard File::Spec vous permet de manipuler les chemins de fichier en toute sécurité et de façon portable. Il est un peu maladroit, mais bien documenté.

Le module Path::Class du CPAN a une interface plus agréable. Utilisez la fonction dir() pour créer un objet représentant un répertoire et la fonction file() pour créer un objet représentant un fichier :

 
Sélectionnez
use Path::Class;

my $repas = dir( 'taches', 'cuisiner' );
my $file  = file( 'taches', 'sante', 'robots.txt' );

Vous pouvez obtenir des objets de type Fichier à partir de répertoires et vice versa :

 
Sélectionnez
my $diner      = $repas->file( 'calzone_vegetarienne' );
my $robots_dir = $liste_robots->dir;

Vous pouvez même ouvrir des descripteurs de fichiers de répertoires et fichiers :

 
Sélectionnez
my $dir_fh    = $dir->open;
my $robots_fh = $liste_robots->open( 'r' )
                    or die "Ouverture échouée : $!";

Tant Path::Class::Dir que Path::Class::File offrent des comportements utiles — faites attention pourtant si vous utilisez un objet Path::Class quelconque avec un opérateur ou une fonction qui attend une chaîne contenant un chemin de fichier, car vous devez convertir vous-même l'objet en chaîne. C'est un inconvénient persistant, mais mineur. (Si vous trouvez cela pénible, essayez Path::Tiny comme solution de rechange.)

 
Sélectionnez
my $contenu = lire_dans_nomFichier( "$diner" );

X-C-3. Manipuler des fichiers

En plus de lire et écrire des fichiers, vous pouvez également les manipuler comme vous le feriez directement depuis une ligne de commande ou d'un gestionnaire de fichiers. Les opérateurs de test de fichier, collectivement appelés opérateurs -X, examinent les attributs des fichiers et répertoires. Pour tester si un fichier existe :

 
Sélectionnez
say 'Present !' if -e $nomFichier;

L'opérateur -e a un seul opérande, le nom d'un fichier ou un descripteur de fichier ou de répertoire. Si le fichier existe, l'expression retournera une valeur vraie. perldoc -f -X répertorie tous les autres tests de fichiers ; les plus populaires sont :

  • -f
    qui retourne la valeur vraie si son opérande est un simple fichier ;
  • -d
    qui retourne la valeur vraie si son opérande est un répertoire ;
  • -r
    qui retourne la valeur vraie si les autorisations du fichier de son opérande permettent la lecture par l'utilisateur courant ;
  • -s
    qui renvoie la valeur vraie si son opérande est un fichier non vide.

Recherchez la documentation de ces opérateurs avec perldoc -f -r, par exemple.

La commande interne rename peut renommer un fichier ou le déplacer entre des répertoires. Elle prend deux opérandes, l'ancien et le nouveau chemin du fichier :

 
Sélectionnez
rename 'etoile_de_la_mort.txt', 'puits_de_carbone.txt';

# ou plus élégant :
rename 'etoile_de_la_mort' => 'puits_de_carbone .txt';

Il n'y a pas de commande interne pour copier un fichier, mais le module standard File::Copy fournit à la fois les fonctions copy() et move(). Utilisez la commande interne unlink pour supprimer un ou plusieurs fichiers. (La commande interne delete supprime un élément d'un hachage, pas un fichier du système de fichiers.) Ces fonctions et commandes internes retournent toutes la valeur vraie pour succès et initialisent $! en cas d'erreur.

Mieux que File::Spec

Path::Class fournit des méthodes pratiques pour vérifier certains attributs de fichier ainsi que pour supprimer complètement des fichiers de façon portable.

Perl suit son répertoire de travail courant. Par défaut, c'est le répertoire actif d'où vous avez lancé le programme. La fonction cwd() du module standard Cwd retourne le nom du répertoire de travail courant. La commande interne chdir tente de modifier le répertoire de travail courant. Pour travailler avec des fichiers ayant des chemins relatifs, il est essentiel de travailler à partir du répertoire correct.

Changer de répertoire

Si vous aimez les outils de ligne de commande pushd et popd, essayez File::pushd.

X-D. Modules

Beaucoup de gens considèrent le CPAN (Le CPANLe CPAN) comme l'avantage principal de Perl. Le CPAN est un système pour trouver et installer des modules. Un module est un paquetage contenu dans son propre fichier et pouvant être chargé avec use ou require. Un module doit être du code Perl valide. Il doit se terminer par une expression qui retourne une valeur vraie de sorte que l'analyseur Perl sache qu'il a chargé et compilé le module avec succès. Il n'y a aucune autre exigence, juste des conventions fortes.

Lorsque vous chargez un module, Perl fractionne le nom du paquetage sur les doubles deux-points (::) et transforme les composants du nom de paquet dans un chemin de fichier. Cela signifie que use SingeEtrange; indique à Perl de rechercher un fichier nommé SingeEtrange.pm dans chaque répertoire du tableau @INC, dans l'ordre, jusqu'à ce qu'il en trouve un ou épuise la liste.

De même, use SingeEtrange::Persistance; demande à Perl de rechercher un fichier nommé Persistance.pm dans chaque répertoire nommé SingeEtrange/ présent dans chaque répertoire dans @INC, et ainsi de suite. use SingeEtrange::UI::Mobile; indique à Perl de rechercher un chemin relatif du fichier SingeEtrange/UI/Mobile.pm dans chaque répertoire dans @INC.

Le fichier résultant peut contenir ou pas une déclaration de paquetage correspondant à son nom de fichier — ce n'est pas une exigence technique, mais cela facilite la compréhension.

Astuces perldoc

perldoc -l Module::Name affichera le chemin complet vers le fichier .pm pertinent, si ce fichier contient de la documentation sous forme de POD. perldoc -lm Module::Name affichera le chemin complet vers le fichier .pm. perldoc -m Module::Name affichera le contenu du fichier .pm.

X-D-1. Utiliser et importer

Lorsque vous chargez un module avec use, Perl le charge à partir du disque, puis appelle sa méthode import(), en passant les arguments que vous avez éventuellement fournis. Par convention, la méthode import() d'un module prend une liste des noms et exporte des fonctions et autres symboles dans l'espace de noms appelant. Ce n'est qu'une convention ; un module peut refuser de fournir une méthode import(), ou sa méthode import() peut avoir d'autres comportements. Des pragmas (PragmasPragmas) tels que strict utilisent les arguments pour modifier le comportement de la portée lexicale appelante au lieu d'exporter des symboles :

 
Sélectionnez
use strict;
# ... appelle strict->import()

use CGI ':standard';
# ... appelle CGI->import( ':standard' )

use feature qw( say switch );
# ... appelle feature->import( qw( say switch ) )

La commande interne no appelle la méthode unimport() d'un module, si elle existe, en passant tous les arguments. Cela est plus fréquent avec les pragmas qui introduisent ou modifient un comportement par le biais de import() :

 
Sélectionnez
use strict;
# aucune référence symbolique, aucun mot nu
# exige la déclaration des variables

{
    no strict 'refs';
    # références symboliques permises
    # les 'subs' et 'vars' de strict toujours en vigueur
}

Tant use que no prennent effet lors de la compilation, de sorte que :

 
Sélectionnez
use Module::Name qw( liste arguments );

... est identique à :

 
Sélectionnez
BEGIN
{
    require 'Module/Name.pm';
    Module::Name->import( qw( liste arguments ) );
}

De la même façon :

 
Sélectionnez
no Module::Name qw( liste arguments );

... est identique à :

 
Sélectionnez
BEGIN
{
    require 'Module/Name.pm';
    Module::Name->unimport(qw( liste d'arguments ));
}

... y compris require du module.

Les méthodes manquantes ne nous manquent jamais

Si les méthodes import() ou unimport() n'existent pas dans le module, Perl ne va pas générer un message d'erreur. Elles sont vraiment facultatives.

Vous pouvez appeler directement import() et unimport(), bien qu'il soit peu logique de le faire en dehors d'un bloc BEGIN ; une fois la compilation terminée, les effets de import() ou unimport() peuvent être limités.

Tant use que require respectent la casse (c'est-à-dire la distinction entre majuscules et minuscules). Bien que Perl connaisse la différence entre strict et Strict, votre combinaison de système d'exploitation et système de fichiers ne la connaît peut-être pas. Si vous écrivez use Strict;, Perl ne trouvera pas strict.pm sur un système de fichiers sensible à la casse. Avec un système de fichiers insensible à la casse, Perl chargera volontiers Strict.pm, mais rien ne se passera quand il essaie d'appeler Strict->import(). (strict.pm déclare un paquetage nommé strict.)

Les programmes portables font attention à la casse, même s'ils n'en ont pas besoin.

X-D-2. Exporter

Un module peut rendre les symboles globaux de paquetage disponibles à d'autres paquetages à travers un processus connu sous le nom d'exportation — souvent en appelant import() implicitement ou directement.

Le module standard Exporter est le moyen habituel pour exporter les symboles d'un module. Exporter repose sur la présence des variables globales de paquetages — en particulier @EXPORT_OK et @EXPORT, qui contiennent une liste de symboles à exporter sur demande.

Examinons un module SingeEtrange::Utilites qui offre plusieurs fonctions autonomes utilisables dans tout le système :

 
Sélectionnez
package SingeEtrange::Utilites;

use Exporter 'import';

our @EXPORT_OK = qw( round translate screech );

...

Tout autre programme ou module peut maintenant utiliser ce module et éventuellement, importer une partie ou chacune des trois fonctions exportées. Vous pouvez également exporter des variables :

 
Sélectionnez
push @EXPORT_OK, qw( $aragnee $saki $ecureuil );

Exportez des symboles par défaut en les énumérant dans @EXPORT au lieu de @EXPORT_OK :

 
Sélectionnez
our @EXPORT = qw( singe_danse singe_dort );

... de sorte que toute instruction use SingeEtrange::Utilities; importera les deux fonctions. Sachez cependant que le fait de spécifier des symboles à importer conduira à la non-importation des symboles par défaut ; vous obtenez seulement ce que vous demandez. Pour charger un module sans importer les symboles, utilisez une liste vide explicite :

 
Sélectionnez
# rend le module disponible, mais n'importe rien
use StrangeMonkey::Utilities ();

Indépendamment de toute liste d'importations, vous pouvez toujours appeler des fonctions depuis un autre paquetage avec leurs noms complets :

 
Sélectionnez
SingeEtrange::Utilities::cri();

Exportation simplifiée

Le module Sub::Exporter du CPAN fournit une interface plus agréable pour exporter des fonctions sans utiliser des variables globales de paquetage. Il offre également des options plus puissantes. Toutefois, Exporter peut exporter des variables, tandis que Sub::Exporter exporte uniquement des fonctions.

X-D-3. Organiser le code en modules

Perl ne vous oblige pas à utiliser des modules, paquetages ou espaces de noms. Vous pouvez mettre tout votre code dans un seul fichier .pl ou dans plusieurs fichiers .pl que vous appelez avec require selon vos besoins. Vous avez la possibilité de gérer votre code de la façon la plus appropriée, compte tenu de votre style de développement, du formalisme, du risque et de l'importance du projet, ainsi que de votre expérience et de votre facilité à déployer du code.

Cela dit, un projet ayant plus de quelques centaines de lignes de code gagne à être organisé en modules :

  • les modules aident à imposer une séparation logique entre des entités distinctes du système ;
  • les modules offrent une interface de séparation, procédurale ou OO ;
  • les modules encouragent une organisation naturelle du code source ;
  • l'écosystème de Perl possède de nombreux outils consacrés à la création, au maintien, à l'organisation et au déploiement des modules et des distributions ;
  • les modules fournissent un mécanisme de réutilisation du code.

Même si vous n'utilisez pas une approche orientée objet, la modélisation de chaque entité ou responsabilité distincte dans votre système avec son propre module rassemble le code lié et sépare le code séparé.

X-E. Distributions

Une distribution est une collection de métadonnées et de modules (ModulesModules) qui forme une seule unité redistribuable, testable et installable. La façon la plus simple de configurer, construire, rassembler dans un paquetage, tester et installer du code Perl est de suivre les conventions du CPAN. Ces conventions régissent la manière de créer le paquetage d'une distribution et de résoudre ses dépendances, où installer le logiciel, comment vérifier qu'il fonctionne, comment afficher la documentation et comment gérer un dépôt logiciel. Toutes ces directives sont issues du consensus de milliers de contributeurs travaillant sur des dizaines de milliers de projets. Une distribution construite selon les normes CPAN peut être testée sur plusieurs versions de Perl sur plusieurs plates-formes matérielles différentes dans les quelques heures suivant son ajout sur le CPAN, avec les erreurs signalées automatiquement aux auteurs — tout cela sans intervention humaine.

Vous pouvez choisir de ne jamais diffuser tout votre code sous forme de distributions publiques sur CPAN, mais vous pouvez utiliser les outils et conventions CPAN pour gérer même le code privé. La communauté Perl a construit cette étonnante infrastructure ; pourquoi ne pas en profiter ?

X-E-1. Attributs d'une distribution

Outre un ou plusieurs modules, une distribution comprend plusieurs autres fichiers et répertoires :

  • Build.PL ou Makefile.PL, un programme pilote permettant de configurer, construire, tester, regrouper et installer la distribution ;
  • MANIFEST, une liste de tous les fichiers contenus dans la distribution. Cela permet aux outils de vérifier si la distribution est complète ;
  • META.yml et/ou META.json, un fichier contenant des métadonnées à propos de la distribution et de ses dépendances ;
  • README, une description de la distribution, son intention, et les informations sur son droit d'auteur et octroi de licences ;
  • lib/, le répertoire contenant des modules Perl ;
  • t/, un répertoire contenant des fichiers de test ;
  • Changes, un fichier journal reprenant chaque changement significatif à la distribution.

Une distribution bien formée doit contenir un nom et un numéro de version unique (souvent obtenues de son module principal). Toute distribution que vous téléchargez à partir du réseau public CPAN doit se conformer à ces normes. Le service CPANTS (http://cpants.perl.org/) évalue si chaque distribution téléchargée respecte les lignes directrices et les conventions de paquetage et recommande des améliorations. Suivre les directives CPANTS ne signifie pas que le code fonctionne, mais que les outils de paquetage CPAN devraient comprendre la distribution.

X-E-2. Outils CPAN pour gérer les distributions

Perl est livré en standard avec plusieurs outils pour gérer les distributions :

  • CPAN.pm est le client officiel de CPAN. Bien que par défaut ce client installe des distributions du CPAN public, vous pouvez également l'utiliser sur votre propre dépôt logiciel à la place ou en plus du dépôt public ;
  • Module::Build est une suite d'outils en pur Perl pour configurer, construire, installer et tester de distributions. Elle fonctionne avec les fichiers Build.PL ;
  • ExtUtils::MakeMaker est un outil plus ancien, que Module::Build est destiné à remplacer. Il est encore largement utilisé, mais il est en mode maintenance et reçoit uniquement des corrections de bogues critiques. Il fonctionne avec les fichiers Makefile.PL ;
  • Test::More (TestsTests) est le module fondamental et le plus largement utilisé pour écrire des tests automatisés pour les logiciels Perl ;
  • TAP::Harness et prove (Exécuter des testsExécuter des tests) exécutent des tests et interprètent et communiquent leurs résultats.

En outre, plusieurs modules non standard du CPAN vous facilitent la vie en tant que développeur :

  • App::cpanminus est un client CPAN sans configuration. Il gère les cas les plus courants, utilise peu de mémoire, et travaille rapidement ;
  • App::perlbrew vous aide à gérer les installations multiples de Perl. Installez des nouvelles versions de Perl pour tests ou production, ou pour isoler des applications et leurs dépendances ;
  • CPAN::Mini et la commande cpanmini vous permettent de créer votre propre miroir (privé) du réseau public CPAN. Vous pouvez ajouter dans ce référentiel vos propres distributions et gérer les versions des modules publics disponibles dans votre organisation ;
  • Dist::Zilla automatise à distance les tâches de distribution courantes. Comme il utilise soit Module::Build, soit ExtUtils::MakeMaker, il peut remplacer votre utilisation directe de ceux-ci. Voir http://dzil.org/ pour un tutoriel interactif ;
  • Test::Reporter vous permet de générer le rapport des résultats des suites de tests automatisés pour les distributions que vous installez, fournissant à leurs auteurs plus de données sur les défaillances ;
  • Carton et Pinto sont deux projets récents qui aident à gérer et installer les dépendances du code. Aucun n'est encore largement utilisé, mais ils sont tous deux en cours de développement actif.

X-E-3. Conception des distributions

Le processus de conception d'une distribution pourrait remplir un livre (voir Writing Perl Modules for CPAN de Sam Tregar), mais quelques principes de conception vous aideront. Commencez avec un utilitaire tel que Module::Starter ou Dist::Zilla. Le coût initial de l'apprentissage de la configuration et des règles peut sembler un investissement exagéré, mais l'avantage d'avoir tout mis en place de la bonne façon (et dans le cas de Dist::Zilla, ne jamais devenir obsolète) vous dispensera d'un travail fastidieux.

Une distribution devrait suivre plusieurs lignes directrices n'ayant pas à voir avec le code :

  • chaque distribution effectue un seul but bien défini. Ce but peut même inclure plusieurs distributions connexes en les regroupant dans un seul paquet installable. Décomposez votre logiciel en distributions individuelles pour gérer correctement leurs dépendances et respecter leur encapsulation ;
  • chaque distribution contient un numéro unique de version. Les numéros de version doivent toujours augmenter. La politique de « version sémantique » (http://semver.org/) à trois nombres (version majeure, version mineure, version corrective) est saine et compatible avec l'approche de Perl ;
  • chaque distribution fournit une API bien définie. Une suite complète de tests automatisés peut vérifier si vous maintenez cette API entre les versions différentes. Si vous utilisez un miroir CPAN local pour installer vos propres distributions, vous pouvez réutiliser l'infrastructure CPAN pour tester les distributions et leurs dépendances. Vous obtenez facilement l'intégration en testant les composants réutilisables ;
  • les tests de distribution sont utiles et reproductibles. L'infrastructure CPAN met en œuvre des rapports de test automatisés. Utilisez-les ! ;
  • les interfaces sont simples et efficaces. Évitez l'utilisation des symboles globaux et les exports par défaut ; permettez aux gens d'utiliser uniquement ce dont ils ont besoin. Ne polluez pas leurs espaces de noms.

X-F. Le paquetage UNIVERSAL

Le paquetage standard UNIVERSAL fourni par Perl est l'ancêtre de tous les autres paquetages, au sens orienté objet (MooseMoose). UNIVERSAL fournit à ses classes filles quelques méthodes à utiliser, hériter ou redéfinir.

X-F-1. La Méthode VERSION()

La méthode VERSION() retourne la valeur de la variable $VERSION du paquetage ou de la classe qui l'invoque. Si vous fournissez un numéro de version en tant que paramètre optionnel, la méthode lèvera une exception si la $VERSION interrogée n'est pas égale ou supérieure au paramètre.

Étant donné un module SingeHurleur de version 1.23 :

 
Sélectionnez
my $hm = SingeHurleur->new;

say SingeHurleur->VERSION;    # affiche 1.23
say $hm->VERSION;             # affiche 1.23
say $hm->VERSION( 0.0  );     # affiche 1.23
say $hm->VERSION( 1.23 );     # affiche 1.23
say $hm->VERSION( 2.0  );     # exception !

Il est très rarement utile de surcharger VERSION().

X-F-2. La méthode DOES()

La méthode DOES() prend en charge l'utilisation des rôles (RôlesRôles) dans les programmes. Passez-lui un invoquant et le nom d'un rôle, et la méthode retournera la valeur vraie si la classe appropriée remplit ce rôle d'une façon ou d'une autre, que ce soit par héritage, délégation, composition, application de rôle, ou tout autre mécanisme.

La mise en œuvre par défaut de DOES() revient à isa(), parce que l'héritage est un mécanisme par lequel une classe peut remplir un rôle. Étant donné un Capucin :

 
Sélectionnez
say Capucin->DOES( 'Singe'      );  # prints 1
say $cappy->DOES(  'Singe'      );  # prints 1
say Capucin->DOES( 'Invertebre' );  # prints 0

Surchargez DOES() si vous consommez manuellement un rôle ou fournissez d'une quelconque façon une équivalence allomorphique.

X-F-3. La méthode can()

La méthode can() accepte une chaîne contenant le nom d'une méthode. Elle retourne une référence vers la fonction qui implémente cette méthode, si elle existe. Sinon, elle retourne la valeur fausse. Vous pouvez l'appeler sur une classe, un objet, ou un nom d'un paquetage. Dans ce dernier cas, elle renvoie une référence de fonction et non de méthode... mais vous ne pouvez pas faire la différence, vu que ce n'est qu'une référence...

Étant donnée une classe nommée SingeAraignee ayant une méthode appelée crier, obtenez une référence à la méthode comme ceci :

 
Sélectionnez
if (my $methode = SingeAraignee->can( 'crier' )) {...}

Cette technique conduit au modèle de vérification de l'existence d'une méthode avant de l'appeler :

 
Sélectionnez
if (my $methode = $sm->can( 'crier' )
{
    # méthode; pas une fonction
    $sm->$methode();
}

Utilisez can() pour tester si un paquetage met en œuvre une fonction ou une méthode spécifique :

 
Sélectionnez
use Class::Load;

die "Impossible de charger $module!"
    unless load_class( $module );

if (my $enregistrer = $module->can( 'enregistrer' ))
{
    # fonction; pas une méthode
    $enregistrer->();
}

Module::Pluggable

Comme son nom l'indique, le module Class::Load du CPAN simplifie le chargement des classes. De même, le module Module::Pluggable effectue la plus grande part du travail de construction et gestion des systèmes de plugin. Familiarisez-vous avec les deux distributions.

X-F-4. La méthode isa()

La méthode isa() reçoit une chaîne contenant le nom d'une classe ou le nom d'un type de base (SCALAR, ARRAY, HASH, Regexp, IO, et CODE). Appelez-la sur un objet comme une méthode de classe ou d'instance. isa() retourne la valeur vraie si son invoquant est ou dérive de la classe nommée, ou si l'invoquant est une référence liée au type donné.

Étant donné un objet $poivre (une référence de hachage liée à la classe Singe, qui hérite de la classe Mammifere) :

 
Sélectionnez
say $poivre->isa( 'Singe'    );  # affiche 1
say $poivre->isa( 'Mammifere');  # affiche 1
say $poivre->isa( 'HASH'     );  # affiche 1
say Singe->isa(   'Mammifere');  # affiche 1

say $poivre->isa( 'Dauphin'  );  # affiche 0
say $poivre->isa( 'ARRAY'    );  # affiche 0
say Singe->isa(   'HASH'     );  # affiche 0

Toute classe peut surcharger isa(). Cela peut être utile lorsque vous simulez des objets (voir Test::MockObject et Test::MockModule sur le CPAN) ou pour du code qui n'utilise pas les rôles (RôlesRôles). Sachez que toute classe qui redéfinit isa() doit généralement avoir une bonne raison pour le faire.

Est-ce qu'une classe existe ?

Alors que UNIVERSAL::isa() et UNIVERSAL::can() sont toutes les deux des méthodes (L'équivalence méthode-fonctionL'équivalence méthode-fonction), vous pouvez utiliser en toute sécurité cette dernière en tant que fonction uniquement pour déterminer si une classe existe en Perl. Si UNIVERSAL::can ( $classname, 'can' ) retourne la valeur vraie, quelqu'un a défini quelque part une classe nommée $classname. Cette classe peut ne pas être utilisable, mais elle existe.

X-F-5. Étendre UNIVERSAL

Il est tentant de stocker d'autres méthodes dans UNIVERSAL pour les rendre disponibles à tous les autres classes et objets Perl. Évitez cette tentation ; ce comportement global peut avoir des effets secondaires subtils, en particulier dans le code que vous n'avez pas écrit et ne maintenez pas, car il n'y a aucune contrainte.

Cela dit, l'abus occasionnel de UNIVERSAL à des fins de débogage et de correction des mauvais comportements par défaut peut être excusable. Par exemple, la distribution UNIVERSAL::ref de Joshua ben Jore rend utilisable l'opérateur quasi inutile ref(). Les distributions UNIVERSAL::can et UNIVERSAL::isa peuvent vous aider à déboguer des bogues en rapport avec le polymorphisme (L'équivalence méthode-fonctionL'équivalence méthode-fonction). Perl::Critic peut détecter ceux-ci et d'autres problèmes.

En dehors de bouts de code très soigneusement contrôlés et des situations très spécifiques, très pragmatiques, il n'y a aucune raison de mettre le code directement dans UNIVERSAL. Il y a presque toujours beaucoup de meilleurs choix de conception.

X-G. Génération de code

Les programmeurs débutants écrivent plus de code qu'ils n'ont besoin d'en écrire, en partie par manque de familiarité avec les langages, bibliothèques et idiomes, mais aussi en raison de l'inexpérience. Ils commencent par écrire de longues listes de code procédural, puis découvrent les fonctions, les paramètres, les objets, et — peut-être — les fonctions d'ordre supérieur et les fermetures.

Lorsque vous devenez un meilleur programmeur, vous allez écrire moins de code pour résoudre les mêmes problèmes. Vous utiliserez de meilleures abstractions. Vous écrirez du code plus général. Vous pouvez réutiliser le code — et quand vous pouvez ajouter des fonctionnalités en supprimant du code, vous obtiendrez quelque chose de grandiose.

Écrire des programmes qui écrivent des programmes pour vous — métaprogrammation ou génération de code — vous permet de construire des abstractions réutilisables. Alors que vous pouvez faire un énorme gâchis, vous pouvez également créer des choses étonnantes. Par exemple, les techniques de métaprogrammation rendent Moose possible (MooseMoose).

La technique de AUTOLOAD (AUTOLOADAUTOLOAD) pour les fonctions et les méthodes manquantes illustre cette technique sous une forme spécifique : le système de Perl de recherche des fonctions et méthodes vous permet de contrôler ce qui se passe lorsque la recherche normale de méthodes échoue.

X-G-1. eval

La technique de génération de code la plus simple est de construire une chaîne de caractères contenant un fragment de code Perl valide et de la compiler avec l'opérateur eval sur cette chaîne. À la différence de la forme eval de bloc, qui intercepte les exceptions, la forme eval de chaîne compile le contenu de la chaîne dans la portée courante, y compris le paquetage courant et les liaisons lexicales.

Une utilisation courante de cette technique fournit une solution de repli si vous ne pouvez pas (ou ne voulez pas) charger une dépendance optionnelle :

 
Sélectionnez
eval { require Singe::Traceur }
    or eval 'sub Singe::Traceur::log {}';

Si Singe::Traceur n'est pas disponible, ce code définit une fonction log() qui ne fera rien. Cet exemple simple est trompeur, parce qu'utiliser eval de façon correcte nécessite un peu de travail. Vous devez gérer des problèmes de guillemets lors de l'inclusion des variables au sein de votre code eval. Ajoutez plus de complexité pour interpoler certaines variables, mais pas d'autres :

 
Sélectionnez
sub generer_accesseurs
{
    my ($methname, $attrname) = @_;

    eval <<"END_ACCESSEUR";
    sub get_$methname
    {
        my \$self = shift;
        return \$self->{$attrname};
    }

    sub set_$methname
    {
        my (\$self, \$value) = \@_;
        \$self->{$attrname}  = \$value;
    }
END_ACCESSEUR
}

Malheur à ceux qui oublient un antislash ! Bonne chance pour convaincre votre coloration syntaxique de ce qui se passe ! Pire encore, chaque invocation de chaîne eval génère une nouvelle structure de données représentant l'ensemble du code. La compilation du code n'est pas gratuite non plus. Pourtant, même avec ses limites, cette technique est simple et utile.

X-G-2. Fermetures paramétriques

Alors que la construction des accesseurs et mutateurs avec eval est simple, les fermetures (FermeturesFermetures) vous permettent d'ajouter des paramètres au code généré lors de la compilation sans nécessiter une évaluation supplémentaire :

 
Sélectionnez
sub generer_accesseurs
{
    my $attrname = shift;

    my $getter = sub
    {
        my $self = shift;
        return $self->{$attrname};
    };

    my $setter = sub
    {
        my ($self, $value) = @_;
        $self->{$attrname} = $value;
    };

    return $getter, $setter;
}

Ce code permet d'éviter les problèmes désagréables de guillemets et compile chaque fermeture une seule fois. Il utilise même moins de mémoire en partageant le code compilé entre toutes les instances de fermeture. Tout ce qui diffère est la liaison à la lexicale $attrname. Dans un processus de longue durée ou une classe avec beaucoup d'accesseurs, cette technique peut être très utile.

L'installation dans les tables de symboles est relativement facile, mais assez laide :

 
Sélectionnez
my ($get, $set) = generer_accesseurs( 'tarte' );

no strict 'refs';
*{ 'get_tarte' } = $get;
*{ 'set_tarte' } = $set;

Cette syntaxe bizarre d'astérisque (11), qui déréférence un hachage, renvoie à un symbole dans la table des symboles courante, qui est la section de l'espace de noms courant contenant des symboles globalement accessibles tels que des paquetages globaux, fonctions et méthodes. L'affectation d'une référence à une entrée de la table de symboles installe ou remplace cette entrée. Afin de promouvoir une fonction anonyme au rang de méthode, stockez la référence de cette fonction dans la table de symboles.

L'affectation d'une chaîne, et non d'une variable littérale, à un symbole de la table de symboles, est une référence symbolique. Vous devez désactiver la vérification des références de strict pour l'opération. De nombreux programmes ont un bogue subtil dans le code similaire, car ils font l'affectation et la génération de code en une seule ligne :

 
Sélectionnez
no strict 'refs';

*{ $methname } = sub {
    # bogue subtil : strict refs désactivé ici également
};

Cet exemple désactive strict pour le bloc externe ainsi que pour le corps de la fonction elle-même. Or, seule l'affectation viole la vérification stricte de référence, donc désactivez les restrictions pour cette seule opération :

 
Sélectionnez
{
    my $sub = sub { ... };

    no strict 'refs';
    *{ $methname } = $sub;
}

Si le nom de la méthode est une chaîne littérale dans votre code source, plutôt que le contenu d'une variable, vous pouvez l'attribuer directement au symbole pertinent :

 
Sélectionnez
{
    no warnings 'once';
    (*get_tarte, *set_tarte) =
         generer_accesseurs( 'pie' );
}

L'affectation directement à la glob ne viole pas les restrictions, mais mentionner chaque glob une seule fois fait produire un avertissement « used only once » que vous pouvez désactiver avec le pragma warnings.

Tables de symboles simplifiées

Utilisez le module Package::Stash du CPAN au lieu de manipuler vous-même les tables de symboles.

X-G-3. Manipulation lors de la compilation

À la différence du code écrit explicitement comme du code, le code généré par une chaîne eval sera compilé à l'exécution. De même, alors que vous pourriez vous attendre à ce qu'une fonction normale soit disponible pendant toute la durée de votre programme, une fonction générée dynamiquement pourrait ne pas être disponible quand vous vous y attendez.

Forcez Perl à exécuter ce code — pour générer un autre code — lors de la compilation en l'enveloppant dans un bloc BEGIN. Lorsque l'analyseur Perl rencontre un bloc étiqueté BEGIN, il analyse et compile l'ensemble du bloc, puis l'exécute (sauf s'il comporte des erreurs de syntaxe). Lorsque c'est terminé, la compilation continuera comme s'il n'y avait eu aucune interruption.

La différence entre :

 
Sélectionnez
sub get_age   { ... }
sub set_age   { ... }

sub get_nom   { ... }
sub set_nom   { ... }

sub get_poids { ... }
sub set_poids { ... }

... et :

 
Sélectionnez
sub creer_accesseurs { ... }

BEGIN
{
    for my $accesseur (qw( age nom poids ))
    {
        my ($get, $set) =
            creer_accesseurs( $accesseur );

        no strict 'refs';
        *{ 'get_' . $accesseur } = $get;
        *{ 'set_' . $accesseur } = $set;
    }
}

... est surtout une question de maintenabilité.

Dans un module, tout le code se trouvant en dehors de fonctions s'exécute lorsque vous appelez la fonction use pour charger le module, en raison de l'instruction implicite BEGIN que Perl ajoute autour du require et de l'import (ImportationImportation) exécutés par use. Tout code en dehors d'une fonction, mais à l'intérieur du module s'exécute avant que l'appel de import() ne se produise. Si en revanche vous utilisez require pour charger le module, il n'existe pas de bloc BEGIN implicite. Après la fin de la compilation du code du module, Perl exécutera le code en dehors des fonctions.

Méfiez-vous de l'interaction entre la déclaration lexicale (l'association d'un nom à une portée) et l'affectation lexicale. La première se produit lors de la compilation, tandis que la seconde a lieu à l'exécution. Ce code a un bogue subtil :

 
Sélectionnez
use UNIVERSAL::require;

# bogué; ne faites pas cela
my $package_voulu = 'Singe::Jetpack';

BEGIN
{
    $package_voulu->require;
    $package_voulu->import;
}

... car le bloc BEGIN s'exécutera avant que l'affectation de la valeur chaîne à $package_voulu ne se produise. Le résultat sera une exception à cause de la tentative d'appel de la méthode require()  (12) sur une valeur indéfinie.

X-G-4. La classe Class::MOP

À la différence de l'installation des références de fonction pour remplir des espaces de noms et créer des méthodes, il n'y a aucun moyen simple en Perl pour créer des classes à l'exécution. Moose vient à la rescousse, avec sa bibliothèque interne Class::MOP. Il fournit un protocole métaobjet — un mécanisme pour créer et manipuler un système objet en manipulant des objets.

Plutôt que d'écrire votre propre code fragile utilisant un eval de chaîne ou d'essayer de modifier manuellement des tables de symboles, vous pouvez manipuler les entités et les abstractions de votre programme par des objets et des méthodes.

Pour créer une classe :

 
Sélectionnez
use Class::MOP;

my $class = Class::MOP::Class->create(
                'Clef::Anglaise'
            );

Ajoutez des attributs et méthodes à la classe lorsque vous la créez :

 
Sélectionnez
my $class = Class::MOP::Class->create(
    'Clef::Anglaise' =>
    (
        attributes =>
        [
            Class::MOP::Attribute->new('$materiel'),
            Class::MOP::Attribute->new('$couleur'),
        ]
        methods =>
        {
            serrer    => sub { ... },
            desserrer => sub { ... },
        }
    ),
);

... ou à la métaclasse (l'objet qui représente cette classe) une fois créée :

 
Sélectionnez
$class->add_attribute(
    experience => Class::MOP::Attribute->new('$xp')
);

$class->add_method( bash_zombie => sub { ... } );

Vous pouvez inspecter la métaclasse :

 
Sélectionnez
my @attrs = $class->get_all_attributes;
my @meths = $class->get_all_methods;

De même Class::MOP::Attribute et Class::MOP::Method vous permettent la création, la manipulation et l'introspection des attributs et méthodes.

X-H. Surcharge

Perl n'est pas un langage principalement orienté objet. Ses types de données standard (scalaires, tableaux et hachages) ne sont pas des objets avec des méthodes, mais vous pouvez contrôler le comportement de vos propres classes et objets, en particulier quand ils subissent une coercition ou évaluation contextuelle. Cela est la surcharge.

La surcharge est subtile, mais puissante. Un exemple intéressant est de surcharger le comportement d'un objet dans le contexte booléen. Dans le contexte booléen, un objet sera évalué à une valeur vraie, sauf si vous surchargez l'évaluation en contexte booléen.

Surcharger le contexte booléen

Pourquoi surcharger le contexte booléen ? Supposons que vous utilisez le modèle Null Object (http://www.c2.com/cgi/wiki?NullObject) pour faire évaluer à faux vos propres objets dans un contexte booléen.

Vous pouvez surcharger le comportement de l'objet pour presque chaque opération ou coercition : conversion en chaîne, en nombre, en booléen, itération, invocation, accès à un tableau, accès à un hachage, opérations arithmétiques, opérations de comparaison, correspondance intelligente, opérations binaires et même affectation. Les conversions en chaîne, en nombre et en booléen sont les plus importantes et les plus courantes.

X-H-1. Surcharge des opérations courantes

Le pragma overload associe des fonctions aux opérations susceptibles à être surchargées en passant des paires d'arguments, dont la clé est le nom d'un type de surcharge et la valeur est une référence de fonction. Une classe Null, qui surcharge l'évaluation booléenne de sorte qu'elle soit toujours évaluée à une valeur fausse, pourrait ressembler à ceci :

 
Sélectionnez
package Null
{
    use overload 'bool' => sub { 0 };

    ...
}

Il est facile d'ajouter une conversion en chaîne :

 
Sélectionnez
package Null
{
    use overload
        'bool' => sub { 0 },
        '""'   => sub { '(null)' };
}

La surcharge de la coercition numérique est plus complexe, car les opérateurs arithmétiques ont tendance à être opérateurs binaires (AritéArité). Dans le cas de deux opérandes, les deux ayant la méthode d'addition surchargée, quelle surcharge doit être prioritaire ? La réponse doit être cohérente, facile à expliquer, et compréhensible par les personnes qui n'ont pas lu le code source de l'application.

perldoc overload essaie d'expliquer cela dans les sections intitulées Calling Conventions for Binary Operations et MAGIC AUTOGENERATION, mais la solution la plus simple consiste à surcharger la conversion en nombre (en utilisant '0+') et de dire à overload d'utiliser les surcharges fournies comme des solutions de repli si possible :

 
Sélectionnez
package Null
{
    use overload
        'bool'   => sub { 0 },
        '""'     => sub { '(null)' },
        '0+'     => sub { 0 },
        fallback => 1;
    }

L'affectation d'une valeur vraie à fallback permet à Perl d'utiliser toute autre surcharge définie pour composer l'opération demandée lorsque c'est possible. Si ce n'est pas possible, Perl agira comme s'il n'y avait aucune surcharge en vigueur. C'est souvent ce que vous voulez.

Sans fallback, Perl utilisera uniquement les surcharges spécifiques que vous avez fournies. Si quelqu'un essaie d'effectuer une opération que vous n'avez pas surchargée, Perl signalera une exception.

X-H-2. Surcharge et héritage

Les sous-classes héritent des surcharges de leurs parentes. Elles peuvent redéfinir ce comportement de l'une des deux façons suivantes. Si la classe parente utilise la surcharge comme montré, avec des références de fonctions fournies directement, une classe enfant doit redéfinir le comportement surchargé du parent en utilisant directement overload.

Si vous écrivez une classe parente, utilisez un nom de méthode plutôt qu'une référence de fonction anonyme. Cela va permettre aux classes enfants de prouver leurs propres surcharges en redéfinissant les méthodes nommées :

 
Sélectionnez
package Null
{
    use overload
        'bool'   => 'get_bool',
        '""'     => 'get_string',
        '0+'     => 'get_num',
        fallback => 1;
}

Toute classe enfant peut faire quelque chose de différent pour la coercition booléenne, par exemple en surchargeant get_bool().

X-H-3. Utilisations de surcharge

La surcharge peut sembler un outil tentant à utiliser pour produire des raccourcis symboliques de nouvelles opérations, mais c'est rarement pour une bonne raison. Le module IO::All du CPAN pousse cette idée à sa limite. En retour, vous obtenez une API simple et élégante. Pourtant, pour chaque API brillante affinée grâce à l'utilisation appropriée de la surcharge, une douzaine de problèmes surgissent. Parfois, le meilleur code évite l'habileté en faveur de la simplicité.

La redéfinition de l'addition, multiplication, et même concaténation sur une classe Matrice est logique, seulement parce que la notation existante pour ces opérations est omniprésente. Un nouveau domaine de problème sans une notation établie est un mauvais candidat à la surcharge, car c'est un domaine de problème où vous devez plisser les yeux pour faire les opérateurs existants de Perl correspondre à une notation différente.

Le livre De l'art de programmer en Perl de Damian Conway suggère une autre utilisation de la surcharge : pour éviter les abus accidentels d'objets. Par exemple, une surcharge de la conversion en nombre avec croak() pour des objets qui n'ont pas de représentation numérique unique raisonnable peut vous aider à trouver et à corriger les bogues réels.

X-I. Données d'origine non fiable et le mode taint

Certaines fonctionnalités de Perl peuvent vous aider à écrire des programmes sécurisés. Ces outils ne remplacent pas la réflexion et planification approfondie, mais ils récompensent la prudence et la compréhension et peuvent vous aider à éviter les erreurs subtiles.

X-I-1. Utilisation du mode taint

Le mode taint attache une métadonnée adhésive à toutes les données qui viennent de l'extérieur de votre programme. Toutes les données obtenues à partir des données d'origine peu fiable sont également contaminées. Vous pouvez utiliser des données contaminées au sein de votre programme, mais si vous les utilisez pour influer sur le monde extérieur — si vous les utilisez de façon non sécurisée — Perl lancera une exception fatale.

perldoc perlsec explique le mode taint en grand détail.

Lancez votre programme avec l'argument de ligne de commande -T pour activer le mode taint. Si vous utilisez cet argument sur la ligne #! d'un programme, vous devez exécuter directement le nom du programme. Si vous l'exécutez avec perl mytaintedappl.pl et omettez l'option -T, Perl se terminera avec une exception — au moment où Perl rencontre l'option sur la ligne #!, il a raté sa chance de vérifier les données de l'environnement représenté par %ENV, par exemple.

X-I-2. Sources de contamination

La contamination peut provenir de deux sources : fichier d'entrée et environnement d'exploitation du programme. Le premier est ce que vous lisez à partir d'un fichier ou recevez de la part des utilisateurs dans le cas de la programmation Web ou un réseau. Le second comprend tous les arguments de ligne de commande, les variables d'environnement et des données provenant des appels système. Même les opérations telles que la lecture d'un descripteur de répertoire produisent des données contaminées.

La fonction tainted() du module standard Scalar::Util retourne vrai si son argument est contaminé :

 
Sélectionnez
die 'Oh non ! Données contaminées !'
    if Scalar::Util::tainted( $valeur_suspecte );

X-I-3. Décontamination de données

Pour décontaminer des données, vous devez en extraire certaines parties vérifiées avec des expressions régulières capturantes. Ces données capturées seront décontaminées.

Par exemple, si votre utilisateur doit saisir un numéro de téléphone américain, vous pouvez décontaminer la donnée obtenue avec :

 
Sélectionnez
die 'Numéro toujours contaminé !'
    unless $numero =~ /(\(/d{3}\) \d{3}-\d{4})/;

my $numero_verifie = $1;

Plus votre motif d'expression régulière est spécifique sur ce que vous autorisez, plus votre programme peut être sûr. L'approche inverse qui refuse certains éléments ou formes spécifiques court le risque de négliger quelque chose de dangereux. Mieux vaut interdire quelque chose de sûr, mais inattendu, que d'accepter quelque chose de nocif qui paraît sûr. Pourtant, rien ne vous empêche d'écrire un motif capturant la totalité du contenu d'une variable, mais, dans ce cas, pourquoi utiliser le mode taint ?

X-I-4. Décontamination de l'environnement

La variable superglobale %ENV représente les variables d'environnement du système. Ces données sont contaminées parce que des forces qui échappent au contrôle du programme peuvent y manipuler des valeurs. Toute variable d'environnement qui modifie la façon dont Perl ou le shell trouvent des fichiers et des répertoires est un vecteur d'attaque. Un programme sensible à contamination devrait supprimer plusieurs clés de %ENV et définir $ENV{PATH} à un chemin spécifique et bien sécurisé :

 
Sélectionnez
delete @ENV{ qw( IFS CDPATH ENV BASH_ENV ) };
$ENV{PATH} = '/path/to/app/binaries/';

Si vous ne définissez pas correctement $ENV{PATH}, vous recevrez des messages sur son insécurité. Si cette variable d'environnement contenait le répertoire de travail courant, ou si elle contenait des répertoires relatifs, ou si les répertoires spécifiés avaient des autorisations d'écriture à tout le monde, un attaquant ingénieux pourrait détourner les appels système pour commettre des méfaits.

Pour des raisons similaires, @INC ne contient pas le répertoire de travail courant en mode taint. Perl ignorera également les variables d'environnement PERL5LIB et PERLLIB. Utilisez le pragma lib ou l'option -I de la ligne de commande perl pour ajouter des répertoires de bibliothèques au programme.

X-I-5. Les pièges et limites du mode taint

Le mode taint est tout ou rien. Il est soit actif, soit inactif. Cela amène parfois les gens à utiliser des motifs permissifs pour décontaminer des données, et donne l'illusion de sécurité. Dans ce cas, le mode taint n'offre aucune sécurité réelle. Examinez attentivement vos règles de décontamination.

Malheureusement, tous les modules ne traitent pas les données contaminées de façon appropriée. C'est un bogue que les auteurs du CPAN devraient prendre au sérieux. Si vous devez sécuriser du code hérité, envisagez l'utilisation de l'option -t, qui active le mode taint, mais fait passer les violations du mode taint du rang d'erreurs fatales à celui de simples avertissements. Ce n'est pas une solution de rechange au substitut pour le mode taint complet, mais il vous permet de sécuriser les programmes existants sans l'approche tout ou rien de -T.


précédentsommairesuivant
 Pensez-y comme à un sigil de typeglob, où typeglob est un terme du jargon Perl pour la « table de symboles » (NdA).
 UNIVERSAL::require ajoute une méthode require() à UNIVERSAL (NdA).

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par chromatic et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2015 Developpez.com.