Pragma mark et Xcode4
Rocou
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?
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?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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.
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)
Voila, c'est exactement le cas. J'ai des pages et des pages d'état et j'ai bien du mal à m'y retrouver.
Oui, j'ai essayé bien sûr mais le comportement est le même.
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.
Sans aucun doute. Reste à savoir si une structuration plus optimisé résoudrait mon problème de marques.
;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).
"Machine d'états" est un terme technique précis.
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.
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
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 !"
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.
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!
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.
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, ...)
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 ?
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:
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.
Par exemple moi sans trop connaà®tre en détail pour autant ton logiciel, j'aurais déjà plutôt fait :
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 :
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...
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.
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é.
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..