COBOL考古(十二)
字符处理示例
示例 6 展示了 COBOL 函数 UPPER-CASE 的用法,其中由 UPPER-CASE 处理的字符串或字母变量会将任何小写字符转换为大写。
1 |
|
1 |
|
示例 6. 字符处理内部函数
使用带有引用修改的内部函数
引用修改通过指定数据项的最左边字符位置和可选长度来定义数据项,其中冒号 (:) 用于区分最左边字符位置和可选长度,如示例 7 所示。
1 |
|
示例 7. 引用修改
引用修改,LNAME(1:1),将仅返回数据项 LNAME 的第一个字符,而引用修改,LNAME(4:2),将返回从第四个字符位置开始长度为两个字符的 LNAME 的结果。如果 LNAME 的值是 SMITH,并且它是在内部函数中引用的数据项,那么第一个引用将输出 S。考虑相同的规格,第二个引用将输出 TH。
实验
这个实验包含一个包含姓氏的数据,其中姓氏全部大写。它演示了如何使用内部函数和引用修改来将姓氏字符转换为小写,但姓氏的第一个字符除外。
此实验需要两个 COBOL 程序,CBL0011 和 CBL0012,以及两个相应的 JCL 作业,CBL0011J 和 CBL0012J,用于编译和执行 COBOL 程序。这些都可以在您的 VS Code - Zowe Explorer 中找到。
使用 VS Code 和 Zowe Explorer
提交作业 CBL0011J。
观察报告输出,姓氏的第一个字符为大写,其余字符为小写。
图 1. 下面对比了与数据类型实验相比,本实验的输出差异。请注意,在先前的实验中,姓氏以大写字母列出,而如前所述,本实验的输出只有姓氏的第一个字符大写。
观察 WRITE-RECORD 段中的 PROCEDURE DIVISION 内部函数 lower-case。该内部函数与引用修改配对使用,结果是姓氏的第一个字符大写,其余字符小写。
提交 CBL0012J
观察编译错误。
先前的实验程序使用了一个日期/时间内部函数。这个实验中的日期/时间内部函数存在语法错误,需要识别和纠正。
修改 id.CBL(CBL0012),纠正编译错误。*
重新提交 CBL0012J
纠正后的 CBL0012 源代码应该能够成功编译和执行程序。成功编译将导致与 CBL0011J 相同的输出。
实验提示
参考 CBL0011 的第 120 行,查看引起编译错误的函数名的正确格式。
ABEND 处理
在前面的章节中进行实验时,您可能遇到了异常终止或称为 ABEND。有各种常见的 COBOL 错误类别会导致 ABEND,在生产中,软件错误可能会带来高昂的成本 - 无论是财务成本还是声誉成本。
本章介绍了 ABEND,并概述了 COBOL 应用程序程序员可能会遇到的常见 ABEND 类型。我们将审查程序员调试 ABEND 类型的可能原因和常见原因。我们还将审查一些避免 ABEND 的常见最佳实践,并审查程序员可能会在其应用程序中故意调用 ABEND 程序的原因。
为什么会发生 ABEND?
常见 ABEND 类型
S001 - 记录长度 / 块大小不一致
S013 - DCB 参数冲突
S0C1 - 无效指令
S0C4 - 存储保护异常
S0C7 - 数据异常
S0CB - 除零
S222/S322 - 超时 / 作业已取消
S806 - 未找到模块
B37/D37/E37 - 数据集或 PDS 索引空间超限
避免 ABEND 的最佳实践
ABEND 程序
为什么会发生 ABEND?
与您的普通工作站不同,主机使用一种称为 z/Architecture 的指令集架构。该指令集描述了在较低的机器代码级别可以执行哪些指令。
如果系统遇到不允许的指令,将发生 ABEND。这可能发生在编译、链接编辑或执行 COBOL 程序期间。
常见 ABEND 类型
以下列出了九种常见的 ABEND,供您参考。请注意,作为 COBOL 程序员,您可能会遇到更多的 ABEND 类型和情况,而且 z/OS 有时会根据 ABEND 发生在系统软件层的情况而产生不同的 ABEND 代码。
这些 ABEND 代码偶尔会伴随一个原因代码,可以用来进一步缩小错误可能原因。
- S001 - 记录长度 / 块大小不一致
- S013 - DCB 参数冲突
- S0C1 - 无效指令
- S0C4 - 存储保护异常
- S0C7 - 数据异常
- S0CB - 除零
- S222/S322 - 超时 / 作业已取消
- S806 - 未找到模块
- B37/D37/E37 - 数据集或 PDS 索引空间超限
在接下来的章节中,我们将介绍 ABEND,以及可能的原因和 ABEND 的常见原因。请注意,原因和原因并不是穷尽的。
S001 - 记录长度 / 块大小不一致
z/OS 使用数据集来管理数据,数据集是包含一个或多个记录的文件。这些数据集在创建期间具有预定的记录长度和与之关联的存储块的最大长度(块大小)。大多数情况下,不一致发生是由于编程错误。
原因代码:
- S001-0:记录长度规范之间存在冲突(程序与 JCL 与数据集标签之间)
- S001-2:损坏的存储介质或硬件错误
- S001-3:致命的 QSAM 错误
- S001-4:块规范之间存在冲突(程序与 JCL 之间)
- S001-5:尝试读取超出文件末尾
常见原因:
- S001-0:FD 语句或 JCL 中的拼写错误
- S001-2:损坏的磁盘或磁带数据集
- S001-3:z/OS 内部问题
- S001-4:忘记在 FD 语句中编写 BLOCK CONTAINS 0 RECORDS
- S001-5:逻辑错误
S013 - DCB 参数冲突
当程序期望数据定义(DD)语句具有特定的数据控制块(DCB),但 DD 具有不同的 DCB 时,将发生 S013 ABEND。这可能是块大小、记录长度或记录格式之类的内容。
要了解更多有关数据集的信息,请访问 IBM 知识中心:
https://www.ibm.com/docs/en/zos-basic-skills?topic=more-what-is-data-set
原因代码:
S013-10:虚拟数据集需要缓冲区空间;在 JCL 中指定 BLKSIZE
S013-14:DD 语句必须指定 PDS
S013-18:未找到 PDS 成员
S013-1C:在搜索 PDS 目录时发生 I/O 错误
S013-20:块大小不是记录长度的倍数
S013-34:记录长度不正确
S013-50:尝试为输入打开打印机
S013-60:未分块大小的块大小与记录长度不相等
S013-64:尝试虚拟索引或相对文件
S013-68:块大小大于 32752
S013-A4:SYSIN 或 SYSOUT 不是 QSAM 文件
S013-A8:SYSIN 或 SYSOUT 的记录格式无效
S013-D0:尝试使用 FBS 或 FS 记录格式定义 PDS
S013-E4:尝试连接超过 16 个 PDS
常见原因:
这个 ABEND 代码的大多数原因是 JCL 和 COBOL 程序之间的不一致性。
S0C1中,CPU试图执行一个无效或不受支持的指令。
原因:
- 缺少SYSOUT DD语句
- 在AFTER ADVANCING子句中的值小于0或大于99
- 索引或下标超出范围
- 针对未打开的数据集发出了I/O动词
- CALL子例程链接不与调用程序的记录定义匹配
常见原因:
- 在设置AFTER ADVANCING子句时逻辑不正确
- 表处理代码中的逻辑错误,或表条目溢出
S0C4 - 存储保护异常
当在z/OS中运行COBOL程序时,操作系统会分配一个虚拟内存块,称为地址空间。地址空间包含执行程序所必需的内存地址。
原因:
S0C4表示程序试图访问未分配的内存地址。
常见原因:
- 缺少或不正确的JCL DD语句
- 表处理代码中的逻辑错误
- 表条目溢出
- 初始化一个未打开的文件FD
S0C7 - 数据异常
正如您之前所见,COBOL程序使用PICTURE子句来处理数据,该子句确定了特定变量的数据类型。但是偶尔,您可能会遇到放置不当的数据。
原因:
S0C7表示程序期望是数字数据,但它发现了其他无效类型的数据。当您尝试将PIC 9字段中的非数字内容MOVE到PIC X字段时,就会发生这种情况。
常见原因:
- 不正确初始化或未初始化的变量
- 缺少或不正确的数据编辑
- 从01级别移动到01级别,如果发送字段比接收字段短
- 将零MOVE到组级数字字段
- 不正确的MOVE CORRESPONDING
- 从一个字段MOVE到另一个字段时,分配语句不正确
S0CB - 除零错误
就像数学一样,在Enterprise COBOL中尝试除以0的数字是未定义的操作。
原因:
CPU试图用0除以一个数字。
常见原因:
- 不正确初始化或未初始化的变量
- 缺少或不正确的数据编辑
S222/S322 - 超时/作业已取消
当您提交JCL时,可以确定要为作业分配多少时间。如果作业超过了分配的时间,它将超时。根据系统设置方式,作业可能会被操作员手动取消,也可能会被自动取消。
原因:
超时,通常是由于程序逻辑陷入无法退出的循环(无限循环)而导致的。具体而言,S322 ABEND指的是超时,而S222指的是作业被取消。
常见原因:
- 无效的逻辑
- 无效的文件末尾逻辑
- 文件末尾开关被覆盖
- 下标不够大
- 在不更改EOF开关的情况下,执行UNTIL End-Of-File
- 执行UNTIL End-Of-File而不更改EOF开关
S806 - 未找到模块
我们之前已经看到,在COBOL中可以调用子例程。为了让编译器知道我们要调用哪个子例程,我们需要在JCL上指定它们。如果没有指定它们,编译器将尝试首先检查系统库,然后失败。
原因:
调用了无法找到的子例程。
常见原因:
- 从库中删除了模块
- 模块名称拼写不正确
- 在JCL上未指定包含模块的加载库
- 当z/OS搜索库的目录时发生I/O错误
B37/D37/E37 - 数据集或PDS索引空间已耗尽
我们之前已经看到,z/OS中的数据集有一个分配的大小。当我们创建许多数据时,某一时刻,数据集将没有足够的空间来存储任何新数据。
原因代码:
- B37 - 磁盘卷空
间不足
- D37 - 超出主空间,未定义次要扩展
- E37 - 主要和次要扩展都已满
- E37-04 - 磁盘卷目录已满
常见原因:
- 没有足够的空间来分配输出文件
- 逻辑错误导致无限写入循环
避免ABEND的最佳实践
为了避免ABEND,我们可以采用一种称为防御性编程的方法。这是一种编程形式,我们在代码中进行防御性设计,以确保在未预见的情况下仍然可以运行。
通过进行防御性编程,我们可以减少错误的数量,并使程序更具可预测性,无论输入是什么。
以下是在COBOL中可以采取的一些措施:
在例程开始时INITIALIZE字段。这将确保字段在程序开始时具有正确的数据。但需要特别注意确保任何标志或累加器具有适当的INITIALIZE数据。
I/O语句检查。这可以通过使用FILE STATUS变量并在进行任何进一步的I/O操作之前检查它们来实现。此外,我们需要检查空文件和其他可能的异常情况。
数字字段检查。一般的策略是不相信我们正在进行数学运算的数字字段。假设输入可能无效。建议使用ON OVERFLOW和ON SIZE ERROR短语来捕获无效或异常数据。当需要进行四舍五入时,需要特别小心,因为在某些情况下可能会截断。
代码格式化。这将确保您的代码可维护,并且易于被任何阅读或维护代码的人理解。
一致使用作用域终止符。最好的做法是使用诸如END-IF、END-COMPUTE或END-PERFORM等作用域终止符明确终止一个作用域。
测试、检查和同行审查。可以进行适当的测试和同行审查,以捕获可能在程序中滑过的错误。此外,我们还可以确保业务逻辑是正确的。
即使系统没有发生ABEND,仍然存在可能情况,你将被期望调用一个ABEND例程。这可能发生在你的程序遇到无效的输入数据或从子例程返回错误时。
通常,这些例程将由你的雇主提供。但也可以像以下示例一样简单:
1 |
|
这种例程可以显示更多信息,允许你确定程序失败的位置和原因。