Ok

En poursuivant votre navigation sur ce site, vous acceptez l'utilisation de cookies. Ces derniers assurent le bon fonctionnement de nos services. En savoir plus.

07 avril 2012

Ordre de grandeur

Estimer rapidement

Souvent, avant même de démarrer un projet, on désire avoir une idée, un « ordre de gradeur », de l'envergure de la tâche à accomplir. Il y a quelque temps, j'ai participé à un exercice de ce genre. Il s'agissait d'estimer l'effort pour la réécriture d'une application dont on savait très peu de chose et où il fallait donner un « ordre de gradeur » rapidement. Une partie de l'équipe avait préalablement colligé un minimum d'information sur l'application, essentiellement « cette application fait la même chose que tel autre application (qu'on connait mal). »

Après une grosse demi-heure de tentatives infructueuses en équipe où tout le monde lançait des chiffres et des hypothèses sans converger vers un chiffre, j'ai proposé une approche alternative (on était un vendredi après-midi et il n'était pas question que je finisse tard!). En cinq minutes nous avons obtenu un ordre de grandeur!

Première question : selon vous l'effort requis tombe dans quelle catégorie parmi les valeurs suivantes : 1 jour, 10 jours, 100, 1000, ou 10000 jours? Tour de table. Consensus pour 100 jours. Autrement dit, il s'agit d'un projet dont l'envergure sera exprimée en centaines de jours, pas en dizaines, ni en milliers.

Pas très précis me direz-vous, mais pour une minute d'effort, c'est pas mal. Comment rapidement obtenir plus de précision, sans trop d'efforts? Deuxième question.

Deuxième question : selon vous l'effort requis tombe près de quelle valeur parmi les suivantes : 100 jours, 200 jours ou 500 jours? Nouveau tour de table, consensus autour de 200 jours, bien que ça semble être un minimum.

Donc, en quelques minutes tous s'entendent pour dire qu'il s'agit d'un projet entre 200 et 500 jours. Avec le peu d'information dont nous disposons, il est inutile de croire que l'on puisse être plus précis. Idéalement on donnerait une fourchette, mais s'il faut donner un chiffre, on dirait ici 300 ±150 jours. Le danger de l'évaluation d'un ordre de grandeur est d'y mettre trop de temps et de croire à une grande précision!

Ordre de grandeur

Mais qu'est-ce qu'un « ordre de grandeur »? En français le terme est un peu déroutant. Qu'entend-t-on par grandeur? En fait, le terme anglais, « order of magnitude » nous donne une piste. La magnitude est l'exposant de la valeur. Par exemple, on parle de la magnitude d'une étoile ou d'un tremblement de terre, et dans chacun des cas une différence de 1 indique une différence d'intensité d'un facteur 10. Un tremblement de terre de magnitude 5 est 10 fois plus faible qu'un de magnitude 6. C'est la raison pour laquelle on entend peu parler des tremblements de terre de moins de 5, car ils sont 10, 100, ou 1000 fois plus faible que ceux qui sont destructeurs. De la même façon, une étoile de magnitude 5 est 10 fois plus faible qu'une de magnitude 4.

C'est la raison pour laquelle la première question que j'ai posée, cherchait à connaitre la magnitude des efforts, c'est-à-dire l'exposant.

1 = 100 = magnitude 0

10 = 101 = magnitude 1

100 = 102 = magnitude 2

1000 = 103 = magnitude 3

10000 = 104 = magnitude 4

Cette échelle, logarithmique, permet de déterminer rapidement l'ordre de grandeur. Quand on parle d'ordre de grandeur à ce niveau, on est peu précis. Ainsi, si on donne un ordre de grandeur de l'effort de 100, on ne sera pas surprit si l'effort réel est de 60, ou de 400. De la même façon il est peu probable que le réel soit de 30 ou 800.

Comme ce niveau de précision est généralement peu acceptable, est-il possible de faire mieux? Oui. Considérons la suite de valeurs suivantes :

1, 3, 10, 30, 100, 300, 1000, 3000… soit 10N et 10N.5

Ou encore

1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000… soit 10N, 10N.33 et 10N.66

C'est cette série que j'ai utilisé dans notre exemple. Je ne crois pas, en termes d'ordre de grandeur, qu'il soit possible d'être plus précis.

Exemple

Quel est l'ordre de grandeur de la taille d'un édifice comme la Place Ville Marie à Montréal (42 étages)? 1 mètre, 10 m, 100 m ou 1000 m (1 km)? Rapidement on peut éliminer 1 et 1000. A vue de nez pour une quarantaine d'étages, je vais pencher pour 100 m. Pour être plus précis : 50 m, 100 m, 200 m ou 500 m. J'hésite, mais avec mon expérience, je dirais 200 mètres. L'idée ici est d'établir des comparatifs avec des références que vous avez. Une piscine olympique de 50 m, le coin de rue où je cours, 300 mètres. Une personne normale mesure un peu moins de 2 mètres, disons qu'un étage fait 4 mètres pour l'espace entre les planchers et les plafonds. 4 m x 40 étages donne 160 mètres. Donc ma réponse rapide : 200 mètres. Réelle, mesurée : 188 mètres. Pas mal!

Provenance de la technique

Cette technique me vient de mes cours de physique (nucléaire si mon souvenir est bon) à l'université. On avait à évaluer rapidement des équations dans laquelle on trouvait des valeurs infinitésimales et gigantesques comme la charge électrique d'un électron (1,602 x10-19) et le nombre d'Avogadro (6,022 x1023). Dans ce genre de calcul, on avait simplement à additionner les exposants pour avoir une idée de la réponse, un ordre de grandeur. Par exemple, la charge d'une mole matière est de 6,022 x1023 x
1,602 x10-19, soit environ 1023-19 = 104 , ce qui est suffisamment proche de la vrai réponse qui est  9,4×104 , mais qui est plus long à calculer.

Lorsqu'on cherche un ordre de grandeur on sacrifie la précision à la rapidité. Très tôt dans un projet, alors qu'il y a peu d'information, il est illusoire de penser obtenir une précision élevée, donc il est inutile d'y consacrer beaucoup de temps. La technique proposée ici permet d'obtenir des résultats facilement.


 

14:42 Publié dans Agile | Lien permanent | Commentaires (2)

Utiliser Word pour blogger

Il est possible de blogger directement depuis Microsoft Word vers Blogspirit. Il suffit de simplement utiliser le gabarit de « Nouveau message de blog » de Word et de configurer votre compte « Autre » pour pointer vers l'api de Blogspirit avec votre compte et mot de passe normal.

http://api.blogspirit.com/xmlrpc/weblog.php

20 septembre 2011

Mobile avec Sencha Touch

Dans l'article précédent, je présente le cadre d'évaluation de plusieurs frameworks pour le développement d'application mobile. Cet article présente le détail pour Sencha Touch.

Sencha Touch est le plus évolué des frameworks que nous avons évalué. La qualité de finition des composants et le support avancé des gestes propres à un écran tactile en font un framework haut de gamme qui supporte des ambitions élevées. Cependant, cette richesse vient au prix d'une courbe d'apprentissage beaucoup plus importante, typique du développement d'application "desktop", plus que du web. Alors que les autres frameworks évalués capitalisaient sur une fondation HTML, Sencha touch est basé exclusivement sur du javascript. En effet, dans la page HTML de l'application, on ne trouve absolument rien dans le BODY, tout le DOM est créé dynamiquement au chargement de la page.

Contrairement aux autres, qui se limitent à l'interface, Sencha touch offre plus de support pour la portion de gestion des données.

Bien qu'il soit relativement facile de monter une petite application à partir des exemples et de la documentation très complète, la réalisation se heurte parfois à des écueils difficiles à passer. Peut-être la version 2 qui vient d'être publiée facilitera le travail.

A plus de 350 kb, Sencha touch à un chargement initial assez long, mais il répond très bien par la suite.

 arbrSenchaListe.png

 

arbrSenchaDetail.png

Voici le code pour l'application avec Sencha Touch:

 

<!DOCTYPE html>
<!-- Sencha touch ... -->
<html>
<head>
<title>Demo Sencha touch</title>
<%--
<metahttp-equiv="Content-Type"content="text/html; charset=UTF-8">
<metacharset="utf-8"/>
<metaname="viewport"
    content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<metaname="apple-touch-fullscreen"content="yes">
<metaname="apple-mobile-web-app-capable"content="yes"><!--this is buggy??!!-->
<metaname="apple-mobile-web-app-status-bar-style"content="black">
<linkrel="apple-touch-startup-image"href="../images/splash.png"/>
<linkrel="apple-touch-icon"href="../images/logo.png">
<linkrel="icon"type="image/ico"href="../images/logo.png">
--%>
<linkrel="stylesheet"
    href="${resource(dir:'css',file:'sencha-touch.css')}"/>
<scripttype="text/javascript"
    src="${resource(dir:'js',file:'sencha-touch.js')}">
    </script>
<script  type="text/javascript">
Ext.setup({
    tabletStartupScreen:'tablet_startup.png',
    phoneStartupScreen:'phone_startup.png',
    icon:'icon.png',
    glossOnIcon:false,        
 
    onReady:function(){
        Ext.regModel('Arbre',{
            fields:['nomLatin','nomFrancais','vignette','image','type','distance','distanceNumerique']
        });
        
        
        var store =new Ext.data.Store({
            model:'Arbre',
            sorters:'nomLatin',
            getGroupString :function(record){
                return record.get('nomLatin')[0];
            },
            data:[
                   <g:each in="${arbreInstanceList}" status="i"var="arbreInstance">
                     { nomLatin:"${fieldValue(bean: arbreInstance, field: "nomLatin")}",
                       nomFrancais:" ${fieldValue(bean: arbreInstance, field:"nomFrancais")}",
                       image:" ${fieldValue(bean: arbreInstance, field: "imageURL")}",
                       type:" ${fieldValue(bean: arbreInstance, field: "type")}",
                       distance:" ${fieldValue(bean: arbreInstance, field:"distanceMinimale")}",
                       distanceNumerique:" ${fieldValue(bean: arbreInstance, field:"distanceAsInteger")}",
                       vignette:" ${fieldValue(bean: arbreInstance, field:"vignetteURL")}"},
                  </g:each>                 
              ]
        });
        
 var sliderDistance =new Ext.form.Slider({
     width:300,
     value:14.0,
    increment:1.0,
    minValue:0,
    maxValue:15.0,
    label:'Distance',
    listeners:{
         change:function(slider, thumb,newValue){
              this.label ='changed!';
     
             console.log('slider changed to : '+newValue);
             store.filterBy(function(record,id){
                 var b =(record.data.distanceNumerique <= newValue);
                 return b;
                 })
         }
        }
});
 
 
 
        var detailToolbar =new Ext.Toolbar({
            height:66,
            items:[{
                text:'Liste',
                ui:'back',
                handler:function(){
                    corePanel.setActiveItem('listwrapper',{type:'slide', direction:'right'});
                }
              },
        
            {
                xtype:'spacer'   
            },
                sliderDistance
            ,
            {
                xtype:'checkboxfield',
                label:'Feuillus',
                width:150,
                listeners:{
                    check:function(self){
                        store.filter('type',' Feuillu');
                    },
                    uncheck:function(self){
                        store.clearFilter(false);
                    }
                }
                 },
            {
                xtype    :'textfield',
                name     :'field1',
                emptyText:'enter search term',
                listeners:{
                     keyUp:function(self, e){
                         console.log('field changed to : '+this.getValue());
                         var arr =new Array();
                         arr = document.getElementsByClassName('x-list-item');
                         for(var i =0; i < arr.length; i++){
                             var obj = document.getElementsByClassName('x-list-item').item(i);
                             if(obj.textContent
                                     .indexOf(this.getValue())==-1){
                                 obj.style.display ="none";
                             }else{
                                 obj.style.display ="block";
                             }
                         }
                     }
                    }
            }
            ]
        });
        
         var detailPanel =new Ext.Panel({
            id:'detailpanel',
            tpl:'<div class=detail>'+
                '<img src="{image}"/>'+
                '<p class=distance>{distance}</p>'+
                '<p>{type}</p>'+
                '<p class="lt">{nomLatin}</p>'+
                '<p>{nomFrancais}</p>'+
                '  </div>',
            dockedItems:[detailToolbar],
 
            afterRender:function(){
                this.mon(this.el,{
                    swipe:this.handleEvent,
                    
                });
            },
                    
            handleEvent:function(e){
                if(e.direction=='right'){
                    corePanel.setActiveItem('listwrapper',{type:'slide', direction:'right'});}
            }
 
        });
        
        var nestedList =new Ext.List({
             id:'indexlist',
            fullscreen:true,
            title:'Arbres',
            itemTpl:'<div class="arbre"><img class=inList src="{vignette}" /><p class="lt"> {nomLatin} </p><p class="fr">{nomFrancais}</p><p class="dist">{distance}</p></div>',
            grouped:true,
            indexBar:true,
            dock:'top',
            store: store,
            listeners:{
                  itemtap:function(record, index,item, e){
                      detailToolbar.setTitle(record.getStore().getAt(index).data.nomLatin);      
                    detailPanel.update(record.getStore().getAt(index).data);
                    corePanel.setActiveItem('detailpanel');
                    }
       },
            onItemDisclosure:function(record, btn, index){
                detailPanel.update(record.data);
                corePanel.setActiveItem('detailpanel');
                
            }
        });
 
        var listWrapper =new Ext.Panel({
            id:'listwrapper',
            layout:'fit',
            items:[nestedList],
            dockedItems:[
               detailToolbar
            ]
        });
 
        var btnSpecTop =[
<%--            { ui:'back', text:'Back'},--%>
<%--            { xtype:'spacer'},--%>
<%--            { ui:'default', text:'Login'}--%>
        ]// end btnSpecTop
 
 
        var tapHandler =function(btn, evt){
            alert("Button '"+ btn.text +"' tapped.");
        }        
 
        var windowHauteur = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
 
        var corePanel  =new Ext.Panel ({
            fullscreen:true,
            layout:'card',
            cardSwitchAnimation:'slide',
            items:[listWrapper,detailPanel]
            
        });
        
        var dockedItems =[{
            xtype:'toolbar',
            dock:'top',
            title:'Arbr',
            items: btnSpecTop,
            defaults:{ handler: tapHandler }
            },
            {
                xtype:'tabpanel',
               height: windowHauteur  -48,
                defaults:{
                    scroll:'vertical'
                },
               
                tabBar:{
                    dock:'bottom',
                    ui:'dark',
                    layout:{
                        pack:'center'
                    }
                },
                items:[{
                    title:'Liste',
                    iconCls:'home',
                    cls:'card card1',
                    badgeText:'',
                    items:[corePanel]
              
                },
                {
                    title:'Zone',
                    html:'<img src="http://www.hydroquebec.com/arbres/images/img_zones_rusticite.gif" class="fitToPort"></img>',
                    iconCls:'locate',
                    cls:'card card3',
                    
                },
                {
                    title:'Préférences',
                    html:'<p>test 3</p>',
                    iconCls:'settings',
                    cls:'card card3'
                }]
             
            }                
        ]
 
        var appPanel =new Ext.Panel({
            id:'appPanel',
            fullscreen:true,
            dockedItems: dockedItems            
        });
        
        
    }   // end onReady 
    });
</script>
<style>
.card3 {
    background-color:rgb(220,221,223);
    text-align:center;
    }
.fitToPort {
width:100%;}
.detail {
margin:1em;}
</style>
</head>
<body>
 
 
</body>
</html>

14:55 Publié dans Mobile | Lien permanent | Commentaires (0)

Mobile avec jQuery mobile

Dans l'article précédent, je présente le cadre d'évaluation de plusieurs frameworks pour le développement d'application mobile. Cet article présente le détail pour jQuery Mobile.

La particularité de jQuery mobile est son support pour un grand, très grand, nombre d'appareils mobiles. La mise en œuvre est relativement facile et très axée sur le code HTML avec l'utilisation d'attributs "data-quelqueChose". La variété de composants d'interface est assez riche et offre une bonne base. Les rendus sont de qualité, comme le montre la figure suivante. La documentation est correcte, sans plus et la qualité générale est au-dessus de la moyenne.

La version testée, 1.0b1, se traîne souvent les pieds, autant au chargement que dans les effets de transition. Espérons que les nouvelles versions amélioreront cet aspect.

Pour un framework de base fonctionnant sur plusieurs appareils c'est un excellent choix.

 

arbrJQmobileListe.png

arbrJQMobileDetail.png

Voici le code pour jQuery Mobile

 

<!DOCTYPE html>
<html>
<head>
<title>Demo JQMobile</title>
 
<linkrel="stylesheet"
    href="${resource(dir:'js/jquery.mobile-1.0b1',file:'jquery.mobile-1.0b1.min.css')}"/>
<scripttype="text/javascript"
    src="${resource(dir:'js/jquery.mobile-1.0b1',file:'jquery-1.6.1.min.js')}">
    </script>
<scripttype="text/javascript"
    src="${resource(dir:'js/jquery.mobile-1.0b1',file:'jquery.mobile-1.0b1.min.js')}">
    </script>
 
<script type="text/javascript">
$(document).ready(function(){
    // le masquage pourrait être fait avec des CSS plus efficacement!
       var arr =new Array();
    var disp;
    $('.ui-slider').live('mouseup',function(index,el){
        
        arr = document.getElementsByClassName('listJD');
           for(var i =0; i < arr.length; i++){
               var obj = arr[i];
               if(parseInt(obj.children[0].children[0].children[0].children[5].textContent)==0){
                   obj.style.display ="none";
               }else{
                   if(parseInt(obj.children[0].children[0].children[0].children[5].textContent)<=parseInt($("#slider")[0].value)){
                       obj.style.display ="block";}
                   else{
                       obj.style.display ="none";}
                   
               }
           };
        });
var filterFeuillus =true;
    $("#feuillus").next('label').click(function(index,el){
        filterFeuillus = filterFeuillus==false?true:false;   
            if(filterFeuillus){disp ="block"}
                else
               {disp ="none"}
            arr = document.getElementsByClassName('listJD');
            for(var i =0; i < arr.length; i++){
                var obj = arr[i];
                if(obj.textContent
                        .indexOf("Feuillu")!=-1){
                    obj.style.display = disp;
                }else{    
                }
            }
    });
var filterConiferes =true;
    $("#coniferes").next('label').click(function(index,el){
        filterConiferes = filterConiferes==false?true:false;
            if(filterConiferes){disp ="block"}
                else
               {disp ="none"}
            arr = document.getElementsByClassName('listJD');
            for(var i =0; i < arr.length; i++){
                var obj = arr[i];
                if(obj.textContent
                        .indexOf("Conifère")!=-1){
                    obj.style.display = disp;
                }else{
                    
                }
            }
    });
});  
</script>
 
 
<style type="text/css">
.fr {
    font-weight:bold;
}
 
.lt {
    font-style:italic;
}
 
.fitToPort {
    width:100%;}
 
.Feuillu a.fr{
    background-image:url("../images/feuillu.jpg");
    background-repeat:no-repeat;
    background-position:010px;}
.Conifère a.fr{
    background-image:url("../images/conifere.jpg");
    background-repeat:no-repeat;
    background-position:010px;}
 
/* media queries from http://stuffandnonsense.co.uk/blog/about/hardboiled_css3_media_queries */
</style>
 
</head>
<body>
 
<!-- Start of FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF F F F F first page  1 1 1 1 1 1 -->
<divdata-role="page"id="home">
    <divdata-role="header"data-position="fixed">
        <h1>Arbr</h1>
    </div><!-- /header -->
 
    <divdata-role="content">
        <divdata-role="controlgroup"data-type="horizontal">
            <inputtype="checkbox"name="feuillus"id="feuillus"  checked/>
            <labelfor="feuillus">Feuillus</label>
            <inputtype="checkbox"name="coniferes"id="coniferes"  checked/>
            <labelfor="coniferes">Conifères</label>
        </div>
    
        <divdata-role="fieldcontain">
            <labelfor="slider">Distance  (m) :</label>
            <inputtype="range"name="slider"id="slider"value="14" min="0" max="15"/>
        </div>
    
        <uldata-role="listview"data-inset="true"data-filter="true">
            <g:each in="${arbreInstanceList}" status="i" var="arbreInstance">
                <liclass="listJD ${fieldValue(bean: arbreInstance, field: "type")}">
                    <ahref='#detail_${fieldValue(bean: arbreInstance, field: "id")}'class="ui-link-inherit fr">
                        ${fieldValue(bean: arbreInstance, field: "nomFrancais")}
                    </a>
                </li>
            </g:each>
        </ul>
    </div>
    <!-- /content -->
 
    <divdata-role="footer"data-position="fixed"data-fullscreen="true"style="text-align:center;">
        <divdata-role="navbar"data-type="horizontal">
            <ahref="#home"data-role="button"data-icon="grid"class="ui-btn-active">Liste</a>
            <ahref="#zone"data-role="button"data-icon="star">Zones</a>
            <ahref="#home"data-role="button"data-icon="gear">Préférences</a>
        </div>
    </div>
    <!-- /footer -->
</div>
<!-- /page -->
 
<!-- Start of second page  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  DETAILS  -->
<!-- DETAILS pages  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-->
<g:each in="${arbreInstanceList}" status="i" var="arbreInstance">
<divdata-role="page"data-url='detail_${fieldValue(bean: arbreInstance, field: "id")}'>
        <divdata-role="header"data-position="fixed">
            <h1>Arbr</h1>
        </div>
        <!-- /header -->
 
    <divdata-role="content"><ahref="#home"class="ui-link-inherit">
        <imgsrc='${fieldValue(bean: arbreInstance, field: "imageURL")}'alt=""/>
        <pclass="fr">${fieldValue(bean: arbreInstance, field: "nomFrancais")}</p>
        <pclass="lt">${fieldValue(bean: arbreInstance, field: "nomLatin")}</p>
        <pclass="en">${fieldValue(bean: arbreInstance, field: "nomAnglais")}</p>
        <pclass="zone">${fieldValue(bean: arbreInstance, field: "zone")}</p>
        <pclass="distance">${fieldValue(bean: arbreInstance, field: "distanceMinimale")}</p>
    </div>
    <!-- /content -->
 
    <divdata-role="footer"data-position="fixed"data-fullscreen="true"style="text-align:center;">
        <divdata-role="navbar"data-type="horizontal">
            <ahref="#home"data-role="button"data-icon="grid">Liste</a>
            <ahref="#zone"data-role="button"data-icon="star"class="ui-btn-active">Zones</a>
            <ahref="#home"data-role="button"data-icon="gear">Préférences</a>
        </div>
    </div>
    <!-- /footer -->
</div><!-- /page -->
</g:each>
 
<!-- Start of 3rd page  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-->
<!-- ZONE pages  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-->
<divdata-role="page"data-url="zone">
    <divdata-role="header"data-position="fixed">
    <h1>Arbr</h1>
    </div><!-- /header -->
 
    <divdata-role="content">
        <imgsrc="http://www.hydroquebec.com/arbres/images/img_zones_rusticite.gif"
            class="fitToPort"alt="Carte des zones de rusticité du Québec"/>
    </div><!-- /content -->
 
    <divdata-role="footer"data-position="fixed"data-fullscreen="true"
        style="text-align:center;">
        <divdata-role="navbar"data-type="horizontal">
            <ahref="#home"data-role="button"data-icon="grid">Liste</a>
            <ahref="#zone"data-role="button"data-icon="star">Zones</a>
            <ahref="#home"data-role="button"data-icon="gear"class="ui-btn-active">Préférences</a>
        </div>
    </div>
<!-- /footer -->
</div><!-- /page -->
 
</body>
</html>

14:42 Publié dans Mobile | Lien permanent | Commentaires (0)