写代码那么多年,对代码,输出日志有一种近似洁癖的要求。对于不整齐、不整洁、命名混乱的代码,真心看不下去,总会想办法去整顿——或用工具,或人工。曾因这个原因耽误时间,虽然想着改,但一时也改不了多少。今年唯一例外的,应该我手上维护的那套98年开始写的 delphi 工程。
本文从小处着手,单说一些输出日志的对齐方法。
在 golang 中,常用的命令行库为 cobra,内部实现了帮助信息的对齐。以 docker 为例,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # docker --help Usage: docker [OPTIONS] COMMAND A self-sufficient runtime for containers Options: --config string Location of client config files (default "/root/.docker") -D, --debug Enable debug mode Management Commands: builder Manage builds config Manage Docker configs container Manage containers context Manage contexts Commands: attach Attach local standard input, output, and error streams to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes
在自己实现的小型命令终端程序框架时,也使用了 cobra ,但做了一些精简,比如子命令的命令,按官方方法是使用多级子命令形式,但觉得麻烦,将“子命令的命令”作为子命令的参数处理,但如此一来,有些输出信息就不整齐了。 为解决问题,参考了 cobra 源码,提炼出核心代码,最终达到预期目标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ ./cmdtool.exe test test... Available Commands: foo just a foo help info watch watch config file Usage: cmdtool.exe test [flags] Examples: example comming up... Flags: -h, --help help for test -m, --mode int set the test mode Global Flags: -c, --config string config file (config.yaml) -o, --output string specify the output file name -p, --print verbose output
其中,Available Commands:
为自定义函数中的输出,将参数作为子命令的命令。核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // rpad adds padding to the right of a string. func rpad(s string, padding int) string { truetemplate := fmt.Sprintf("%%-%ds", padding) truereturn fmt.Sprintf(template, s) } //返回字符串的 func GetHelpInfo(theCmd []conf.UserCmdFunc) (ret string) { truevar cmdMaxLen int = 0 trueret = fmt.Sprintf("Available Commands:\n"); truefor _, item := range theCmd { truetruenameLen := len(item.Name) truetrueif nameLen > cmdMaxLen { truetruetruecmdMaxLen = nameLen truetrue} true} true truefor _, item := range theCmd { truetrueret += fmt.Sprintf(" %v %v\n", rpad(item.Name, cmdMaxLen), item.ShortHelp) true} true truereturn }
rpad
为组装格式化字符串(因此会出现多个%
,-
为左对齐)函数,输入的padding
为一列数据的最大值,该值是遍历用户命令列表的名称,获取得到 的最大长度。用户命令列表如下:
1 2 3 4 5 6 7 8 var theCmd = []conf.UserCmdFunc{ conf.UserCmdFunc { Name: "foo", ShortHelp: "just a foo help info", Func: foo, }, conf.UserCmdFunc {"watch", "watch config file", testWatch,}, }
依据上述思路,在C中也容易实现类似的对齐需求。示例如下:
1 2 3 4 5 char fmt[128] = {0}; sprintf(fmt, "autoTestFee %%s%%-%ds -> %%s%%-%ds vehicleType %%s payFee %%-6d outFee %%-9.2f realFee %%-6d\n", nameLen1+2, nameLen2+2); // printf("%s\n", fmt); printf(fmt, item->en, item->enName, item->ex, item->exName, type, payFee, outFee, realFee);
代码中使用 sprintf 组装格式化字符串,该语句转化后如下:
1 "autoTestFee %s%-10s -> %s%-9s vehicleType %s payFee %-6d outFee %-9.2f realFee %-6d\n" # 数字为示例
其中 nameLen1 和 nameLen2 分别为 enName 和 exName 列表的最大值(方便对齐)。 结果如下:
1 2 autoTestFee 100(hello) -> 200(foobarrrrbbbbbbbbbbb) vehicleType 未名 payFee 2018 outFee 20.00 realFee 2003 autoTestFee 101(hello) -> 201(f) vehicleType 未名 payFee 2019 outFee 20.00 realFee 2003
这样,在搜索日志时就容易分辨了。
经测试,如果字符串含有中文且中文长度不等(或中英混合),对齐还是有问题,待后续解决。