Examen 2025-26

1. Software construction

Expliquez la différence entre la "Construction" et la "Mise en œuvre" (Implémentation) de logiciels.

Solution

La mise en œuvre est un concept plus ancien, qui désigne une phase du cycle de développement, pendant laquelle on se limitait à traduire une solution décrite pendant la phase de Conception, en code source. Cette phase précédait la phase de Test et Intégration.

La construction est un concept plus récent, qui désigne une activité de développement, qui consiste à élaborer un logiciel robuste, et qui comprend la conception, l’écriture de code et les tests unitaires et d’intégration.

Expliquez la différence entre la "Construction" et "l’Évolution" de logiciels.

Solution

La construction logicielle consiste à créer un logiciel fonctionnel et pertinent en combinant le codage, la vérification, les tests unitaires, les tests d’intégration et le débogage.

L'évolution logicielle est le terme utilisé en maintenance logicielle pour désigner toutes les activités de programmation visant à générer une nouvelle version logicielle à partir d’une version opérationnelle antérieure.

Ces deux domaines de connaissances (terme employé dans le SWEBOK) ont en commun plusieurs activités, comme la conception, le test, l’écriture de code et le refactoring.

2. Mapping UML to code

Énumérez les inconvénients d’UML, le langage de modélisation unifié.

Solution
  • UML dispose d’une syntaxe unique, permettant son utilisation dans différents contextes (analyse du domaine, conception, implémentation, bases de données, etc.).
    Cette syntaxe unique augmente les risques d’interprétation erronée et de confusion

  • UML ne propose pas une sémantique unique, chaque développeur peut interpréter un diagramme d’une façon différente.

  • https://gl.univ-nantes.io/construction/slides/introduction.html#/14

Dans ce module, nous avons étudié 3 stratégies différentes de mise en œuvre, en Java, d’attributs UML: naïve, les méthodes d’accès (setter/getter) et les classes-enveloppes (wrappers).

Expliquez les avantages de l’approche par méthodes d’accès par rapport aux deux autres.

Solution
  • Respecte l’encapsulation (wrt naïve)

  • Moins gourmande en mémoire (wrt wrappers)

  • Evite la prolifération d’objets (wrt wrappers)

  • Permet la mise en œuvre d’attributs lecture seule et dérivés. (wrt naïve)

Un modèle de conception UML est totalement indépendant des langages de programmation à objets, ce qui lui assure un certain niveau d’abstraction et en même temps, complexifie sa mise en œuvre, qui doit faire appel à une Stratégie d’implémentation

Expliquez en quoi consiste une stratégie d’implémentation et pourquoi elle est nécessaire.

Solution
  • Une stratégie d’implémentation consiste en un ensemble de règles qui spécifient comment traduire les modèles de conception en code source. Elle se compose de plusieurs parties correspondant à différents concepts UML : composants, classes, attributs, opérations, diagrammes d’état, etc.

  • Parce que la correspondance entre UML et les langages de programmation n’est pas triviale, d’autant plus que UML n’a pas de sémantique.

3. Refactorings

Quelles sont les origines des "opérations de refactoring" ?

Solution
  • Maintenance et évolution des logiciels

  • Développement d’applications logicielles

  • Développement de cadres logiciels (frameworks)

Énumérez les opérations de refactoring de base portant sur les classes :

Solution

Add, Remove, Rename

Énumérez les opérations de refactoring de base portant sur les attributs :

Solution
  • Add, Remove, Rename, Move, PushDown, PullUp

  • Ces opérations peuvent s’appliquer sur des attributs de classe (statiques) ou d’instance.

Énumérez les opérations de refactoring de base portant sur les méthodes, en excluant les opération internes ou intra-méthodes :

Solution
  • Add, Remove, Rename, Move, PushDown, PullUp, Changement de signature

  • Ces opérations peuvent s’appliquer sur des méthodes de classe (statiques) ou d’instance.

4. Software Testing Basics

Le test unitaire est une technique qui consiste à tester individuellement chaque unité d’un logiciel.

Définissez "unité".

Solution

Une unité est la plus petite partie testable d’un logiciel.

Un bon cas de test unitaire assure trois *propriétés : (i) l’isolation ; (ii) la reproductibilité ; et l’atomicité.

Définissez ces propriétés.

Solution
Isolation

les tests ne doivent jamais dépendre d’autres tests ni de l’ordre dans lequel ils sont exécutés.

Reproductibilité

plusieurs exécutions de tests doivent donner systématiquement le même résultat, à condition qu’aucune modification n’ait été apportée au logiciel testé.

Atomicité

Les tests doivent réussir ou échouer, ils ne peuvent pas réussir partiellement.

Pour être efficace, un test unitaire doit exécuter le plus ou le moins de code possible ? Justifiez votre réponse.

Solution
  • Un test unitaire doit exécuter le moins de code possible.

  • Justification : si le test est en échec, la localisation de l’erreur est plus simple, car le domaine de recherche est plus petit.

5. Design Patterns

La Figure suivante présente la classe Thread et la machine d’état associée à cette classe. La classe possède 3 opérations, qui, après leur exécution, conduisent à un changement d’état. Chaque opération ne peut être appelée que lorsqu’un thread est dans un état précis.

Class Diagram
Figure 1. Class Diagram
State Machine Diagram
Figure 2. State Machine Diagram

Quelles sont les alternatives plausibles d’implémentation d’une machine d’état ?

Solution
  1. Appliquer le patron "État"

  2. Ajouter un attribut représentant l’état courant à la classe liée à cette machine d’état et ensuite des conditionnels dans chaque méthode. Le type de l’attribut peut être une énumération ou un entier.

Quels sont les avantages et les inconvénients de chaque alternative ?

Solution
  • L’utilisation d’une énumération est plus rapide, cependant moins maintenable et difficile à étendre

  • Le patron État est un peu plus lent, l’appel d’une méthode est plus lente qu’une comparaison simple. Cependant, cette solution est plus facilement extensible, l’ajout d’un nouvel état est plus simple. Cette solution est aussi plus maintenable, car tout le code dépendant d’un état en particulier se trouve dans la même classe.

Proposez une implémentation, en Java, de la classe Thread` et de ses opérations. Faites en sorte qu’une exception soit levée si une opération est appelée à un état inapproprié.

Solution
Listing 1. Version utilisant une énumération
package fr.unantes.sce.patterns.thread.enumeration;

public class Thread {
    enum State {BLOCKED,INTERRUPT,READY,RUNNING,TERMINATED}

    private State state = State.READY;

    public void interrupt() {
        if (this.state != State.RUNNING) {
            throw  new IllegalStateException();
        }
        // Do interrupt behavior
        this.state = State.TERMINATED;
    }

    public void run() {
        if (this.state != State.READY) {
            throw  new IllegalStateException();
        }
        // Do run behavior
        this.state = State.RUNNING;
    }

    public void sleep() {
        if (this.state != State.RUNNING) {
            throw  new IllegalStateException();
        }
        // Do sleep behavior
        this.state = State.BLOCKED;
    }

    public void timeout() {
        if (this.state != State.BLOCKED) {
            throw  new IllegalStateException();
        }
        // Do timeout behavior
        this.state = State.RUNNING;
    }
}
Listing 2. Version utilisant le patron État
package fr.unantes.sce.patterns.thread.state;

public class Thread {
    private ThreadState state = new Ready(this);

    public void interrupt() {
        this.state.interrupt();
    }

    public void run() {
        this.state.run();
    }

    public void sleep(int millis) {
        this.state.sleep(millis);
    }

    void setStateRunning() {
        state = new Running(this);
    }

    void setStateBlocked() {
        state = new Blocked(this);
    }

    void setStateTerminated() {
        state = new Terminated(this);
    }
}

package fr.unantes.sce.patterns.thread.state;

public interface ThreadState {

    public default void interrupt() {
        throw new IllegalStateException();
    }

    public default void run() {
        throw new IllegalStateException();
    }

    public default void sleep(int millis) {
        throw new IllegalStateException();
    }
}

package fr.unantes.sce.patterns.thread.state;


public abstract class CommonState {
    private final Thread thread;

    public CommonState(Thread thread) {
        this.thread = thread;
    }

    Thread thread() {
        return thread;
    }
}

package fr.unantes.sce.patterns.thread.state;

public class Ready extends CommonState implements ThreadState {

    public Ready(Thread thread) {
        super(thread);
    }

    public void run() {
        // run method behavior
        thread().setStateRunning();
    }
}

package fr.unantes.sce.patterns.thread.state;

import java.lang.Thread;

public class Blocked extends CommonState implements ThreadState {

    public Blocked(Thread thread) {
        super(thread);
    }

    public void timeout() {
        thread().setStateRunning();
    }
}

package fr.unantes.sce.patterns.thread.state;

import java.lang.Thread;

public class Terminated extends CommonState implements ThreadState {
    public Terminated(Thread thread) {
        super(thread);
    }
}

package fr.unantes.sce.patterns.thread.state;

import java.lang.Thread;

public class Running extends CommonState implements ThreadState {

    public Running(Thread thread) {
        super(thread);
    }

    public void interrupt() {
        // Interrupt behavior
        thread().setStateTerminated();
    }

    public void sleep() {
        // Sleep behavior
        thread().setStateBlocked();
    }
}

Un des objectifs des patrons de conception (design patterns) est d’améliorer la réutilisation.

Qu’est-ce qu’est réutilisable dans un patron de conception ?

Solution
  • Essentiellement : la connaissance, l’expérience de développeurs expérimentés.

  • La logique derrière la solution, la stratégie utilisée pour résoudre un problème recurrent.