Perl:Getopt::Long 怎样区分缺参数、零值与业务错误
命令行工具的边界从参数开始。最低耗时 0 是合法筛选条件,缺少这个选项表示不筛选耗时;--min-duration -1 则违反业务规则。三种情况不能都用变量真假判断。参数解析器负责识别选项形状,应用负责定义值的含义,最后还要把失败落实到退出码和输出流。 本篇对应原定 25,使用新版 bin/logtool。原 bin/logtool.pl 的位置参数仍用于旧四字段实验。新入口采用 Getopt::Long,支持 import、query、report 三个子命令;--version 单独给出工具版本与实际解释器版本。命令例必须使用冻结解释器或明确标注本机差异,不能把环境中的第一个 perl 自动当成 5.38.2。 先选子命令,再解析它允许的选项 Getopt::Long 不会自动知道某个选项属于哪个子命令。本工具先取出命令名,为该命令构造允许的选项集合,之后调用 GetOptionsFromArray。import 允许 input、output、rejects;query 允许 input、level、min-duration、contains;report 允许 inpu...
Perl:JSON Lines 怎样保留数字、缺失值与原始消息
日志进入交换文件后,字段仍须保持原来的含义。耗时为零是一条已完成的观测,耗时缺失表示没有这个观测;消息为空仍然是一条记录。若把三者都写成空字符串,后续查询无法恢复它们的区别。JSON Lines 给每条记录独立的 JSON 文本,但行格式本身不保证类型、字段集合、排序或错误策略,这些都需要由工具定义。 本篇补齐原定课程 28,并作为 00、10、13、25、38、39 的累计数据契约。工程有两个明确入口:bin/logtool.pl 使用旧四字段 TSV,保留原系列实验;bin/logtool 使用五字段输入与 JSONL 交换。两个文件格式分别验证,不能把旧版三条记录的通过当成新版导入已经完成。完整实现位于 lib/LogTool/Course.pm,断言位于 t/course-core.t,命令重放位于 tools/replay.pl。 字段契约先于序列化 五字段顺序为 UTC 时间、等级、服务标识、毫秒耗时、消息。字段之间只有实际 TAB;消息中的 TAB、LF、反斜杠分别用 \t、\n、\\ 表达。空耗时映射到 undef,非空耗时只接受不带符号、不带前导零、最多九位的十...
Perl 原定 E08:遗留代码迁移先守住行为
遗留 Perl 常见问题不是语法旧,而是依赖藏得太深:全局路径、全局文件句柄、二参数 open、隐式 $_ 都会让调用者难以判断输入从哪里来、输出写到哪里去。迁移的第一步不是重写成新风格,而是先写出旧行为的最小回归,再把隐式依赖改成显式参数。 累计实验 examples/perl-language/experiments/course-legacy.pl 先保留一个小的 legacy 计数器:它读取全局 $CURRENT_PATH,使用全局 FH 和二参数 open,统计一行输入。迁移版本改成 migrated_count($file),使用三参数 open my $fh, '<', $file 和词法文件句柄。TAP 断言旧版计数和新版计数都是 1,同时断言三项迁移事实:全局路径由参数替代,二参数 open 由三参数 open 替代,全局文件句柄由词法文件句柄替代。 12345678sub migrated_count { my ($file) = @_; open my $fh, '<', $file or die &qu...
Perl 原定 E07:SV、AV、HV 与 op tree 只能作诊断证据
Perl 代码里的标量、数组引用和哈希引用,在解释器内部分别会落到 SV、AV、HV 等结构;表达式会编译成 op tree。B 和 Devel::Peek 能把这些诊断信息打印出来,但它们不是跨版本 API。课程可以用它们解释“为什么引用只是指向内部值的句柄”,不能把某一行 dump 当成业务契约。 累计实验 examples/perl-language/experiments/course-internals.pl 用 B::svref_2object 查看 INFO、[1,2]、{INFO=>2},得到 PV、AV、HV 一类名称;再用子进程执行 Devel::Peek::Dump 保存真实输出,检查其中含 PV 与 REFCNT。另一个子进程运行 B::Concise -exec 解释 my ($x, $y) = (1, 2); print $x + $y,证据文件保存完整 op tree。 123454 <$> const(IV 1) s5 <$> const(IV 2) s6 <0> padrange[$x:1,2; $...
Perl 原定 E04:Moo 角色不是给对象套一层名字
Moo 的价值不只是 has。属性负责对象状态,角色负责可组合的行为契约;如果写一个空 role 再 with,只是换了语法,没有证明组合边界。课程实验把传统 bless 聚合器和 Moo 聚合器放在同一个 new/add/rows 协议下比较,避免把对象系统演示变成另一份不相干代码。 LogTool::Course::Role::LevelRows 是真实角色:level_from_record 从 hash 或拥有 level 方法的对象取级别,并拒绝非 ERROR|INFO|WARN;sorted_level_rows 把计数 hash 转成按 level 排序的 {level,count} 数组引用。LogTool::Course::MooAggregator 用 with 'LogTool::Course::Role::LevelRows' 组合角色,counts 属性保存状态,add 复用角色校验,rows 复用角色排序。TAP 断言该类 does 这个角色,并与传统 LogTool::Course::Aggregator 的输出逐项相同。 123456package...
Perl 原定 E03:SQLite 事务和绑定参数要一起验证
DBI 把连接、预处理、绑定参数和事务边界分成不同层。DBI->connect('dbi:SQLite:dbname=:memory:', ...) 得到数据库句柄;begin_work 关闭自动提交直到 commit 或 rollback;? 占位符由驱动绑定值,不把字符串拼进 SQL。只证明 SELECT 1 能跑,不足以覆盖课程里的工程用法。 累计实验 examples/perl-language/experiments/course-sqlite.pl 创建内存表 events(level, message)。第一段事务插入 INFO/kept 后 commit,第二段插入 ERROR/rollback 后 rollback;最后 SELECT COUNT(*) 只剩一行,按绑定参数查询 level = ? 返回 kept。t/course-dependencies.t 同样断言回滚行消失、参数化查询返回预期计数。 123456my $payload = q{x' OR 1=1 --};$dbh->do('INSERT ...
Perl 原定 E02:Text::CSV_XS 要实测多行字段与转义边界
CSV 的难点不在逗号,而在记录边界和引号。Text::CSV_XS 的 binary => 1 允许字段中出现换行等字节;双引号字段内部的 "" 表示一个真实的 " 字符;未闭合引号必须被拒绝。课程若只用 join ',' 或只测 a,b,无法证明报表能处理接收方真正会遇到的数据。 累计实验 examples/perl-language/experiments/course-csv-xs.pl 固定加载 Text::CSV_XS 1.64,解析 id,"two\nlines","a""b"。测试断言字段结果是 id、带真实换行的 two\nlines、以及 a"b,同时把 id,"unterminated 判为 rejected。t/course-dependencies.t 又在同一数据上运行 Text::CSV_PP,要求字段结果一致,但后端身份不同。XS 和 PP 的行为可以比较,不能互相代验。 1234use Text::CSV_XS ();my $csv...
Perl 原定 E01:单行脚本要能还原成完整脚本
perl -n、perl -p、perl -a 常被写成命令行技巧,但课程验收不能只看屏幕上几行输出。Perl 5.38.2 的 perlrun 说明里,-n 给程序包一层 while (<>) { ... },不会自动打印;-p 同样包循环,但每轮末尾打印当前 $_;-a 在隐式循环里把 split 结果放进 @F,并且文档明说 -a 会隐含设置 -n,-F 会隐含设置 -a 和 -n。这些开关改变的是解释器包裹源码的方式,不是另一套迷你语言。 累计实验 examples/perl-language/experiments/course-oneliners.pl 先写入三行 TSV,再运行三条单行脚本:-ane 'print if $F[0] eq "INFO"' 过滤 INFO,-pe 's/^WARN/ERROR/' 生成替换后的流,-F'\t' -lane 'print "$F[0]:$F[1]"' 输出字段对。实验还把第一条单行脚本还原成完整脚本:显式 while (<>)、显式 split、条件 prin...
Kotlin E01:Compose 离开页面后 Flow 还会收集吗
31 已经验证冷流每次收集会重新执行上游,32 则区分状态与事件。把进度显示搬进 Android 界面后,还多出一个问题:用户按 Home,界面不再可见,收集者是否继续工作?如果旋转屏幕,按钮计数和进度又会怎样?这两个问题不能只靠 APK 编译结果回答。 一个界面里其实有两种状态 隔离工程 examples/kotlin-language/android-compose/ 的 TaskActivity 只做一个可观察的练习。按钮计数由界面本地维护;冷 flow 每秒发出一个递增的 tick,collectAsStateWithLifecycle 把它转换为 Compose 可读取的值: 12345678910111213141516var clicks by rememberSaveable { mutableIntStateOf(0) }val progress = remember { flow { var tick = 0 try { while (true) ...
如何画和理解轨道图:以 Elasticsearch Query DSL 为例
《查询与检索》里有一张像铁路线路的图:从 start 出发,经过分叉和回路(loop),最后抵达 end。它不是 Elasticsearch 的执行流程图,也不是网络拓扑图,而是一张轨道语法图(railroad diagram)。 轨道图只回答一个问题:一段结构化输入有哪些合法的排列方式? 以 Query DSL 为例,图中可以同时表达请求体的顶层参数、query 的值以及 bool 的嵌套方式。读图就是沿着轨道走;画图就是把语法规则翻译成路径。 这里的“节点是语法元素”需要再分细一点。query、sort、{ 和 , 是终结符,最终会出现在输入中;QueryClause、SortValue 和 Number 是非终结符,只表示还要继续展开的语法类别。标准铁路图用两侧边界标记起点与终点。借用 Mermaid flowchart 画关系图时,可以增加“引用”之类的连线标签,但它们不属于实际输入。 EBNF 是 Extended Backus–Naur Form 的缩写,中文通常译为“扩展巴科斯范式”。这里的 form 是“形式、记法”的意思,不是另外一个隐藏缩写;Back...















