ST功能块800行拆不拆?PLC代码拆分逻辑与判断标准

发布时间:2026/10/13 5:37:52
ST功能块800行拆不拆?PLC代码拆分逻辑与判断标准 朋友发来一张截图问我一段ST功能块里写了800行看着就头皮发麻是不是该拆成20行一段我回他你这不是在问行数你是在问这程序还想不想让人维护。STStructured Text结构化文本是IEC 61131-3标准里应用最广的文本编程语言西门子平台里叫SCLTwinCAT、Codesys、罗克韦尔里叫ST语法细节略有不同核心逻辑都是同一套。很多PLC工程师写ST的习惯是把它当成能写IF的脚本一个功能块里从头写到尾五六个气缸、十几个传感器、一堆报警全部平铺在一个FB里最后代码行数一路冲到七八百。年轻工程师可能觉得能跑就行但等设备到了现场调试、客户提了个新需求、或者半夜报警却定位不到原因你就会发现800行一段的代价远超想象。这篇文章不是教条地告诉你必须20行而是把大段ST和小段ST的真实差异摊开来讲。适合正在写PLC程序、SCL程序或者在团队里做代码规范的人参考。我会把拆分逻辑、实操步骤、踩过的坑都写清楚也会给出我这些年验证过的判断标准。1. 800行一段的ST程序问题从来不只在行数多1.1 人的工作记忆装不下这么多分支人的大脑在阅读代码时本质上是在维护一个状态栈每看到一个IF你就要记住这个条件成立时改变了什么每看到一个变量你就要追溯它上一次在哪里被赋值每看到一个定时器你还要记住它触发之后流程会跳到哪个分支。一个800行的ST块假设它里面有40多个IF分支、100多个变量、若干定时器和计数器读代码的人即便拿着放大镜在电脑前坐一个小时也很难把这个逻辑完整装进脑子里。认知科学里有个著名的7±2法则说的是人在某一瞬间能记住的组块数量大约只有5到9个。800行代码里的信息量早就远远超出了这个上限。大多数时候你读完前200行再看后600行时已经完全忘了前面的变量被谁改过。这不是工程师水平问题是人类的生理限制。1.2 真正致命的是改一处、崩三处行数多本身不致命更致命的是大段代码几乎必然伴随高耦合。所有变量都在同一个作用域里所有分支都共享同一个状态空间任何一个局部参数都有可能被多个不相干的动作引用。我说一个很常见的场景某个设备块里有一个定时器用来控制气缸推进超时报警。某天工艺调整你觉得2.5秒太短了把PT改成3秒。结果第二天发现另一个本来互不相关的吹气动作也跟着变了时序。为什么因为当时写代码的人图省事在这个大块里复用了同一个定时器的输出信号作为吹气动作的触发条件。你改的是气缸的定时连带的却是吹气的动作。这种改一处、崩三处的事故在大块代码里几乎每周都在发生。因为耦合关系藏在几十个IF分支和一百多个变量里没有任何工具能帮你一眼看清这个变量到底被谁依赖。1.3 我见过的大段代码长什么样有一次我接手一个物流设备项目某个设备控制块整整850行。功能上它包含了5个气缸的顺序控制、3个光电传感器的到位判断、2路真空压力监控、一个手动自动切换逻辑外加所有报警的输出。这些功能被平铺在一个FB里用二十多层IF嵌套串联起来局部变量多达七八十个命名也从v1、v2一直到v30。最折磨人的是报警排查。有一次现场报气缸未到位我的第一反应是去查气缸超时报警那段逻辑结果发现超时报警的状态位被后续一个IF又覆盖了翻了三层才找到真正的原因。那次排查花了我将近两个半小时其中真正的问题只花了五分钟就能解决剩下的时间全部浪费在这行代码到底会被谁执行到的推理上。从那天之后我就彻底明白了800行一段的ST不是技术问题是项目管理隐患。2. 20行一段为什么被推崇耦合、内聚、接口与可测试性2.1 单一职责——一段ST只做一件完整的事20行一段的背后真正的核心不是行数这个数字而是职责边界。如果一段ST代码超过20行大概率意味着你在这个功能块里塞了不止一件事。举个例子一个功能块里既写了气缸推进的顺序逻辑又写了真空压力监控还写了报警灯闪烁控制——这三件事虽然属于同一台设备但它们的生命周期、变更频率和测试方式完全不同。气缸顺序逻辑跟着工艺走压力监控跟着气路系统走报警灯则跟着人机交互走。硬把它们绑在一个块里将来任何一个需求变化都会导致整个块被推倒重来。拆成小功能块之后每个块只干一件事而且这一件事可以完整地被描述清楚。单一职责听起来像是软件工程里的老生常谈但在PLC工程里同样适用。IEC 61131-3标准本身就给了一套完美的表达工具——FB和FC。很多人不是不会用而是没把API设计思维带到PLC程序里。2.2 接口即契约把数据流动变显式20行一段的另一个隐藏价值在于它逼迫你设计接口。大块代码靠共享变量通信数据流是隐性的藏在几百行代码里。小块代码靠FB的输入输出参数通信数据流是显性的一眼看过去就知道谁在给谁传数据。这就是接口即契约FB的VAR_INPUT和VAR_OUTPUT就是这个块跟外部世界打交道的边界边界清晰耦合自然降低。我在拆分改造之后发现一个额外的红利Code review轻松多了。看一对FB之间的调用关系只需要检查接口参数是否匹配而不用把两个块的全部代码从头捋一遍。这相当于给程序加了一圈围墙围墙上开了几个门每个门走什么人一清二楚。2.3 状态机天然是20行的好载体我拆过很多设备控制逻辑发现一个规律设备控制类的ST代码60%以上都可以改写成状态机。而状态机本身的结构特性决定了它天然适合20行左右的粒度。经典的状态机有4到6个状态每个状态的处理逻辑3到5行加上状态转移条件、定时器触发、报警分支一个CASE语句写下来通常就是二十多行。外层套一个判断这个功能块就是一个完整、独立、可测试的状态机。相比之下用二十层IF嵌套表达同一个流程代码量会膨胀到一百行以上而且可读性差很多。这不是说所有ST都要改成状态机但当你发现一个块里充满了某条件成立且某条件不成立……这样的嵌套时就该反问自己为什么不把它拆成几个状态状态机把时间维度的复杂性显式化了IF嵌套则把复杂性隐藏在条件组合里。3. 实操拆解把一个850行ST设备控制块拆成7个小块3.1 第一步画职责地图别急着动手改接到一个800行的ST块最忌讳的就是打开编辑器直接剪切粘贴。正确的做法是先画一张职责地图把这个FB里所有输入输出变量、内部变量、定时器、计数器全部列出来然后给它们归堆。我一般用表格来做这件事变量/逻辑属于哪个功能域建议去向气缸1动作到位判断气缸顺序控制FB_CylinderSeq气缸2动作到位判断气缸顺序控制FB_CylinderSeq真空压力上限报警真空监控FB_VacuumCheck吹气延时和吹气阀吹气动作FB_BlowOff报警汇总输出报警处理FB_AlarmCollect手动/自动切换模式管理FB_ModeSwitch复位逻辑模式管理FB_ModeSwitch这张表画完之后你会吃惊地发现很多变量的归属是重叠的。比如同一个压力信号既被真空监控用到又被报警等级判断用到。这时候不要急着决定它属于谁而是先想清楚它到底表达什么。在拆分过程中归属不清的信号通常就是耦合的根源。3.2 第二步按动作保护状态切分功能域做PLC设备控制我习惯把逻辑分成三层执行动作、设备保护、生产状态。执行动作管阀和电机怎么动设备保护管压力、温度、超时这些安全边界生产状态管这个设备当前处于什么模式、什么阶段。按照这个思路850行的块可以切成这样FB_CylinderSeq5个气缸的时序动作每个气缸一个状态机FB_VacuumCheck真空压力监控与报警FB_ModeSwitch手动/自动/复位/急停状态的统一管理FB_AlarmCollect报警汇总、报警分级、报警输出的集合。每个FB的正文控制在20到40行。你可能注意到我并没有严格卡在20行以内这是因为有些功能块天生需要多一点上下文。这个后面会说别急。3.3 第三步用FB接口约束数据流动拆分之后最关键的一步是设计接口。我定了一条规则拆出来的新FB一律不允许通过全局变量跟外部交换数据。要数据就从VAR_INPUT进要结果就从VAR_OUTPUT出。这样做的直接好处是我可以在不打开其他代码的情况下单测一个FB。接口的参数数量也要克制。一个功能块的输入输出加在一起超过10个就要反思是不是粒度又粗了。输入参数超过5个内部逻辑大概率在同时处理多个业务维度。我给一个实际例子原来的850行块里有一个气缸推进逻辑接口参数我最终设计了6个FUNCTION_BLOCK FB_CylinderAdvance VAR_INPUT bStart : BOOL; (* 推进启动信号 *) bHomeSensor : BOOL; (* 原位传感器 *) bWorkSensor : BOOL; (* 工作位传感器 *) rTimeoutTime : TIME : T#2.5S; (* 超时时间 *) bEnable : BOOL; (* 使能信号 *) END_VAR VAR_OUTPUT bAdvancing : BOOL; (* 正在推进 *) bAtWorkPos : BOOL; (* 已到工作位 *) bTimeoutAlarm : BOOL; (* 超时报警 *) END_VAR VAR eStep : INT : 0; (* 状态机步号 *) tonTimeout : TON; (* 超时定时器 *) END_VAR3.4 第四步一个20行级别的ST功能块长什么样接着上面这个接口写出来的功能块正文大概是这样的CASE eStep OF 0: (* 待机 *) IF bEnable AND bStart AND bHomeSensor THEN eStep : 1; bAdvancing : TRUE; END_IF; 1: (* 推进 *) tonTimeout( IN : TRUE, PT : rTimeoutTime); IF bWorkSensor THEN bAdvancing : FALSE; bAtWorkPos : TRUE; eStep : 2; ELSIF tonTimeout.Q THEN bAdvancing : FALSE; bTimeoutAlarm : TRUE; eStep : 3; END_IF; 2: (* 到位保持 *) IF NOT bWorkSensor THEN bAtWorkPos : FALSE; eStep : 0; END_IF; 3: (* 报警等待复位 *) IF NOT bEnable THEN bTimeoutAlarm : FALSE; eStep : 0; END_IF; END_CASE;这段代码连声明带正文加起来大概30行。它没有严格卡在20行以内但它的结构非常清晰4个状态每个状态做的事情一眼能看明白超时报警和到位判断互不干扰。这就是我所说的20行一段的真实含义——不是数字硬指标而是结构上要让一段代码能被一次性读懂。这个FB最大的好处是可独立测试我在测试环境里只需要把传感器信号模拟好就能完整验证推进、到位、超时、复位四个路径完全不用管其他设备动作。3.5 第五步拆完之后的实测效果改造完那个850行的块之后我做了一个简单对比。改造前扫描周期略有波动改造后单个设备块的扫描时间从1.2毫秒降到了0.8毫秒——其实这个数字在PLC场景里不算什么关键收益真正的关键收益是报警定位时间。原来的定位时间是一两个小时改造之后变成几分钟。原因很简单报警被关在了FB_VacuumCheck或者FB_CylinderAdvance里面哪一路报警触发了直接看功能块的输出状态位同时打开该功能块看它的内部状态走到哪一步问题基本一目了然。代码评审也轻松了很多。以前评审这个850行的块三个工程师坐在一起看了半天谁也不敢说改哪里。拆完之后每个FB单独评审二十分钟搞定。4. 别把20行当教条拆过头的副作用和判断标准4.1 我见过最糟糕的20行教条有一次我在某个项目里看到一套编码规范规定所有功能块正文一律不得超过20行。结果一位同事为了满足这个指标把一段本来50行、结构也算合理的逻辑硬拆成了7个互相调用的迷你功能块每个块5到8行。结果读代码的时候你需要在7个文件之间反复跳转层层点进去才能把一个完整动作串起来。有一次为了查一个吹气阀没启动的问题我从主程序跳到FB_AFB_A里调用了FB_BFB_B里调用了FB_CFB_C里又调用了FB_D……等我找到最终控制吹气阀输出的那行代码时已经过去了40分钟。改之前用普通的长块找5分钟就能找到。这就是典型的为了指标而拆。4.2 逻辑碎片化的三个典型症状拆过头之后最容易出现的三个症状一是调用链超过三层。我从主程序往下数如果要在代码里连续跳转三个以上的FB才能找到最终的赋值语句那这个拆法就已经在制造新的阅读成本了。二是对象之间互相调用对方。FB_A需要FB_B的结果FB_B又需要FB_A的结果两个块形成环状依赖。这种情况在PLC程序里非常棘手因为功能块的调用顺序会直接影响功能块的执行结果而环状依赖在扫描周期里必然有一个块读到的是上一次扫描的旧数据。三是为了调用关系写了大段注释。如果你发现每个功能块顶部都要用二十行注释来解释我为什么要调用下一个功能块那说明这个拆分本身已经违背了让代码自解释的初衷。4.3 三条实用的粒度判断标准经过这些年的反复试错我总结出三条判断拆分粒度是否合适的标准每条标准都不依赖具体的行数。第一条能不能独立测试。把一个功能块单独拉出来不管外部环境只给定输入信号就能验证它所有输出是否符合预期。能做到粒度就合格。做不到要么是这个块对外部世界依赖太深要么是它内部塞了太多不相干的事。第二条能不能一屏之内讲明白。我标准的屏幕是1080P的编辑器窗口代码加上注释最好能在不滚动的情况下看完整个功能块。如果你需要翻页才能看懂自己在写什么这个块该拆了。这条标准比20行更有弹性因为有些人一个块写60行只要结构统一、状态清晰也能一屏看完。第三条调用链不超过三层。从主程序开始到最终动作输出中间的功能块嵌套层级控制在三层以内。超过三层我宁可把中间层的几个小块合并一下。因为PLC世界里深层次嵌套意味着跨扫描周期的数据同步问题会成倍增加。4.4 量化参考圈复杂度与接口参数数量如果还是拿不准我给两个比较量化的参考值。第一个是圈复杂度。简单计算方式一个功能块里共有多少个判断分支IF、CASE分支、AND/OR组合等加1得到的数值就是圈复杂度。我一般要求功能块正文的圈复杂度不超过10到15。超过15不管行数多少这个块已经复杂到容易埋bug了。第二个是接口参数数量。一个功能块的输入变量加输出变量之和尽量控制在8个以内最多不要超过12个。接口参数太多意味着数据在块与块之间传递得过于频繁那你要思考的是接口设计的问题而不是再拆一层的问题。这两个量化指标配合前面三条定性标准基本上就够用了。行数可以作为一个感性参考但不是硬指标。5. 在团队里让20行一段真正落地5.1 编码规范不靠口号靠模板回回有人说我们要写规范的ST程序结果规范文档写了一堆应简洁应清晰的空话工程师们看完不知道到底应该怎么做。我的经验是规范必须配上能直接抄的模板。我自己在团队里推行ST规范时给了一套模板每个设备类功能块必须包含状态机声明区、输入接口区、输出接口区、内部变量区、CASE状态区、报警处理区。每个区的作用和写法都有示例。新人拿到模板之后不需要理解什么叫内聚和耦合照着模板填逻辑写出来的代码天然就是20行一段的结构。5.2 代码审查与自动化检查规范定了还要有执行的抓手。我们团队的做法是两件事同时做。第一件是Code Review清单。清单里明确列出了几项硬检查功能块正文是否超过40行接口参数是否超过12个是否存在全局变量跨块通信是否有超过三层的调用链每项都不讲道理只讲事实。评审会上直接对照清单打勾哪一项不过打回去改。第二件是自动化检查。Twincat、Codesys这些IDE本身没有强制的代码行数检查插件但可以借助版本控制工具做最低限度的检查在提交代码前写一个小的脚本扫描ST源文件统计每个FUNCTION_BLOCK、FUNCTION的正文行数超过阈值就阻断提交。脚本很简单用正则匹配抓到FUNCTION_BLOCK和END_FUNCTION_BLOCK之间的代码数一下有效代码行数就行。5.3 老项目改造的渐进路线最后说老项目怎么改。我强烈建议不要一次性重写一个大块。老设备程序改造最安全的路线是先围栏、后平移、再优化三步走。第一步先围栏在旧的850行FB外面包一层新的FB接口把原来所有输入输出映射到新接口上让外部调用关系先稳定下来。第二步换内部实现把旧块里的逻辑按职责拆成新的小FB外部看到的接口不变内部实现逐渐替换。第三步逐步优化新逻辑跑稳定之后再回头看那些接口参数能不能精简、命名是不是清晰、注释是不是准确。我接手过的老项目里按这个路线走几乎没有一次因为重构导致现场停机的。反而是那些不分步骤、上来就大刀阔斧重写的人常常被各种隐藏的耦合坑到加班。我自己的感受是800行一段和20行一段的争论本质上是写代码时图自己方便和读代码时为别人着想的分岔口。PLC程序的特点是会被现场反复调试和修改代码的阅读次数远远多于编写次数。所以在ST的世界里多花点时间把逻辑拆成小块把接口设计清楚是所有投资里回报率最高的一笔。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询