Les jeux de mon enfance #2

 

Et voilà enfin l'article que vous attendiez tous ! (Inutile de le nier, je le sais. Wink)

Comme dans mon précédent article, j'avais envie de vous parler de quelques jeux qui m'ont marqué, touché et pour lesquels je ne peux m'empêcher d'être nostalgique (ou devrais-je dire nostalgeek ? Cool).

J'ai réussi, encore une fois, à sélectionner des jeux qui datent d'avant 2000. Ce n'était pas facile, et je pense que mon prochain article de ce genre ne respectera pas cette règle. Mais bon, tant pis après tout ! C'est mon blog, je fais ce que je veux ! Tongue out

 

Metal Gear Solid

Ahhhh Metal Gear Solid. Difficile de repenser à ce jeu sans avoir envie d'y rejouer. Pour moi, il s'agit du meilleur jeu d'action / infiltration de tous les temps.

Pour ceux qui ne connaissent pas, le scénario est simple : vous êtes Solid Snake, un soldat d'élite, et vous devez vous infiltrer sur l'île de Shadow Moses, une base nucléaire contrôlée par des terroristes, afin de libérer les otages et d'empêcher tout tir nucléaire depuis cette île.

Magnifique, prenant, maniable, doté d'un scénario et d'une bande son extraordinaires, ce jeu était juste parfait. A l'instar des Call of Duty ou autres Assassin's Creed d'aujourd'hui, on était face à un véritable film interactif. L'histoire, ses rebondissements et ses mystères, ainsi que la psychologie des personnages rendaient ce jeu incroyablement intense. Il m'était juste impossible de poser la manette dans certains passages du jeu. Smile

D'ailleurs c'est intéressant, mais de ces jeux (Metal Gear Solid, Call of Duty, Assassin's Creed), je ne garde que de bons souvenirs. Même s'ils avaient de nombreux défauts, une fois le jeu terminé, on ne pouvait que dire "Déjà ?" ou "A quand le prochain ?". D'ailleurs, à quand le prochain ? Wink

 

 

Crash Team Racing

Déjà, pour ceux qui ne le connaissent pas, Crash Bandicoot est le héros d'une série de jeux de plate-formes en 3D sur PlayStation. Face au succès de Mario Kart 64, Naughty Dog, la société qui éditait Crash Bandicoot, a décidé de sortir un jeu de karting "concurrent" (toujours sur la même console). Crash Team Racing est donc arrivé en 1999.

Jouable à 4, ce qui était rare pour les jeux de l'époque, il offrait une expérience de jeu juste excellente. Il était très facile à prendre en main, équilibré, beau et très fun. En 1 tour de circuit on savait jouer, et en 2 tours on savait faire des dérapages turbo. Pas de temps mort, pas d'ennui, et n'importe qui pouvait gagner. Doté de la même qualité que Mario Kart, le jeu était, pour moi, plus équilibré et doté d'une bien meilleure finition.

Ce jeu n'avait que deux défauts, qui ont joué contre lui et l'ont empêché de se faire vraiment connaître :

  • Il est arrivé bien après Mario Kart 64, sur la fin de vie de la première PlayStation,
  • Il fallait un connecteur multi-manettes très cher pour y jouer à 4 (300 Francs à l'époque ! Soit le prix d'un jeu ! Merci Sony ! Yell).

J'ai passé des dizaines d'heures à m'éclater sur ce jeu, avec amis et voisins. Des heures et des heures de fun... C'était, et c'est toujours d'ailleurs, mon jeu de kart préféré, loin devant les Mario Kart !

 

 

Les Chevaliers de Baphomet

Pour le troisième et dernier jeu de cet article, j'ai beaucoup hésité entre les Chevaliers de Baphomet et Full Throttle. J'avais vraiment envie de parler d'un point'n'click sympa, et j'ai donc choisir de vous parler des Chevaliers de Baphomet, car c'est sur ce point'n'click que j'ai passé le plus de temps.

Déjà qu'est ce qu'un point'n'click ? Pour ceux qui ne connaissent pas ce genre de jeux, les point'n'click sont des jeux d'aventure dans lesquels on n'utilise que la souris pour donner des ordres au personnage principal. On ne contrôle pas le personnage comme dans n'importe quel FPS ou RPG, on lui dit juste quoi faire en cliquant sur des éléments du décor ou des objets de l'inventaire. Ce type de jeux demandait de la réflexion, et parfois un peu de chance (ou de triche Embarassed) ou pouvoir avancer dans l'histoire.

Pour en revenir au jeu, les Chevaliers de Baphomet raconte l'histoire d'un sympathique touriste américain, George Stobbart, qui, alors qu'il prenait son café tranquillement, échappe de peu à une explosion. La police n'ayant aucune piste, et George étant plutôt curieux, il va mener l'enquête pour essayer de comprendre le fin mot de l'histoire. Son périple va l'amener à visiter de nombreux pays et à découvrir une conspiration millénaire digne des romans de Dan Brown (et oui, l'histoire des templiers était déjà à la mode en ce temps là !).

Le jeu n'est pas spécialement beau, mais son côté bande dessinée à la Tintin le rend très agréable à parcourir. Les personnages sont attachants, drôles, et l'histoire est très bien ficelée. On regrettera juste certaines galères propres aux point'n'click (devoir trouver quel objet utiliser sur quel élément du décor pour se décoincer... ARGHH ! Yell).

En bref, un jeu très sympa, sur lequel on passe rapidement des heures des heures à cliquer sur tous les éléments du décor pour trouver COMMENT se décoincer ou à quoi peut bien servir tel objet.

 

 

Voilà, je pense avoir fait le tour des principaux jeux sortis avant 2000 qui m'ont vraiment marqué. Il y a quelques autres jeux dont je pourrais parler (Secret of Mana, Total Annihilation, Dragon Ball Z Final Bout, ...), mais je n'ai pas vraiment le courage d'aborder tous les jeux dont je me souviens, sinon j'y passerais des heures et des heures... Je pense plutôt, à partir du prochain article, me concentrer sur mes jeux préférés sortis après 2000.

Starcraft, Diablo 2, Warcraft 3, Super Smash Bros Melee, Time Splitters 2, Phantasy Star Online, World Of Warcraft, Assassin's Creed, ... J'ai largement de quoi faire...

Alors je vous dit à bientôt pour un autre article sur les jeux de mon enfance !

image modifiée de stormwarning, sous licence CC

Un exemple d'architecture .Net complète

13. décembre 2011 09:30 by Tommy in Développement  //  Tags: , , , , , , , , , , , ,   //   Commentaires (7)   //  Partager sur Facebook  / Twitter

 

Ces derniers temps, j'ai pas mal réfléchi à la meilleure façon de séparer un projet .Net en couches. Classes métier, accès simplifié à la BDD, tests unitaires avec bouchons, injection de dépendances, mapping, ... Tellement de problématiques à gérer lorsqu'on crée une architecture .Net.

Du coup je me suis fixé un objectif simple : créer une architecture de solution .Net "propre, avec

  • des couches bien séparées (classes métier, logique métier, accès aux données, services, tests unitaires, interfaces utilisateur),
  • aucun Framework pour les tests unitaires (histoire que le code reste simple à appréhender et à comprendre pour un débutant),
  • de l'injection de dépendances pour pouvoir brancher facilement les tests unitaires sur des "bouchons",
  • enfin, un accès aux données simplifié, sans SQL ni procédures stockées (donc avec Linq et Entity Framework).

Au final, j'ai créé un simpliste "moteur de blog", capable d'afficher des articles puisés dans une BDD SQL Server 2008 Express. Pour éviter que ce beau projet .Net ne se perde dans les méandres de mon disque dur avant de sombrer dans l'oubli, je me suis dit que le partager avec vous serait le meilleur moyen de lui offrir une seconde vie. Qui sait, peut être sera-t-il utile à de nombreuses personnes ! Smile

Le voici donc : projet HTA.DemoBlog.zip (1,54 mb). Le code source est sous licence LGPL, donc vous pouvez l'utiliser comme bon vous semble ! Wink

Voici, déjà, ce qu'il y a à noter sur cette architecture (pour chaque couche, j'ai créé un solution folder) :

1) Couche Business

  • BusinessEntities : les objets métier, qu'on manipulera dans l'application. L'intérêt est d'avoir des objets complètement détachés de la BDD et qu'on pourra manipuler dans une autre application, dans une bibliothèque de classes, dans un module à part, ... Les objets ne sont vraiment que des conteneurs indépendants de tout le reste.
  • BusinessLogic : Tous les traitements métier. Ici on retrouvera la logique métier, les Try Catch, les calculs compliqués, les logs, ... Ce code sera celui utilisé par les applications (qu'il s'agisse de Winforms, d'ASP.Net, de WPF, ...). Cette couche interagit avec la couche DataAccess et utilise les objets BusinessEntities.

2) Couche Resources

  • DataAccessInterfaces : les interfaces qui définiront les méthodes de la couche DataAccess.
    Intérêt double : pouvoir utiliser des bouchons (mock) qui implémentent cette interface dans les tests unitaires, afin d'avoir des tests unitaires indépendants de la BDD et donc qui peuvent être lancés à chaque build / release.
    L'autre intérêt est de pouvoir changer de source de données ou les méthodes qui interagissent avec la BDD sans avoir à recoder des choses dans d'autres couches (vu qu'elles implémentent ces interfaces et non pas les objets DataAccess directement).
  • DataAccess : la couche qui interroge la BDD. Elle renvoie des objets BusinessEntities et possède des méthodes définies dans DataAccessInterfaces. Vu qu'elle est détachée, on peux utiliser ce qu'on veut pour stocker nos données : Entity Framework, Linq 2 Sql, des procédures stockées, des fichiers XML, ... Si on doit changer de source de données ou de façon d'exploiter notre source de données, on n'aura qu'à recréer une couche qui implémentera les interfaces DataAccessInterfaces, et le tour sera joué.

3) Couche Tests

  • Tests.Mock : des classes bouchon (donc avec des données en dur) qui implémentent les interfaces DataAccessInterfaces.
  • Tests : les tests unitaires. Ces tests testent la couche BusinessLogic via les mocks plutôt que via la couche DataAccess (afin de ne tester que la logique métier, sans risquer de modifier les données en base).

4) Couche Interfaces (UI)

  • La couche qui contiendra les interfaces utilisateur (WebApp, Winforms, WPF, ASP.Net MVC, ...) qui utiliseront uniquement le code métier dans la couche BusinessLogic et qui manipulera des objets BusinessEntities.

5) Couche Core

  • La couche Core contient des méthodes génériques utiles au projet mais qui pourront être réutilisées / utiles pour d'autres projets. (Cryptographie, Logs, ...)

6) Couche Database

  • Le projet Database permet de gérer le contenu de la BDD SQL Server, de mettre à jour les procédures stockées et de modifier les scripts de déploiement, sans avoir à le faire à la main.

7) Couche Service

  • Cette couche permet de créer des services qui utilisent la couche métier et donc de rendre le code accessibles de l'extérieur. (Pour l'utiliser, par exemple, via une application web distante ou via une application Windows Phone 7).

 L'architecture ressemble donc à ceci :

 

Voyons maintenant le code un peu plus en détail.

Dans un premier temps, intéressons-nous à la classe ArticleBE, de la couche BusinessEntities, qui définit la structure d'un article :

    public class ArticleBE : ICloneable
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public string Author { get; set; }
        public string Tags { get; set; }
        public DateTime Date { get; set; }

        public object Clone()
        {
            ArticleBE result = new ArticleBE()
            {
                Id = Id,
                Date = Date,
                Author = Author.Clone() as string,
                Content = Content.Clone() as string,
                Tags = Tags.Clone() as string,
                Title = Title.Clone() as string
            };
            return result;
        }
    }

Comme vous pouvez le voir, le code est vraiment le plus simple possible. Nous avons juste nos propriétés et la méthode Clone (j'expliquerais l'intérêt de la méthode Clone plus tard). Pas de dépendances, pas de contraintes, pas de spécificités, ... ce code peut véritablement être réutilisé n'importe où. Il est possible, dans le cas d'une application demandant une exposition de services, de rendre notre classe Serializable.

Maintenant, nous allons travailler sur la couche DataAccessInterfaces. Dans cette couche, nous allons définir les interfaces et donc les méthodes que notre couche d'accès aux données devra mettre à disposition. Ici, étant donné que nous ne travaillons que sur une table, nous n'allons créer qu'une interface, à savoir IArticleDA :

public interface IArticleDA : IDisposable
    {
        List<ArticleBE> GetArticles();
        ArticleBE GetArticleById(int idArticle);
        void AddArticle(ArticleBE article);
        void EditArticle(ArticleBE article);
        void DeleteArticle(ArticleBE article);
        void Save();
    }

Le code, encore une fois, est assez simple. Nos classes qui implémenteront IArticleDA devront nous fournir toutes ces méthodes + Dispose, puisque nous implémentons IDisposable.

Maintenant que cette interface est définie, nous allons voir la partie accès aux données. Comme vous pouvez le voir, j'ai ajouté au projet DataAccess un ADO.Net Entity Data Model pour pouvoir interroger la base de données très facilement via Linq. Cependant, pour pouvoir transformer nos objets Entity en objets BusinessEntities, j'ai créé une class ArticleMap dans le dossier Mapping :

public static class ArticleMap
    {
        internal static List<ArticleBE> Map(List<Article> articles)
        {
            if (articles == null || articles.Count == 0)
            {
                return new List<ArticleBE>();
            }

            List<ArticleBE> result = new List<ArticleBE>();

            foreach (Article article in articles)
            {
                result.Add(Map(article));
            }

            return result;
        }

        internal static ArticleBE Map(Article article)
        {
            if (article == null)
            {
                return null;
            }

            ArticleBE result = new ArticleBE();

            result.Id = article.IdArticle;
            result.Author = article.Author;
            result.Content = article.FullContent;
            result.Date = article.Date;
            result.Tags = article.Tags;
            result.Title = article.Title;

            return result;
        }

        internal static Article Map(ArticleBE article, Article result = null)
        {
            if (result == null)
            {
                result = new Article();
            }

            result.Author = article.Author;
            result.FullContent = article.Content;
            result.Date = article.Date;
            result.Tags = article.Tags;
            result.Title = article.Title;

            return result;
        }
    } 

Le code est, encore une fois, très simple. Déjà, toutes les méthodes sont en internal car elles ne doivent être accessibles qu'aux classes de notre projet DataAccess. Le mapping Article => ArticleBe est simple, mais le mapping ArticleBE => Article a une petite spécificité. En effet, dans le constructeur, j'ai passé un paramètre facultatif : un objet Article.

Pourquoi ? Lorsqu'on veut mettre à jour un objet Article en base depuis un objet ArticleBE, plutôt que d'avoir à modifier chaque propriété dans notre code d'accès aux données, il suffit d'appeler la méthode Map avec comme second paramètre notre objet Article à mettre à jour. (Il s'agit de la méthode la plus simple que j'ai pu trouver pour mettre à jour un objet Entity à partir d'une classe BusinessEntities.)

Intéressons-nous ensuite au code de notre classe ArticleDA, qui implémente IArticleDA :

public class ArticleDA : IArticleDA
    {
        private HTADemoBlogEntities _entities = null;

        public ArticleDA()
        {
            _entities = new HTADemoBlogEntities();
        }

        public List<ArticleBE> GetArticles()
        {
            return ArticleMap.Map(_entities.Article.OrderByDescending(e => e.IdArticle).ToList());
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            return ArticleMap.Map(_entities.Article.Where(e => e.IdArticle == idArticle).FirstOrDefault());
        }

        public void AddArticle(ArticleBE article)
        {
            _entities.Article.AddObject(ArticleMap.Map(article));
        }

        public void EditArticle(ArticleBE article)
        {
            Article existingArticle = _entities.Article.Where(e => e.IdArticle == article.Id).FirstOrDefault();

            if (existingArticle != null)
            {
                ArticleMap.Map(article, existingArticle);
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            Article existingArticle = _entities.Article.Where(e => e.IdArticle == article.Id).FirstOrDefault();

            if (existingArticle != null)
            {
                _entities.Article.DeleteObject(existingArticle);
            }
        }

        public void Save()
        {
            _entities.SaveChanges();
        }

        public void Dispose()
        {
            if (_entities != null)
            {
                _entities.Dispose();
            }
        }
    }

Rien de très compliqué. J'ai juste une méthode Save (déclarée dans IArticleDA) qui permet de sauver en base les modifications faites à nos objets Article, à l'aide de SaveChanges. Ca nous permet, dans le cas où nous avons de nombreux articles à créer ou à modifier, de ne pas faire de SaveChanges à chaque fois. Nous n'appellerons la méthode qu'à la fin de notre traitement métier.

Voyons maintenant la couche BusinessLogic, avec la classe ArticleBL :

public class ArticleBL : IDisposable
    {
        private IArticleDA _dataFactory = null;

        public ArticleBL()
            : this(new ArticleDA())
        {

        }

        public ArticleBL(IArticleDA dataFactory)
        {
            _dataFactory = dataFactory;
        }

        public List<ArticleBE> GetArticles()
        {
            try
            {
                return _dataFactory.GetArticles();
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
                return new List<ArticleBE>();
            }
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            try
            {
                if (idArticle < 0)
                {
                    throw new ArgumentException();
                }

                return _dataFactory.GetArticleById(idArticle);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
                return null;
            }
        }

        public void AddArticle(ArticleBE article)
        {
            try
            {
                if (string.IsNullOrEmpty(article.Title)
                    || string.IsNullOrEmpty(article.Author)
                    || article.Date == DateTime.MinValue)
                {
                    throw new ArgumentException();
                }

                _dataFactory.AddArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void EditArticle(ArticleBE article)
        {
            try
            {
                if (article.Id < 0
                    || string.IsNullOrEmpty(article.Title)
                    || string.IsNullOrEmpty(article.Author)
                    || article.Date == DateTime.MinValue)
                {
                    throw new ArgumentException();
                }

                _dataFactory.EditArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            try
            {
                if (article.Id < 0)
                {
                    throw new ArgumentException();
                }

                _dataFactory.DeleteArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void Save()
        {
            try
            {
                _dataFactory.Save();
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void Dispose()
        {
            try
            {
                if (_dataFactory != null)
                {
                    _dataFactory.Dispose();
                }
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }
    }

Ici, le code devient un peu plus intéressant. Pour ne pas manipuler directement d'objets ArticleDA (ce qui permet de changer très facilement de source de données), le constructeur de ma classe demande en paramètre un objet IArticleDA. Si aucun objet implémentant IArticleDA n'est passé au constructeur, la classe utilisera le constructeur vide qui créera automatiquement un objet ArticleDA qu'elle passera au constructeur avec paramètre.

Vous pouvez également voir que les tests "métier", à savoir une tentative de lecture d'un article ayant un Id < 0, la tentative d'ajout d'un article sans titre, ... déclencheront la levée d'une ArgumentException. Le code de chaque méthode étant dans un Try Catch, cette exception sera attrapée et logguée (voir classe LogError dans la couche Core).

Enfin, la méthode Dispose appellera la méthode Dispose de notre classe implémentant IArticleDA, ce qui coupera la connexion à la BDD.

Voyons maintenant un exemple d'utilisation de nos méthodes métier. Si nous nous rendons dans le contrôleur ArticleController de l'application MVC, nous pouvons voir ceci :

public class ArticleController : Controller
    {
        public ActionResult Index(string message = "")
        {
            ViewBag.Message = message;

            using (ArticleBL business = new ArticleBL())
            {
                return View(business.GetArticles());
            }
        }

        public ActionResult Details(int id)
        {
            using (ArticleBL business = new ArticleBL())
            {
                return View(business.GetArticleById(id));
            }
        }

        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(ArticleBE article)
        {
            using (ArticleBL business = new ArticleBL())
            {
                business.AddArticle(article);
                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been created." });
        }

        public ActionResult Edit(int id)
        {
            ArticleBE article = null;

            using (ArticleBL business = new ArticleBL())
            {
                article = business.GetArticleById(id);
            }

            if (article == null)
            {
                return RedirectToAction("Index", new { message = "This article doesn't exist." });
            }

            return View(article);
        }

        [HttpPost]
        public ActionResult Edit(ArticleBE article)
        {
            using (ArticleBL business = new ArticleBL())
            {
                business.EditArticle(article);
                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been updated." });
        }

        public ActionResult Delete(int id)
        {
            ArticleBE article = null;

            using (ArticleBL business = new ArticleBL())
            {
                article = business.GetArticleById(id);

                if (article == null)
                {
                    return RedirectToAction("Index", new { message = "This article doesn't exist." });
                }

                business.DeleteArticle(article);

                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been deleted." });
        }

Et oui, pour utiliser nos données en base, il suffit de faire un using. A la sortie du using, la méthode Dispose de l'objet sera appelée, et donc la connexion sera automatiquement coupée. Cette méthode a ses avantages (sécurité, pas de risques de fuite mémoire) comme ses inconvénients (nombreuses connexions / déconnexions), mais rien ne vous empêche de la modifier.

Regardons enfin, en détail, les tests unitaires. Déjà, dans la couche Tests.Mock, le code est assez parlant :

public class MockArticleDA : IArticleDA
    {
        private List<ArticleBE> articles = new List<ArticleBE>()
        {
            new ArticleBE() {
                Id = 1,
                Title = "Demo 1",
                Tags = "demo,article,.Net",
                Date = DateTime.UtcNow,
                Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            },
            new ArticleBE() {
                Id = 2,
                Title = "Demo 2",
                Tags = "demo,article,PHP",
                Date = DateTime.UtcNow,
                Content = "PHP : Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            },
            new ArticleBE() {
                Id = 3,
                Title = "Demo 3",
                Tags = "demo,article,Java",
                Date = DateTime.UtcNow,
                Content = "JAVA : Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            }
        };

        public List<ArticleBE> GetArticles()
        {
            // we clone the list to avoid to avoid any ref problem
            return articles.Select(e => e.Clone() as ArticleBE).ToList();
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            ArticleBE result = articles.Where(e => e.Id == idArticle).FirstOrDefault();

            if (result != null)
            {
                // we clone the item to avoid to avoid any ref problem
                return result.Clone() as ArticleBE;
            }

            return null;
        }

        public void AddArticle(ArticleBE article)
        {
            article.Id = articles.Max(e => e.Id) + 1;
            articles.Add(article);
        }

        public void EditArticle(ArticleBE article)
        {
            ArticleBE result = articles.Where(e => e.Id == article.Id).FirstOrDefault();
            if (result != null)
            {
                result.Author = article.Author;
                result.Content = article.Content;
                result.Date = article.Date;
                result.Tags = article.Tags;
                result.Title = article.Title;
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            ArticleBE result = articles.Where(e => e.Id == article.Id).FirstOrDefault();
            if(result != null) 
            {
                articles.Remove(result);
            }
        }

        public void Save()
        {

        }

        public void Dispose()
        {

        }
    }

On voit donc, ici, qu'on implémente bien notre interface d'accès aux données IArticleDA. Ainsi, pour tester notre couche de logique métier sur cette classe, il nous suffira de passer un objet MockArticleDA dans le constructeur de notre classe métier, et le tour est joué !

Notre classe MockArticleDA possède une liste d'articles créée à chaque construction d'un objet. La spécificité, ici, est que j'utilise la méthode Clone de nos objets ArticleBE pour ne renvoyer que des copies de ces objets à notre classe de tests. Cela permet d'éviter de modifier involontairement les données de nos objets par référence (par exemple dans le cas d'un test de modifications des données sans sauvegarde).

Enfin, je vous laisse vous intéresser à la classe ArticleBLTest, dont voici 2/3 méthodes :

[TestInitialize()]
        public void MyTestInitialize()
        {
            IArticleDA mockDataFactory = new MockArticleDA();
            business = new ArticleBL(mockDataFactory);
        }

        [TestMethod()]
        public void DeleteArticle_Generic_Test()
        {
            // params
            int nbArticles = 3;
            // --

            // we check if we have 3 articles
            List<ArticleBE> articles = business.GetArticles();
            Assert.AreEqual(nbArticles, articles.Count);

            // we delete the article
            business.DeleteArticle(articles.First());

            List<ArticleBE> actual = business.GetArticles();
            Assert.AreEqual(nbArticles - 1, actual.Count);
        }

        [TestMethod()]
        public void GetArticleById_With_Wrong_Parameters()
        {
            // params
            int fakeId = -1;
            // --

            ArticleBE article = business.GetArticleById(fakeId);

            Assert.IsNull(article);
        }

        [TestMethod()]
        public void AddArticle_With_Wrong_Parameters()
        {
            // params
            string author = "Me";
            string content = "It's not working !";
            string title = "Test article";
            string tags = "great,article,news";
            DateTime now = DateTime.Now;
            int nbArticles = 3;
            // --

            // we check if we have 3 articles
            List<ArticleBE> articles = business.GetArticles();
            Assert.AreEqual(nbArticles, articles.Count);

            ArticleBE article1 = new ArticleBE()
            {
                Author = author,
                Content = content,
                Tags = tags,
                Title = title
            };

            ArticleBE article2 = new ArticleBE()
            {
                Content = content,
                Date = now,
                Tags = tags,
                Title = title
            };

            ArticleBE article3 = new ArticleBE()
            {
                Author = author,
                Content = content,
                Date = now,
                Tags = tags,
            };

            // we create the articles. it shouldn't work
            business.AddArticle(article1);
            business.AddArticle(article2);
            business.AddArticle(article3);

            articles = business.GetArticles();

            // we check if we still have 3 articles
            Assert.AreEqual(nbArticles, articles.Count);
        }

Il est à noter que la méthode MyTestInitialize est appelée avant chaque test unitaire. Ensuite, pour chaque test, on vérifie les données déjà présentes, les données à la fin du test, et, en cas d'erreur, on verra tout de suite que le test a échoué. Comme vous avez pu le voir, mes méthodes de tests ont des noms très clairs, afin de savoir, au premier coup d'oeil, pourquoi nos tests ont pu échouer.

Je pense avoir fait le tour des spécificités du code de cette minuscule application. Si vous ne comprenez pas quelque chose, ou si vous avez besoin d'une précision, n'hésitez pas à demander dans les commentaires.

Enfin, pour déployer cette application chez vous, il vous suffit de restaurer la BDD dans un SQL Server 2008 (script Backup.bak, dans le zip fourni) et de modifier les chaînes de connexion dans les 3 fichiers de configurations (HTA.DemoBlog.DataAccess, HTA.DemoBlog.WebService et HTA.DemoBlog.Tests).

N'hésitez donc pas à tester cette "application" (HTA.DemoBlog.zip (1,54 mb)) et à me donner votre avis. Wink

Bonne journée à tous !

P.S. Oui je sais, le code n'est pas commenté, mais étant donné la simplicité des classes, je n'ai pas pensé que c'était nécessaire.

P.S.2 Quand j'aurais le temps, je ferais probablement une autre version un peu plus poussée avec Moq + StructureMap.

P.S.3 Je crois que c'est mon article le plus long depuis le début de mon blog. 7869 mots, pour presque 30 000 caractères. L'équivalent d'un document Word de 15 pages ! Wouah !

image modifiée de Bert Kaufmann, sous licence CC

Nouvelle home pour HowTommy.net

3. décembre 2011 09:00 by Tommy in Actualité, Développement  //  Tags: ,   //   Commentaires (5)   //  Partager sur Facebook  / Twitter

 

Bonjour à tous,

Comme vous l'avez peut être remarqué, mon site profite depuis hier soir d'une nouvelle page d'accueil. Mais là vous vous demandez... Pourquoi ? 

Et bien l'intérêt pour moi est multiple :

  • Pouvoir développer une section plus adaptée à l'hébergement de tutoriels (affichage du code, mise en page, navigation...).
  • Pouvoir ajouter un module au site me permettant de mettre à disposition mes applications (qu'il s'agisse d'applications entières ou de morceaux de code opensource) avec une partie code, une partie architecture, une partie documentation, ...
  • Enfin, pouvoir changer un peu du design du blog que je traine depuis 10 mois maintenant (même si je l'aime bien Wink).

N'hésitez pas à me dire ce que vous en pensez, sachant que ça risque de changer dans les prochaines semaines.

Allez, bon weekend à tous !

Tout reprendre à zéro

2. décembre 2011 12:50 by Tommy in Développement  //  Tags: , ,   //   Commentaires (9)   //  Partager sur Facebook  / Twitter

 

Dans l'informatique, il y a une célèbre phrase qu'aucun responsable n'aime entendre : "Il faudrait tout reprendre à zéro". La plupart des responsables feront semblant de n'avoir rien entendu, et les autres diront que ce ne serait qu'une perte de temps.

Cette phrase est, pour moi, un indicateur critique de l'état d'un projet. Lorsqu'un développeur en vient à dire cela, c'est qu'il faut vraiment faire un point sur l'évolution du projet, sa viabilité et sa durée de vie attendue...

Mais comment cette phrase peut-elle finir par sortir ?

Assez simplement. Il suffit de développer sans méthodologie. Simple non ? Dans la majorité des projets informatiques (tous ?), les besoins changeront au fil du développement. Que ces changements soient métier ou technique, ils auront des conséquences très importantes pour la sortie du projet. L'ajout de fonctionnalités, le changement de la date de livraison, la mise à jour du design, un autre hébergement des données, ... sont autant de changements qui poseront d'énormes problèmes au développeur, surtout si la fin du projet se rapproche.

Plus un projet avance, plus les conséquences de chaque changement seront importantes. Une modification mineure dans les specs du projet ne prendra que quelques minutes et n'aura pas de conséquences. La même modification quelques jours avant la mise en production aura des conséquences dramatiques et forcera les développeurs à rester très tard le soir pour sortir, dans les temps ou presque, une application pas forcément très stable.

Seulement voilà. Tous ces changements, survenus au cours du projet, vont avoir deux conséquences :

  • Ils vont demander aux développeurs de faire des choix : remettre en cause ce qui est déjà fait ou développer des rustines pour adapter l'application à la demande.
  • Miner peu à peu le moral des développeurs qui vont se lasser d'avoir un projet qui change tous les jours, sachant que la date de livraison, ELLE, ne change pas. Sans compter que passer son temps à faire des rustines et à débuguer l'application est loin d'être motivant et gratifiant...

Il ne faut pas se leurrer, la majorité des projets ayant des délais bien trop courts, la plupart des développeurs vont se tourner vers les rustines pour répondre au besoin en espérant tenir les délais.

Et plus un projet durera, plus les besoins changeront, et plus il y aura de rustines sur l'application. Et à un moment, l'application sera totalement obsolète et bordélique, et un des développeurs en viendra à sortir la-phrase-qu'il-ne-faut-pas-prononcer : "Il faudrait tout reprendre à zéro".

Et on arrive à ce moment clef. Au moment où tout va se jouer. Au moment où le bon choix de la bonne personne pourra sauver le projet. Mais ce (bon) choix ne viendra probablement pas.

Pour les dirigeants et les chefs de projets, tout reprendre à zéro signifie jeter à la poubelle l'existant. Donc, pour eux, tout ce qui a été fait sera perdu. Ils demanderont donc aux développeurs de continuer à travailler sur la solution existante, même si celle-ci est complètement décalée par rapport au besoin. Et les développeurs, peu à peu, vont perdre la volonté de changer les choses, même si, à l'origine, ils étaient persuadés que refaire l'application ne serait que bénéfique pour tout le monde...

Ca s'appelle l'engagement. On a tous connu ça au supermarché : plus on attendra longtemps dans une file, moins on envisagera de se diriger vers la file qui vient d'ouvrir juste à côté, même si celle-ci nous permettrait de passer plus vite. On aurait l'impression d'avoir perdu notre temps à attendre dans cette file.

Pour moi, recommencer à zéro n'est absolument pas une perte de temps. Ca peut même, parfois, faire gagner beaucoup de temps et remotiver les développeurs.

En effet, même si on abandonne l'application existante, on ne perdra ni le code (on pourra toujours récupérer les algorithmes métier et techniques !) ni les compétences acquises par les développeurs durant le projet !

Ainsi, en décidant de tout reprendre à zéro et de recréer une base stable sur laquelle baser ses efforts, on perdra peut être quelques jours à recréer l'application et à reprendre le code métier.

Mais derrière, une fois cette étape terminée, les développeurs pourront enfin arrêter de travailler sur les bugs et les rustines, pour se concentrer sur les demandes et les besoins. Les développeurs regagneront également confiance en eux, car ça leur permettra de redécouvrir application, de mettre à jour leurs compétences et de retrouver leur motivation. Et ce n'est pas rien, car la productivité entre un développeur motivé et un développeur qui en a marre n'est absolument pas la même...

D'ailleurs, lorsqu'on développe des projets pour nous, ou à l'école, on abandonne souvent tout ce qu'on a fait pour recommencer à zéro le tout, avec une nouvelle architecture solide, une autre base de données plus complète ou juste un code plus propre.

Alors pourquoi ne pas le faire dans le monde du travail ? Vous croyez que les peintres ne recommencent jamais une peinture ?

Tout recommencer à zéro ne pourra, la plupart du temps, que faire du bien à un projet qui s'essouffle.

Dommage que ce soit souvent si difficile à faire comprendre aux responsables. Tout recommencer ne signifie pas tout perdre ! Et les développeurs sont les mieux placés pour voir quand une application va dans le mur ou sera impossible à évoluer / maintenir, non ?

image modifiée de e3000, sous licence CC

Comment mettre une valeur par défaut non constante dans un constructeur en C# ?

1. décembre 2011 09:00 by Tommy in Développement  //  Tags:   //   Commentaires (3)   //  Partager sur Facebook  / Twitter

 

Il y a quelques jours, j'ai été confronté à un problème intéressant...

Je faisais, dans ma couche de logique métier, de l'injection de dépendance à travers le constructeur de ma classe, comme ceci :

public class ArticleBL
{
    private IArticleDA _dataFactory = null;

    public ArticleBL(IArticleDA dataFactory)
    {
        _dataFactory = dataFactory;
    }
}

Mais, ne voulant pas avoir à déclarer un nouvel objet ArticleDA (implémentant mon interface IArticleDA) à chaque création d'objet ArticleBL, je voulais utiliser une valeur par défaut (merci C# 4 Smile) dans mon constructeur, comme ceci :

public class ArticleBL
{
    private IArticleDA _dataFactory = null;

    public ArticleBL(IArticleDA dataFactory = new ArticleDA())
    {
        _dataFactory = dataFactory;
    }
}

Seulement voilà, ce n'est pas possible. En effet, au moment de la compilation, les valeurs par défaut d'une méthode doivent être des constantes. Du coup comment faire ?

Et bien la solution est très simple : il suffit de créer un constructeur sans paramètres qui appellera notre constructeur avec paramètre (avec ":"), en lui fournissant notre valeur par défaut, comme ceci :

public class ArticleBL
{
    private IArticleDA _dataFactory = null;

    public ArticleBL() : this(new ArticleDA())
    {

    }

    public ArticleBL(IArticleDA dataFactory)
    {
        _dataFactory = dataFactory;
    }
}

Et le tour est joué ! Smile

image modifiée de Anton Fomkin, sous licence CC

Quelle époque préférez-vous dans les jeux ?

24. novembre 2011 17:45 by Tommy in Actualité, Développement, Jeux vidéos  //  Tags: , , , ,   //   Commentaires (8)   //  Partager sur Facebook  / Twitter

 

Je travaille depuis quelques jours à un petit jeu de stratégie basique pour smartphone. Et j'en suis venu à me poser une question : quelle époque ou univers plait d'avantage aux joueurs ?

  • L'époque contemporaine ? Avec les tanks, les soldats, l'armement nucléaire, l'armement chimique, ...
  • L'âge du bronze ou du fer ? Comme dans Age of Empire ? (Cavalerie, lanciers, archers, catapultes, ...)
  • Le monde de l'Héroic Fantasy ? Façon Warcraft ou Warhammer avec les orcs, les gobelins, les elfes, ...
  • Ou enfin la science-fiction ? Façon Star Wars ou Star Trek avec des possibilités infinies ?

Personnellement, j'ai toujours préféré les univers fantastiques, qu'il s'agisse d'Heroic Fantasy ou de science-fiction. Je ne sais pas pourquoi. Peut être car ils font d'avantage travailler notre imagination...

Cependant, pour mon jeu, je pense opter pour un mix époque contemporaine / science-fiction. Pas vraiment aujourd'hui, mais pas vraiment demain non plus, avec une évolution, petit à petit, vers ce le monde de la SF (armes futuristes, conquête de planètes, champs de force, pouvoirs mystiques, ...).

Vous en pensez quoi ? Et vous, à quoi vous jouez quel est votre univers préféré ?

image modifiée de John-Morgan, sous licence CC

Tintin n'est plus belge, mais américain...

19. novembre 2011 01:45 by Tommy in Actualité  //  Tags: , , ,   //   Commentaires (12)   //  Partager sur Facebook  / Twitter

 

Je sors tout juste du cinéma... Et mon premier réflexe est de sauter sur mon ordinateur pour parler du film que je viens de voir : Les aventures de Tintin, le secret de la Licorne. Je sais que c'est mal d'écrire à chaud mais là... je ne peux pas m'en empêcher. Yell

Déjà, il faut savoir que depuis tout petit, je suis "fan" de Tintin. Je connais toutes les bandes dessinées par coeur, mes deux préférées étant Le secret de la Licorne et Le trésor de Rackham le Rouge.

Sachant que le film ne pourrait jamais égaler les bandes dessinées, je me suis décidé à regarder le film d'un oeil neuf, sans faire le lien avec notre bon vieux reporter Belge. Wink

Malgré cela, j'ai pu voir le plus mauvais film qu'il m'ait été donné de voir. Il ne m'a fallu que 20 minutes pour avoir envie de m'en aller. Je ne suis resté que parce que j'étais avec des amis. Et je n'étais pas le seul, plusieurs personnes ont quitté la salle bien avant la fin du film.

Mais pourquoi ?

Je vais essayer de faire bref, car si je m'y met, je vais y passer la nuit. Voici les points qui font de Tintin un très très très très très (et encore, il manque des "très") mauvais film :

  • L'histoire est abracadabrantesque. Spielberg a eu la fausse bonne idée de prendre 6/7 bandes dessinées Tintin et de mélanger toutes les histoires. On se retrouve avec un scénario qui part dans tous les sens et qui nous gave très rapidement...
  • La réalisation est pitoyable. Moins de la moitié des choses qui se passent dans ce film sont possibles dans la réalité. Entre Tintin qui nage 800 mètres en apnée en moins de vingt secondes, la bibliothèque géante façon Poudlard complètement vide, le bateau qui passe à 50 mètres au dessus de l'autre, les 3 gouttes d'alcool dans le feu qui déclenchent une explosion, l'avion qui rebondit sans problème sur les dunes de sable, le faucon du méchant mieux dressé qu'aucun chien ne le sera jamais, le combat de grues, les scènes et les plans qui manquent, ... On a vraiment l'impression d'assister à une parodie des premiers Indiana Jones, en exagéré. C'est vous dire !
  • Le mauvais jeu des acteurs. Spielberg a réussi l'exploit de sortir un film en images de synthèse dans lequel le jeu des acteur est vraiment, vraiment, vraiment nul. Je ne pensais pas que c'était possible, et pourtant il l'a fait. Je me demande si le jeu des acteurs dans les films X n'est pas meilleur. Pffff...
  • Le rythme est atroce. Il n'y a pas la moindre pause pendant tout le film. Pas de scène calme ou de scène de réflexion. Il n'y a que de l'action, de l'action et de l'action. Pourquoi ? Peut être pour éviter que les gens ne réfléchissent au scénario pendant les scènes calmes, qui sait ?
  • Tintin n'est plus belge, mais américain. Tout le texte en français de l'histoire d'origine a été américanisé. Ainsi, la Licorne est devenue "Unicorn", notre cher capitaine François de Haddock laisse à ses 3 fils français 3 parchemins en anglais, ... Et ceci pour tout. Navrant.
  • L'humour ? Quel humour ? Si le film se veut drôle, c'est raté. Au mieux, certaines scènes m'ont fait sourire. C'est tout. Entre les "acteurs" qui surjouent et la mauvaise réalisation, on n'a pas vraiment envie de rire...
  • Enfin, l'histoire et les personnages ne sont en aucun cas ceux créés par Hergé. Tintin est capable, au premier coup d'oeil d'identifier le nombre de canons du bateau, son siècle d'origine et le nom du roi français qui régnait quand voguait ce navire. Depuis quand ? Et où est Tournesol, qui a un rôle phare dans les BD ? Et qu'est ce que la Castafiore fait là ? Et Allan ? Et pourquoi M. Sakharine, qui n'avait rien demandé à personne, est devenu un personnage méchant ? Et où sont passés les frères Loiseau ? Tant de choses qui gâchent véritablement l'histoire. Hergé doit se retourner dans sa tombe, le pauvre...

Et encore, je pense avoir oublié pas mal de choses... Foot in mouth

En tout cas, je ne sais pas pour vous, mais j'ai clairement perdu 2 heures de ma vie au cinéma devant ce film. Cry Et je n'arrive pas à comprendre comment il peut avoir 3,6 étoiles sur Allociné. Ca me dépasse. Quand je pense que j'ai du payer pour ça, j'en ai mal au coeur.

En bref, un film à éviter à tout prix, et que je ne souhaiterais jamais revoir. Jamais. Je me contenterais des BD. Undecided

Bref, j'ai joué à Starcraft 2

17. novembre 2011 09:00 by Tommy in Actualité, Jeux vidéos  //  Tags: , , ,   //   Commentaires (11)   //  Partager sur Facebook  / Twitter

 

Bon, je ne vous ferais pas l'affront de vous présenter la série Bref, je pense que tout le monde connait...

Mais pour ceux qui ne le savent pas, l'acteur principal Kyan Khojandi joue à Starcraft 2 ! Laughing

Du coup il a eu la bonne idée, à la dernière soirée oGaming de Pomf et Thud, de nous faire un petit sketch à sa façon : Bref, j'ai joué à Starcraft 2.

Je vous laisse le découvrir sans plus attendre (attention, connaissances sur le jeu requises Wink) :

EDIT : J'ai expliqué la majorité des gags dans les commentaires.

 

Arrêter Visual Studio en pleine charge

16. novembre 2011 09:00 by Tommy in Développement  //  Tags: ,   //   Commentaires (2)   //  Partager sur Facebook  / Twitter

 

Haaaaaaaaaaaa !

Je viens juste de découvrir une astuce INDISPENSABLE à tout développement .Net sur Visual Studio (je précise, des fois que vous soyez sur Mono Wink).

Comme vous le savez tous (ou pas), lorsqu'on veut lancer une application ou un site web sur Visual Studio, on utilise souvent le raccourci ultime : F5. (Ou parfois F6, lorsqu'on veut juste build notre application).

Seulement, qui n'a jamais appuyé sur une de ces touches pile au moment où il se rendait compte qu'il avait oublié un morceau de code ?

On se retrouve, à chaque fois, à attendre comme un idiot la fin du build pour pouvoir couper l'application ou le serveur web et faire notre modification.

Et bien... On peut très facilement arrêter le build Visual Studio !

Sisi !

Il suffit de faire CTRL + PAUSE (ou d'aller dans Build/Cancel Build), et votre Visual Studio arrêtera tout ce qu'il était en train de faire.

Rien qu'hier, je m'en suis servi une bonne demi-douzaine de fois, donc je me suis dit que ça pourrait vous être utile...

Bonne journée à tous ! Smile

image modifiée de Anton Fomkin, sous licence CC

Quelques BDs sur nos héros préférés !

15. novembre 2011 09:00 by Tommy in Actualité  //  Tags: , , , ,   //   Commentaires (10)   //  Partager sur Facebook  / Twitter

 

Un de mes amis et ancien camarade d'école vient tout juste de lancer son blog.

Au programme : de courtes bandes dessinées humoristiques avec des personnages qu'on connait tous : Superman, Batman, Iron Man, Agent J (MIB), Doc et Marty, Buzz l'éclair, ... (En fait en plus la liste avance, en plus on s'éloigne des super héros... Les prochains seront-ils Timon et Pumba ? Wink)

Du coup je me suis dit que j'allais lui faire un peu de pub, étant donné que j'ai bien aimé ses dessins. (Bon, le terme pub est relatif, je suis loin d'avoir beaucoup de visiteurs. Quoique quand sebsauvage et le hollandais volant me citent... Wink).

Alors n'hésitez pas à aller y faire un tour, c'est par ici : myfamousfriends.net.

Et pour ceux qui ont la flemme d'y aller, voici ma préférée :

 

 

 

image modifiée de myfamousfriends.net

Bienvenue

Logo Tommy

Bonjour à tous et bienvenue sur mon blog. Je me présente : Tommy, ingénieur développeur .Net.

Que trouverez-vous ici ? Beaucoup de choses, principalement mon point de vue sur l'actualité numérique, mais également des infos diverses, des idées, des astuces pour mieux développer, des bouts de code, ...

N'hésitez pas m'ajouter dans vos favoris ou à me suivre sur Facebook / Twitter !

Bonne visite !

Liens

Raccourci pour les développeurs : http://dev.howtommy.net

Mes liens dédiés aux développeurs : http://liens.howtommy.net/?searchtags=d%C3%A9veloppement

Commentaires

Comment RSS

Par mois

Dernier posts

Hall of fame

microsoft certified professional