《编程指南》指的是全球资源库中的
ID109478084_Programming_Styleguide_DOC_v20_CN.PDF
ID81318674_Programming_Styleguide_DOC_v20_EN.PDF
两份文档,在文档中有几条内容是和“耦合”联系密切的,在此详细说明一下。
提及“耦合”(Coupling)则必提“内聚”(Cohesion),这就像一枚硬币的两面一样。
耦合和内聚的概念,有着悠久的历史,在传统文化中有很多它们的身影;要是从西方的软件工程溯源的话,早在20世纪60年代,结构化编程强调代码的逻辑清晰和可维护性,推动了模块化设计的发展。模块化要求将系统分解为独立的功能单元(模块),而内聚与耦合成为衡量模块设计质量的核心指标。概念最早由软件工程先驱Larry Constantine提出,并于1968年正式确立。
1974年,一些学者在论文《Structured Design》中系统化提出了模块内聚与耦合的分类标准,奠定了现代软件设计的基础。强调模块应具备“高内聚(即模块内部应该高度协作)、低耦合(却模块间应尽量减少依赖)”的特性以提升系统可维护性。
一句话,耦合和内聚的概念已经是存在了很久很久,没有什么好神秘的,更没有什么好炫耀的。
耦合可以分为七种,按耦合度从高到低排列如下:
一、内容耦合
二、公共耦合
三、外部耦合
四、控制耦合
五、标记耦合
六、数据耦合
七、非直接耦合
而指南中,有对应的相关细则,只是指南并没有说它是哪种耦合。
-----------------------------------------------------------------------------
1、内容耦合
一个模块直接访问另一模块的内容,则称这两个模块为内容耦合。
若在程序中出现下列情况之一,则说明两个模块之间发生了内容耦合:
1)一个模块直接访问另一个模块的内部数据。
2)一个模块不通过正常入口而直接转入到另一个模块的内部。
3)两个模块有一部分代码重叠(该部分代码具有一定的独立功能)。
4)一个模块有多个入口。
指南内容:
DA006 规则: 仅从块内访问静态变量
函数块的静态数据只能在声明它们的块内使用。
说明: 通过直接访问实例的静态变量, 兼容性无法保证, 因为将来的更新会影响到
相应的访问。 此外, 静态变量的修改对 FB 执行的影响无法判断。
说明,这种耦合度最强,上面1)和2)结合起来则对应了《指南》中的DA006,静态变量的直接访问,是需要格外规避的,也是不推荐的。
-----------------------------------------------------------------------------
2、公共耦合
一组模块都访问同一个全局数据结构,则称之为公共耦合。
公共数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。如果模块只是向公共数据环境写数据,或是只从公共数据环境读数据,这属于比较松散的公共耦合;如果模块既向公共数据环境写数据又从公共数据环境读数据,这属于比较紧密的公共耦合。
公共耦合会引起以下问题:
1)无法控制各个模块对公共数据的读写,严重影响了程序模块的可靠性和适应性。
2)使程序的可维护性变差。若一个模块修改了公共数据,则会影响相关模块。
3)降低了程序的可理解性。不容易清楚知道哪些数据被哪些模块所共享,排错困难。
一般地,仅当模块间共享的数据很多且通过参数参数很不方便时,才使用公共耦合。
指南内容:
RU004 规则: 只使用局部变量
在可重用块中, 只能使用局部变量, 不允许从 FC 或 FB 内访问全局数据。 全局数据可以通过块接口的形参传入。
RU005 规则: 使用本地符号常量
为了进一步封装块, 可以使用局部常量。 当需要使用全局常量时, 必须通过块接口的形参将它们传递到块中。 全局常量应在其自身的 PLC 变量表中定义。
说明:论坛中昵称为“宝冬”的朋友发的帖子中,对在HMI/SCADA等UI上的操作,便抽象为了read()和write()两部分,可说是很妙。
如何解决公共耦合的问题,那是另一个话题,以待后述。
-----------------------------------------------------------------------------
3、外部耦合
一组模块都访问同一全局简单变量,而且不通过参数表传递该全局变量的信息,则称为外部耦合。
指南内容参见上一条,这两种耦合有部分共同点。
-----------------------------------------------------------------------------
4、控制耦合
模块之间传递的不是数据信息,而是控制信息例如标志、开关量等,一个模块控制了另一个模块的功能。
指南内容:
PE005 建议: 避免形参 “mode”
避免开发根据输入参数(例如“mode”) 操作不同功能的块。
说明: 这可以防止不需要的代码片段(“死代码”), 因为模式参数通常是静态连接的。
相反, 您应该将不同功能分到不同的程序块:
? 这减少了内存消耗, 并通过减少代码来提高性能
? 它通过更好的区分和更好的命名来增加可读性
? 它通过更小的代码片段来增加可维护性, 这些代码片段彼此独立
-----------------------------------------------------------------------------
5、标记耦合
调用模块和被调用模块之间传递数据结构而不是简单数据,同时也称作特征耦合。表示模块间传递的不是简单变量,而是像高级语言中的数据名、记录名和文件名等结果,这些名字即为标记,其实传递的是地址。
指南内容:
PE003 建议: 使用引用传递结构化参数
为了保证最大性能(内存和运行最佳) 的将数据传递到块接口的形参中, 建议使用“按引用调用”模式。
说明:调用块时, 将传递对实际参数的引用, 不复制实际参数。
注意: 使用这种方法,可以修改原始数据。
在调用块时,如果将优化的数据传递给禁用了“优化的块访问”属性的块(反之亦然), 则该数据总是作为复制进行传递。 如果块包含许多结构化参数, 这会导致块的临时内存区域溢出。 您可以通过为两个块设置“优化”访问类型来避免这种情况。
说明:详见相关文档“传址”部分说明。
另外,现在的TIA Portal平台中有了REF_TO数据类型,在接口区的”数据类型“下的清单中已经有了REF_TO_BLOCK_FB和REF_TO_BLOCK_FC,还有引用的其它类型,相信不久便可以真的使用了。到时,给程序设计带来的将是更大的便利和快捷。
-----------------------------------------------------------------------------
6、数据耦合
调用模块和被调用模块之间只传递简单的数据项参数。相当于高级语言中的值传递。
说明:详见相关文档“传值”部分说明。
-----------------------------------------------------------------------------
7、非直接耦合
两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。耦合度最弱,模块独立性最强。
-----------------------------------------------------------------------------
结束语:
上文中耦合的分类引用自网络内容,同样“内聚”也可以系统地细分为种,在此不多言,感兴趣者可以自行搜索,同样在相关书籍中也有详细描述。至于和《指南》中的相关内容是否能够完全对应地起来,纯属个人观点,仅供参考。
OO-面向对象不是万灵丹,更不是救命草,从OOA到OOD到OOP,人,才是起关键作用的主体,其它皆是工具。

-----------------------------------------------------------------------------