9.1第47章
44.1. 格式
代码格式使用每个制表符(tab)4列的空白, 目前是保留制表符状态(也就是说制表符不被展开为空白)。 每个逻辑缩进层次都是更多的一个 tab。布局规则(花括弧定位等)遵循 BSD 传统。
虽然提交的补丁并不需要我那全遵循这些格式化规则,我们最好还是这么做。 你的代码将会被 pgindent 处理,虽然这样做不会让它看起来比其它的格式化习惯更好看。
对于使用 Emacs 的人,把下面行(或者类似的东西)增加到你的 ~/.emacs 初始化文件里面去:
- check for files with a path containing "postgres" or "pgsql"
(setq auto-mode-alist (cons '("\\(postgres\\|pgsql\\).*\\.[ch]\\'" . pgsql-c-mode) auto-mode-alist)) (setq auto-mode-alist (cons '("\\(postgres\\|pgsql\\).*\\.cc\\'" . pgsql-c-mode) auto-mode-alist))
(defun pgsql-c-mode ()
;; sets up formatting for PostgreSQL C code (interactive) (c-mode) (setq-default tab-width 4) (c-set-style "bsd") ; set c-basic-offset to 4, plus other stuff (c-set-offset 'case-label '+) ; tweak case indent to match PG custom (setq indent-tabs-mode t)) ; make sure we keep tabs when indenting
对于 vi,你的 ~/.vimrc 或者相当的文件应该包含下面的东西:
set tabstop=4
或者在 vi 里面键入
- set ts=4
文本浏览工具 more 和 less 可以用下面命令调用
more -x4 less -x4
这样就可以让他们正确显示 tab 键。
44.2. 报告服务器里的错误
在服务器代码里生成的错误,警告以及日志信息应该用 ereport,或者它的老前辈 elog 创建。 这个函数的使用已经复杂得足够我们做些解释了。
每条消息都有两个必须的要素:一个严重级别(范围从 DEBUG 到 PANIC)和一个主要消息文本。 除此之外还有可选的元素,最常见的就是一个遵循 SQL 标准的 SQLSTATE 习惯的错误标识码。 ereport 本身只是一个壳函数,它的存在主要是为了便于让消息生成看起来像 C 代码里的函数调用。 ereport 直接接受的唯一参数是严重级别。 主消息文本和任何附加消息元素都是通过在 ereport 调用里调用辅助函数,比如 errmsg,生成的。
典型的调用 ereport 的方式看起来可能像下面这样:
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero")));
这样就声明了严重级别 ERROR (一个错误)。 调用 errcode 则使用一个定义在 src/include/utils/errcodes.h 里面的宏声明 SQLSTATE 错误代码。 errmsg 调用提供主要的消息文本。 请注意额外的圆括弧包围在辅助函数调用周围 — 这么做虽然烦人,但是语法上是必须的。
然后是一个更复杂的例子:
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION), errmsg("function %s is not unique", func_signature_string(funcname, nargs, actual_arg_types)), errhint("Unable to choose a best candidate function. " "You may need to add explicit typecasts.")));
这个例子演示了使用格式化代码把运行时数值嵌入一个消息文本的用法。 同样,还提供了一个可选的"暗示"信息。
ereport 可用的附属过程有:
*
errcode(sqlerrcode) 为该条件声明 SQLSTATE 错误标识符代码。 如果没有调用这个过程,并且错误严重级别是 ERROR 或更高,那么错误标识符缺省是 ERRCODE_INTERNAL_ERROR, 如果错误严重级别是 WARNING,则为 ERRCODE_WARNING, 否则(用于 NOTICE 或者更低级别)为 ERRCODE_SUCCESSFUL_COMPLETION。 虽然这些缺省都很方便,但是最好还是在省略 errcode() 调用之前三思。 *
errmsg(const char *msg, ...) 声明主错误消息文本,以及可能的插入其中的运行时数值。 插入是使用 sprintf 风格的格式代码插入的。 除了 sprintf 接受的标准格式代码,还接受 %m 用于插入 strerror 为当前 errno 值返回的错误信息。 [1] %m 并不要求在 errmsg 的参数列表里有任何对应的项目。 请注意这个消息字串在格式代码得到处理之前将不会通过 gettext 运行获取合适的本地化。 *
errmsg_internal(const char *msg, ...) 和 errmsg 一样, 只是消息字串将不会包含在国际化消息字典里。 这个函数应该用于"不可能发生"的情况,也就是不值得展开进行翻译的场合。 *
errdetail(const char *msg, ...) 提供一个可选的"详细"信息; 在存在额外的的信息,并且很适合放在主消息里面的时候使用这个函数。 消息字串处理的方法和 errmsg 完全一样。 *
errhint(const char *msg, ...) 提供一个可选的"暗示"消息; 这个函数用于提供如何修补问题的建议,而不是提供错误的事实。 消息字串处理的方式和 errmsg 一样。 *
errcontext(const char *msg, ...) 通常不会直接从 ereport 消息点里直接调用; 而是用在 error_context_stack 回调函数里提供有关错误发生的环境的信息, 比如,当前的位置是在一个 PL 函数里等等。 消息字串的处理和 errmsg 完全一样。 和其它辅助函数不同,这个函数在一次 ereport 调用里可以调用多次; 随后的调用生成的字串将带着各自的换行符连接在原来的字串上。 *
errposition(int cursorpos) 声明一个错误在查询字串里的文本位置。 目前它只是在汇报查询处理过程中的词法和语法分析阶段检测到的错误有用。 *
errcode_for_file_access() 是一个便利函数,它可以为一个文件访问类的系统调用选择一个合适的 SQLSTATE 错误标识符。 它利用保存下来的 errno 判断生成哪个错误代码。 通常它应该和主消息文本里的 %m 结合使用。 *
errcode_for_socket_access() 是一个便利函数,它可以为一个套接字相关的系统调用选择一个合适的 SQLSTATE 错误标识符。
还有一个老一些的 elog 函数,仍然在频繁使用。 一个 elog 调用
elog(level, "format string", ...);
完全等效于
ereport(level, (errmsg_internal("format string", ...)));
请注意 SQLSTATE 错误代码总是缺省的,并且消息字串并没有包含在国际化信息字典里。 因此,elog 应该只用于内部错误以及低层的调试日志。 任何普通用户感兴趣的消息都应该通过 ereport 生成。 当然,还有大量内部的"不可能发生"的错误检查使用 elog; 因为这些信息最好还是表示得简单些好。
书写好的错误消息的建议可以在 Section 44.3 找到。 Notes [1]
也就是说,在到达 ereport 调用的时候当前的数值; 在附属报告过程里对 errno 的修改将不会影响他。 但是如果你在 errmsg 的参数列表里明确地写 strerror(errno), 这一点就不能保证了,因此,请不要这么做。
44.3. 错误消息风格指导
这份风格向导的目的是希望能把所有 PostgreSQL 生成的消息维护一个一致的,用户友好的风格。 44.3.1. 何去何从
主信息应该简短,基于事实,并且避免引用类似特定函数名等这样的实现细节。 "简短"意味着"在正常情况下应该能放在一行里"。 如果必要,比如你觉得需要提到失败的特定系统调用之类的实现细节,可以使用一个详细信息以保持主信息的简短。 使用一个提示消息给出一个修补问题的提示,特别是在提出的建议可能并不总是有效的情况下。
比如,我们可以不这么写:
IpcMemoryCreate: shmget(key=%d, size=%u, 0%o) failed: %m (plus a long addendum that is basically a hint)
而是
Primary: could not create shared memory segment: %m Detail: Failed syscall was shmget(key=%d, size=%u, 0%o). Hint: the addendum
基本原理:保持主消息的简短可以使它的内容有效, 并且让客户端的屏幕空间布局可以做出给错误信息保留一行就足够的假设。 而详细信息和提示信息可以转移到一个冗余模式里,或者使一个弹出的错误细节的窗口。 同样,详细信息和提示信息通常都会在服务器日志里消除,以节约空间。 对实现细节的引用最好避免,因为毕竟用户不知道细节。 44.3.2. 格式化
不要在消息文本里放任何有关格式化的特定的假设。 除非是客户端或者服务器日志为了复合自己需要回卷了长行。 在长信息里,新行字符(\n)可以用于分段建议。 不要用新行结束一条消息。不要使用tab或者其它格式化字符。 (在错误环境下的显示里,系统会自动给独立级别的环境,比如,函数调用,增加新行。)
基本原理:信息不一定非得在终端类型的显示器上显示。 在 GUI 显示或者在浏览器里,这些格式指示器最好被忽略。 44.3.3. 引号
在需要的时候,英文文本应该使用双引号引起来。 其它语言的文本应该一致地使用一种引号,这种用法应该和出版习惯以及其它程序的计算输出一致。
基本原理:选择双引号而不是单引号从某种角度来说是随机选择, 但是应该是最优的选择。有人建议过根据 SQL 传统,在不同对象类型上使用不同的引号(也就是说,字串单引号,标识符双引号)。 但是这是一种语言内部的技巧,许多用户甚至都不熟悉,并且也不能厌战到其它类型的引号场合, 也不能翻译成其它语言,而且也没啥意义。 44.3.4. 使用引号
总是用引号分隔文件名,用户提供的标识符,以及其它可能包含字的变量。 不要用引号包含那些不会包含字的变量(比如,操作符名字)。
在后端里有些函数会根据需要在他们的输出上加双引号(比如,format_type_be())。 不要在这类函数的输出上加额外的引号。
基本原理:对象的名字嵌入到信息里面之后,可能造成歧义。 在一个插入的名气的启示和终止的位置保持一致。 但是不要在信息里混杂大量不必要的或者重复的引号。 44.3.5. 语法和标点
对于主错误信息和详细/提示信息,规则不同:
主错误信息:首字母不要大写。不要用句号结束信息。绝对不要用叹号结束一条信息。
详细和提示信息:使用完整的句子,并且用句号终止每个语句。句子首字母大写。
基本原理:避免标点可以让客户端应用比较容易把信息嵌入到各种语言环境中。 并且,主消息也经常不是完整的句子。(并且,如果信息长得超过一个句子,那么就应该把他们分裂撑主信息和详细信息部分。) 不过,细节和提示信息长得多并且可能需要包含在多个句子中。 为了保持一致,这些句子应该遵循完整得句子得风格,即使他们只有一个句子。 44.3.6. 大写字符与小写字符比较
消息用语使用小写字符,包括主错误信息的首字母。 如果消息中出现 SQL 命令和关键字,用大写。
基本原理:这样很容易让所有东西看起来都一样,因为有些消息是完整的句子,有些不是。 44.3.7. 避免被动语气
使用主动语气。如果有主语,那么就使用完整的句子("A 不能做 B")。 如果主语是程序自己,那么就使用电报风格的语言;不要用"我"作为程序的主语。
基本原理:程序不是人。否则不要装成人。 44.3.8. 现代时与过去时的比较
如果尝试某事失败,但可能下次尝试的时候成功(可能是修补了某些问题之后),那么使用过去时。 如果错误肯定是永久的,那么用现代时。
下面的两个形式的句子之间的差别并不小
could not open file "%s": %m
和
cannot open file "%s"
第一个句子的意思是打开某个文件的企图失败。这个信息应该给出一个原因, 比如说"磁盘满"或者"文件不存在"之类的。 过去时的语气应该是合适的,因为下次磁盘可能不再是满的,或者有问题的文件存在了。
第二种形式表示打开指定文件的功能根本就不在程序里存在, 或者是这么做概念上是错误的。现代时语气是合适的,因为这个条件将无条件存在。
基本原理:当然,普通用户将不会仅仅从信息的时态上得出大量的结论,但是既然语言提供给我们语法,那么我们就应该正确使用。 44.3.9. 对象类型
在引用一个对象的名字的时候,说明它是什么类型的对象。
基本原理:否则,没人知道 "foo.bar.baz" 是什么。 44.3.10. 方括弧
方括弧只用在(1)命令语法里表示可选的参数,或者(2)表示一个数组下标。
基本原理:任何其它的东西都不能对应这两种中所周知的习惯用法并且会让人混淆。 44.3.11. 组装错误信息
如果一个信息包含其它地方生成的文本,用下面的风格包含它:
could not open file %s: %m
基本原理:我们很难估计所有可能放在这里的错误代码并且把它放在一个平滑的句子里, 所以需要某种方式的标点。也曾经建议把嵌入的文本放在圆括弧里,但是如果嵌入文本是信息的最重要部分,那么就不太自然, 而这种情况是很经常的。 44.3.12. 错误的原因
消息应该总是说明为什么发生错误。比如:
BAD: could not open file %s BETTER: could not open file %s (I/O failure)
如果不知道原因,那么你最好修补代码。 44.3.13. 函数名
不要在错误信息里包含报告过程的名字。需要的时候,我们有别的机制找出这个函数, 并且,对于大多数用户,这个信息也没什么用。如果错误信息在缺少函数名的情况下没有什么意义,那么重新措辞。
BAD: pg_atoi: error in "z": can't parse "z" BETTER: invalid input syntax for integer: "z"
也避免提及被调用的函数名字;应该说代码视图做什么:
BAD: open() failed: %m BETTER: could not open file %s: %m
如果确实必要,在详细信息里提出系统调用。 (在某些场合下,提供给系统调用的具体数值是适合放在详细信息里。)
基本原理:用户不知道这些函数都干些啥。 44.3.14. 尽量避免的字眼
Unable/不能. "Unable/不能"几乎是被动语气。最好使用 "cannot/无法" 或者 "could not"。
Bad/坏的. 类似"bad result/坏结果"这样的信息真的是很难聪明地解释。 最好写出为什么结果是"bad/坏的",比如,"invalid format/非法格式"。
Illegal/非法. "Illegal/非法" 表示违反了法律,其它的就是 "invalid/非法"。但是最好还是说非法。
Unknown/未知. 应该避免使用"unknown/未知"。想想:"error: unknown response"。如果你不知道响应是什么,你怎么知道是错误? "Unrecognized/无法识别的"通常是更好的选择。 还有最好药包括被比较的数值。
BAD: unknown node type BETTER: unrecognized node type: 42
找到与存在的对比. 如果程序使用一个相当复杂的算法来定位一个资源(比如,一个路径搜索), 并且算法失败了,那么说程序无法"找到"改资源是合理的。 但是,如果语气的资源位置是已知的但是程序无法在那里访问它,那么说这个资源不"存在"。 这种情况下用"找到"听起来语气比较弱并且会混淆事实。 44.3.15. 正确地拼写
用单词的全拼。比如,避免下面这样的缩写:
*
spec *
stats *
parens *
auth *
xact
基本原理:这样将改善一致性。 44.3.16. 本地化
请记住,错误信息文本是需要翻译成其它语言的。 遵循 Section 45.2.2 里面的指导以避免给翻译家造成太多麻烦。