Tcopen: Conventions

Créé le 30 oct. 2020  ·  31Commentaires  ·  Source: TcOpenGroup/TcOpen

Document des conventions ici

Veuillez contribuer à la discussion ci-dessous

  • Gardons les discussions ici pour le suivi.
  • Chats rapides ici :TcOpen Slack

  • [ ] Conventions de dénomination des variables (VAR, VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_INST, TEMP)

  • [ ] Convention de nommage des méthodes
  • [ ] Convention de dénomination des propriétés
  • [ ] Convention de dénomination des blocs (FB, FC, PRG etc.)
discussion

Commentaire le plus utile

J'aurais une suggestion sur les tableaux. Il existe une exigence formelle pour le compilateur inxton qui transpile uniquement les tableaux basés sur 0. La raison est d'éviter la confusion lorsqu'il est utilisé en C#.

_array : TABLEAU[0..10] DE BOOL ; // trans-piles
tandis que
_array : TABLEAU[1..10] DE BOOL ; // ne trans-empile pas

Un commentaire à ce sujet ?

Tous les 31 commentaires

@mark-lazarides @Roald87 @philippleidig @jozefchmelar gardons la discussion sur les conventions ici... détendez-vous pour une conversation rapide ; discussions ici pour suivre l'activité

  • À mon avis, les propriétés doivent être définies comme suit "IsEnabled". Le nom lui-même devrait déjà indiquer de quel type il s'agit.

  • J'aime la valeur de retour de la méthode en tant que booléen. Je trouve les types de données plus complexes inappropriés, car ils doivent être instanciés à l'extérieur ou renvoyés par référence.

  • L'héritage de chaque classe de base de "fbComponent" est-il nécessaire pour utiliser Inxton ou tc.prober ?

  • En ce qui concerne la dénomination des types, je suis personnellement un peu radical et omet généralement les préfixes. Sauf pour les interfaces, les références et les pointeurs.
    par exemple

    Dénomination des types


| Type de bloc | notation | Préfixe | Exemple |
| :------------- | :--------- | :------------ | :-------------------------------------------------------------- -- |
| Nom FB/CLASSE | PascalCas |Non | Cyclinder |
| Nom du type ENUM | PascalCas |Non | MachineState.Start |
| Nom INTERFACE | PascalCase | I | ICyclinder |
| Nom de la FONCTION | PascalCas |Non | Add() |
| Nom STRUCT | PascalCase | Non | Data |
| nom UNION | PascalCase | Non | Control |

@philippleidig

  • À mon avis, les propriétés doivent être définies comme suit "IsEnabled". Le nom lui-même devrait déjà indiquer de quel type il s'agit.

Complètement d'accord.

  • J'aime la valeur de retour de la méthode en tant que booléen. Je trouve les types de données plus complexes inappropriés, car ils doivent être instanciés à l'extérieur ou renvoyés par référence.

Nous l'utilisons comme décrit avec nos composants. C'est utile pour contrôler l'état d'une séquence. Dans la grande majorité des cas boo suffit. Parfois, ce serait bien d'avoir plus d'informations sur l'état de la méthode ... mais cela nécessiterait une discussion plus large (peut-être une syntaxe fluide comme quelque chose)

  • L'héritage de chaque classe de base de "fbComponent" est-il nécessaire pour utiliser Inxton ou tc.prober ?

Non, il n'y a pas d'exigence spécifique pour cela, ni Inxton ni tc.prober. Nous l'utilisons de cette manière. ComponentBase est une classe abstraite qui a un contrat public (méthode manuelle, etc.), mais elle peut implémenter certaines fonctionnalités communes pour les composants. Je ne suis pas un grand fan de l'héritage (je préfère la composition), mais dans ce cas, j'aimerais avoir une option ouverte pour l'avenir.

Dans inxton, si vous souhaitez collecter tous les composants d'une collection, vous pouvez le faire lorsqu'il existe something is copmonent mécanisme pour cela.

Il y a aussi une autre raison à cela. Nous travaillons ces jours-ci sur l'open source de notre bibliothèque de base, qui a certaines exigences en ce qui concerne. J'espère pouvoir proposer quelque chose la semaine prochaine. Pour vous donner plus de détails.

  • En ce qui concerne la dénomination des types, je suis personnellement un peu radical et omet généralement les préfixes. Sauf pour les interfaces, les références et les pointeurs.
    par exemple

Dénomination des types

Type de bloc Notation Préfixe Exemple
Nom FB/CLASS PascalCase Non Cyclinder
Nom du type ENUM PascalCase Non MachineState.Start
Nom de l'INTERFACE PascalCase I ICyclinder
Nom de la FONCTION PascalCase Non Add()
Nom STRUCT PascalCase Non Data
Nom UNION PascalCase Non Control

Pas fan des préfixes non plus. Le tableau ressemble au système de préfixes que nous utilisons... mais encore une fois, si nous décidions de nous en débarrasser, cela me rendrait tout simplement heureux.

Dans la plupart des cas, je ne vois pas d'avantage à utiliser des préfixes. Je suis d'accord avec la proposition de @philippleidig .

Je dirais que le pointeur et la référence sont une exception ici.

J'ai proposé mes conventions dans PR #5

Dénomination des membres et dénomination du type

Je ne vois pas d'avantage à utiliser le préfixe. Cela ne m'aide en rien.

Variables membres

Les variables de membre de classe (FB) doivent être masquées et commencer par un petit nom
~ PascalVAR{attribut 'masquer'}déclencheur : BOOL ;{attribut 'masquer'}compteur : INT;{attribut 'masquer'}analogStatus : AnalogStatus;END_VAR~

@jozefchmelar

Dans la plupart des cas, je ne vois pas d'avantage à utiliser des préfixes. Je suis d'accord avec la proposition de @philippleidig .

Je dirais que le pointeur et la référence sont une exception ici.
👍
J'ai proposé mes conventions dans PR #5

Dénomination des membres et dénomination du type

Je ne vois pas d'avantage à utiliser le préfixe. Cela ne m'aide en rien.
👍👍

Variables membres

Les variables de membre de classe (FB) doivent être masquées et commencer par un petit nom

    VAR
        {attribute 'hide'}
        trigger : BOOL;
        {attribute 'hide'}
        counter : INT;
        {attribute 'hide'}
        analogStatus : AnalogStatus;
    END_VAR
  • les variables masquées ne seront pas visibles sur les publicités, donc si elles ne sont pas nécessaires sur l'IHM, elles peuvent être masquées. Mais si nous avons besoin de les voir dans l'IHM, nous ne devons pas utiliser l'attribut 'hide'.
  • Tc3 est insensible à la casse donc trigger (nom de variable) et Trigger (nom de propriété) seraient en conflit. Nous devrons le préfixer avec _ comme proposé, je suppose

Entièrement d'accord 👍

Il y a aussi l'attribut "conditionalshow". Mais cela ne peut être utilisé qu'en conjonction avec une bibliothèque compilée.
https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/8095402123.html &id=7685156034373049758
Puisque je suppose que nous fournirons une bibliothèque ouverte, cela n'aurait qu'un sens limité.

_ comme préfixe pour les variables membres est nécessaire comme l'a dit @PTKu .

J'aime généralement m'en tenir aux conventions de dénomination et à la sélection de noms de C#.

J'aime la valeur de retour de la méthode en tant que booléen. Je trouve les types de données plus complexes inappropriés, car ils doivent être instanciés à l'extérieur ou renvoyés par référence.

@philippleidig qu'entendez-vous par valeurs de retour ? Dans ce cas, ils sont utilisés comme contrôles d'erreurs ? Habituellement, la valeur de retour dépend de la méthode. CalculcateArea renverrait un REAL `LREAL`.

Entièrement d'accord sur la dénomination de variable suggérée !

J'aime la valeur de retour de la méthode en tant que booléen. Je trouve les types de données plus complexes inappropriés, car ils doivent être instanciés à l'extérieur ou renvoyés par référence.

@philippleidig qu'entendez-vous par valeurs de retour ? Dans ce cas, ils sont utilisés comme contrôles d'erreurs ? Habituellement, la valeur de retour dépend de la méthode. CalculcateArea renverrait un REAL``LREAL .

@ Roald87 l'idée serait que la méthode d'un composant qui exécute une action renverrait 'true' lorsque l'action est terminée (MoveToHome() lorsque le capteur/position d'origine est atteint). Cela n'empêche pas d'autres types de retour si nécessaire.

Entièrement d'accord sur la dénomination de variable suggérée !

J'aurais une suggestion sur les tableaux. Il existe une exigence formelle pour le compilateur inxton qui transpile uniquement les tableaux basés sur 0. La raison est d'éviter la confusion lorsqu'il est utilisé en C#.

_array : TABLEAU[0..10] DE BOOL ; // trans-piles
tandis que
_array : TABLEAU[1..10] DE BOOL ; // ne trans-empile pas

Un commentaire à ce sujet ?

Idem pour TwinCAT HMI (TE2000)

Idem pour TwinCAT HMI (TE2000)

Ce serait très pratique d'avoir les tableaux synchronisés avec l'IHM en effet !

@philippleidig || @ Roald87 est -ce que l'un d'entre vous aurait des conventions de relations publiques sur les tableaux, alors s'il vous plaît ... J'aime juste voir plus de contributeurs dans le dépôt :).

J'aurais une suggestion sur les tableaux. Il existe une exigence formelle pour le compilateur inxton qui transpile uniquement les tableaux basés sur 0. La raison est d'éviter la confusion lorsqu'il est utilisé en C#.

_array : TABLEAU[0..10] DE BOOL ; // trans-piles
tandis que
_array : TABLEAU[1..10] DE BOOL ; // ne trans-empile pas

Un commentaire à ce sujet ?

En raison du fonctionnement de la boucle de texte structuré, je préfère conserver les tableaux PLC dimensionnés 1..X. Le code est ainsi plus facile à lire partout dans l'automate. Je pense que nous devrions toujours écrire du code attrayant et maintenable sur l'automate. Si nous avons besoin de shims pour que cela fonctionne mieux sur des morceaux de code tiers, nous pouvons les gérer séparément.

// Declaration
NUMBER_OF_DRIVES : INT := 10;
drives  : ARRAY[1..NUMBER_OF_DRIVES] OF I_Drive;

// now in the code
FOR i := 1 to NUMBER_OF_DRIVES DO
   drives[i].SomethingCool();
END_FOR

// Compared to

// Declaration
drives : ARRAY[0..(NUMBER_OF_DRIVES -1) ] OF I_Drive;
// Code
FOR i := 0 to (NUMBER_OF_DRIVES -1) DO
   drives[i].SomethingCool();
END_FOR

J'aime la valeur de retour de la méthode en tant que booléen. Je trouve les types de données plus complexes inappropriés, car ils doivent être instanciés à l'extérieur ou renvoyés par référence.

Je pense que les méthodes devraient retourner ce qui est raisonnable pour la méthode. Le nom de la méthode devrait vous aider à comprendre cela.

c'est à dire

IF NOT piece.PassesValidation() THEN
 LogError('Piece does not pass validation');
END_IF

// OR

IF sequence.Finished THEN
  axis.Disable(); // No return type necessary.
  state := WaitForAxisDisabled;
END_IF

@ Roald87 l'idée serait que la méthode d'un composant qui exécute une action renverrait 'true' lorsque l'action est terminée (MoveToHome() lorsque le capteur/position d'origine est atteint). Cela n'empêche pas d'autres types de retour si nécessaire.

Je n'aime vraiment pas l'approche consistant à appeler à plusieurs reprises une méthode publique, où cette méthode exécute la fonctionnalité à plusieurs reprises jusqu'à ce qu'elle soit correcte. Il y a un seul cas, je pense que c'est acceptable (s'il y a une méthode de type "Execute" sur une interface, mais il y a aussi de meilleures façons pour que cela fonctionne aussi, je crois).

Les problèmes avec cela;

  • Vous pouvez/créez des chemins d'exécution sur une classe qui pourraient être appelées simultanément. C'est à dire
atEnd :=  axis.GoToEnd();
atBeginning := axis.GoToBeginning();
  • Les classes doivent gérer leur état en interne. Les méthodes peuvent être utilisées pour faire des demandes et des modifications de et vers cet état, et les propriétés peuvent être utilisées pour accéder à l'état ou définir un état simple également.
  • Comment nommez-vous la méthode pour qu'il soit clair comment et pourquoi elle est utilisée de cette manière ? axe.GoToEndTrueWhenComplete() ?
  • Que se passe-t-il si l'état sous-jacent de la classe change d'une manière qui modifie la façon dont cet appel est exécuté ?
  • Les conditions de course peuvent être générées plus facilement. Si nous appelons à plusieurs reprises .Enable() sur quelque chose, mais que nous obtenons une erreur pour une seule analyse, alors .Enable() peut bien Activer quand même en utilisant l'approche "continuer à appeler jusqu'à ce que ce soit fait". Cela devrait plutôt être .RequestEnable : BOOL, qui indique si les conditions sous-jacentes au point de demande sont correctes (permettant au code appelant de se replier gracieusement à ce point). Si la demande peut être faite, le code appelant peut alors surveiller .IsEnabled et .InError pour l'achèvement.

@philippleidig ne connaît pas TwinCAT HMI. Comment les tableaux non basés sur 0 sont-ils gérés ici ?

@mark-lazarides

Je pense que les méthodes devraient retourner ce qui est raisonnable pour la méthode. Le nom de la méthode devrait vous aider à comprendre cela.
👍

La concurrence et les conditions de concurrence sont toutes des préoccupations raisonnables. À mon humble avis, ces problèmes doivent être résolus autant que possible au niveau des composants, mais surtout au niveau de la coordination lors de la consommation des composants. Les méthodes de composant doivent être appelées à partir de primitives de type contrôleur d'état correctement implémentées (qu'il s'agisse de simples CASE, IF, ELSIF ou d'un séquenceur/sélecteur/itérateur plus complexe) qui empêcheraient les appels simultanés de méthodes en conflit de la même instance d'un composant .

Quelque chose comme ça devrait être empêché dans le code consommateur du composant
~atEnd := axe.GoToEnd();atBeginning := axe.GoToBeginning();~

Le executing methods retournant true fois terminé permet une utilisation déclarative propre.

Ce que j'ai en tête, c'est quelque chose comme ça :

~~~
VAR
_état : INT ;

END_VAR

CAS _état DE
0 :
SI(axe.MoveAbsolute(Position : 100.0)) ALORS
_état := 1;
FIN SI;
1:
SI(axe.MoveRelative(Position : 100.0)) ALORS
_état := 2;
FIN SI;
2 :
SI(axe.MoveAbsolute(Position : 300.0)) ALORS
_état := 3;
FIN SI;
3 :
_état := 0;
END_CASE
~~~

Celle-ci pourrait être réduite à

~~~
VAR
_état : INT ;

END_VAR

CAS _état DE
0 :
Attendre(axis.MoveAbsolute(Position : 100.0),1);
1:
Attendre(axis.MoveRelative(Position : 100.0),2);
2 :
Attendre(axis.MoveAbsolute(Position : 300.0),3);
3 :
Attendre(vrai,0);

END_CASE

===================================

MÉTHODE Attendre
VAR_INPUT
fait : BOOL
nextState : INT;
END_VAR
SI (fait) ALORS
_state := nextState;

FIN SI;

~~~
edit: je suppose que le composant est utilisé dans une seule tâche plc

Cela impose des contraintes sur la consommation du code. Nos composants ne doivent pas être sujets à des erreurs si le consommateur les utilise dans une commande incorrecte. Ils doivent bien réagir à toutes les interactions. Ils ne "fonctionneront" pas nécessairement (c'est-à-dire qu'appeler res := axis.MoveTo(Position:=100); avant axis.Enable() ne fonctionnerait pas), mais le code consommateur doit recevoir suffisamment d'informations à tous les points pour comprendre d'où vient le problème.

Il ne se lit toujours pas bien pour moi non plus. Vous ne pouvez pas lire axis.MoveAbsolute(syx) et comprendre qu'il doit être appelé de manière cyclique. Vous ne comprendriez que si vous saviez que notre style idiomatique exigeait cela de vous et à ce stade, je pense que nous avons échoué à créer quelque chose de très utilisable.

Je dirais également que, que les erreurs puissent ou non être factorisées, elles sont encore plus probables. Avec l'approche d'appel de méthode cyclique, vous pouvez créer une méthode qui vérifie si l'état de l'objet est prêt à être appelé, puis appeler la méthode cyclique, puis surveiller l'autre état de l'objet pour s'assurer que rien ne s'est passé entre-temps. Ou vous faites la demande, qui vous indique si elle a réussi ou non, puis surveillez l'état de l'achèvement. Vous pouvez enregistrer un rappel pour cela dans le cadre de la demande si vous le souhaitez, ce qui est une approche OO décente et réduit davantage les appels / interrogations de manière cyclique.

@mark-lazarides correct ! I execute methods (appelons-les ainsi) ne devrait pas implémenter de logique cyclique. J'ai supposé ce dont nous avons discuté précédemment que nous nous assurons que ce qui doit être exécuté de manière cyclique serait placé soit dans le corps de FB, soit dans une méthode Cyclic ; qui devrait être appelé à un endroit approprié dans le programme du consommateur.

D'ACCORD. Alors pourquoi appelons-nous la méthode de manière cyclique ? Je pense toujours que les arguments originaux tiennent. La méthode devrait faire un travail. Commencez quelque chose (et donc signalez le succès de cela) ou obtenez quelque chose (et retournez-le).

D'ACCORD. Alors pourquoi appelons-nous la méthode de manière cyclique ? Je pense toujours que les arguments originaux tiennent. La méthode devrait faire un travail. Commencez quelque chose (et donc signalez le succès de cela) ou obtenez quelque chose (et retournez-le).

Aucune objection Mark, nous n'avons pas besoin d'appeler les méthodes d'exécution de manière cyclique, mais cela ne devrait pas être un problème si nous le faisons.

Je ne vois pas pourquoi une méthode ne pourrait pas renvoyer le résultat de l'opération.

Je pense que nous devrions proposer des composants plus complexes et pour prototyper ces idées (le piston pneumatique n'est pas assez complexe pour cette discussion), je pense que nous pourrions commencer par l'entraînement/l'axe.

D'accord, Pierre.

En raison du nom du fil, je pensais que nous visions cela comme une convention ! Toutes mes excuses si j'ai mal compris.

Chris a un bloc d'axe de base en tant que PR pour le moment - je l'ai commenté, mais il a besoin de plus d'attention.

@mark-lazarides aucune excuse nécessaire Mark, nous sommes ici pour discuter librement, je jetterai un œil aux relations publiques demain...

@philippleidig @Roald87 @dhullett08 Discussion sur la conception des composants également ici

Quelques suggestions aléatoires tirées des documents PLCopen.

plcopen_coding_guidelines_version_1.0.pdf

Constantes
Doit être en MAJUSCULES pour être facilement identifiable

Longueur de nom acceptable
Minimum de 4 caractères maximum de 24 caractères ?

Quelques suggestions aléatoires tirées des documents PLCopen.

plcopen_coding_guidelines_version_1.0.pdf

Merci pour le lien

Constantes
Doit être en MAJUSCULES pour être facilement identifiable

👍

Longueur de nom acceptable
Minimum de 4 caractères maximum de 24 caractères ?

Les noms plus longs ne devraient pas poser de problème jusqu'à ce qu'ils expriment l'intention, 24 caractères devraient suffire, mais je ne mettrais pas de limite à max. personnages. Les noms trop courts sont en effet suspects ils doivent faire plus de 4 caractères.

@Seversonic vos remarques ajoutées au document...

discussion suite ici #11

Je n'aime vraiment pas vos conventions mais je pense que votre projet est assez intéressant !
perso je préfère une manière plus classique
Bloc fonction FB_ fb
Méthode M_Add()
P_Paramètre Prop

Salut, @PeterZerlauth et merci. C'est un peu laborieux de se mettre d'accord sur les conventions car on est à mi-chemin entre l'automate et le génie logiciel classique.

Voici le sondage du début des discussions :

TcOpen.Survey.Result.pdf

En plus de cela, il y a eu une discussion ici et dans la chaîne Slack, il pourrait aussi y avoir quelque chose dans le repo @ dhullett08 TcOpen.

Il y avait un sentiment général (ou du moins je l'interprète de cette façon) que nous devrions abandonner les préfixes s'ils ne fournissent pas d'informations utiles ou si l'IDE moderne fournit les informations que nous transmettions avec les préfixes dans le passé.

Je comprends qu'il s'agit de préférences personnelles et qu'il n'y a vraiment pas de bonne ou de mauvaise façon de le faire. Faut juste qu'on s'entende sur quelque chose.

Clôturant ici, la discussion se poursuit ici : https://github.com/TcOpenGroup/TcOpen/discussions/11

Cette page vous a été utile?
0 / 5 - 0 notes