Et là j'ai peur en me voyant en vidéo ....
Friday, January 28, 2011
Tuesday, January 25, 2011
Pharo GUI with Polymorph
I've written a tiny address book to prepare a screencast on Polymorph basis. If you want to look at it:
Start by looking how the contact list is built:
When the Add button is clicked, message #addButtonClick is sent on the ContactListEditor object:
ContactEditor defines a modal dialog to edit the firstName and lastName of a Contact:
Disabling the acceptOnCR for each text frield allows the default dialog handling for the return key (defaults to OK).
Normally the initial keyboard focus for a dialog is the default button, if specified. Remembering the first name field prior to opening will give that field focus.
Now it should be easier to understand Polymorph examples found in
UITheme class>>exampleBasicControls and friends (in examples protocol).
Gofer it
squeaksource: 'Pharocasts';
package: 'ContactManager';
load.
(Smalltalk at:#ContactListEditor) open.
Start by looking how the contact list is built:
ContactListEditor>>open
|builder content|
builder := UITheme builder.
content := builder newColumn: {
builder
newListFor: self
list: #contacts
selected: #contactSelectedIndex
changeSelected: #contactSelectedIndex:
help: 'contacts'.
builder newRow: {
builder newButtonFor: self
action: #addButtonClick
label: 'Add'
help: 'Create a new contact'.
builder newButtonFor: self
action: #removeButtonClick
getEnabled: #hasSelectedContact
label: 'Remove'
help: 'Remove selected contact'.
builder newButtonFor: self
action: #editButtonClick
getEnabled: #hasSelectedContact
label: 'Edit'
help: 'Edit selected contact' }}.
(content openInWindowLabeled: 'Contacts') extent: 400@500.
#newRow: and #newColumn: are an easy way to align elements on the window.When the Add button is clicked, message #addButtonClick is sent on the ContactListEditor object:
ContactListEditor>>addButtonClick
|newContact|
newContact := Contact new.
ContactEditor new
contact: newContact;
onOK: [ Contact database add: newContact.
selectedContactIndex := Contact database size.
self
changed: #contacts;
changed: #hasSelectedContact];
openModal.
The closure given to onOK: adds the new Contact and tells the view to refresh components which depends on #contacts and #hasSelectedContact selectors - that means the contact list and the Remove and Edit buttons.ContactEditor defines a modal dialog to edit the firstName and lastName of a Contact:
ContactEditor>>openModal
|builder dialog content firstName|
builder := UITheme builder.
content := (builder newLabelGroup: {
'First name' -> (
firstName := (builder
newTextEntryFor: contact
getText: #firstName
setText: #firstName:
help: 'Enter the first name of the contact')
acceptOnCR: false;
minWidth: 200).
'Last name' -> (
(builder
newTextEntryFor: contact
getText: #lastName
setText: #lastName:
help: 'Enter the last name of the contact')
acceptOnCR: false;
minWidth: 200) }).
dialog := builder
newPluggableDialogWindow:'Edit contact'
for: content.
dialog rememberKeyboardFocus: firstName.
builder openModal: dialog.
dialog cancelled ifFalse: [self doOnOK].
From Gary Chambers (and thanks !):Disabling the acceptOnCR for each text frield allows the default dialog handling for the return key (defaults to OK).
Normally the initial keyboard focus for a dialog is the default button, if specified. Remembering the first name field prior to opening will give that field focus.
Now it should be easier to understand Polymorph examples found in
UITheme class>>exampleBasicControls and friends (in examples protocol).
Monday, January 24, 2011
CodeRetreat à Grenoble
Vendredi dernier je me suis rendu au premier CodeRetreat en France organisé par Johan Martinsson, Remy Sanlaville et Miguel Moquillon, avec le soutien du CARA et Alpes JUG - merci tout le monde !
En résumé, le CodeRetreat consiste à enchaîner des itérations d'une heure en pair-programming et Test-Driven Development sur le même problème tout au long de la journée. Le problème choisi était le jeu de la vie, qui a les avantages d'offrir plusieurs approches possibles et d'être assez complexe pour ne pas être résolu en 45mn.
Chaque itération se déroule de la manière suivante:
C'est agile, le temps est chronométré (belle organisation).
A la fin des 45mn de programmation, le code est effacé. Comme l'a répété Johan, ce n'est pas le résultat qui compte, mais la démarche, l'approche du développement qu'on doit s'efforcer de faire le plus parfaitement. C'est à dire:
Côté langages nous avions beaucoup de Java. Miguel et moi en avons profité pour faire découvrir Pharo / Smalltalk et faire une session ensemble en Haskell. Il y a aussi eu un peu de Ruby.
Les 15mn de rétrospective permettaient d'échanger sur les différentes approches, les difficultés rencontrées, succès. On voit bien qu'il y a plusieurs manières d'attaquer le problème. Au fur et à mesure des automatismes se mettent en place ce qui permet de gagner du temps pour aborder d'autres aspects du problème.
Suit ce que j'en retire.
Pair-programming:
Chaque session se déroulait avec un binôme différent et chaque expérience, démarche a du coup été significativement différente. J'ai rencontré plusieurs cas:
Dans tous les cas, il y a toujours une bonne ambiance à coder avec des inconnus :)
Test-Driven Development:
Je suis convaincu de la démarche et la pratique depuis que j'ai lu eXtreme Programming de Kent Beck vers 2002/2003. Donc je fais partie des gens qui ont des automatismes assez arrêtés sur le sujet :). Pour avoir vu d'autres façons de faire au cours de la session, je reste persuadé que la manière la plus claire est d'avoir:
Un exemple concret est la règle: "Si une cellule a exactement deux voisines vivantes, elle reste dans son état actuel à l’étape suivante".
Avec Rémy nous avons codé comme suit (grosso modo, le code a été effacé :)
Le soucis c'est que quand le test est en échec, il faut débugger pour savoir si ça coince pour deux ou trois cellules.
Dans ce cas je préfère avoir deux cas:
Ceci dit cela risque d'amener une duplication de code, vu que les tests sont identiques dans chaque cas. Une solution codée avec Aline est d'utiliser des Traits pour définir les tests de manière générique puis de mettre seulement les différences dans les TestCase.
Du coup pour nos cas de test:
de même:
Approches
Les deux approches principales consistaient à commencer par implémenter la Cellule ou bien la Grille / Univers. Il était aussi possible de passer par une classe Neighbours intermédiaire.
Haskell qui est un langage fonctionnel permet d'avoir une approche par les ensembles.
Une solution liée à l'imagerie en travaillant en binaire a été évoquée mais pas implémentée à ma connaissance.
Pour simplifier l'algorithme qui calcule si la cellule doit vivre ou mourir en connaissant son état actuel et le nombre de ses voisins vivants, deux variantes:
Avec Smalltalk une solution utilisée pour implémenter le pattern strategy se basait sur les Closures et évitait de créer des classes supplémentaires:
Bilan
Journée enrichissante et éreintante. La retrospective finale en a un peu pâti vu que tout le monde était fatigué. Une proposition d'amélioration qui me plaît est de découper la session de 45mn en deux pomodoro de 20mn pour ménager notre énergie.
Quelques "Smalltalk c'est cool" font plaisir ;)
En tout cas je reste convaincu par l'approche pair-programming qui est trés enrichissante et donne du courage pour explorer de nouvelles pistes et aller plus vite (car on se perds moins).
C'est dans ces moments là qu'on sent que la programmation est un artisanat au sens noble du terme, où la beauté du résultat est une source de motivation.
Par @bootis quality = you know why it works
Le compte-rendu de Johan
En résumé, le CodeRetreat consiste à enchaîner des itérations d'une heure en pair-programming et Test-Driven Development sur le même problème tout au long de la journée. Le problème choisi était le jeu de la vie, qui a les avantages d'offrir plusieurs approches possibles et d'être assez complexe pour ne pas être résolu en 45mn.
Chaque itération se déroule de la manière suivante:
- Constitution des binômes (on change à chaque itération).
- 45mn de programmation.
- 15mn de rétrospective.
C'est agile, le temps est chronométré (belle organisation).
A la fin des 45mn de programmation, le code est effacé. Comme l'a répété Johan, ce n'est pas le résultat qui compte, mais la démarche, l'approche du développement qu'on doit s'efforcer de faire le plus parfaitement. C'est à dire:
- développement piloté uniquement par les tests
- pas de duplication de code
- noms clairs et significatifs
- code le plus simple possible
Côté langages nous avions beaucoup de Java. Miguel et moi en avons profité pour faire découvrir Pharo / Smalltalk et faire une session ensemble en Haskell. Il y a aussi eu un peu de Ruby.
Les 15mn de rétrospective permettaient d'échanger sur les différentes approches, les difficultés rencontrées, succès. On voit bien qu'il y a plusieurs manières d'attaquer le problème. Au fur et à mesure des automatismes se mettent en place ce qui permet de gagner du temps pour aborder d'autres aspects du problème.
Suit ce que j'en retire.
Pair-programming:
Chaque session se déroulait avec un binôme différent et chaque expérience, démarche a du coup été significativement différente. J'ai rencontré plusieurs cas:
- une personne qui a l'idée et une autre qui suit: c'est du coup souvent la personne qui veut voir son idée réalisée qui pilote et qui tape. La personne qui suit dépense beaucoup d'énergie pour rester dans le coup (j'ai été dans cette position sur une session). Il est nécessaire que la personne qui pilote temporise.
- deux personnes qui ont des idées: cela amène assez vite a quelques débats sympathiques mais c'est l'expérience qui me semble la plus enrichissante, d'où émerge des solutions de type "waouh, ça c'est cool".
- deux personnes ont la même idée, suivent la même route: dans ce cas c'est plus la volonté de bien faire qui ressort et la recherche de qualité / perfection dans la démarche est vraiment élevée
Dans tous les cas, il y a toujours une bonne ambiance à coder avec des inconnus :)
Test-Driven Development:
Je suis convaincu de la démarche et la pratique depuis que j'ai lu eXtreme Programming de Kent Beck vers 2002/2003. Donc je fais partie des gens qui ont des automatismes assez arrêtés sur le sujet :). Pour avoir vu d'autres façons de faire au cours de la session, je reste persuadé que la manière la plus claire est d'avoir:
- un TestCase par contexte. Le setUp établit le contexte, les tests font les vérifications.
- un assert par test. Avoir plusieurs assert doit rester exceptionnel car ça complexifie les tests (ce n'est pas interdit, mais il faut avoir conscience du compromis).
Un exemple concret est la règle: "Si une cellule a exactement deux voisines vivantes, elle reste dans son état actuel à l’étape suivante".
Avec Rémy nous avons codé comme suit (grosso modo, le code a été effacé :)
ALivingCellWithTwoOrThreeLivingNeighbours>>#testShouldLive
|aliveCell|
aliveCell := Cell new revive.
self assert: aliveCell isAlive.
2 to: 3 do: [:numberOfNeighbours|
aliveCell numberOfNeighbours: numberOfNeighbours.
self assert: aliveCell shouldLive.
]
Le soucis c'est que quand le test est en échec, il faut débugger pour savoir si ça coince pour deux ou trois cellules.
Dans ce cas je préfère avoir deux cas:
- ALivingCellWithTwoLivingNeighbours
- ALivingCellWithThreeLivingNeighbours
Ceci dit cela risque d'amener une duplication de code, vu que les tests sont identiques dans chaque cas. Une solution codée avec Aline est d'utiliser des Traits pour définir les tests de manière générique puis de mettre seulement les différences dans les TestCase.
Trait named: #TCellIsAliveAndShouldLive
TCellIsAliveAndShouldLive>>#testIsAlive
self assert: self aliveCell isAlive
TCellIsAliveAndShouldLive>>#testShouldLive
self assert: self aliveCell shouldLive
TCellIsAliveAndShouldLive>>#aliveCell
self explicitRequirement
Du coup pour nos cas de test:
TestCase subclass: #ALivingCellWithTwoLivingNeighbours
uses: TCellIsAliveAndShouldLive
ALivingCellWithTwoLivingNeighbours>>#aliveCell
^ Cell new
revive;
numberOfNeighbours: 2.
de même:
TestCase subclass: #ALivingCellWithThreeLivingNeighbours
uses: TCellIsAliveAndShouldLive
ALivingCellWithThreeLivingNeighbours>>#aliveCell
^ Cell new
revive;
numberOfNeighbours: 3.
Approches
Les deux approches principales consistaient à commencer par implémenter la Cellule ou bien la Grille / Univers. Il était aussi possible de passer par une classe Neighbours intermédiaire.
Haskell qui est un langage fonctionnel permet d'avoir une approche par les ensembles.
Une solution liée à l'imagerie en travaillant en binaire a été évoquée mais pas implémentée à ma connaissance.
Pour simplifier l'algorithme qui calcule si la cellule doit vivre ou mourir en connaissant son état actuel et le nombre de ses voisins vivants, deux variantes:
- avoir deux classes LiveCell et DeadCell qui ont chacune leur algorithme
- implémenter le pattern strategy et donc un objet Cell utiliserait soit un DeadCellStrategy, soit un LiveCellStrategy. Ceci à l'avantage de ne pas avoir à changer le type des instances dans la grille
Avec Smalltalk une solution utilisée pour implémenter le pattern strategy se basait sur les Closures et évitait de créer des classes supplémentaires:
Cell>>#liveCellStrategy
^ [(liveNeighbours < 2) or: [ liveNeighbours > 3 ]]
Cell>>#deadCellStrategy
^ [(liveNeighbours = 3) not]
Cell>>die
shouldDieStrategy := self deadCellStrategy
Cell>>revive
shouldDieStrategy := self liveCellStrategy
Cell>>#shouldDie
^ shouldDieStrategy value
Bilan
Journée enrichissante et éreintante. La retrospective finale en a un peu pâti vu que tout le monde était fatigué. Une proposition d'amélioration qui me plaît est de découper la session de 45mn en deux pomodoro de 20mn pour ménager notre énergie.
Quelques "Smalltalk c'est cool" font plaisir ;)
En tout cas je reste convaincu par l'approche pair-programming qui est trés enrichissante et donne du courage pour explorer de nouvelles pistes et aller plus vite (car on se perds moins).
C'est dans ces moments là qu'on sent que la programmation est un artisanat au sens noble du terme, où la beauté du résultat est une source de motivation.
Par @bootis quality = you know why it works
Le compte-rendu de Johan
Friday, January 14, 2011
PharoConf Annecy 2011
La première PharoConf Annecy aura lieu le jeudi 10 février à l'IMUS.
Site web de l'évènement: http://pharoconf-annecy.seasidehosting.st/
Les interventions seront plus tournées vers des coding-dojo et ateliers, développer du vrai code qui tourne :)
Deux points forts:
- Randori Test-Driven Development animé par Miguel Moquillon du Club Agile Rhône Alpes
- PharoSprint animé par Stéphane Ducasse
Entrée gratuite, mettez à jour vos agendas !
(Et merci de faire passer le message :)
Site web de l'évènement: http://pharoconf-annecy.seasidehosting.st/
Les interventions seront plus tournées vers des coding-dojo et ateliers, développer du vrai code qui tourne :)
Deux points forts:
- Randori Test-Driven Development animé par Miguel Moquillon du Club Agile Rhône Alpes
- PharoSprint animé par Stéphane Ducasse
Entrée gratuite, mettez à jour vos agendas !
(Et merci de faire passer le message :)
Monday, January 10, 2011
Petite horloge
Morphic basics with a little Watch (Horloge in french).
Add the class:
One method called periodically thanks to stepping mechanism of Morphic:
Then evaluate in a Workspace:
Stop the watch:
Start it again:
Finally close it:
Add the class:
StringMorph subclass: #Horloge
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Sandbox'
One method called periodically thanks to stepping mechanism of Morphic:
Horloge>>step
self contents: Time now printString.
Then evaluate in a Workspace:
Horloge new openInWorld.
Stop the watch:
Horloge allInstances last stopStepping
Start it again:
Horloge allInstances last startStepping
Finally close it:
Horloge allInstances last delete
Subscribe to:
Posts (Atom)