Pragma mark et Xcode4

RocouRocou Membre
Bonjour,

Y-a-t-il un moyen pour simuler ou retrouver l'ancien comportement de pragma mark?
En effet, depuis la version 4 de Xcode, les pragma marks ne sont visibles que hors des méthodes.
Je trouvais fort utile de pouvoir installer des marques à  l'intérieur des méthodes.

Est-il possible de retrouver cette fonctionnalité fort utile?

Réponses

  • CéroceCéroce Membre, Modérateur
    décembre 2011 modifié #2
    Mes stagiaires me l'ont fait remarquer, mais mon avis est que si tu as besoin de marques dans tes méthodes, c'est qu'elles sont fort longues, et tu devrais plutôt t'efforcer de les raccourcir.

    Une méthode d'une page est anormale, à  moins d'y implémenter une machine d'état.

    Utiliser beaucoup de courtes méthodes améliore vraiment la lecture du code et le débogage. Par exemple, plutôt qu'avoir un commentaire qui dit:
    [tt]// Trier les animaux par ordre alphabétique [/tt]

    Mieux vaut retirer le commentaire et écrire une méthode:
    [tt]+ (NSArray *) _sortArrayByNameAlphabetically:(NSArray *)arrayToSort[/tt]

    Dans le code appelant, ça donne:
    [tt]NSArray *sortedAnimals = [[self class] _sortArrayByNameAlphabetically:animals];[/tt]

    Cela impose de bien nommer ses variables et ses méthodes: c'est une bonne école.
  • AliGatorAliGator Membre, Modérateur
    02:27 modifié #3
    Tu as essayé avec la syntaxe alternative ?
    Perso je n'utilise plus du tout les "#pragma mark xxx" mais plutôt les "// MARK: xxx" et autre "// TODO: xxx"

    Sinon je rejoins totalement l'avis de Céroce ;) Et je n'ai jamais eu à  utiliser des marqueurs à  l'intérieur même de mes méthodes (juste des commentaires, mais pas de nécessité de les voir dans le menu)
  • RocouRocou Membre
    02:27 modifié #4
    dans 1323165081:

    Mes stagiaires me l'ont fait remarquer, mais mon avis est que si tu as besoin de marques dans tes méthodes, c'est qu'elles sont fort longues, et tu devrais plutôt t'efforcer de les raccourcir.

    Une méthode d'une page est anormale, à  moins d'y implémenter une machine d'état.


    Voila, c'est exactement le cas. J'ai des pages et des pages d'état et j'ai bien du mal à  m'y retrouver.
  • RocouRocou Membre
    02:27 modifié #5
    dans 1323165321:

    Tu as essayé avec la syntaxe alternative ?
    Perso je n'utilise plus du tout les "#pragma mark xxx" mais plutôt les "// MARK: xxx" et autre "// TODO: xxx"

    Oui, j'ai essayé bien sûr mais le comportement est le même.
  • AliGatorAliGator Membre, Modérateur
    décembre 2011 modifié #6
    dans 1323167055:

    Voila, c'est exactement le cas. J'ai des pages et des pages d'état et j'ai bien du mal à  m'y retrouver.
    o_O

    Alors dans ce cas c'est que ton code est mal structuré.

    Soit tu as ta machine à  état générale dans une méthode assez courte, qui ne fait que tester l'état courant et déterminer l'état suivant, et pour cela selon l'état appelle une méthode différente, et donc ensuite tu sépares donc le code de chaque état dans une méthode séparée

    Soit tu implémentes un pattern différent, genre le pattern command, et tu implémentes tes états façon objet.

    Par exemple j'imagine une solution à  tester, où en gros tu aurais un état = un objet sous-classe d'une classe abstraite StateClass, qui définit une méthode [tt]-(StateClass)nextState;[/tt], et à  chaque itération de ta machine à  état tu appelles [tt]currentClass = [currentClass nextState];[/tt]. C'est après à  chaque sous-classe de StateClass de déterminer l'état suivant en fonction de l'état courant (donc d'elle-même, d'où l'intérêt de sous-classer chaque état) et du contexte.
    Bon j'ai jamais pratiqué ça de cette façon mais ça peut être une piste. En tout cas une approche + "objet" et moins "procédurale" pour permettre de mieux structurer ton code.
  • RocouRocou Membre
    02:27 modifié #7
    dans 1323167916:

    Alors dans ce cas c'est que ton code est mal structuré.


    Sans aucun doute. Reste à  savoir si une structuration plus optimisé résoudrait mon problème de marques.

    dans 1323167916:

    Soit tu as ta machine à  état générale dans une méthode assez courte, qui ne fait que tester l'état courant et déterminer l'état suivant


    ;D

    Je viens de m'apercevoir, la honte, qu'on ne parle pas de la même chose. La honte est pour moi. Par état, j'entendais préparation d'impressions.
    Quoiqu'il en soit, dessiner un état (impression, donc) aux petits oignons, cela demande beaucoup, beaucoup de code et il me semble peu judicieux de structurer tout ça en davantage de classes et sous-classes que je ne l'ai déjà  fait; le code finirait par être encore moins lisible (spaghettis).
  • CéroceCéroce Membre, Modérateur
    décembre 2011 modifié #8
    dans 1323171404:

    Je viens de m'apercevoir, la honte, qu'on ne parle pas de la même chose. La honte est pour moi. Par état, j'entendais préparation d'impressions.

    "Machine d'états" est un terme technique précis.

    dans 1323171404:

    Quoiqu'il en soit, dessiner un état (impression, donc) aux petits oignons, cela demande beaucoup, beaucoup de code et il me semble peu judicieux de structurer tout ça en davantage de classes et sous-classes que je ne l'ai déjà  fait; le code finirait par être encore moins lisible (spaghettis).

    Sans entrer dans des architectures complexes, tu peux sans doute découper une grosse méthode en plusieurs petites. Sans connaà®tre ton cas particulier, une page est composée d'élements distincts: une en-tête, un pied, un corps qui comporte lui-même des paragraphes, ou des tableaux qui comportent eux-mêmes des cellules. Essaie simplement de dessiner chaque élément dans une méthode propre en passant à  chacune uniquement les éléments qui lui sont nécessaires.
    Non, seulement, c'est possible, mais ça va mettre en évidence les dépendances de chaque objet, et tu verras que tu peux simplifier ton code.
  • RocouRocou Membre
    02:27 modifié #9
    dans 1323172649:

    Sans entrer dans des architectures complexes, tu peux sans doute découper une grosse méthode en plusieurs petites. Sans connaà®tre ton cas particulier, une page est composée d'élements distincts: une en-tête, un pied, un corps qui comporte lui-même des paragraphes, ou des tableaux qui comportent eux-mêmes des cellules. Essaie simplement de dessiner chaque élément dans une méthode propre en passant à  chacune uniquement les éléments qui lui sont nécessaires.
    Non, seulement, c'est possible, mais ça va mettre en évidence les dépendances de chaque objet, et tu verras que tu peux simplifier ton code.


    Merci. Après la lecture de ton message, cela me parait évident mais plongé dans mon code tel que je l'étais, je commençais à  m'emmêler les pinceaux. Il est bon de prendre du recul de temps en temps.
    Merci encore.
  • AliGatorAliGator Membre, Modérateur
    décembre 2011 modifié #10
    +10 Céroce

    De plus, tu peux aussi certainement utiliser des méthodes pour généraliser la construction de ton état, par exemple avoir une méthode pour dessiner une zone de texte, une autre pour une zone d'image...
    Ou même classe Etat qui utilise un pattern de composition pour contenir différents éléments (rectangle rempli, zone de texte, zone d'image, ... et utiliser des patterns comme IoC ou Visitor pour demander à  ces éléments de se dessiner tout seuls dans un contexte graphique... Ce qui permet de déléguer à  chacun une partie du boulot ;)

    Bref y'a des pistes à  étudier je pense :)
  • CéroceCéroce Membre, Modérateur
    02:27 modifié #11
    Un dernier conseil: ne change pas tout d'un coup. Sépare une partie. Teste. Si ça fonctionne, sépare une autre partie. Teste.
    En chirurgie, si on a trois organes à  rafistoler, on ne fait pas une grosse opération, mais trois opérations !
  • muqaddarmuqaddar Administrateur
    02:27 modifié #12
    dans 1323182504:

    En chirurgie, si on a trois organes à  rafistoler, on ne fait pas une grosse opération, mais trois opérations !


    Ou alors on tue volontairement le malade parce que y'a trop de boulot pour donner naissance à  un tout beau tout neuf. :)
  • NasatyaNasatya Membre
    02:27 modifié #13
    Tu as une vision très particulière de la médecine...  B) Mais en dev ça fonctionne par contre :)
  • DrakenDraken Membre
    02:27 modifié #14
    dans 1323183280:

    dans 1323182504:

    En chirurgie, si on a trois organes à  rafistoler, on ne fait pas une grosse opération, mais trois opérations !


    Ou alors on tue volontairement le malade parce que y'a trop de boulot pour donner naissance à  un tout beau tout neuf. :)


    "L'opération a été une réussite parfaite sur le plan technique, même si le patient soit mort !"

  • laudemalaudema Membre
    02:27 modifié #15
    dans 1323270195:

    "L'opération a été une réussite parfaite sur le plan technique, même si le patient soit mort !"

    Généralement résumée par "Le malade est mort guéri" :)
    Et puis en chirurgie ça marche vraiment comme ça, on opère qu'une chose à  la fois sauf quand on peut pas faire autrement.
    Quelqu'un a des liens sur la manière de bien utiliser l'outil de "Refactoring" ?
    Comment faire dans une grosse méthode qu'on veut éclater si, dans cette méthode, on a des variables qui sont calculées en son sein et dépendantes les unes des autres ? Quand j'essaye le refactoring je me retrouve avec des pointeurs sur des pointeurs =>- (void) doZisWiz:(ZisClass **)obj. La doc présente bien l'outil mais ne donne pas de conseils sur son utilisation.
  • RocouRocou Membre
    02:27 modifié #16
    dans 1323493942:

    Comment faire dans une grosse méthode qu'on veut éclater si, dans cette méthode, on a des variables qui sont calculées en son sein et dépendantes les unes des autres ?

    C'est bien le problème,  à  force d'être saucissonné, le code devient illisible.


    Merci à  tous pour vos conseils, il y a bien évidemment une bonne dose d'optimisation du source à  faire dans mon code mais j'aurais quand même bien aimé trouver une solution à  cette régression dans l'utilisation de pragma mark!
  • CéroceCéroce Membre, Modérateur
    02:27 modifié #17
    dans 1323493942:

    Comment faire dans une grosse méthode qu'on veut éclater si, dans cette méthode, on a des variables qui sont calculées en son sein et dépendantes les unes des autres ?


    On est obligé de passer les paramètres en argument des méthodes. ça semble contraignant, et pourtant, cette manière de faire a la grande qualité de rendre explicites les paramètres nécessaires à  la méthode. En procédant ainsi, on se rend compte que:
    - certains paramètres ne sont pas nécessaires
    - on effectue des tâches similaires d'une méthode à  une autre, alors qu'on pourrait factoriser le code et passer le résultat en paramètre.

    Je vous assure que le code est bien plus lisible écrit ainsi:
    - si les méthodes sont bien nommées, on peut comprendre le code appelant sans jamais aller regarder leur implémentation.
    - les méthodes étant plus courtes et faisant appel à  moins de paramètres, elles sont plus faciles à  comprendre et à  mettre au point.


  • AliGatorAliGator Membre, Modérateur
    02:27 modifié #18
    Je ne peux que plussoyer Céroce.

    Je rajoute que des méthodologies comme le TDD (Ecrire les Tests U en premier) permettent également de faciliter ce genre de découpage et au final de faire du code plus unitaire et plus propre aussi.

    Concernant tes paramètres, dans tous les cas il y a sûrement une conception qui peut être améliorée. Soit en rendant un peu plus indépendant tes calculs, ou en les factorisant, soit en fonctionnement en terme objets (si tu abordes ton algo en terme de patterns objets cela peut te simplifier son appréhension, si tu as bcp de paramètres ils seront aussi regroupés/identifiés dans des classes spécifiques, ...)
  • laudemalaudema Membre
    02:27 modifié #19
    Ok, il va falloir que je reparte de zéro pour cette foutue méthode où je commence à  avoir bien du mal à  m'y retrouver moi même.
    Par contre je ne comprends rien à  ces tests unitaires.
    Si je prends ce que fait cette méthode: elle scanne un fichier texte dont elle extrait plusieurs dictionnaires à  partir desquels elle crée un objet, disons une facture.
    Si on prend l'exemple d'une facture un dictionnaire serait le client, un autre les lignes de factures (avec pour chaque entrée comme clef le nom de la colonne et en objet un tableau des valeurs de chaque ligne pour cette colonne), un autre le commercial, un autre le pied de la facture avec le total à  payer et les échéances des paiements. (ce n'est pas moi qui génère ces fichiers, ça serait trop facile).
    Vu comme ça c'est assez simple sauf que des dictionnaires peuvent manquer ou être incomplets nécessitant des recherches ou des calculs. Par exemple si le commercial est absent mais que le client est connu on peut chercher dans les factures précédentes son commercial de référence (en demandant confirmation à  l'utilisateur), parfois le commercial est bien indiqué mais il n'existe pas encore dans l'application et doit donc être créé en utilisant les données pour soumettre à  nouveau un formulaire de confirmation de cette entrée; un autre exemple serait un taux de TVA qui n'est pas toujours là  mais qu'on peut deviner en partant du prix hors taxe ou de la référence de l'article visible dans les lignes de factures et en faisant le rapport avec le total ttc visible dans le dictionnaire de pied de page ou la profession du client.
    Au total ça donne une méthode truffée de if-else if-else avec plein de spaghettis dedans.
    Mon petit doigt me dit que cette méthode n'est pas idéale pour partir à  la découverte des "Tests Unitaires", dit il n'importe quoi ?
  • CéroceCéroce Membre, Modérateur
    décembre 2011 modifié #20
    En fait, il y a de bonnes raisons d'écrire les tests unitaires avant le code: il te faut des points d'entrée et de sortie sur la méthode.
    Ecrire des tests automatisés prend du temps, alors il faut être sûr que le gain temps d'écriture/temps de mise au point reste compétitif par rapport à  l'absence de test. De fait, un test unitaire doit être facile à  écrire!

    Voici un exemple:

    <br />- (void) test_stringWithCoordinate<br />{<br />	// Test with positive coordinates<br />	// 45.3239576; -2.8934588 =&gt; 45°19&#039;26.25&quot;; 2°53&#039;36.45&quot;<br />	CLLocationCoordinate2D coordinate = {45.3239576, 2.8934588};<br />	NSString* sexaCoord = [CeLocator _stringWithCoordinate:coordinate];<br />	STAssertEqualObjects(sexaCoord, @&quot;45°19&#039;26.25&#092;&quot;; 2°53&#039;36.45&#092;&quot;&quot;, sexaCoord);<br />	<br />	// Test with negative coordinates<br />	// -200.0; -0.30 =&gt; -200°00&#039;00.00&quot;; -0°18&#039;00.00&quot;<br />	CLLocationCoordinate2D negCoordinate = {-200.0, -0.30};<br />	NSString* negSexaCoord = [CeLocator _stringWithCoordinate:negCoordinate];<br />	STAssertEqualObjects(negSexaCoord, @&quot;-200°0&#039;0.00&#092;&quot;; -0°18&#039;0.00&#092;&quot;&quot;, negSexaCoord);	<br />}<br />
    


    La méthode -[CeLocator _stringWithCoordinate:] doit renvoyer les coordonnées géographiques (décimales) sous forme d'une chaà®ne sexagésimale. Le test est simple: on essaie avec quatre valeurs fixes et réalistes.
    Les entrées et sorties sont bien définies: les coordonnées en entrée, la chaà®ne en sortie.


    Laudema, le problème de ta méthode, c'est qu'elle fait bien trop de choses pour être facilement testable. Les seul tests que tu peux faire sont en bout de chaà®ne. Il te faut isoler chaque action dans une méthode pour pourvoir injecter des entrées et contrôler les sorties.
    Dans ton exemple, tu n'as une grosse méthode:
    [tt]- (Facture) factureAvecFichierTexte:(NSString *)path[/tt]

    D'après ce que tu décris, tu pourrais beaucoup la découper:
    [tt]- (NSArray*) _dictionnairesAvecFichierTexte:(NSString *)path;[/tt]
    elle même ferait appel à :
    [tt]- (NSDictionary *) _dictionnaireClientAvecData:(NSData *)data;
    - (NSDictionary *) _dictionnaireLignesDeFactureAvecData:(NSData *)data;
    - (NSDictionary *) _dictionnaireCommercialAvecData:(NSData *)data;[/tt]
    etc.

    Ces petites méthodes peuvent encore être découpées. À un moment, ton test unitaire doit tenter d'ouvrir un fichier de test et contrôler que le dictionnaire client est conforme à  ce qu'on attend, etc.
    L'alternative est de contrôler au débogueur dans ton code. C'est une manière de faire qui fonctionne, mais si tu modifies une méthode, il faudra retester au débogueur. Alors qu'avec les tests autos, tu n'auras qu'à  relancer le test. Si le test passe toujours, alors c'est bon.

    En général, les tests automatisés sont faciles à  mettre au point et donc plus intéressants pour la couche "modèle". Je te conseille de commencer par là . Il me semble que ta méthode fait partie de la couche modèle, donc tu dois pouvoir commencer à  appliquer les tests U dessus. Il peut être plus intéressant de partir d'une page blanche et commencer par écrire les tests sur des méthodes courtes plutôt que tenter de découper ton code actuel. De toute façon, tu le conserves et n'auras pas à  te creuser la tête à  nouveau pour l'implémentation.
  • AliGatorAliGator Membre, Modérateur
    décembre 2011 modifié #21
    Oui je te conseille aussi de repartir de zéro pour mettre au point tes tests U. Tu pourras toujours copier/coller des bouts de ton code existant quand viendra le moment de faire l'implémentation réelle pour pas tout refaire, mais le fait de repartir de zéro depuis une page blanche au niveau structure et donc découpage en méthodes te permettra de mieux appréhender ton découpage de ton algo en petits blocs plus simples à  appréhender.

    Par exemple moi sans trop connaà®tre en détail pour autant ton logiciel, j'aurais déjà  plutôt fait :
    • Une classe Personne, abstraite (nom, prénom, ...)
    • Une classe Client, héritant de personne (Personne + n° de client + ...)
    • Une classe Commercial, héritant de personne
    • Une classe Produit (ref produit, intitulé, PU ...)
    • Une classe Facture (composition d'un Client, un Commercial, une liste de <Produit, Quantité>, une date, un statut, etc)


    Une fois le modèle bien établi, et ce idéalement plutôt indépendamment de ce que tu vas trouver en entrée (il ne faut pas forcément construire son modèle sur la structure du fichier d'entrée, mais plutôt si possible sur une représentation des objets que ton logiciel va manipuler représentant des objets "métier", et ce quelle que soit la structure du baraguinage que tu as en entrée), tu pourras voir comment remplir ce modèle.



    Et pour ça, plutôt que de faire tout d'un coup comme tu sembles avoir fait, une alternative pourrait être de faire ça en plusieurs passes par exemple : une première passe où tu remplis ton modèle d'après les données que tu lis dans le fichier texte, quitte à  laisser des données vides (à  nil) genre le commercial ou le taux de TVA s'ils ne sont pas renseignés. Et dans un 2e temps, une fois la lecture du fichier texte terminée, de consolider ton modèle pour essayer de remplir les trous.

    Ceci aurait plusieurs avantages :
    • Tu séparerais ainsi clairement les tâches d'import et de consolidation. Tu pourrais ainsi importer sans consolider (pour pouvoir mettre en avant à  celui qui te file le fichier texte qu'il exagère quand même car il y a bcp de trous), ou plus utilement consolider un import existant, si tu importes un fichier texte puis un autre, et que le 2e rajoute des infos qui permettent de consolider le premier, ce qui n'était pas possible avant d'avoir ce 2e fichier... bref ça te laisse de la liberté
    • Ta consolidation serait indépendante de ton entrée : si ton fichier d'entrée change de format un jour et que ce n'est plus le même fichier texte mais un autre ou un fichier XML ou autre, tu n'auras pas à  changer ton algo de consolidation qui sera totalement indépendant
    • Ta consolidation risque d'être plus efficace : si tu as dans ton fichier texte une première facture listée avec un client mais pas de commercial, et que plus loin tu as le mm client avec un commercial associé, tu auras l'info "trop tard" si tu étais resté sur un import en une seule phase. Alors qu'en 2 phases plus de problème

    Mais surtout, le fait de décorreller les 2 phases va te simplifier ton code. Tu ne vas pas devoir faire tout plein de tests en plein milieu de ton import pour savoir si tu as un commercial de renseigné et sinon aller le chercher et tout, tests qui vont "parasiter" ton code d'import et qui sont à  priori la raison de la complexification de ton code. Du coup tu pourras tester proprement et séparément les choses, l'import d'un côté la consolidation de l'autre, et avoir des blocs propres chacun de leur côté.



    Et en plus la consolidation peut aussi se découper en modules du coup. Tu peux avoir, imaginons, un [tt]CommercialSolver[/tt] qui prend un objet [tt]Facture[/tt] imcomplet auquel il manque le commercial et va essayer de le "résoudre" donc de trouver le (ou les) commercial(-aux) que tu pourrais suggérer pour remplir le blanc. Un [tt]PriceSolver[/tt] qui essayerai de son côté de deviner la TVA d'après le prix HT et le TTC, ou le prix TTC d'après HT+TVA, etc.
    Là  encore, tu peux donc séparer tes modules de consolidation proprement, chacun étant dédié à  une tâche bien identifié. Et un MasterSolver pourait orchestrer tout ça, tu lui files juste une facture et il regarde les cases qui manquent et quels solvers (CommercialSolver, PriceSolver, ...) appliquer dessus, et présente toutes les suggestions pour remplir les blancs de cette facture dans une interface unifiée ensuite.



    Bref ce n'est qu'une suggestion et possibilité parmi tant d'autres, mais tu vois qu'en découpant ainsi, séparant l'import de la consolidation, éclatant la consolidation en modules spécialisés et dédiés, tu auras un code plus propre, plus flexible et évolutif (facile de rajouter un "Solver" un jour si besoin) et plus compréhensible (à  manipuler des classes aux noms évocateurs comme Commercial ou Produit et pas des Dictionnaires avec des clés dont il faut que tu te souviennes et des valeurs dont il te faut savoir le type attendu...
  • CéroceCéroce Membre, Modérateur
    02:27 modifié #22
    Je suis d'accord avec Ali, même si dans mon exemple je n'ai pas voulu trop m'éloigner de l'implémentation actuelle.
  • laudemalaudema Membre
    02:27 modifié #23
    Merci beaucoup à  Ali (et à  Ceroce aussi qui m'a incité à  me lancer) je viens d'imprimer et ton texte et vais l'emmener pour réfléchir et coucher sur le papier la manière dont je vais m'y prendre. J'avais, il y a longtemps, imaginé que je devrais faire de cette façon mais je ne savais pas par quel bout le prendre, et puis pressé d'avoir un résultat je suis rentré dans mes dictionnaires et la méthode a gonflé gonflé gonflé pour englober tous les cas et elle n'ne finit plus de gonfler, d'ailleurs. Donc je repars à  zéro avec ce que je pense être de très bons conseils.

    Sinon STAssertEqualObjects(object_1, object_2, failure_description, ...) ne semble prévu que pour les applis iOS, je ne vois rien pour Mac OS. Le seul item proposé dans la documentation Xcode (Mac OS) est "Test Driving Your Code with OCUnit" sauf que ça amène la page web de présentation de Xcode sur le site d'Apple ce qui n'est pas d'un grand intérêt pour moi.
  • CéroceCéroce Membre, Modérateur
    décembre 2011 modifié #24
    Mon code d'exemple est issu d'un projet iOS, mais OCUnit est identique sur les deux plateformes. La framework s'appelle en réalité SenTestingKit. Elle se trouve dans Developer/Library/Frameworks.
    Sa configuration dans une application n'est pas simple, il faudra créer une target uniquement pour le test, qui sera une dépendance de la première. J'avais un lien sur la procédure, mais Apple l'a retiré...

    Sous Xcode 4, il n'y a qu'une case à  cocher à  la création du projet. Par contre, si tu veux ajouter SenTestingKit après, c'est toujours aussi compliqué.
  • laudemalaudema Membre
    02:27 modifié #25
    dans 1323874331:

    J'avais un lien sur la procédure, mais Apple l'a retiré...

    J'en ai trouvé un sur le site d'Oreilly qui utilise un framework externe car en ce temps là  Apple ne l'avait pas encore intégré aux outils de développement. Mais ça reste compliqué.
    Le mieux pour moi c'est d'attendre quand je reprendrais tout de zéro (avec Core Data que je n'ai pas utilisé non plus pour ce premier projet). Mais pour ça il me faudra beaucoup de temps libre pour bien planifier tout..
Connectez-vous ou Inscrivez-vous pour répondre.