建议(advice)的一个常见用途是作用于命名函数与宏。你可以直接像下面这样使用 add-function:
(add-function :around (symbol-function 'fun) #'his-tracing-function)
但对此你应该改用 advice-add 和 advice-remove。这套专门用于操作命名函数上附加建议的函数,相比 add-function 提供了以下额外特性:它们能正确处理宏和自动加载函数,能让 describe-function 保留原始文档字符串并同时记录附加的建议,还允许你在函数尚未定义时就提前添加或移除建议。
advice-add 适用于在不重新定义整个函数的前提下,修改现有函数被调用时的行为。但它也可能成为 bug 的来源,因为调用该函数的原有代码可能会假设函数保持旧有行为,一旦行为被建议修改,就可能运行出错。如果调试者没有注意到或忘记该函数已被建议修改,建议机制还会给调试带来困惑。
需要注意的是,这些问题并非源于建议机制本身,而是源于修改命名函数这一行为。如果通过 fset、defalias 或 cl-letf 这类底层原语来修改命名函数,问题会更加严重。从这个角度来看,使用建议是修改命名函数的更好方式,因为它会记录所有修改,便于查看与撤销。
修改命名函数应当只用于没有其他办法改变 Emacs 行为的场景。如果可以通过钩子(hook)实现相同效果,优先使用钩子(see Hooks)。如果你只是想修改某个按键的功能,更好的做法通常是:编写一个新命令,并将旧命令的按键绑定重映射到新命令上(see Remapping Commands)。
如果你在编写供他人使用的发布代码,请尽量避免在其中使用建议。如果你想添加建议的函数没有对应的钩子来完成需求,请与 Emacs 开发者沟通,添加合适的钩子。特别注意:Emacs 自身的源码不应对内置函数添加建议。(这一惯例目前存在少数例外,但我们计划逐步修正。)通常来说,更清晰的做法是:在 foo 中创建一个新钩子,然后让 bar 使用这个钩子,而不是让 bar 为 foo 添加建议。
特殊形式(see 特殊形式)无法被添加建议,但宏可以像函数一样被添加建议。当然,这不会影响已经完成宏展开的代码,因此你需要确保建议在宏展开之前就已安装。
尽管可以为原语(see 什么是函数?)添加建议,但通常不建议这样做,原因有二:其一,部分原语会被建议机制自身调用,为它们添加建议可能导致无限递归;其二,许多原语会被 C 代码直接调用,而这类调用会忽略建议 —— 最终会出现一种混乱的情况:有些调用(来自 Lisp 代码)会遵循建议,而另一些调用(来自 C 代码)则不会。
该宏用于定义一段建议,并将其添加到名为 symbol 的函数上。若 name 为非 nil 值,这段建议会被命名为 symbol@name,且会以 name 作为标识安装;否则,该建议为匿名建议。其他参数的说明参见 advice-add。
将建议函数 function 添加到命名函数 symbol 上。其中 where 和 props 的含义与 add-function 中一致(see 操作建议的原语)。
从命名函数 symbol 中移除建议函数 function。function 也可以是某段建议的 name(名称)。交互式调用时,会分别提示输入已添加建议的函数 function 以及要移除的建议。
如果建议函数 function 已经存在于命名函数 symbol 中,则返回非 nil。function 也可以是某段建议的 name(名称)。
对添加到命名函数 symbol 上的每一段建议,都调用一次 function。调用 function 时会传入两个参数:建议函数本身及其属性列表。