ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

嵌入式调试自动化:基于Lua脚本的J-Link RTT命令行工具rttsh设计与实现

嵌入式调试自动化:基于Lua脚本的J-Link RTT命令行工具rttsh设计与实现 嵌入式调试这件事最让人抓狂的往往不是代码写错了而是你根本不知道板子里面现在是什么状态。J-Link RTT 算是把这个痛点解决了一大半实时打印、不占串口、速度还快用过就回不去了。但官方那个 RTT Viewer 是图形界面的手动点来点去一旦遇到跑一百组参数、每组都要抓日志、跑完还要导出文件这种批量活人肉操作就完全顶不住了。我这次干脆写了个命令行工具 rttsh把 RTT 的读写、脚本化控制、数据落盘、批量验证全串起来底层用 Lua 做脚本引擎让整个调试流程可以像写测试用例一样被描述和执行。这篇就把这个工具的设计思路、核心实现、踩过的坑和实际用法完整讲一遍适合做嵌入式固件、驱动、量产验证以及想把调试流程自动化的朋友参考。1. 为什么图形化 RTT 工具在批量场景下会失效1.1 RTT Viewer 的舒适区与天花板RTTReal Time Transfer本质上是 SEGGER 在 J-Link 调试器上实现的一套内存读写协议。目标芯片里放一块环形缓冲区上行和下行各一块调试器通过后台内存访问background memory access直接读写这块内存不需要 CPU 停下来也不需要占用 UART。所以它的实时性极好打印几乎不影响目标运行这也是它在嵌入式圈子里口碑这么好的原因。RTT Viewer 作为官方 GUI日常看日志、发几条命令确实够用。但它的设计前提是人在旁边盯着屏幕。一旦你的工作模式变成下面这几种它就开始难受了需要连续跑几十上百轮测试每轮都要记录完整日志需要在特定时刻向目标发送命令并根据返回内容决定下一步动作需要把日志按规则切分、过滤、导出成结构化文件需要把整套流程塞进持续集成流水线无人值守执行。这些场景的共同点是流程需要被描述、被重复、被机器执行。GUI 的交互模型天然不适合描述流程你没法把点一下 Connect、再点一下 Start、等三秒、复制日志这种动作可靠地固化下来。1.2 命令行 脚本才是自动化的正确抽象命令行工具的核心价值在于它是可组合的、可被其他程序调用的、可被脚本描述的。一个rttsh命令加上一段 Lua 脚本就能把连接、读日志、发命令、判断、导出整条链路表达清楚而且这段脚本可以进版本库、可以 review、可以在流水线上跑。我选 Lua 而不是 Python 或 JavaScript 作为脚本引擎理由很实际体积极小Lua 解释器编译出来几百 KB嵌进 C/C 工具里毫无压力和 C 的互操作极其干净通过 Lua C API 暴露几个函数就能用语法轻写调试脚本不需要引入一堆依赖和虚拟环境启动快毫秒级适合被频繁调用的场景。提示脚本引擎的选择不要盲目跟风。调试工具往往要分发到各种机器上依赖越少、启动越快落地阻力越小。Lua 在这两点上几乎是最优解。1.3 rttsh 到底解决了什么问题一句话概括把 J-Link RTT 从一个人看的窗口变成一个程序能调用的接口。具体来说它提供了这些能力能力说明对应场景命令行直读一条命令把 RTT 日志打到标准输出快速查看、管道过滤脚本化控制Lua 脚本里读写 RTT、控制流程批量验证、条件触发数据落盘日志按规则写入文件数据导出、归档批量执行一次运行跑多组用例回归测试、参数扫描可集成退出码、标准输入输出规范持续集成流水线这五件事叠起来才构成了可自动化的完整闭环。缺任何一环流程都会在某个地方断掉又得回到人肉操作。2. rttsh 的整体架构与 J-Link 底层交互2.1 分层设计从 J-Link 驱动到 Lua 脚本整个工具我分成四层每层职责清晰方便单独替换和测试驱动层调用 SEGGER 提供的 J-Link SDK也就是 JLinkARM 动态库里的 RTT 相关接口负责真正的内存读写。会话层管理连接生命周期包括打开 J-Link、配置目标、启动 RTT、维护上下行缓冲区状态。脚本层嵌入 Lua 解释器把会话层的操作包装成 Lua 可调用的函数。命令行层解析参数、加载脚本、处理标准输入输出和退出码。这样分层的好处是如果哪天要换成别的调试探针只需要替换驱动层上面的脚本和命令逻辑完全不用动。这也是我在做工具时一贯坚持的原则把易变的部分隔离在最小的边界内。2.2 RTT 控制块的定位逻辑RTT 能工作的前提是找到目标内存里的 RTT 控制块RTT Control Block。这个控制块是目标固件里由 RTT 库分配的一块结构体里面记录了上下行缓冲区的地址、大小、读写指针等信息。调试器必须先找到它才能开始读写。定位方式主要有两种按地址搜索在指定内存范围内扫描特征字符串SEGGER RTT找到控制块起始位置。这是最通用的方式但扫描范围大了会慢。按符号定位如果固件的 ELF 文件里有_SEGGER_RTT符号直接读符号地址一步到位最快最准。我在 rttsh 里两种都支持默认优先用符号定位找不到再回退到地址扫描。实测下来符号定位几乎瞬间完成而全内存扫描在几百 KB 范围内可能要几百毫秒甚至更久。注意地址扫描的范围一定要给准。范围给小了找不到控制块给大了拖慢连接速度还可能误命中其他内存里的巧合字符串。建议从固件的 map 文件里确认 RTT 缓冲区大致落在哪个段。2.3 上下行缓冲区的读写模型RTT 的缓冲区是环形结构理解它的读写指针语义是正确实现的关键。上行缓冲区目标到主机由目标写、主机读下行缓冲区主机到目标由主机写、目标读。每块缓冲区都有WrOff写偏移和RdOff读偏移两个指针。主机读上行数据的逻辑是读当前WrOff和RdOff如果两者相等说明没有新数据等待否则从RdOff读到WrOff处理环形回绕更新RdOff写回目标内存。这里有个容易踩的坑读指针的更新必须写回目标内存否则目标端会以为缓冲区满了停止写入。我一开始图省事只在本地维护读指针结果目标打印几行之后就卡住了排查了半天才反应过来是没回写。下行方向同理主机写数据后要更新WrOff目标才能读到。而且下行缓冲区通常比上行小很多默认可能只有几十到几百字节写长命令时要分片不能一次性怼进去。2.4 连接参数里那些不起眼但致命的选项J-Link 连接目标时有一堆参数其中几个对 RTT 稳定性影响特别大接口类型SWD 还是 JTAG。现在绝大多数 Cortex-M 都用 SWD线少速度快。接口速度单位 kHz。速度太低会导致 RTT 读取跟不上目标打印速度缓冲区溢出丢数据速度太高在长线或干扰环境下又不稳。我一般从 4000 kHz 起步不稳再降。目标电压如果目标板是独立供电要确认 J-Link 的参考电压设置正确否则电平识别会出问题。复位方式连接时是否复位、用哪种复位会影响 RTT 控制块是否已经初始化。如果连接太快固件还没跑到 RTT 初始化就会找不到控制块。这几个参数我在 rttsh 里都做成了可配置项并且给了合理的默认值。经验是默认值要保守让工具开箱能用但每个参数都要能覆盖因为现场情况千奇百怪。3. Lua 脚本引擎的嵌入与 API 设计3.1 为什么脚本 API 要少而正交设计脚本 API 时最大的诱惑是把所有能想到的功能都暴露出去结果就是 API 面越来越大文档越来越厚用户越来越懵。我刻意克制只暴露了一组最小但正交的原语rtt.connect(options)建立连接返回会话对象rtt.read(session, timeout)读一批上行数据rtt.write(session, data)写下行数据rtt.close(session)关闭连接log.write(path, data)写文件log.append(path, data)追加文件。就这六个。剩下的所有复杂逻辑比如读到某个关键字就发命令按行切分日志循环跑 N 次全部用 Lua 自己的语法去组合。这样 API 稳定用户也只需要学很少的东西。提示脚本 API 的设计哲学应该是提供积木而不是提供房子。积木少而通用用户能搭出你没想到的房子房子给多了反而限制了想象力。3.2 会话对象与生命周期管理会话对象是连接的核心句柄。在 Lua 里我用一张表table来表示里面存了底层 C 指针的引用用 lightuserdata 包装以及一些状态字段比如是否已连接、缓冲区配置等。生命周期管理有两个关键点必须显式关闭。J-Link 的连接是独占资源脚本异常退出时如果不关闭下次连接可能失败。我在 C 层做了保护脚本正常结束或报错时都会尝试关闭会话。防止悬空引用。如果脚本里把会话对象存到全局变量然后手动 close 了再用这个对象就会出问题。我在每个操作函数入口都检查会话状态已关闭的直接报错而不是让底层崩掉。这两点看起来是细节但在实际使用中脚本报错后连接没释放导致下一次跑就连不上是最常见的抱怨之一。把生命周期管好能省掉大量莫名其妙的故障。3.3 阻塞读与超时控制的取舍rtt.read是阻塞还是非阻塞这个设计我纠结了很久。最终方案是带超时的阻塞读。传入 timeout 参数单位毫秒在超时时间内只要有数据就返回超时后返回空不报错。为什么不做纯非阻塞因为纯非阻塞会让脚本陷入忙等循环CPU 空转代码也难看。为什么不做纯阻塞因为纯阻塞在等一个可能永远不来的信号时会卡死脚本没法做超时处理。带超时的阻塞读是两者的平衡点。脚本里可以这样写local s rtt.connect({ device STM32F407VG, speed 4000 }) while true do local data rtt.read(s, 500) if data and #data 0 then io.write(data) if data:find(TEST_DONE) then break end end end rtt.close(s)这段脚本会持续读日志直到出现TEST_DONE才退出。超时 500 毫秒意味着即使没有数据循环也会定期醒来检查退出条件不会永久卡住。3.4 把 C 层错误翻译成 Lua 能懂的话底层 J-Link SDK 返回的错误码往往是一串数字直接抛给用户等于没说。我在 C 层做了一层错误翻译把常见错误码映射成人类可读的消息再通过 Lua 的 error 机制抛出。比如底层错误翻译后的提示连接失败无法连接目标请检查供电、接线和接口速度找不到 RTT 控制块未找到 RTT 控制块请确认固件已初始化 RTT 或检查搜索范围读取超时读取超时目标可能未运行或缓冲区为空设备被占用J-Link 已被其他程序占用请关闭其他调试工具这层翻译的价值在于用户看到错误时能立刻知道往哪个方向排查而不是去翻 SDK 手册查错误码。好的错误信息本身就是文档。4. 从零跑通第一个脚本完整实操链路4.1 环境准备中最容易被忽略的三件事在写第一行脚本之前有三件事必须先确认否则后面全是玄学问题J-Link 驱动版本与固件版本匹配。驱动太老可能不支持新芯片太新有时又和旧固件有兼容问题。建议用官方最新的稳定版驱动并确认 J-Link 自身固件也更新到配套版本。目标固件里确实初始化了 RTT。RTT 不是调试器单方面能用的目标代码里必须调用 RTT 初始化并周期性刷新缓冲区。如果固件里根本没集成 RTT 库工具再强也读不到东西。调试接口没有被其他程序占用。IDE、烧录工具、其他调试软件如果还开着会独占 J-Link。跑脚本前先关掉它们。这三件事听起来是废话但我见过太多人卡在工具报错上最后发现是固件压根没初始化 RTT或者 IDE 还占着调试器。4.2 连接参数怎么填才不翻车连接参数我建议按这个顺序确定先确定芯片型号。型号决定了内存布局和默认的 RTT 搜索范围。填错型号可能连得上但找不到控制块。再确定接口和速度。SWD 4000 kHz 是通用起点。如果目标主频很低或者线很长降到 1000 kHz 试试。最后确定 RTT 控制块定位方式。有 ELF 就用符号没有就手动指定搜索范围。一个典型的连接配置长这样local s rtt.connect({ device STM32F407VG, interface SWD, speed 4000, rtt_mode symbol, -- 或 scan elf build/firmware.elf, scan_range { start 0x20000000, size 0x20000 } })rtt_mode为symbol时用 ELF 里的符号定位为scan时在scan_range指定的范围内扫描。两个都填的话优先用符号。4.3 一个能直接抄的日志采集脚本下面这个脚本是我实际项目里用的日志采集模板功能是连接目标、持续采集日志、按行写入文件、遇到结束标记或超时后退出。-- collect.lua local OUT logs/run_ .. os.date(%Y%m%d_%H%M%S) .. .log local MAX_IDLE 10000 -- 连续 10 秒无数据则退出 local idle 0 local s rtt.connect({ device STM32F407VG, interface SWD, speed 4000, rtt_mode symbol, elf build/firmware.elf }) local f assert(io.open(OUT, w)) local buf while true do local data rtt.read(s, 200) if data and #data 0 then idle 0 buf buf .. data -- 按行切分完整行立即落盘 while true do local nl buf:find(\n) if not nl then break end local line buf:sub(1, nl) f:write(line) buf buf:sub(nl 1) if line:find(TEST_DONE) then f:close() rtt.close(s) os.exit(0) end end else idle idle 200 if idle MAX_IDLE then -- 把残留的不完整行也写出去 if #buf 0 then f:write(buf) end f:close() rtt.close(s) os.exit(0) end end end这个脚本有几个设计点值得说按行切分而不是整块写。RTT 读回来的数据不保证按行对齐可能半行半行地来。先攒到buf里凑齐一行再写日志文件才整齐。空闲超时退出。目标可能跑完就停了不会主动发结束标记。连续一段时间没数据就认为结束避免脚本永久挂着。退出前 flush 残留。最后可能有一行没换行符直接丢掉会丢数据所以退出前把buf里剩的也写出去。4.4 运行与验证怎么确认工具真的在工作跑脚本之前先用最简单的命令行模式验证连接是否正常rttsh --device STM32F407VG --speed 4000 --elf build/firmware.elf --read这条命令会连接目标并把 RTT 日志直接打到终端。如果能看到日志说明连接、控制块定位、读取链路全通了再去跑复杂脚本就稳了。如果这一步没输出按这个顺序排查目标是否在运行RTT 是否已初始化控制块定位方式是否正确换scan模式试试接口速度是否合适降速重试J-Link 是否被占用。提示永远先用最简单的命令验证底层链路再去跑复杂脚本。把问题隔离在最小范围内排查效率会高一个数量级。5. 批量脚本验证与数据导出的实战套路5.1 参数扫描一次跑完一百组配置批量验证最典型的场景是参数扫描同一份固件跑不同的参数组合看哪组表现最好。用 rttsh 可以这样组织local cases { { freq 100, gain 1 }, { freq 200, gain 1 }, { freq 200, gain 2 }, -- ... 更多组合 } for i, c in ipairs(cases) do local s rtt.connect({ device STM32F407VG, speed 4000, rtt_mode symbol, elf build/firmware.elf }) -- 下发参数 rtt.write(s, string.format(SET freq%d gain%d\n, c.freq, c.gain)) rtt.write(s, RUN\n) local out logs/case_ .. i .. .log local f assert(io.open(out, w)) local idle 0 while idle 5000 do local data rtt.read(s, 200) if data and #data 0 then idle 0 f:write(data) else idle idle 200 end end f:close() rtt.close(s) end每组用例独立连接、独立日志文件互不干扰。跑完之后所有日志按编号排列方便后续分析。这里有个关键决策每组用例之间要不要重新连接。重新连接更干净能保证目标状态复位但连接本身有开销一百组下来可能多花不少时间。我的做法是默认重连如果确认目标支持软复位且状态可控再改成复用连接。5.2 条件触发根据日志内容决定下一步有些测试不是固定流程而是要根据目标输出动态决策。比如等到目标打印 READY 再发命令看到 ERROR 就立刻抓现场并停止。Lua 的条件判断天然适合这种逻辑local s rtt.connect({ device STM32F407VG, speed 4000, rtt_mode symbol, elf build/firmware.elf }) local buf local ready false while not ready do local data rtt.read(s, 500) if data then buf buf .. data if buf:find(READY) then ready true elseif buf:find(ERROR) then log.append(logs/errors.log, buf) rtt.close(s) os.exit(1) end end end rtt.write(s, START\n) -- 后续采集...这种读-判断-动作的循环是脚本化调试的精髓。GUI 工具做不到这一点因为它的交互是给人看的不是给逻辑用的。5.3 数据导出从原始日志到结构化文件RTT 日志往往是半结构化的文本直接归档价值有限。我通常会在脚本里做一层解析把关键数据提取成 CSV 或 JSON方便后续用表格工具或分析脚本处理。假设目标打印的格式是DATA,timestamp,value可以这样解析local rows {} local buf while true do local data rtt.read(s, 500) if not data or #data 0 then break end buf buf .. data while true do local nl buf:find(\n) if not nl then break end local line buf:sub(1, nl - 1) buf buf:sub(nl 1) local ts, val line:match(^DATA,(%d),([%d%.%-])$) if ts then rows[#rows 1] { tonumber(ts), tonumber(val) } end end end local csv assert(io.open(out/data.csv, w)) csv:write(timestamp,value\n) for _, r in ipairs(rows) do csv:write(string.format(%d,%.6f\n, r[1], r[2])) end csv:close()导出成 CSV 之后无论是丢进表格软件画图还是用脚本做统计分析都比翻原始日志高效得多。调试的终点不是看到日志而是从日志里得到结论这一步自动化能省大量时间。5.4 批量场景下的稳定性经验批量跑最怕的是中途某一组失败导致整个流程中断。我的处理原则是单组失败不影响整体。用pcall包住每组逻辑失败就记录并继续下一组。每组都有独立日志。出问题时能精确定位是哪一组。最后汇总结果。跑完输出一个总表哪些成功哪些失败一目了然。local results {} for i, c in ipairs(cases) do local ok, err pcall(run_case, i, c) results[i] ok and PASS or (FAIL: .. tostring(err)) end for i, r in ipairs(results) do print(string.format(case %d: %s, i, r)) end这套模式跑下来一百组用例里即使有几组因为偶发问题失败也不会让整个验证白跑反而能通过失败列表快速定位问题。6. 踩坑实录那些让我熬夜的 RTT 问题6.1 缓冲区溢出导致日志静默丢失最常见的坑日志看着看着就少了或者干脆不更新了。原因通常是上行缓冲区溢出。RTT 的缓冲区大小是固定的如果目标打印速度超过主机读取速度写指针追上读指针新数据就会覆盖旧数据或者被丢弃。排查思路先确认缓冲区大小。默认可能只有 1KB 左右高频打印很容易撑爆。提高读取频率缩短rtt.read的超时让循环转得更快。提高 J-Link 接口速度加快内存读取。实在不行在固件里把 RTT 上行缓冲区调大。我遇到过一次目标每毫秒打印一行缓冲区 1KB主机读取间隔 200ms结果每次只能拿到最后几行。把超时降到 20ms 之后问题消失。读取频率必须匹配打印速率这是硬约束。6.2 控制块找不到的几种真实原因未找到 RTT 控制块这个错误背后可能有好几种原因不能一概而论现象可能原因处理方式刚连接就找不到固件还没跑到 RTT 初始化连接后延时再找或先复位再等一直找不到固件没集成 RTT 库检查固件是否调用了 RTT 初始化换固件后找不到控制块地址变了更新 ELF 或调整扫描范围偶尔找不到扫描范围不对或内存未初始化用符号定位或扩大扫描范围我印象最深的一次是固件里 RTT 初始化放在了一个条件分支里某个配置下根本不执行结果工具一直报找不到控制块。查了半天固件代码才发现。工具报错时先怀疑目标状态再怀疑工具本身。6.3 读指针不回写引发的假死前面提过读指针必须写回目标内存。我一开始的实现只在本地维护读指针结果目标端看到读指针不动以为缓冲区满就不再写入了。表现就是日志打印几行之后彻底停住但目标其实还在正常运行。这个问题的隐蔽之处在于它看起来像目标卡死了实际是通信协议没走完。修复方法就是在每次读取后把更新后的读指针写回控制块。RTT 是一个双向协议任何一方偷懒都会导致另一方停摆。6.4 多线程/多进程抢占 J-Link 的冲突J-Link 是独占设备同一时刻只能被一个进程使用。如果脚本在跑同时 IDE 也想连接就会冲突。表现是其中一方报设备被占用。处理原则跑脚本前确保没有其他调试工具在连接如果必须在多个脚本间共享用文件锁或串行化调度脚本异常退出时确保释放连接避免留下僵尸占用。我在持续集成环境里就吃过这个亏上一个任务异常退出没释放 J-Link下一个任务直接连不上。后来在流水线里加了清理步骤每次跑之前先确保设备空闲。6.5 脚本异常退出后的资源清理Lua 脚本如果中途报错默认会直接退出连接可能没关。我在 C 层做了兜底注册退出处理无论脚本怎么结束都尝试关闭会话。同时在 Lua 层提供了rtt.close让脚本主动释放。双保险的意义在于正常路径靠脚本主动关闭异常路径靠 C 层兜底。资源清理这种事永远不要只依赖一条路径。7. 把 rttsh 接进持续集成流水线7.1 退出码约定让流水线能判断成败持续集成靠退出码判断任务成败。我给 rttsh 定了清晰的约定0脚本正常执行完毕所有断言通过1脚本执行出错或断言失败2连接或环境问题比如找不到设备。这样流水线就能区分代码有问题和环境有问题分别触发不同的处理。比如环境问题可以自动重试代码问题则直接失败。7.2 无人值守场景下的超时与重试流水线里没人盯着脚本必须自己处理异常。我的做法是每个脚本设置总超时超过就强制退出连接失败自动重试若干次间隔递增每次运行生成独立日志目录方便事后追溯。local function connect_with_retry(opts, retries) for i 1, retries do local ok, s pcall(rtt.connect, opts) if ok then return s end os.execute(sleep .. i) -- 递增等待 end error(连接失败已重试 .. retries .. 次) end重试机制在硬件调试里特别重要因为偶发的连接失败太常见了一次失败就判定整个任务失败太浪费。7.3 日志归档与结果汇总流水线跑完日志和结果要归档。我通常这样组织目录runs/ 20240101_120000/ case_1.log case_2.log ... summary.txtsummary.txt里记录每组用例的通过情况和关键指标。这样即使过了很久回头看某次运行的结果也很清晰。归档结构要在设计阶段就定好事后补很痛苦。7.4 和版本管理配合的实践脚本本身要进版本库和固件代码一起管理。这样每次固件改动对应的验证脚本也能同步更新历史可追溯。我习惯把脚本放在固件仓库的tools/rtt/目录下和构建产物分开。另外脚本里尽量不写死路径和参数用环境变量或命令行参数传入。这样同一份脚本能在不同机器、不同流水线上复用。8. 一些让工具更好用的细节设计8.1 命令行参数与脚本参数的分离rttsh 支持两种使用方式纯命令行模式和脚本模式。命令行模式适合快速查看脚本模式适合复杂流程。两者的参数要分开设计避免混淆。命令行模式的参数直接对应连接配置和读取行为脚本模式则把连接配置通过参数传入脚本内部用arg表读取。这样脚本可以复用连接配置可以灵活变化。8.2 输出格式的可配置日志输出格式我做了几种模式原始模式原样输出适合管道处理带时间戳模式每行加主机时间戳适合分析时序带颜色模式按关键字高亮适合人看。默认用原始模式因为工具的输出经常要被其他程序消费保持干净最重要。需要人看的时候再开颜色。8.3 性能上的小优化几个实测有效的优化批量读取一次读尽量多的数据减少内存访问次数减少字符串拼接Lua 里频繁拼接大字符串开销不小用 table 缓存再 concat避免不必要的写回读指针只在真正读了数据后才更新空读不写。这些优化单看都不大但在高频读取场景下累积起来效果明显。8.4 可扩展性留给未来的接口工具设计时我留了几个扩展点驱动层抽象成接口方便支持其他调试探针脚本 API 预留了自定义命令注册机制输出层支持插件式的格式化器。这些扩展点现在可能用不上但一旦需求变化不用大改就能接上。做工具要有留门的意识但不要过度设计。9. 关于这套工具的一些个人体会写这个工具的过程中我最大的感受是调试自动化的瓶颈往往不在技术而在对流程的理解。一开始我只想着把 RTT 读出来后来才发现真正难的是怎么把读出来的东西变成可判断、可归档、可复现的结果。工具只是载体流程设计才是核心。另一个体会是关于脚本引擎的选择。Lua 确实轻但它的生态不如 Python 丰富。如果你的调试流程需要复杂的数据分析、机器学习之类的可能 Python 更合适。但如果只是流程控制、字符串处理、文件读写Lua 完全够用而且部署起来省心得多。选型要看实际需求不要被生态大这种笼统的理由带偏。还有一点工具的错误信息一定要认真写。我见过太多工具报错就一句操作失败用户完全不知道下一步该干嘛。把错误信息写清楚等于帮用户省下了大量查文档和试错的时间这是工具作者能给用户的最直接的善意。最后如果你也在做类似的调试自动化我的建议是先从最小可用版本开始能连接、能读日志、能写文件这三件事跑通就已经能覆盖大部分日常需求了。剩下的批量、条件、归档都是在使用中逐步长出来的。不要一上来就设计一个大而全的框架那样往往做不完或者做完了发现根本不好用。
返回列表