"J'adore les westerns spaghetti, je déteste le code spaghetti"Le «code spaghetti» est une expression idéale pour décrire un logiciel qui est un chaos fumant d'un point de vue cognitif et esthétique. Dans cet article, je vais parler d'un plan en trois points pour détruire un code spaghetti:
- Nous discutons pourquoi le code de spaghetti n'est pas si savoureux.
- Présentation d'un nouveau regard sur ce que fait réellement le code.
- Nous discutons de Frame Machine Notation (FMN) , qui aide les développeurs à démêler une boule de pâte.
Nous savons tous combien il est difficile de lire le code de quelqu'un d'autre. Cela peut être dû au fait que la tâche elle-même est difficile ou parce que la structure du code est trop ... "créative". Souvent, ces deux problèmes vont de pair.
Les défis sont des tâches difficiles, et rien d'autre qu'une découverte révolutionnaire ne peut généralement les simplifier. Cependant, il arrive que la structure logicielle elle-même ajoute une complexité inutile, et ce problème
mérite d'être résolu.
La laideur du code spaghetti réside dans sa logique conditionnelle complexe. Et bien que la vie puisse être difficile à imaginer sans les nombreuses constructions difficiles si-alors-autre, cet article vous montrera une meilleure solution.
Pour illustrer la situation avec du code spaghetti, nous devons d'abord tourner ceci:
Pâtes croustillantesEn cela:
Al dente!Commençons à cuisiner.
État implicite
Pour faire des pâtes, nous avons vraiment besoin d'eau pour cuisiner. Cependant, même un élément apparemment simple impliquant du code spaghetti peut être très déroutant.
Voici un exemple simple:
(temp < 32)
Que fait vraiment ce contrôle? De toute évidence, il divise la droite numérique en deux parties, mais que
signifient ces parties? Je pense que vous pouvez faire une hypothèse logique, mais le problème est que le code ne le communique pas
explicitement .
Si je confirme vraiment qu'elle vérifie si l'eau est SOLIDE
[env. voie: selon l'échelle Fahrenheit, l'eau gèle à +32 degrés] , que signifiera logiquement le retour faux?
if (temp < 32) {
Bien que la vérification ait divisé les chiffres en deux groupes, il existe en fait trois états logiques - solide, liquide et gazeux (SOLIDE, LIQUIDE, GAZ)!
Autrement dit, cette ligne de nombre:
divisé par contrôle de condition comme suit:
if (temp < 32) {
} else {
}
Remarquez ce qui s'est passé car il est très important de comprendre la nature du code spaghetti. Une vérification booléenne a divisé l'espace numérique en deux parties, mais n'a PAS catégorisé le système comme une structure logique réelle à partir de (SOLID, LIQUID, GAS). Au lieu de cela, le chèque a divisé l'espace en (SOLIDE, tout le reste).
Voici une vérification similaire:
if (temp > 212) {
Visuellement, cela ressemblera à ceci:
if (temp > 212) {
} else {
}
Notez que:
- l'ensemble complet des états possibles n'est annoncé nulle part
- nulle part dans les constructions conditionnelles ne sont déclarés des états logiques ou des groupes d'états vérifiables
- certains États sont indirectement regroupés par la structure de la logique conditionnelle et de la ramification
Un tel code est fragile, mais très courant et pas assez volumineux pour causer des problèmes avec son support. Alors, aggravons la situation.
Je n'ai jamais aimé ton code de toute façonLe code ci-dessus implique l'existence de trois états de la matière - SOLIDE, LIQUIDE, GAZ. Cependant, selon les données scientifiques, il existe en fait
quatre états observables dans lesquels le plasma (PLASMA) est inclus (en fait, il y en a beaucoup d'autres, mais cela nous suffira). Bien que personne ne prépare une pâte à partir de plasma, si ce code est publié sur Github, puis qu'un étudiant de troisième cycle en physique des hautes énergies le bifurquera, nous devrons également maintenir cet état.
Cependant, lorsque du plasma est ajouté, le code ci-dessus fera naïvement ce qui suit:
if (temp < 32) {
Il est probable que l'ancien code, ajouté à de nombreux états du plasma, se cassera dans les autres branches. Malheureusement, rien dans la structure du code n'aide à signaler l'existence d'un nouvel état ou à influencer les changements. De plus, tous les bugs sont susceptibles d'être discrets, c'est-à-dire que les trouver sera le plus difficile. Dites simplement non aux insectes dans les spaghettis.
En bref, le problème est le suivant: les vérifications booléennes sont utilisées pour déterminer
indirectement des états. Les états logiques ne sont souvent pas déclarés et ne sont pas visibles dans le code. Comme nous l'avons vu ci-dessus, lorsque le système ajoute de nouveaux états logiques, le code existant peut se casser. Pour éviter cela, les
développeurs doivent réexaminer chaque vérification conditionnelle individuelle et chaque branche pour s'assurer que les chemins de code sont toujours valides pour
tous leurs états logiques! C'est la principale raison de la dégradation des gros fragments de code à mesure qu'ils deviennent plus complexes.
Bien qu'il n'y ait aucun moyen de se débarrasser complètement des vérifications de données conditionnelles, toute technique qui les minimise réduira la complexité du code.
Jetons maintenant un œil à une implémentation orientée objet typique d'une classe qui crée un modèle
très simple du volume d'eau. La classe gérera les changements dans l'état de la substance de l'eau. Après avoir étudié les problèmes de la solution classique à ce problème, nous discutons ensuite d'une nouvelle notation appelée
Frame et montrons comment elle peut faire face aux difficultés que nous avons découvertes.
Portez d'abord l'eau à ébullition ...
La science a donné des noms à toutes les transitions possibles qu'une substance peut effectuer lorsque la température change.
Notre cours est très simple (et pas particulièrement utile). Il répond aux défis d'effectuer des transitions entre les états et modifie la température jusqu'à ce qu'elle devienne adaptée à l'état cible souhaité:
(Remarque: j'ai écrit ce pseudo-code. Utilisez-le dans votre travail uniquement à vos risques et périls.)
class WaterSample { temp:int Water(temp:int) { this.temp = temp }
Par rapport au premier exemple, ce code présente certaines améliorations. Tout d'abord, les nombres «magiques» codés en dur (32, 212) sont remplacés par les constantes des limites de température des états (WATER_SOLID_TEMP, WATER_GAS_TEMP). Ce changement commence à rendre les États plus explicites, quoique indirectement.
Des vérifications de «programmation défensive» apparaissent également dans ce code, ce qui restreint l'appel de méthode s'il n'est pas dans le bon état pour l'opération. Par exemple, l'eau ne peut pas geler si ce n'est pas un liquide - cela viole la loi (de la nature). Mais l'ajout de conditions de surveillance rend la compréhension de l'objectif du code plus difficile. Par exemple:
Cette vérification conditionnelle effectue les opérations suivantes:
- Vérifie si la
temp
inférieure à la température limite GAS - Vérifie si la
temp
dépasse la température limite SOLIDE - Renvoie une erreur si l'une de ces vérifications n'est pas vraie
Cette logique prête à confusion. Tout d'abord, être à l'état liquide est déterminé par ce que la substance n'est
pas - un solide ou un gaz.
(temp < WATER_GAS_TEMP && temp > WATER_SOLID_TEMP)
Deuxièmement, le code vérifie si l'eau est liquide pour savoir si une erreur doit être renvoyée.
!(temp < WATER_GAS_TEMP && temp > WATER_SOLID_TEMP)
La première fois pour comprendre cette double négation des états n'est pas facile. Voici une simplification qui réduit légèrement la complexité de l'expression:
bool isLiquidWater = (temp < WATER_GAS_TEMP && temp > WATER_SOLID_TEMP) if (!isLiquidWater) throw new IllegalStateError()
Ce code est plus facile à comprendre car l'état
isLiquidWater est
explicite .
Nous explorons maintenant des techniques qui fixent un
état explicite comme le meilleur moyen de résoudre des problèmes. Avec cette approche, les états logiques du système deviennent la structure physique du logiciel, ce qui améliore le code et simplifie sa compréhension.
Notation de trame machine
Frame Machine Notation (FMN) est un langage spécifique au domaine (Domain Specific Language, DSL) qui définit une approche catégorique, méthodologique et simple pour définir et implémenter divers types de
machines . Par souci de simplicité, j'appellerai les automates Frame simplement des «machines», car cette notation peut définir des critères théoriques pour différents types (machines d'état, automates de stockage et évolution des automates - Turing machines). Pour connaître les différents types de machines et leur application, je recommande d'étudier la page sur
Wikipédia .
Bien que la théorie des automates puisse être intéressante (une déclaration TRÈS douteuse), dans cet article, nous nous concentrerons sur l'application pratique de ces concepts puissants pour construire des systèmes et écrire du code.
Pour résoudre ce problème, Frame introduit une notation standardisée qui fonctionne à trois niveaux intégrés:
- DSL texte pour définir les contrôleurs Frame avec une syntaxe élégante et concise
- Un ensemble de modèles de codage de référence pour implémenter des classes orientées objet sous la forme de machines que Frame appelle des «contrôleurs»
- Notation visuelle dans laquelle FMN est utilisé pour exprimer des opérations complexes qui sont difficiles à représenter graphiquement - Frame Visual Notation (FVN)
Dans cet article, j'examinerai les deux premiers points: FMN et modèles de référence, et je laisserai la discussion sur FVN pour les prochains articles.
Le cadre est une notation qui a plusieurs aspects importants:
- FMN possède des objets de premier niveau liés au concept d'automates, qui ne sont pas disponibles dans les langages orientés objet.
- La spécification FMN définit des modèles d'implémentation standard dans un pseudo-code qui montrent comment la notation FMN peut être implémentée.
- FMN pourra bientôt compiler (travaux en cours) dans n'importe quel langage orienté objet
Remarque: l'implémentation de référence est utilisée pour démontrer l'équivalence absolue de la notation FMN et un moyen simple de l'implémenter dans n'importe quel langage orienté objet. Vous pouvez choisir n'importe quelle méthode.
Je vais maintenant vous présenter les deux objets de premier niveau les plus importants de Frame -
Frame Events et
Frame Controllers .
Événements de cadre
FrameEvents fait partie intégrante de la simplicité de la notation FMN. Un FrameEvent est implémenté en tant que structure ou classe qui a au moins les variables membres suivantes:
- identifiant du message
- dictionnaire ou liste de paramètres
- objet de retour
Voici le pseudocode de la classe FrameEvent:
class FrameEvent { var _msg:String var _params:Object var _return:Object FrameEvent(msg:String, params:Object = null) { _msg = msg _params = params } }
La notation de trame utilise le symbole
@ , qui identifie l'objet FrameEvent. Chacun des attributs FrameEvent requis possède un jeton spécial pour y accéder:
@|message| : - _msg @[param1] : [] @^ : _return
Souvent, nous n'avons pas à spécifier avec quoi FrameEvent fonctionne. Étant donné que la plupart des contextes ne fonctionnent qu'avec un seul FrameEvent à la fois, la notation peut certainement être simplifiée afin qu'elle n'utilise que des sélecteurs d'attributs. Par conséquent, nous pouvons simplifier l'accès:
|buttonClick|
Une telle notation peut sembler étrange au début, mais nous verrons bientôt comment une syntaxe aussi simple pour les événements simplifie grandement la compréhension du code FMN.
Contrôleurs de trames
Un Frame Controller est une classe orientée objet, ordonnée de manière bien définie pour implémenter une machine Frame. Les types de contrôleurs sont identifiés par le préfixe
# :
#MyController
cela équivaut au pseudocode orienté objet suivant:
class MyController {}
Évidemment, cette classe n'est pas particulièrement utile. Pour qu'il puisse faire quelque chose, le contrôleur a besoin d'au moins un état pour répondre aux événements.
Les contrôleurs sont structurés de manière à contenir des blocs de différents types, identifiés par un tiret entourant le nom du type de bloc:
#MyController<br> -block 1- -block 2- -block 3-
Un contrôleur ne peut avoir plus d'une instance de chaque bloc et les types de bloc ne peuvent contenir que certains types de sous-composants. Dans cet article, nous examinons uniquement le bloc
-machine- , qui ne peut contenir que des états. Les états sont identifiés par le jeton
$ prefix.
Ici, nous voyons le FMN pour un contrôleur contenant une machine avec un seul état:
#MyController
Voici l'implémentation du code FMN ci-dessus:
class MyController {
La mise en œuvre du bloc machine se compose des éléments suivants:
- variable _state , qui fait référence à une fonction de l'état actuel. Il est initialisé avec la première fonction d'état dans le contrôleur.
- une ou plusieurs méthodes d'état
La méthode State Frame est définie comme une fonction avec la signature suivante:
func MyState(e:FrameEvent);
Après avoir défini ces bases de l'implémentation du bloc machine, nous pouvons voir dans quelle mesure l'objet FrameEvent interagit avec la machine.
Unité d'interface
L'interaction de FrameEvents qui contrôlent le fonctionnement de la machine est l'essence même de la simplicité et de la puissance de la notation Frame. Cependant, nous n'avons pas encore répondu à la question, d'où viennent les FrameEvents - comment entrent-ils dans le contrôleur pour le contrôler? Une option: les clients externes eux-mêmes peuvent créer et initialiser FrameEvents, puis appeler directement la méthode pointée par la variable membre _state:
myController._state(new FrameEvent("buttonClick"))
Une bien meilleure alternative serait de créer une interface commune qui encapsule un appel direct à la variable membre _state:
myController.sendEvent(new FrameEvent("buttonClick"))
Cependant, le moyen le plus simple, correspondant à la manière habituelle de créer un logiciel orienté objet, est de créer des méthodes courantes qui envoient un événement au nom du client à la machine interne:
class MyController { func buttonClick() { FrameEvent e = new FrameEvent("buttonClick") _state(e) return e._return } }
Frame définit la syntaxe d'
un bloc d'interface qui contient des méthodes qui transforment les appels en une interface commune pour FrameEvents.
#MyController -interface- buttonClick ...
Le bloc d'
interface
possède de nombreuses autres fonctionnalités, mais cet exemple nous donne une idée générale de son fonctionnement. Je donnerai plus d'explications dans les articles suivants de la série.
Continuons maintenant à étudier le fonctionnement de l'automate Frame.
Gestionnaires d'événements
Bien que nous ayons montré comment définir une voiture, nous n'avons pas encore de notation avec laquelle
faire quoi que
ce soit. Pour traiter les événements, nous devons 1) être en mesure de sélectionner l'événement à traiter et 2) le rattacher au comportement en cours.
Voici un contrôleur Frame simple qui fournit l'infrastructure pour gérer les événements:
#MyController
Comme indiqué ci-dessus, pour accéder à l'attribut
_msg
de l'événement
_msg
, la notation FMN utilise des crochets à partir de lignes verticales:
|messageName|
FMN utilise également un jeton d'exposant représentant l'instruction de retour. Le contrôleur illustré ci-dessus sera implémenté comme suit:
class MyController {
Nous voyons ici à quel point la notation FMN correspond clairement à un modèle d'implémentation facile à comprendre et à coder.
Après avoir défini ces aspects de base des événements, contrôleurs, machines, états et gestionnaires d'événements, nous pouvons procéder à la résolution de problèmes réels avec leur aide.
Machines à foyer unique
Ci-dessus, nous avons examiné un contrôleur apatride qui était assez inutile.
#MyController
Une étape plus haut dans la chaîne alimentaire d'utilité est une classe à un seul état qui, bien que non inutile, est simplement ennuyeuse. Mais au moins, il fait au moins
quelque chose .
Voyons d'abord comment une classe avec un seul état (implicite) sera implémentée:
class Mono { String status() { return "OFF" } }
Aucun état n'est déclaré ou même implicite ici, mais supposons que si le code fait quelque chose, le système est dans l'état "Working".
Nous introduirons également une idée importante: les appels d'interface seront considérés comme similaires à l'envoi d'un événement à un objet. Par conséquent, le code ci-dessus peut être considéré comme une méthode de transmission du | statut | la classe Mono, toujours à l'état $ Working.
Cette situation peut être visualisée à l'aide de la table de liaison d'événements:
Examinons maintenant FMN, qui présente les mêmes fonctionnalités et correspond à la même table de liaison:
#Mono -machine- $Working |status| ^("OFF")
Voici à quoi ressemble l'implémentation:
class Mono {
Vous pouvez remarquer que nous avons également introduit une nouvelle notation pour l'
instruction return , ce qui signifie évaluer l'expression et renvoyer le résultat à l'interface:
^(return_expr)
Cet opérateur est équivalent
@^ = return_expr
ou juste
^ = return_expr
Tous ces opérateurs sont fonctionnellement équivalents et vous pouvez utiliser n'importe lequel d'entre eux, mais
^(return_expr)
semble le plus expressif.
Allumez le poêle
Jusqu'à présent, nous avons vu un contrôleur avec 0 états et un contrôleur avec 1 état. Ils ne sont pas encore très utiles, mais nous sommes déjà au bord de quelque chose d'intéressant.
Pour cuire nos pâtes, vous devez d'abord allumer le poêle. Voici une classe Switch simple avec une seule variable booléenne:
class Switch { boolean _isOn; func status() { if (_isOn) { return "ON"; } else { return "OFF"; } } }
Bien que cela ne soit pas évident à première vue, le code ci-dessus implémente le tableau suivant de liaisons d'événements:
A titre de comparaison, voici une FMN pour le même comportement:
#Switch1 -machine- $Off |status| ^("OFF") $On |status| ^("ON")
Nous voyons maintenant comment exactement la notation Frame correspond à l'objectif de notre code - attacher un événement (appel de méthode) à un comportement basé sur l'état dans lequel se trouve le contrôleur. De plus, la structure d'implémentation correspond également au tableau de liaison:
class Switch1 {
Le tableau vous permet de comprendre rapidement la fonction du contrôleur dans ses différents états. La structure de notation de trame et le modèle d'implémentation présentent des avantages similaires.
Cependant, notre commutateur a un problème fonctionnel notable. Il est initialisé dans l'état $ Off, mais ne peut pas basculer dans l'état $ On! Pour ce faire, nous devons saisir un opérateur de
changement d'état .
Changer d'état
La déclaration de changement d'état est la suivante:
->> $NewState
Maintenant, nous pouvons utiliser cet opérateur pour basculer entre $ Off et $ On:
#Switch2 -machine- $Off |toggle| ->> $On ^ |status| ^("OFF") $On |toggle| ->> $Off ^ |status| ^("ON")
Et voici la table de liaison d'événements correspondante:
Nouvel événement | bascule | déclenche désormais un changement qui passe simplement par les deux états. Comment mettre en œuvre une opération de changement d'état?
Nulle part est plus facile. Voici l'implémentation de Switch2:
class Switch2 {
Vous pouvez également apporter la dernière amélioration dans Switch2 afin qu'il vous permette non seulement de basculer entre les états, mais définit également explicitement l'état:
#Switch3 -machine- $Off |turnOn| ->> $On ^ |toggle| ->> $On ^ |status| ^("OFF") $On |turnOff| ->> $Off ^ |toggle| ->> $Off ^ |status| ^("ON")
Contrairement à l'événement | toggle |, si | turnOn | transmis lorsque Switch3 est déjà activé ou | turnOff | lorsqu'il est déjà désactivé, le message est ignoré et rien ne se passe.
Cette petite amélioration donne au client la possibilité d'indiquer explicitement l'état dans lequel le commutateur doit être: class Switch3 {
La dernière étape de l'évolution de notre commutateur montre à quel point il est facile de comprendre l'objectif du contrôleur FMN. Le code pertinent montre à quel point il est facile à implémenter à l'aide des mécanismes Frame.Après avoir créé la machine Switch, nous pouvons allumer le feu et commencer à cuisiner!État sonore
Un aspect clé, quoique subtil, des automates est que l'état actuel de la machine est le résultat soit d'une situation (par exemple, mise sous tension), soit d'une sorte d'analyse des données ou de l'environnement. Lorsque la machine est passée à l'état souhaité, cela est implicite. que la situation ne changera pas à l'insu de la voiture.Cependant, cette hypothèse n'est pas toujours vraie. Dans certaines situations, une vérification (ou «détection») des données est requise pour déterminer l'état logique actuel:- état initial restauré - lorsque la machine est restaurée à partir d'un état constant
- état externe - définit la «situation réelle» qui existe dans l'environnement au moment de la création, de la restauration ou du fonctionnement de la machine
- état interne volatile - lorsqu'une partie des données internes gérées par une machine en cours d'exécution peut changer en dehors du contrôle de la machine
Dans tous ces cas, les données, l'environnement ou les deux doivent être "sondés" afin de déterminer la situation et de définir l'état de la machine en conséquence. Idéalement, cette logique booléenne peut être implémentée dans une seule fonction qui définit l'état logique correct. Pour prendre en charge ce modèle, la notation Frame a un type spécial de fonction qui sonde l'univers et détermine la situation à l'heure actuelle. Ces fonctions sont indiquées par le préfixe $ avant le nom de la méthode qui renvoie un lien vers l'état : $probeForState()
Dans notre situation, une telle méthode peut être mise en œuvre comme suit: func probeForState():FrameState { if (temp < 32) return Solid if (temp < 212) return Liquid return Gas }
Comme nous pouvons le voir, la méthode renvoie simplement une référence à la fonction d'état correspondant à l'état logique correct. Cette fonction de détection peut ensuite être utilisée pour entrer dans l'état correct: ->> $probeForState()
Le mécanisme de mise en œuvre ressemble à ceci: _state = probeForState()
La méthode de détection d'état est un exemple de notation de trame pour gérer l'état d'une manière donnée. Ensuite, nous apprendrons également la notation importante pour la gestion des FrameEvents.Héritage comportemental et répartiteur
L'héritage comportemental et le répartiteur sont un paradigme de programmation puissant et le dernier sujet sur la notation de trame dans cet article.Frame utilise l'héritage du comportement , pas l'héritage des données ou d'autres attributs. Pour cet état, FrameEvents sont envoyés à d'autres états si l'état initial ne gère pas l'événement (ou, comme nous le verrons dans les prochains articles, veut juste le transmettre). Cette chaîne d'événements peut aller à n'importe quelle profondeur souhaitée.Pour cela, les machines peuvent être implémentées en utilisant une technique appelée chaînage de méthode . La notation FMN pour l'envoi d'événements d'un état à un autre est le répartiteur => : $S1 => $S2
Cette déclaration FMN peut être implémentée comme suit: func S1(e:FrameEvent) { S2(e)
Nous voyons maintenant combien il est facile de chaîner des méthodes d'état. Appliquons cette technique à une situation assez difficile: #Movement -machine- $Walking => $Moving |getSpeed| ^(3) |isStanding| ^(true) $Running => $Moving |getSpeed| ^(6) |isStanding| ^(true) $Crawling => $Moving |getSpeed| ^(.5) |isStanding| ^(false) $AtAttention => $Motionless |isStanding| ^(true) $LyingDown => $Motionless |isStanding| ^(false) $Moving |isMoving| ^(true) $Motionless |getSpeed| ^(0) |isMoving| ^(false)
Dans le code ci-dessus, nous voyons qu'il y a deux états de base - $ Moving et $ Motionless - et les cinq autres états héritent d'eux des fonctionnalités importantes. La liaison d'événement nous montre clairement à quoi ressembleront les liaisons en général:Grâce aux techniques que nous avons apprises, la mise en œuvre sera très simple: class Movement {
Machine à eau
Nous avons maintenant les bases de la connaissance de FMN, ce qui nous permet de comprendre comment réimplémenter la classe WaterSample avec des états et de manière beaucoup plus intelligente. Nous le rendrons également utile à notre étudiant physicien diplômé et lui ajouterons un nouvel état $ Plasma:Voici à quoi ressemble l'implémentation complète de FMN: #WaterSample -machine- $Begin |create|
Comme vous pouvez le voir, nous avons l'état initial de $ Begin, qui répond au message | create | et conserve sa valeur temp
. La fonction de détection vérifie d'abord la valeur initiale temp
pour déterminer l'état logique, puis effectue la transition de la machine vers cet état.Tous les états physiques ($ solide, $ liquide, $ gaz, $ plasma) héritent du comportement protecteur de l'état $ par défaut. Tous les événements qui ne sont pas valides pour l'état actuel sont passés à l'état $ Default, ce qui génère une erreur InvalidStateError. Cela montre comment une programmation défensive simple peut être implémentée en utilisant l'héritage de comportement.Et maintenant l'implémentation: class WaterSample {
Conclusion
Les automates sont un concept de base de l'informatique qui n'a été utilisé pendant trop longtemps que dans des domaines spécialisés du développement de logiciels et de matériel. La tâche principale de Frame est de créer une notation pour décrire les automates et définir des modèles simples pour écrire du code ou des «mécanismes» pour leur implémentation. J'espère que la notation Frame changera la façon dont les programmeurs regardent les machines, fournissant un moyen facile de les mettre en pratique dans les tâches de programmation quotidiennes et, bien sûr, de les sauver des spaghettis dans le code.Terminator mange des pâtes (photo de Suzuki san)Dans les prochains articles, sur la base des concepts que nous avons appris, nous allons créer encore plus de puissance et d'expressivité de la notation FMN. Au fil du temps, j'élargirai la discussion à l'étude de la modélisation visuelle, qui comprend la FMN et résout les problèmes de comportement incertain dans les approches modernes de la modélisation logicielle.