Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Créer un diaporama pas à pas - 6 - Pratique de débogage

Compatible ActionScript 2. Cliquer pour en savoir plus sur les compatibilités.Par lilive (Olivier Tarasse), le 08 juillet 2008

Objectif

Bonjour. Cette série de tutoriaux, à l'usage des débutants en ActionScript 2, présente des notions fondamentales de programmation par le biais de la réalisation d'un diaporama en ligne.

Si vous n'étes pas intéréssés par la création d'un diaporama, et que vous lisez cet article pour vous informer sur les méthodes de débogage, vous pourrez être intéressés en priorité par les sections suivantes:

Pour ceux qui suivent la réalisation du diaporama:
Dans la partie précédente, nous avons encore étoffé notre code, et mis en place la barre de progression du téléchargement. Nous avons maintenant plusieurs fonctions qui s'enchaînent et s'appellent les unes les autres. La complexité augmentant, il devient de plus en plus difficile d'avoir une vision claire du fonctionnement du programme.

Peut-être le résultat que nous avons obtenu vous semble-t-il satisfaisant, mais… peut-être pas!

Nous allons dans cette partie nous intéresser à un moment important dans le développement d'une application: La phase de test et de rectification des erreurs, appelée phase de débogage. Ceci va consister à tester notre application (le diaporama) en la poussant dans ses retranchements, pour découvrir si elle ne contient de bogues, et à tenter de les corriger. En fait, cette partie vous propose un long exercice de débogage, si vous voulez bien vous y livrer.

Avant d'aller plus loin, vous pouvez jeter un oeil à cet article de wikipedia qui nous apprend entre autre l'origine du mot bogue.


I - La phase de tests

1- Essayer tous les cas de figure

Il arrive très fréquemment qu'un programme semble fonctionner, mais que face à une situation inhabituelle une erreur ou un dysfonctionnement se produise. Il est bon de tester un programme dans le maximum de situations différentes. Dans notre cas, nous pouvons essayer de manipuler les boutons de toutes les façons qui nous passent par la tête, pour vérifier que le diaporama répond toujours correctement.

Il est extrêmement difficile (impossible?) d'être sûr d'avoir corrigé toutes les erreurs, car comment savoir si on a envisagé tous les cas de figure? Toujours est-il que j'ai détecté pour ma part 2 situations où le diaporama pourrait mieux réagir.

Si besoin, je vous invite maintenant à refaire des tests, et à voir si vous tombez d'accord avec moi…

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
Testez-moi!

2- Les symptômes

Je ne peux absolument pas affirmer que nos observations soient les mêmes, puisque je ne peux être certain d'avoir essayé toutes les situations. Et d'ailleurs je ne peux tester le diaporama que sur ma propre machine. Un programme peut tourner parfaitement sur une machine, et boguer sur une autre. De ce côté là, flash assure quand même une certaine homogénéité, à condition que la version de FlashPlayer de l'utilisateur soit au moins aussi récente que celle du développeur, ce qui peut être vérifié (voir "Pour en savoir plus").

Quoi qu'il en soit, voici mes observations. Je note un comportement insatisfaisant dans les deux cas suivants:

  • Lors d'un premier parcours de l'album, si l'image 1 est affichée, si le bouton image suivante est cliqué, puis recliqué alors que l'image 2 n'est pas encore complètement chargée et que la barre de progression est déjà assez remplie, la progression semble “geler” pendant un temps, puis reprend, puis l'image 3 s'affiche.
  • Si un bouton est cliqué pendant le fondu enchaîné entre deux images, on a des comportements inattendus. On voir s'afficher une version grisée d'une image pendant un bref instant. Au pire on a l'impression que le diaporama se dérègle et que les images ne se succèdent plus dans le bon ordre 1).

3- Le diagnostic

Pour remédier à ces deux problèmes, il va falloir essayer d'en comprendre la cause. Les symptômes paraissant indépendants, nous allons nous pencher d'abord isolément sur le premier problème, quitte à nous apercevoir ce faisant qu'il est lié au second.

D'après wikipedia, le diagnostic est le raisonnement menant à l'identification de la cause (l'origine) d'une défaillance, d'un problème ou d'une maladie. Comment raisonnerons-nous?


II - La barre de progression ne revient pas à zéro

Je vais proposer une approche pour diagnostiquer le premier problème. Mais auparavant, ce serait un très bon exercice que vous essayiez de le faire par vous-mêmes. Et comme le meilleur moyen de vérifier la justesse du diagnostic est d'essayer de programmer le soin qui devrait y remédier, je vous propose de vous lancer. Je vous encourage vivement à tenter l'expérience, car je crois que cela fait partie des moments les plus formateurs de l'apprenti-programmeur.

C'est à vous… :-)

La suite expose une façon de faire.

1- Simuler le déroulement du programme

Une démarche courante pour comprendre l'origine du problème est de se mettre à la place de l'ordinateur, et de faire comme si on exécutait le programme. Il existe aussi des programmes, nommés débogueurs, qui permettent de simuler l'exécution pas à pas d'un programme, tout en visualisant le résultat en cours. L'IDE de Adobe offre cette possibilité, dont nous ne nous servirons pas ici (pour plus de détails voir le chapitre IV).

Donc mettons-nous à la place de Flash Player, qui exécute notre programme. Imaginons que la première image a été chargée et affichée, que le MovieClip progressMC n'est pas visible, et que l'utilisateur clique sur le bouton “image suivante”.

La fonction chargerImageSuivante est exécutée, donne la valeur 1 à la variable imageActuelle, et appelle la fonction chargerImage.

La fonction chargerImage est exécutée: Le moment d'appel est stocké dans startLoadTime, et le chargement commence.

La fonction onLoadProgress est régulièrement appelée:

  • Lors de la première seconde, progressMC n'étant pas visible, loadTime est calculé, sa valeur étant inférieure à 1000 la fonction afficherProgressMC n'est pas appelée, progressMC reste donc invisible et la fonction prend fin.
  • Au bout d'une seconde afficherProgressMC est exécutée. L'apparence de progressMC est initialisée, la barre de progression est donc “vide”, puis progressMC est affiché.

A partir de là, loadTime n'est plus calculé à chaque appel de onLoadProgress, et comme progressMC est visible, la nouvelle largeur de la barre de progression est calculée. Comme loadedBytes augmente, cette largeur augmente, et un rectangle à chaque fois plus large est dessiné par dessus le rectangle existant. La barre donne l'impression de “se remplir”.

Jusque-là tout va bien.

Imaginons que l'utilisateur clique sur le bouton “image suivante” avant que le chargement de cette image soit terminé. Disons que loadedBytes vaut 80% de totalBytes à ce moment là. Que se passe-t-il?

La fonction chargerImageSuivante est exécutée une seconde fois, donne la valeur 2 à la variable imageActuelle, et appelle la fonction chargerImage.

La fonction chargerImage est exécutée: Le moment d'appel est stocké dans startLoadTime, et le chargement de l'image 2 commence.

progressMC est toujours visible, et son contenu graphique n'a pas changé. On a toujours affiché la barre de progression remplie au niveau qu'elle a atteinte lors du chargement de l'image 1, soit 80%.

La fonction onLoadProgress est régulièrement appelée:

  • Comme progressMC est visible, les instruction conditionnelles des premières lignes ne sont pas exécutées.
  • La nouvelle largeur de la barre de progression est calculée. Au début du chargement, lors des premiers appels de onLoadProgress, loadedBytes est inférieur à 80% de totalBytes (puisque on charge une autre image), la largeur de remplissage est donc inférieure à celle du remplissage déjà dessiné.
  • Les appels de drawFilledRectangle dessinent donc un rectangle blanc moins large par-dessus le rectangle blanc qui était déjà dessiné, ce qui ne change rien à l'apparence de la barre. On a donc l'impression qu'il ne se passe plus rien, la barre de progression semble “gelée”.
  • A partir du moment où loadedBytes dépasse 80%, drawFilledRectangle dessine des rectangles plus grands que ceux déjà affichés, et la barre de progression se remet à grandir.

Le problème est donc que si l'utilisateur clique sur le bouton “image suivante” pendant un chargement, le dessin de la barre n'est pas réinitialisé. Le diagnostic est posé.

2- Le traitement

Nous avons donc besoin d'effacer le remplissage de la barre de progression à chaque fois qu'un bouton est cliqué. C'était donc une mauvaise approche d'écrire une fonction afficherProgressMC qui à la fois initialise le dessin de progressMC et à la fois le rend visible, car il est des situations où il faut réinitialiser le dessin alors que le MovieClip est déjà visible.

Nous allons donc renommer cette fonction en initProgressBar et ne plus lui demander de modifier la propriété _visible de progressMC. Cette fonction devra être appelée à chaque chargement d'image, elle sera donc appelée dans la fonction chargerImage. Le moment de rendre progressMC visible ne change pas, lui, et reste le moment où loadedTime est supérieur à une seconde.

function initProgressBar() {
	progressMC.clear();
	drawFilledRectangle(progressMC, 0, 0, 400, 300, 0, 50);
	drawRectangle(progressMC, 100, 140, 200, 15, 2, 0xffffff, 100);
}
function chargerImage() {
	initProgressBar();
	startLoadTime = getTimer();
	mcl.loadClip("images/image" + imageActuelle + ".jpg", imageChargementMC);
}
mclListener.onLoadProgress = function(mc:MovieClip, loadedBytes:Number, totalBytes:Number) {
	if (!progressMC._visible) {
		var loadTime:Number = getTimer() - startLoadTime;
		if (loadTime > 1000) progressMC._visible = true;
	}
	if (!progressMC._visible) return;
	var width:Number = (loadedBytes / totalBytes) * 200;
	drawFilledRectangle(progressMC, 100, 140, width, 15, 0xffffff, 100);
}

Ce qui nous donne:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Le problème semble résolu :-) !

Le code complet se trouve en pièce jointe: 2)


III - Le clic pendant la transition

Attaquons-nous au deuxième problème. Nous avons repéré que le clic pendant la transition en fondu enchaîné entre deux images génère des “aberrations” de comportement. Vous pouvez tenter de comprendre et corriger par vous-même dès maintenant, mais vous pouvez d'abord lire le chapitre suivant qui va nous doter d'un nouvel outil d'analyse.

1- Usage de trace pour le débogage

Il peut être difficile de suivre le déroulement d'un programme. Nous allons profiter de l'occasion pour voir comment on peut utiliser l'instruction trace à cet effet.

Nous allons demander au programme de nous informer de son déroulement via la fenêtre de sortie, en ajoutant quelques trace dans notre code:

function chargerImage() {
	initProgressBar();
	startLoadTime = getTimer();
	mcl.loadClip("images/image" + imageActuelle + ".jpg", imageChargementMC);
	//Un trace ici:
	trace("Début chargement image "+ imageActuelle + " en profondeur " + imageChargementMC.getDepth());
}
mclListener.onLoadInit = function (mc:MovieClip) {
	// Un autre trace là:
	trace("Fin chargement en profondeur " + mc.getDepth());
	progressMC._visible = false;
	imageChargementMC._width = 400;
	imageChargementMC._height = 300;
	imageChargementMC._alpha = 100;
	imageCouranteMC.onEnterFrame = transition;	
}
function transition() {
	imageCouranteMC._alpha -= 10;
	// Un autre là:
	trace("Transition en profondeur " + imageCouranteMC.getDepth() + " : opacité = " + imageCouranteMC._alpha);
	if (imageCouranteMC._alpha <= 0) {
		imageCouranteMC.onEnterFrame = undefined;
		permuterProfondeurs();
	}
}
function permuterProfondeurs() {
	// Et un dernier ici:
	trace("Permutation des profondeurs")
	imageChargementMC.swapDepths(imageCouranteMC);
	var temp:MovieClip = imageCouranteMC;
	imageCouranteMC = imageChargementMC;
	imageChargementMC = temp;
}

Ainsi nous serons avertis “en temps réel” de l'exécution des fonctions chargerImage, onLoadInit, transition et permuterProfondeurs.

Pour plus de précision, nous utilisons la méthode de MovieClip getDepth qui permet de savoir à quelle profondeur est placé un MovieClip.

En plus d'y mettre un trace, nous changeons l'en-tête de la fonction onLoadInit, afin de récupérer dans la variable mc une référence au MovieClip qui vient de finir son chargement.

Vous pouvez maintenant exécuter le programme et suivre le déroulement de près dans le cas où l'utilisateur clique un bouton pendant le fondu. 3)

2- Le diagnostic

Voici ce que j'obtiens si je clique le bouton image suivante pendant la transition entre l'image 0 et l'image 1:

Début chargement image 1 en profondeur 0
Fin chargement en profondeur 0
Transition en profondeur 1 : opacité = 89.84375
Transition en profondeur 1 : opacité = 79.6875
Transition en profondeur 1 : opacité = 69.53125
Transition en profondeur 1 : opacité = 59.375
Début chargement image 2 en profondeur 0
Transition en profondeur 1 : opacité = 49.21875
Transition en profondeur 1 : opacité = 39.0625
Transition en profondeur 1 : opacité = 28.90625
Transition en profondeur 1 : opacité = 18.75
Transition en profondeur 1 : opacité = 8.59375
Transition en profondeur 1 : opacité = -1.171875
Permutation des profondeurs
Fin chargement en profondeur 1
Transition en profondeur 1 : opacité = 89.84375
Transition en profondeur 1 : opacité = 79.6875
Transition en profondeur 1 : opacité = 69.53125
Transition en profondeur 1 : opacité = 59.375
Transition en profondeur 1 : opacité = 49.21875
Transition en profondeur 1 : opacité = 39.0625
Transition en profondeur 1 : opacité = 28.90625
Transition en profondeur 1 : opacité = 18.75
Transition en profondeur 1 : opacité = 8.59375
Transition en profondeur 1 : opacité = -1.171875
Permutation des profondeurs

On suit bien ce qui se passe 4) :

Au commencement on a l'image 0 affichée au premier plan. Puis:

Début chargement image 1 en profondeur 0
Fin chargement en profondeur 0
Transition en profondeur 1 : opacité = 89.84375
Transition en profondeur 1 : opacité = 79.6875
Transition en profondeur 1 : opacité = 69.53125
Transition en profondeur 1 : opacité = 59.375

» L'image 1 commence à se charger dans le MovieClip d'arrière plan.
» Le fondu démarre, l'opacité de l'image 0 au premier plan diminue.

Début chargement image 2 en profondeur 0
Transition en profondeur 1 : opacité = 49.21875
Transition en profondeur 1 : opacité = 39.0625
Transition en profondeur 1 : opacité = 28.90625
Transition en profondeur 1 : opacité = 18.75
Transition en profondeur 1 : opacité = 8.59375
Transition en profondeur 1 : opacité = -1.171875
Permutation des profondeurs

» Le bouton est cliqué et le chargement de l'image 2 commence dans le MovieClip à l'arrière plan. Le chargement de l'image 1 est donc interrompu puisque qu'un MovieClip ne peut charger qu'une image à la fois.
» Le fondu continue, l'image au premier plan devient complètement transparente.
» La profondeur des images est permutée. On a donc en arrière plan l'image 0 transparente, et au premier plan le MovieClip en cours de chargement, qui est donc encore vide. Résultat: rien n'est plus affiché.

Fin chargement en profondeur 1
Transition en profondeur 1 : opacité = 89.84375
Transition en profondeur 1 : opacité = 79.6875
Transition en profondeur 1 : opacité = 69.53125
Transition en profondeur 1 : opacité = 59.375
Transition en profondeur 1 : opacité = 49.21875
Transition en profondeur 1 : opacité = 39.0625
Transition en profondeur 1 : opacité = 28.90625
Transition en profondeur 1 : opacité = 18.75
Transition en profondeur 1 : opacité = 8.59375
Transition en profondeur 1 : opacité = -1.171875
Permutation des profondeurs

» Le chargement de l'image 2 se termine au premier plan. onLoadInit remet l'opacité de imageChargementMC (le MovieClip de l'arrière plan) à 100%.
» Le fondu est lancé. L'opacité de l'image 2 diminue jusqu'à devenir transparente, ce qui n'est pas du tout le résultat voulu.
» Puis le MovieClip de l'arrière plan, donc l'image 0, est ramené au premier plan, ce qui affiche de nouveau l'image 0, alors qu'on serait sensé voir l'image 2.

3- Le traitement

Plusieurs idées peuvent venir pour résoudre le problème. Vous pouvez trouver une résolution différente de celle exposée ici, et toute aussi satisfaisante.

Une première idée serait de désactiver la possibilité de cliquer sur les boutons pendant la transition. Un MovieClip a justement une propriété nommé enabled qui permet de le désactiver.

Laissons cette possibilité comme exercice, dont la solution est en pièce jointe 5), car elle a pour résultat de gêner le défilement rapide des photos (déjà mises en cache), et l'utilisateur ne peut plus “feuilleter” rapidement l'album.

Une autre idée, que nous allons réaliser, est de “forcer” la fin du fondu et la permutation des profondeurs dans le cas où un bouton est cliqué pendant le fondu. Modifions ces fonctions et renommons permuterProfondeurs en finaliserTransition.

var isFonduEnCours:Boolean = false;
 
function chargerImage() {
	initProgressBar();
	if (isFonduEnCours) finaliserTransition();
	startLoadTime = getTimer();
	mcl.loadClip("images/image" + imageActuelle + ".jpg", imageChargementMC);
}
mclListener.onLoadInit = function (mc:MovieClip) {
	progressMC._visible = false;
	imageChargementMC._width = 400;
	imageChargementMC._height = 300;
	imageChargementMC._alpha = 100;
	imageCouranteMC.onEnterFrame = transition;
	isFonduEnCours = true;	
}
function transition() {
	imageCouranteMC._alpha -= 10;
	if (imageCouranteMC._alpha <= 0) finaliserTransition();
}
function finaliserTransition() {
	imageCouranteMC.onEnterFrame = undefined;
	imageCouranteMC._alpha = 0;
	imageChargementMC.swapDepths(imageCouranteMC);
	var temp:MovieClip = imageCouranteMC;
	imageCouranteMC = imageChargementMC;
	imageChargementMC = temp;
	isFonduEnCours = false;
}

4- Le résultat

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

5- Explications

Nous épurons la fonction transition, et regroupons dans la fonction finaliserTransition toutes les opérations à faire en fin de fondu, c'est-à-dire:

  • Effacer la fonction onEnterFrame
  • Permuter les MovieClips
  • Noter que la transition est terminée

Pour noter qu'une transition est en cours nous introduisons une nouvelle variable:

var isFonduEnCours:Boolean = false;

Le type de cette variable est Boolean (booléen en français). Une variable de ce type peut prendre seulement les valeurs true ou false. On fait souvent précéder le nom de telles variable par is (“est” en français), ce qui n'est absolument pas obligatoire, mais qui a l'avantage de nous faire souvenir de son type, de la même façon que nos variables MovieClip ont un nom terminé par “MC”.

On peut traduire “isFonduEnCours” par “est-ce qu'un fondu est en cours?”. Nous lui donnerons la valeur true quand un fondu est en train de se produire, et la valeur false dans le cas contraire. C'est pour cela que nous initialisons sa valeur à false, puisqu'au démarrage du programme il n'y a pas immédiatement un fondu.

Ensuite nous ajoutons

isFonduEnCours = true;

à la fin de onLoadInit, au moment où le fondu commence.

Et finalement nous ajoutons la ligne

if (isFonduEnCours) finaliserTransition();

dans la fonction chargerImage. C'est-à-dire qu'à chaque clic sur un bouton, quand chargerImage est exécutée, on regarde si un fondu est en cours en testant isFonduEnCours, et si c'est le cas on “force” le fondu à se terminer, de manière à ce que tout soit dans l'ordre voulu au moment du démarrage du chargement.

Remarquons qu'il ne sert à rien d'écrire

if (isFonduEnCours == true) finaliserTransition();

car si isFonduEnCours est vrai alors (isFonduEnCours == true) est vrai, et si isFonduEnCours est faux alors (isFonduEnCours == true) est faux, c'est-à-dire que la valeur contenue dans la variable isFonduEnCours est la même valeur que celle renvoyée par l'évaluation de l'expression (isFonduEnCours == true).


IV - Le débogueur de flash IDE

Pour finir, quelques mots sur le débogueur intégré à l'IDE de Adobe.

L'IDE de Adobe offre la possibilité d'exécuter un programme en contrôlant son déroulement. On a ainsi la possibilité de placer des points d'arrêts à différents endroits du code. Quand on lance le débogage, le programme est exécuté et s'arrête au premier point d'arrêt rencontré.

A partir de là il est possible d'avoir un aperçu de l'état du programme. On peut voir quelles sont les variables créées à cet instant, quelles sont leurs valeurs, quels sont les objets (comme des MovieClips) créés, quelles sont leurs propriétés.

On peut relancer l'exécution, qui reprendra jusqu'au prochain point d'arrêt rencontré, ou bien demander l'exécution pas à pas, c'est-à-dire l'exécution des lignes de code une à une. Ceci permet de suivre finement le déroulement du programme, d'autant plus qu'on voit s'afficher en même temps les lignes qui sont exécutées.

Voyez la section "Pour en savoir plus" pour… en savoir plus.


Conclusion

Notre diaporama est donc débogué (tout du moins jusqu'à ce que l'un d'entre nous remarque un bogue qui n'a pas été traité :-) ). Nous avons pédagogiquement pris le temps dans cet article d'envisager des procédures possibles de corrections des erreurs, face à un programme qui en contient.

Nous allons maintenant continuer d'avancer dans la réalisation du diaporama, et j'essaierais de ne plus introduire d'erreurs dans les codes proposés. Mais je vous fais confiance ;-) pour introduire de vous-même des bogues à corriger, pour vous faire la main, que ce soit à l'occasion des exercices proposés, de vos essais de refaire les tutoriaux sans regarder les codes donnés, ou encore de vos tentatives de variantes et améliorations.

Pour faire progresser ce tutoriel, vos commentaires sont les bienvenus sur le forum. Vous pouvez répondre au message concernant ce tutoriel ou ouvrir un nouveau sujet. N'hésitez pas à partager vos solutions personnelles face aux problèmes exposés dans cette partie.


Pour en savoir plus

Pour avoir des précisions ou des renseignements complémentaires, vous pouvez consulter 6):

  • Au sujet du débogueur de l'IDE:
  • Pour vérifier la version de Flash Player de l'utilisateur
    • Aide de Flash > Utilisation de Flash > Publication du contenu Flash > Utilisation de Flash Player > Configuration des paramètres de publication pour la détection de Flash Player
    • Aide de Flash > Référence du langage ActionScript 2.0 > Classes ActionScript > capabilities (System.capabilities) > version (propriété capabilities.version).
    • Une méthode de vérification en javascript: SWFObject: Script Javascript d'auto-détection du Player Flash.
  • Au sujet de la fonction trace:


Navigation: Sommaire page précédente | page suivante (en cours de rédaction) | Index

1) Ces comportements peuvent différer entre la version du diaporama incluse dans cette page et celle que vous obtenez en exécutant le code chez vous. Ceci est dû au fait que le chargement des images externes est mal supporté par le wiki, et que les swf inclus dans ces pages doivent “simuler” ce téléchargement par un code un peu différemment.
2) , 5) Vous pouvez télécharger les fichiers de code correspondants à cet article et les swf compilés dans une archive compressée. Si vous utilisez Flash IDE, cliquez ici. Si vous utilisez un environnement intégrant mtasc et swfmill, cliquez ici
3) Si vous désirez visualiser les sorties trace dans le véritable contexte d'exécution du diaporama, c'est-à-dire en ligne, c'est facilement possible avec le navigateur Firefox et le plugin flashTracer.
4) Pourquoi les opacités sont-elles des chiffres à virgules et pas des entiers multiples de 10 ? Cela reste un mystère pour moi…
6) Les références à l'aide de Flash concernent la documentation de Flash CS3, accessible par l'IDE de Flash CS3 ou par le site d'Adobe (Support > Documentation).