Tcopen: 约定

创建于 2020-10-30  ·  31评论  ·  资料来源: TcOpenGroup/TcOpen

公约文件在这里

请参与下面的讨论

  • 让我们在此处保留讨论以进行跟踪。
  • 在这里快速聊天:TcOpen Slack

  • [ ] 变量的命名约定(VAR、VAR_INPUT、VAR_OUTPUT、VAR_IN_OUT、VAR_INST、TEMP)

  • [ ] 方法的命名约定
  • [ ] 属性的命名约定
  • [ ] 块的命名约定(FB、FC、PRG 等)
discussion

最有用的评论

我有一个关于数组的建议。 对于 inxton 编译器有一个正式的要求,它只转换基于 0 的数组。 原因是为了防止在 C# 中使用时产生混淆。

_array : ARRAY[0..10] OF BOOL; // 转堆
尽管
_array : ARRAY[1..10] OF BOOL; // 不转堆

对此有何评论?

所有31条评论

@mark-lazarides @Roald87 @philippleidig @jozefchmelar让我们继续讨论这里的约定...... 在此处讨论以跟踪活动

  • 在我看来,属性应该定义如下“IsEnabled”。 名称本身应该已经表明它是什么类型。

  • 我喜欢该方法的返回值作为布尔值。 我发现更复杂的数据类型不合适,因为它们必须在外部实例化或通过引用返回。

  • 使用 Inxton 或 tc.prober 需要继承“fbComponent”的每个基类吗?

  • 说到类型命名,我个人有点激进,通常会省略前缀。 除了接口、引用和指针。
    例如

    类型命名


| 块类型 | 符号 | 前缀 | 示例 |
| :------------- | :--------- | :------------ | :------------------------------------------------- -- |
| FB/班级名称 | 帕斯卡 | 否 | Cyclinder |
| ENUM 类型名称 | 帕斯卡 | 否 | MachineState.Start |
| 接口名称 | 帕斯卡 | I | ICyclinder |
| 功能名称 | 帕斯卡 | 否 | Add() |
| 结构名称 | 帕斯卡 | 没有 | Data |
| 联盟名称 | 帕斯卡 | 没有 | Control |

@philippleidig

  • 在我看来,属性应该定义如下“IsEnabled”。 名称本身应该已经表明它是什么类型。

完全同意。

  • 我喜欢该方法的返回值作为布尔值。 我发现更复杂的数据类型不合适,因为它们必须在外部实例化或通过引用返回。

我们像描述我们的组件一样使用它。 它在控制序列状态时很有用。 在绝大多数情况下,布尔就足够了。 有时会很高兴获得有关该方法状态的更多信息......但这需要更广泛的讨论(可能像一些流利的语法)

  • 使用 Inxton 或 tc.prober 需要继承“fbComponent”的每个基类吗?

不,对此没有具体要求,Inxton 也没有 tc.prober。 我们以这种方式使用它。 ComponentBase是一个抽象类,它有一些公共契约(手动方法等),但它可以实现组件的一些通用特性。 我不是继承的忠实拥护者(我更喜欢组合),但在这种情况下,我希望为将来打开一些选项。

在 inxton 中,如果你想收集一个集合中的所有组件,你可以在something is copmonent有机制时这样做。

还有另一个原因。 这些天我们正在开源我们的基础库,这有一些要求。 我希望下周能想出一些东西。 给你更多的细节。

  • 说到类型命名,我个人有点激进,通常会省略前缀。 除了接口、引用和指针。
    例如

类型命名

块类型符号前缀示例
FB/CLASS 名称 PascalCase No Cyclinder
ENUM 类型名称 PascalCase No MachineState.Start
接口名称 PascalCase I ICyclinder
函数名称 PascalCase 否Add()
结构名称 PascalCase No Data
UNION 名称 PascalCase 否Control

也不喜欢前缀。 该表类似于我们使用的前缀系统......但如果我们再次决定摆脱它会让我很高兴。

在大多数情况下,我看不到使用前缀的好处。 我同意@philippleidig的提议。

我想说指针和引用在这里是一个例外。

我在 PR #5 中提出了我的约定

成员命名和类型命名

我没有看到使用前缀的好处。 它对我没有任何帮助。

成员变量

类 (FB) 成员变量应隐藏并以小名称开头
~帕斯卡VAR{属性“隐藏”}触发器:布尔;{属性“隐藏”}计数器:INT;{属性“隐藏”}类比状态:类比状态;END_VAR~

@jozefchmelar

在大多数情况下,我看不到使用前缀的好处。 我同意@philippleidig的提议。

我想说指针和引用在这里是一个例外。
👍
我在 PR #5 中提出了我的约定

成员命名和类型命名

我没有看到使用前缀的好处。 它对我没有任何帮助。
👍👍

成员变量

类 (FB) 成员变量应隐藏并以小名称开头

    VAR
        {attribute 'hide'}
        trigger : BOOL;
        {attribute 'hide'}
        counter : INT;
        {attribute 'hide'}
        analogStatus : AnalogStatus;
    END_VAR
  • 隐藏变量在广告上不可见,因此如果在 HMI 上不需要它们,它们可以被隐藏。 但是如果我们需要在 HMI 中看到它们,我们不应该使用 'hide' 属性。
  • Tc3 不区分大小写,因此trigger (变量名)和Trigger (属性名)会发生冲突。 我猜我们需要在它前面加上_

完全同意👍

还有属性“conditionalshow”。 但这只能与已编译的库一起使用。
https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/8095402123.html &id=7685156034373049758
因为我假设我们将提供一个开放的库,所以这只是有限的意义。

_作为成员变量的前缀是必要的,正如@PTKu所说。

我通常喜欢坚持 C# 的命名约定和名称选择。

我喜欢该方法的返回值作为布尔值。 我发现更复杂的数据类型不合适,因为它们必须在外部实例化或通过引用返回。

@philippleidig返回值是什么意思? 在这种情况下,它们被用作错误检查? 通常返回值取决于方法。 CalculcateArea将返回REAL `LREAL`。

完全同意建议的变量命名!

我喜欢该方法的返回值作为布尔值。 我发现更复杂的数据类型不合适,因为它们必须在外部实例化或通过引用返回。

@philippleidig返回值是什么意思? 在这种情况下,它们被用作错误检查? 通常返回值取决于方法。 CalculcateArea将返回REAL``LREAL

@Roald87的想法是,执行动作的组件的方法将在动作完成时返回“真”(当到达主传感器/位置时,MoveToHome())。 这不会在必要时阻止其他返回类型。

完全同意建议的变量命名!

我有一个关于数组的建议。 对于 inxton 编译器有一个正式的要求,它只转换基于 0 的数组。 原因是为了防止在 C# 中使用时产生混淆。

_array : ARRAY[0..10] OF BOOL; // 转堆
尽管
_array : ARRAY[1..10] OF BOOL; // 不转堆

对此有何评论?

TwinCAT HMI (TE2000) 相同

TwinCAT HMI (TE2000) 相同

让阵列与 HMI 同步确实非常方便!

@philippleidig || @Roald87 你们中的任何一个关于数组的 PR 约定然后请...我只是喜欢在 repo 中看到更多的贡献者:)。

我有一个关于数组的建议。 对于 inxton 编译器有一个正式的要求,它只转换基于 0 的数组。 原因是为了防止在 C# 中使用时产生混淆。

_array : ARRAY[0..10] OF BOOL; // 转堆
尽管
_array : ARRAY[1..10] OF BOOL; // 不转堆

对此有何评论?

由于结构化文本循环的工作方式,我更喜欢保持 PLC 数组的尺寸为 1..X。 因此,代码在 PLC 中的任何地方都更容易阅读。 我认为我们应该始终在 PLC 上编写有吸引力且可维护的代码。 如果我们需要 shims 使其在第三方代码中更好地工作,我们可以单独管理它。

// 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

我喜欢该方法的返回值作为布尔值。 我发现更复杂的数据类型不合适,因为它们必须在外部实例化或通过引用返回。

我认为方法应该返回对该方法合理的东西。 方法的名称应该可以帮助您理解这一点。

IE

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的想法是,执行动作的组件的方法将在动作完成时返回“真”(当到达主传感器/位置时,MoveToHome())。 这不会在必要时阻止其他返回类型。

我真的不喜欢重复调用公共方法的方法,该方法重复执行功能直到它返回正确。 我认为有一个案例是可以接受的(如果接口上有“执行”类型的方法,但我相信也有更好的方法可以工作)。

它的问题;

  • 您可以/正在创建可以同时调用的类的执行路径。 IE
atEnd :=  axis.GoToEnd();
atBeginning := axis.GoToBeginning();
  • 类应该在内部管理它们的状态。 方法可用于对该状态进行请求和修改,属性也可用于访问状态或设置简单状态。
  • 您如何命名该方法,以便清楚如何以及为什么以这种方式使用它? 轴.GoToEndTrueWhenComplete()?
  • 如果类的底层状态发生改变,改变了调用的运行方式怎么办?
  • 可以更容易地生成竞争条件。 如果我们在某事上反复调用 .Enable() ,但单次扫描出现错误,那么 .Enable() 无论如何都可以使用“继续调用直到完成”方法启用。 这应该是 .RequestEnable : BOOL,它指示请求点的基础条件是否正确(允许调用代码在该点优雅地回退)。 如果可以发出请求,则调用代码可以监视 .IsEnabled 和 .InError 是否完成。

@philippleidig不熟悉 TwinCAT HMI。 那里如何处理基于非 0 的数组?

@mark-lazarides

我认为方法应该返回对该方法合理的东西。 方法的名称应该可以帮助您理解这一点。
👍

并发和竞争条件都是合理的关注点。 恕我直言,这些问题应尽可能在组件级别解决,但更重要的是在使用组件时的协调级别。 组件方法应该从正确实现的类似状态控制器的原语(无论是简单的 CASE、IF、ELSIF 或更复杂的排序器/选择器/迭代器)中调用,这将防止并发调用组件的同一实例的冲突方法.

在组件的消费者代码中应该防止这样的事情
~atEnd := axis.GoToEnd();atBeginning := axis.GoToBeginning();~

完成后返回trueexecuting methods允许干净的声明性使用。

我想到的是这样的:

~~~
VAR
_state:INT;

END_VAR

案例_状态
0:
IF(axis.MoveAbsolute(Position: 100.0)) THEN
_state := 1;
万一;
1:
IF(axis.MoveRelative(Position: 100.0)) THEN
_state := 2;
万一;
2:
如果(轴。移动绝对(位置:300.0))那么
_state := 3;
万一;
3:
_state := 0;
END_CASE
~~~

这可以减少到

~~~
VAR
_state:INT;

END_VAR

案例_状态
0:
等待(轴。移动绝对(位置:100.0),1);
1:
等待(轴。MoveRelative(位置:100.0),2);
2:
等待(axis.MoveAbsolute(位置:300.0),3);
3:
等待(真,0);

END_CASE

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

方法等待
VAR_INPUT
完成:布尔
下一个状态:INT;
END_VAR
如果(完成)然后
_state := 下一个状态;

万一;

~~~
编辑:我假设该组件用于单个 plc 任务

它对使用代码施加了限制。 如果消费者以错误的顺序使用它们,我们的组件不应出错。 他们应该对所有互动做出良好的反应。 他们不一定会“工作”(即在axis.Enable() res := axis.MoveTo(Position:=100); $ 将不起作用),但是应该在所有点上为消费代码提供足够的信息以了解问题出在哪里。

它对我来说仍然不好读。 您无法阅读 axis.MoveAbsolute(syx) 并理解它需要循环调用。 你只会明白,如果你知道我们的惯用风格需要你这样做,而那时我认为我们未能创造出非常有用的东西。

我还要说,无论是否可以排除错误,它们仍然更有可能。 使用循环方法调用方法,您可以创建一个方法来检查对象的状态是否已准备好被调用,然后调用循环方法,然后监视对象的其他状态以确保在此期间没有发生任何事情。 或者您发出请求,它会告诉您它是否成功,然后监视完成状态。 如果需要,您可以为此注册一个回调作为请求的一部分,这是一种不错的 OO 方法,并减少了更多的循环调用/询问。

@mark-lazarides 正确! 我execute methods (让我们这样称呼它们)不应该实现循环逻辑。 我确实假设我们之前讨论过的内容是,我们确保需要以循环方式执行的内容要么放在 FB 的主体中,要么放在Cyclic方法中; 应该在消费者程序中的某个适当位置调用。

好的。 那么为什么我们要循环调用该方法呢? 我仍然认为最初的论点是成立的。 该方法应该完成一项工作。 要么开始一些东西(并因此报告它的成功),要么得到一些东西(并返回它)。

好的。 那么为什么我们要循环调用该方法呢? 我仍然认为最初的论点是成立的。 该方法应该完成一项工作。 要么开始一些东西(并因此报告它的成功),要么得到一些东西(并返回它)。

没有异议 马克,我们不需要循环调用执行方法,但如果我们这样做应该不是问题。

我不明白为什么一个方法不能返回操作的结果。

我认为我们应该提出一些更复杂的组件并对这些想法进行原型制作(气动活塞对于本次讨论来说不够复杂)我认为我们可以从驱动/轴开始。

同意,彼得。

由于线程名称,我认为我们的目标是作为一个约定! 抱歉,如果我有误解。

Chris 目前作为 PR 有一个基本的轴块——我已经评论过它,但它需要更多的关注。

@mark-lazarides 不需要道歉,马克,我们在这里自由讨论,我明天去看看 PR...

@philippleidig @Roald87 @dhullett08关于组件设计的讨论也在这里

从 PLCopen 文档中获取的一些随机建议。

plcopen_coding_guidelines_version_1.0.pdf

常数
应该是全大写,所以它们很容易被识别

可接受的名称长度
最少 4 个字符 最多 24 个字符?

从 PLCopen 文档中获取的一些随机建议。

plcopen_coding_guidelines_version_1.0.pdf

感谢您的链接

常数
应该是全大写,所以它们很容易被识别

👍

可接受的名称长度
最少 4 个字符 最多 24 个字符?

在表达意图之前,更长的名称应该不是问题,24 个字符就足够了,但是我不会限制最大值。 人物。 太短的名字确实很可疑,它们应该超过 4 个字符。

@Seversonic您在文档中添加的评论...

讨论续这里#11

我真的不喜欢你的约定,但我认为你的项目很有趣!
个人我更喜欢更经典的方式
FB_fb 功能块
M_Add() 方法
P_Parameter 道具

嗨, @PeterZerlauth ,谢谢。 就约定达成一致有点费力,因为我们处于 PLC 和经典软件工程之间。

以下是讨论初期的民意调查:

TcOpen.Survey.Result.pdf

除此之外,在此处和 Slack 频道中进行了讨论, @dhullett08 TcOpen 存储库中可能也有一些内容。

有一种普遍的感觉(或者至少我是这样解释的),如果前缀不能提供有用的信息,或者现代 IDE 提供了我们过去用前缀传达的信息,我们应该放弃它们。

我知道这是关于个人喜好的,实际上没有正确或错误的做法。 只是我们必须就某事达成一致。

在这里结束讨论继续在这里: https ://github.com/TcOpenGroup/TcOpen/discussions/11

此页面是否有帮助?
0 / 5 - 0 等级