1

yop,

Je fais une appli avec Qt en ce moment. Comme ça pourrait faire une certaine taille, j'ai bien séparé les données et la GUI. Faisons semblant de faire les choses bien. embarrassed

Je pensais être parti sur un pattern MVC (sans prise de tête, de manière pifométrique hein), et en lisant ce que c'est après avoir pas mal codé, je me suis rendu compte que j'étais plutôt parti sur ça : https://fr.wikipedia.org/wiki/Architecture_trois_tiers

Alors je sais pas si c'est bien d'avoir fait ça, mais au moins je suis parti sur quelque chose qui existe, c'est déjà ça grin

Je me pose une question : qui est responsable de l'édition des données ?

Comme j'ai besoin d'un peu de concret, posons les choses : l'application doit être capable d'enregistrer une liste de vélos. On doit connaitre leur nom et leur nombre de vitesses. Au niveau données, on a donc une string, et deux int (plateaux - pignons).
Donc, dans mon interface (design professionnel bien sûr embarrassed), j'ai un bouton "Nouveau vélo". Cet event est reçu par la partie client, "visuelle". Mais qu'en fait-elle ?
- elle n'a pas à faire popper de dialogue, les contraintes sur les données doivent lui rester inconnues (genre un nombre de pignon, c'est positif)
- elle le passe à l'Application. Pourquoi pas, mais que faire des données recueillies ? Les envoyer aux données et à la vue ? Ca me semble bancal pour plein de raisons
- elle transmet l'event à l'Application qui transmet aux données. Après tout, c'est bien elles qui savent ce dont elles ont besoin pour créer une nouvelle entrée.

Merci d'avance pour vos lumières. J'ai bien mes idées, mais je suis nul en analyse. Mais ce genre de réflexion et de design d'architecture est absolument passionnant. Peut-être parce que je découvre comme un gros dubutant ? grin

2

Ah, j'ai implémenté ça comme ça pour le moment :
- dans l'Interface, l'utilisateur appuie sur "Nouveau vélo". L'Interface crée une structure (C-style) contenant une string pour le nom, et un int pour le nombre de vitesses.
- elle demande à l'Application de lui remplir cette structure avec un nouveau vélo
- l'Application demande aux Données de créer un nouveau vélo, et de remplir la-dite structure
- les Données font popper un dialogue, récoltent les données dont elle a besoin, et renvoie true si le dialogue est validé
- return true-> return true
- si l'appel à l'Application est true, alors l'Interface lit les champs de la structure pour afficher le nouveau vélo dans la liste

Avantages :
- protocole "simple" (seulement 4 appels pour descendre et remonter dans la structure hiérarchique)
- l'Interface et l'Application n'ont pas à gérer des contraintes de données, c'est la partie Données qui le fait et ça tombe bien, c'est son domaine
- efficace en terme de perf : un passe un pointeur et on retourne un booléen

Inconvénient :
- duplication des données, avec une structure qui ressemble furieusement aux membres privés de la partie Données. Ceci dit, ça me semble inévitable ??
- ... autre chose que j'ai pas vu ??


(un autre avantage de ce pattern, mais qui touche l'Application, est qu'elle peut gérer des choses comme la modification du titre de la fenêtre ou que sais-je encore quand elle voit que des nouvelles données ont été rajoutées ou modéifiées, ou encore elle peut activer/désactiver certains menus ; en effet, c'est le genre de choses qui ne dépend ni des données ni de l'interface graphique List de Vélos)

3

Je n'ai pas d'expérience là-dedans donc je ne pourrais pas te conseiller, mais par contre il me semble important de partir sur des bonnes bases : une appli de gestion de vélos, ça se fait en Python, en utilisant la lib flanker.py embarrassed
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

4

Un grand merci pour le conseil grin

Bon, mes élucubrations n'ont pas l'air de vous inspirer, j'y vois trois possibilités :
- c'est très bien comme ça, rien à dire
- c'est trop nul comme ça, c'est foupoudav
- rien compris à l'exemple. Je vous propose alors de remplacer les vélos et leurs noms par les yAronautes et leurs nombres de posts, ça vous sera peut-être beaucoup plus parlant. smile

5

Les 3 couches sont en effet réputées comme étant une bonne conception. Mais normalement les dialogues sont clairement la responsabilité de la couche interface, la couche données n'a pas à produire une interface utilisateurs quelle qu'elle soit.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

6

Si tu ne veux pas coder en dur le contenu du dialogue (parce qu'il doit effectivement correspondre aux données), peut-être que cette bibliothèque va t'intéresser:
https://community.kde.org/KProperty
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

7

Je n'ai pas fait beaucoup d'IHM, mais celles que j'ai faites ressemblaient plutôt à un modèle à deux couches qu'à un MVC: un contrôleur aurait été totalement artificiel. Globalement, la vue se contentait d'utiliser directement les méthodes des objets.
Ce n'est peut-être pas très orthodoxe, mais avec 4 ans de recul (l'application continue à être développée), ça reste facile à comprendre (y compris par d'autres personnes) et à modifier.

Pour ta proposition : imagine que tu doives implémenter une autre interface (web, réseau, ou ligne de commande) , que la partie graphique soit indisponible. Clairement, tes objets ne pourront pas générer une boîte de dialogue.

Pour moi : tes données correspondent à un objet « document », avec un attribut « liste de vélo » et une méthode « nouveau_vélo ». L'interface crée la boîte de dialogue avec 3 champs. Quand l'utilisateur clique sur OK, elle appelle « Document.nouveau_vélo » avec les bonnes valeurs, puis appelle un signal qui dit « hey ! la liste a changé » pour mettre à jour l'interface.
avatar
<<< Kernel Extremis©®™ >>> et Inventeur de la différence administratif/judiciaire ! (©Yoshi Noir)

<Vertyos> un poil plus mais elle suce bien quand même la mienne ^^
<Sabrina`> tinkiete flan c juste qu'ils sont jaloux que je te trouve aussi appétissant

8

En fait, KProperty n'est pas ce qu'il y a de plus pratique, parce que ça nécessite de maintenir les propriétés à la main, il est plus pratique d'utiliser une bibliothèque qui peut travailler avec les Q_PROPERTY de QObject, par exemple:
https://bitbucket.org/eligt/qtpropertybrowser/
(un fork d'une Qt Solution pour Qt 4, porté vers Qt 5)
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

9

./7 pencil

En gros ça donne ça :

interface DataChanged {
   dataChanged() ;//granularité à choisir en fonction du contexte
}


class MonUi implements DataChanged {
  MonUi
  {
     k.addDataChangedListener(this) ;
  }
   Kernel k ;

  @override
  dataChanged()
  {
     myTextArea.setText(k.myText) ;
  }

   buttonCallback()
   {
      kernel.doStuff() ;
   }
}



class Kernel {
   list<DatachangedListerner> list ;
   addDataChangedListener( datachanged dc )
  {
    list.add(dc) ;
  }

   private void notifyAllDataChangedListeners()
  {
    foreach DatachangedListerner dc in list {
      dc.dataChanged() ;
    }
  }

   doStuff()
  { 
    try {
       //fait des trucs que seul un kernel peut faire !!
        //...
       //...
    }
    finally {
         notifyAllDataChangedListeners() ;
    }
   }
}





//--------------
class MonLog implements DataChanged {
  MonUi 
  {
     k.addDataChangedListener(this) ;
  }
   Kernel k ;

  @override
  dataChanged()
  {
     printf(k.myText) ;
  }
}


//--------

main {
   Kernel k ;//instance unique du kernel

   MonUI gui(k) ;
   
   MonLog log(k) ;
}
C'est un peu bancal mais ça illustre quand même le fait que deux media se mettent à jour (le gui et le log) à la suite d'une seule action.
Évidemment le second écouteur ne fait qu'écouter, mais tu pourrais faire aussi un autre pilote à base de CLI qui vienne remplacer MonUI.
L'important c'est en effet que ta couche métier (Kernel) doit être une API qui permette de piloter toute ton application (et c'est la seule qui modifie les données)



(je ne me suis pas trop relu donc désolé si c'est pas clair embarrassed)

10

Un grand merci pour vos réponses. Je vais pas répondre à tout maintenant, mais dans l'ordre :
Kevin Kofler (./5) :
Mais normalement les dialogues sont clairement la responsabilité de la couche interface, la couche données n'a pas à produire une interface utilisateurs quelle qu'elle soit.
Je suis tout à fait d'accord sur le principe, ceci dit :
- comment la couche Application fait-elle pour proposer des valeurs limites correctes lors de la création/édition ? Elle demande pour chaque donnée à la couche Données ? Fastidieux. Et pourtant, pour chaque spinbox et autre entrée, il faut bien valider des valeurs. Une callback vers la couche Données (QValidator...) me semble pénible, pourtant l'Application ne peut pas inventer ces valeurs limites.
- Dans un cas comme celui-ci : // Save the file stream << QString(GAME_FILE_SIGNATURE) << GAME_FILE_VERSION << Name; if (stream.status() != QDataStream::Ok) { QMessageBox::critical(MainWindow::get(), tr("Error"), tr("Couldn't save to the file %1").arg(FullFilename)); return false; } Modified = false; return true;Cette méthode (open()) qui appartient à la couche Données peut tomber sur plusieurs erreurs, alors deux solutions :
-> elle affiche ses dialogues et renvoie false en cas d'échec
-> elle renvoie un code d'erreur, à tester pas la couche Application, qui devra afficher le message idoine. Ca nécessite de créer une énumération en plus, de faire un plus ou moins gros switch dans la partie Application. C'est plus propre sur le papier, mais ça complexifie le code.

J'ai fait ça parce que je pense que c'est mon juste milieu, mais je ne sais pas si c'est vraiment bien. Un illustre programmeur de notre siècle disait en substance que le bon codeur, c'est celui qui arrête de couper les cheveux en 4 au moment où il faut, c'est ce que j'essaye de faire (ok j'ai du mal triso)
Alors c'est sûr qu'au niveau réutilisabilité, je suis dans les choux avec ça, mais je dirais que je ne suis pas concerné par cette problématique.

Alors si vraiment ça vous semble bancal, dites-moi pourquoi, et surtout expliquez-moi comment la couche interface (je pense à toi Flanker, et ton modèle qui ressemble à client-serveur) peut proposer un dialogue de création/édition correct, sans mettre les pieds dans les données, et sans que Données expose ses trippes avec mille getters/setters.

edit -> hmmmarche plus la balise source ??
edit 2 -> merci pour les autres réponses, merci pour ton exemple Pen², je regarde, je comprends et j'y reviens. Kevin -> je n'utiliserai pas ce qui est KDE only, et non officiellement supporté. L'avantage de se cantonner à Qt, c'est que ça devrait éviter les mauvaises surprises. smile

11

Folco (./10) :
Cette méthode (open()) qui appartient à la couche Données peut tomber sur plusieurs erreurs, alors deux solutions :
-> elle affiche ses dialogues et renvoie false en cas d'échec-> elle renvoie un code d'erreur, à tester pas la couche Application, qui devra afficher le message idoine. Ca nécessite de créer une énumération en plus, de faire un plus ou moins gros switch dans la partie Application. C'est plus propre sur le papier, mais ça complexifie le code.
Elle ne doit pas afficher quoi que ce soit (=> imagine que ta couche métier est soudain utilisée sur un serveur sans personne pour cliquer sur le bouton OK cheeky)

Donc oui il faut générer une erreur. Ou une exception (c'est plus pratique). Et comme ça, la couche présentation n'a pas à connaître outre mesure les erreurs possibles. Elle se contente de rattraper les exceptions et d'en faire ce qu'elle veut.

class FolcoRootException {
getMessage() {...}
}


   doStuff() throws FolcoRootException
  { 
    try {
      if ( randomBool() ) {
          throw new FolcoRootException("valeur non valide, sont autorisées les valeurs min<val<max") ;
      }
       //fait des trucs que seul un kernel peut faire !!
        //...
       //...
    }
    finally {
         notifyAllDataChangedListeners() ;
    }
   }

Le code utilisant doStuff sait qu'il doit gérer les FolcoRootException.

   buttonCallback()
   {
      try {
          kernel.doStuff() ;
      }
      catch ( FolcoRootException fre ) {
           msgbox("err!"+ fre.getMessage()) ;
      }
   }

12

Folco (./10) :
- comment la couche Application fait-elle pour proposer des valeurs limites correctes lors de la création/édition ? Elle demande pour chaque donnée à la couche Données ? Fastidieux. Et pourtant, pour chaque spinbox et autre entrée, il faut bien valider des valeurs. Une callback vers la couche Données (QValidator...) me semble pénible, pourtant l'Application ne peut pas inventer ces valeurs limites.
Code en double, malheureusement. Ou alors (mais je ne sais pas si c'est possible en C++), de l'introspection : les classes utilisées pour l'interface sont calculées à partir des classes des données pour éviter la copie de code.
avatar
<<< Kernel Extremis©®™ >>> et Inventeur de la différence administratif/judiciaire ! (©Yoshi Noir)

<Vertyos> un poil plus mais elle suce bien quand même la mienne ^^
<Sabrina`> tinkiete flan c juste qu'ils sont jaloux que je te trouve aussi appétissant

13

(mmm, la copie de quel code ?)

En tout cas si l'interface a besoin de connaitre les détails du type min/max autorisés, je ne vois pas d'autre solution que de l'ajouter d'une manière ou une autre à la couche métier, car c'est un détail d'implémentation.
(ça pourrait être des annotations à la rigueur, oui)

14

flanker (./12) :
Code en double, malheureusement.
Voilà, j'ai pas dit que je préférais éviter ça parce que ça semble évident pour tout programmeur. Peut-être des macros alors ?
flanker (./12) :
Ou alors (mais je ne sais pas si c'est possible en C++), de l'introspection : les classes utilisées pour l'interface sont calculées à partir des classes des données pour éviter la copie de code.
Je sais même pas de quoi tu parles, je vais voir xD
Pen^2 (./13) :
En tout cas si l'interface a besoin de connaitre les détails du type min/max autorisés
Parce que je pense que c'est mieux de dire à ta spinbox : setMinimum(0²); setMaximum(42);, plutôt que d'afficher un message d'exception chaque fois que le mec fait un clic en trop sur la flèche du haut. C'est pour ce genre de choses que les boites de dialogues ont besoin, je pense, de connaitre un minimum ce que permet la couche Données, et c'est pourquoi c'est si tentant et si simple de mettre ces boites dans la-dite couche.

15

Pen^2 (./11) :
=> imagine que ta couche métier est soudain utilisée sur un serveur sans personne pour cliquer sur le bouton OK cheeky
Alors clairement©, je fais une petite appli sous Windows, et même si tu as raison sur le principe, je vais pas tenter un design façon gestionnaire de datacenter, c'est hors norme. Mais en soi, si je codais une appli web en intranet, je devrais réfléchir comme ça. J'avoue que j'y avais pas pensé. smile

(edit -> on me glisse dans l'oreillette que la balise "source" s'appelle "code", en fait. Et ça marche !)

16

Folco (./14) :
Parce que je pense que c'est mieux de dire à ta spinbox : setMinimum(0²); setMaximum(42);, plutôt que d'afficher un message d'exception chaque fois que le mec fait un clic en trop sur la flèche du haut. C'est pour ce genre de choses que les boites de dialogues ont besoin, je pense, de connaitre un minimum ce que permet la couche Données,
En effet, pour ce genre de cas, il faut bien écrire les min et max quelque part... Ton UI ne va pas les deviner, hélas (mourn)
Folco (./14) :
et c'est pourquoi c'est si tentant et si simple de mettre ces boites dans la-dite couche.
Tu dois résister à la tentation embarrassed

Mais sérieusement, tu as raison de vouloir garder du recul.
Ces modèles architecturaux sont là pour simplifier, pas pour ennuyer. Fais au mieux pour TON cas oui

17

Pen^2 (./16) :
Ces modèles architecturaux sont là pour simplifier, pas pour ennuyer.
Tu sais que tu m'ouvres grandement les yeux avec cette phrase, qui passerait pour une lapalissade ? grin Je cherchais absolument quel était le modèle référencé par wikipedia/stackoverflow/whatever et qui ferait l'affaire, au lieu d'en adapter un à mes besoin spécifiques. C'est fou d'être rigide comme ça triso grin

18

Pen^2 (./13) :
(mmm, la copie de quel code ?)
Pour reprendre un exemple que je connais bien, un site web en Python + Django.
On définit une classe (== une table SQL) avec deux attributs (== deux colonnes)

class Velo(models.Model):
    vitesses_avant = models.IntegerField(min=1, max=3)
    vitesses_arriere = models.IntegerField(min=5, max=10)

À partir de là, on veut créer un formulaire HTML pour ajouter un vélo. On va donc devoir préciser dans le formulaire qu'il y a deux champs, dont le premier a une valeur entre 1 et 3 et le second entre 5 et 10.
Même si ce n'est pas exactement le même code, il y a quand même de la copie.

Grâce à l'introspection, on peut générer automatiquement un formulaire qui à partir d'une sous-classe de models.Model, va déduire automatiquement les bons champs du formulaire.
avatar
<<< Kernel Extremis©®™ >>> et Inventeur de la différence administratif/judiciaire ! (©Yoshi Noir)

<Vertyos> un poil plus mais elle suce bien quand même la mienne ^^
<Sabrina`> tinkiete flan c juste qu'ils sont jaloux que je te trouve aussi appétissant

19

Ah oui OK, pas vraiment de la copie de code mais on s'est compris, merci !

Folco> hehe

20

Folco (./10) :
- comment la couche Application fait-elle pour proposer des valeurs limites correctes lors de la création/édition ? Elle demande pour chaque donnée à la couche Données ? Fastidieux.
spinBox->setRange(data.getMinimumValue(), data.getMaximumValue());ne m'a pas l'air trop coûteux. Ou sinon, tu utilises l'introspection de QObject: tu définis une convention (genre que pour la propriété foo, tu as les propriétés en lecture seule foo_min et foo_max) et tu modifies le ObjectController de QtPropertyBrowser pour gérer ça automatiquement.
- Dans un cas comme celui-ci : // Save the file stream << QString(GAME_FILE_SIGNATURE) << GAME_FILE_VERSION << Name; if (stream.status() != QDataStream::Ok) { QMessageBox::critical(MainWindow::get(), tr("Error"), tr("Couldn't save to the file %1").arg(FullFilename)); return false; } Modified = false; return true;Cette méthode (open()) qui appartient à la couche Données peut tomber sur plusieurs erreurs, alors deux solutions :
-> elle affiche ses dialogues et renvoie false en cas d'échec-> elle renvoie un code d'erreur, à tester pas la couche Application, qui devra afficher le message idoine. Ca nécessite de créer une énumération en plus, de faire un plus ou moins gros switch dans la partie Application. C'est plus propre sur le papier, mais ça complexifie le code.
C'est à ça que servent les exceptions. Et tu n'es même pas obligé de définir ta classe d'exceptions, tu peux aussi jeter un QString, un std::string, un const char * ou même (style AMS) un int. (Cela dit, on te dira probablement que c'est crade et que les exceptions sont censées toutes dériver de std::exception. smile)
Kevin -> je n'utiliserai pas ce qui est KDE only, et non officiellement supporté. L'avantage de se cantonner à Qt, c'est que ça devrait éviter les mauvaises surprises. smile
Les KDE Frameworks sont faits pour être utilisés séparément, tu n'as plus besoin de l'ensemble des kdelibs comme dans KDE 4. Cela dit, ce qui devrait t'intéresser le plus est QtPropertyBrowser qui est de toute façon Qt-only.

Cela dit, c'est un peu drôle que je donne des conseils pour ce genre d'architecture quand dans mon propre code (KTIGCC), l'interface utilisateurs est la couche de données. gni (Je stocke toutes les informations sur l'arborescence du projet dans des classes dérivées de QListWidgetItem. tongue)
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

21

Très excitant cette idée d'introspection, mais j'ai peur que je m'embarque dans quelque chose d'overkill qui me fasse perdre de vue mon objectif.
Si je commence à trop gamberger et à pas assez coder, le projet va encore tomber à l'eau grin
Mais je garde l'idée, à explorer un jour.

Pour les exceptions, oui c'est pas mal en effet, ça résoud mon problème. J'avais décidé de m'en passer (Qt style tongue), mais ça serati mieux dans ce cas, en effet. Double question :
- pourquoi dériver de std::exception, et non pas faire ma classe (ça prend 2 minutes) ?
- j'ai jamais compris à quoi sert de rajouter "throw machin-chose" après la déclaration d'une fonction, c'est juste un hint pour le compilateur ?

22

je pense aussi qu'il vaut mieux faire simple, les choses plus compliquées viendront avec l'expérience ^^
avatar
<<< Kernel Extremis©®™ >>> et Inventeur de la différence administratif/judiciaire ! (©Yoshi Noir)

<Vertyos> un poil plus mais elle suce bien quand même la mienne ^^
<Sabrina`> tinkiete flan c juste qu'ils sont jaloux que je te trouve aussi appétissant

23

Folco (./21) :
- j'ai jamais compris à quoi sert de rajouter "throw machin-chose" après la déclaration d'une fonction, c'est juste un hint pour le compilateur ?
Eh bien ça indique à l'utilisateur quoi catcher. C'est un contrat.
Évidemment idéalement ça ne devrait pas compiler si tu ne catchais pas explicitement machin-chose à un moment donné. C'est d'ailleurs le cas en Java. En C++, c'est plus compliqué : il se trouve qu'aux dernières nouvelles, Visual C++ ignore carrément cette indication... G++, je ne sais pas.

24

g++ l'utilise pour l'optimisation. D'ailleurs, on peut aussi déclarer throw() ou nothrow (C++11) pour les fonctions qui ne produisent pas d'exceptions.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

25

Folco (./21) :
- pourquoi dériver de std::exception, et non pas faire ma classe (ça prend 2 minutes) ?
Pour pouvoir tout catcher avec catch (const std::exception &e) et pouvoir quand-même travailler avec l'exception (ce que catch() ne permet pas). Mais c'est une convention, pas une obligation (et personnellement, je n'aime pas les std::machin-chose dans du code Qt smile).
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

26

Ok merci. Moi aussi, j'aime rester full-Qt dans une appli Qt, question de cohérence. J'ai vu des codes où les mecs passent leur temps à mapper des QString sur des std::string, et à convertir dans tous les sens, c'est une véritable horreur sick

27

Sauf que tu l'as bien profond si tu dois utiliser une lib tierce qui n'utilise pas Qt... :/
Heureusement c'est assez complet, mais... Au bout d'un moment ça arrive forcément.
C'est bien pour ça que ça me fait marrer ces gens qui trouvent le C++ tellement merveilleux.
Bref embarrassed

28

On a compris que pour toi, la machine idéale, c'est le CPU qui exécute du bytecode Java, et sur laquelle seul ce langage est supporté. cheeky

29

Nan mais encore hier j'ai un stagiaire qui est venu me voir pour un problème foireux qui ne serait pas arrivé avec un autre langage (je pense qu'il a bien perdu une journée, si c'est pas plus)
Et il n'y a pas que le java, n'importe quel langage moderne est plus utilisable cheeky (sauf ceux qui le sont moins embarrassed)
Cela dit, désolé si je suis pénible avec tout ça, je ne faisais que rebondir sur ce que tu disais à propos des conversions de strings.
(Nan, je voudrais un 68k embarrassed)

30

polar-bear-cafe-penguin.jpg
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo