EmacsLisp风格指南技能Skill emacs-lisp

这个技能提供专家指导,用于编写专业、可维护的Emacs Lisp代码,遵循社区驱动的Emacs Lisp风格指南。覆盖代码布局、命名规范、语法习惯、宏使用、文档编写和最佳实践。适用于Emacs配置编写、包开发、模式创建、命令编写等场景。关键词:Emacs Lisp,风格指南,编码规范,最佳实践,自动加载,社区公约。

其他 0 次安装 0 次浏览 更新于 3/12/2026

name: emacs-lisp description: | 专家指导编写专业的Emacs Lisp代码,遵循Bozhidar Batsov的社区驱动Emacs Lisp风格指南。涵盖布局、命名约定、语法偏好、宏、文档和最佳实践。

使用此技能当编写Emacs配置、创建Emacs包、编写交互命令、开发主/次模式或重构Emacs Lisp代码时。

强调:词法绑定、正确命名、文档标准、自动加载和社区公约。 allowed-tools:

  • Read
  • Edit
  • Write
  • Bash
  • Grep
  • Glob

Emacs Lisp 风格指南技能

此技能提供专家指导,用于编写专业、可维护的Emacs Lisp代码,遵循社区驱动的Emacs Lisp风格指南。

何时使用此技能

使用此技能当:

  • 编写Emacs配置文件(~/.emacs.d/init.el)
  • 创建Emacs包或库
  • 开发主或次模式
  • 编写交互命令和函数
  • 创建键绑定和钩子
  • 重构现有Emacs Lisp代码
  • 设置包元数据和自动加载
  • 编写Emacs Lisp宏
  • 创建自定义变量和面孔

核心原则

1. 源代码布局

缩进和间距

使用空格,永不使用制表符:

;; 好 - 使用空格缩进
(defun my-function (arg1 arg2)
  (let ((result (+ arg1 arg2)))
    (message "Result: %s" result)))

;; 坏 - 混合制表符和空格(永不这样做)

垂直对齐函数参数:

;; 好 - 参数对齐
(defun long-function-name (arg1
                           arg2
                           arg3)
  (body))

;; 好 - 第一个参数在新行,与函数名对齐
(defun long-function-name
    (arg1 arg2 arg3)
  (body))

;; 坏 - 不一致的对齐
(defun long-function-name (arg1
    arg2
  arg3)
  (body))

特殊形式缩进:

;; 特殊形式使用4空格缩进特殊参数,2空格缩进主体

;; 好 - if 使用正确缩进
(if some-condition
    (do-something)  ; 4空格用于 then-子句
  (do-else))        ; 2空格用于 else-子句

;; 好 - let 绑定
(let ((var1 value1)
      (var2 value2))  ; 4空格用于绑定
  (body-form-1)       ; 2空格用于主体
  (body-form-2))

;; 好 - when(无特殊参数)
(when condition
  (do-something)
  (do-more))

最大行长度:

;; 在合理情况下,目标为每行80字符
;; 逻辑地换行长行

;; 好
(defun my-function ()
  "简短描述。"
  (let ((long-variable-name
         (some-function-that-returns-value)))
    long-variable-name))

;; 可接受用于长字符串
(message "这是一个非常长的消息,超过80字符,但保持在一行以提高可读性")

括号:

;; 好 - 文本和开括号之间有空格
(defun my-function (arg)
  (list arg))

;; 坏 - 函数名后无空格
(defun my-function(arg)
  (list arg))

;; 好 - 所有闭括号在同一行
(defun my-function ()
  (when condition
    (let ((x 1))
      (+ x 2))))

;; 坏 - 闭括号在单独行
(defun my-function ()
  (when condition
    (let ((x 1))
      (+ x 2)
    )
  )
)

垂直空白:

;; 好 - 顶级形式之间有空行
(defvar my-var1 "value1")

(defvar my-var2 "value2")

(defun my-function ()
  "做某事。")

;; 例外:相关定义可以分组
(defvar my-var1 "value1")
(defvar my-var2 "value2")
(defvar my-var3 "value3")

2. 语法和惯用法

偏好高级构造

使用 when 代替单分支 if

;; 好
(when condition
  (do-something)
  (do-more))

;; 坏
(if condition
    (progn
      (do-something)
      (do-more)))

使用 unless 用于否定条件:

;; 好
(unless condition
  (do-something))

;; 坏
(when (not condition)
  (do-something))

;; 坏
(if (not condition)
    (do-something))

使用 not 代替 null

;; 好 - 检查 nil/假
(not value)

;; 好 - 专门检查空列表
(null my-list)

;; 坏 - 使用 null 用于一般 nil 检查
(null value)

利用比较函数:

;; 好 - 直接比较
(< 5 x 10)

;; 坏 - 嵌套条件
(and (> x 5) (< x 10))

;; 好 - 多个比较
(= x y z)

;; 坏 - 嵌套比较
(and (= x y) (= y z))

使用 condt 作为捕获全部:

;; 好
(cond
 ((eq x 'foo) (handle-foo))
 ((eq x 'bar) (handle-bar))
 (t (handle-default)))

;; 坏 - 使用 else 在末尾
(cond
 ((eq x 'foo) (handle-foo))
 ((eq x 'bar) (handle-bar))
 (else (handle-default)))

增量和减量:

;; 好
(1+ x)
(1- x)

;; 坏
(+ x 1)
(- x 1)

偏好 with-eval-after-load

;; 好 - 现代方法
(with-eval-after-load 'company
  (setq company-idle-delay 0.2))

;; 可接受但较旧
(eval-after-load 'company
  '(setq company-idle-delay 0.2))

3. 命名约定

通用命名规则

使用烤肉串式(Lisp式):

;; 好
(defun my-function-name ()
  (let ((my-variable 10))
    my-variable))

;; 坏 - 使用蛇形命名法
(defun my_function_name ()
  (let ((my_variable 10))
    my_variable))

;; 坏 - 使用驼峰命名法
(defun myFunctionName ()
  (let ((myVariable 10))
    myVariable))

库名前缀:

;; 对于名为 'my-package' 的库,前缀所有公共符号
(defun my-package-do-something ()
  "公共函数。")

(defvar my-package-option t
  "公共变量。")

;; 私有函数使用双连字符
(defun my-package--internal-helper ()
  "私有函数。")

;; projectile 包的示例
(defun projectile-find-file ()
  "公共命令。")

(defun projectile--get-project-root ()
  "私有助手。")

未使用变量:

;; 好 - 前缀下划线
(lambda (x _y _z)
  (* x 2))

;; 好 - 在解构中
(pcase-let ((`(,first . ,_rest) my-list))
  first)

谓词

单词语词:

;; 好 - 以 'p' 结尾
(defun evenp (n)
  (zerop (mod n 2)))

(defun emptyp (list)
  (null list))

多词语词:

;; 好 - 以 '-p' 结尾
(defun buffer-live-p (buffer)
  (and buffer (buffer-name buffer)))

(defun my-package-valid-state-p (state)
  (member state '(ready active complete)))

特殊命名

面孔名称:

;; 好 - 无 -face 后缀
(defface my-package-error
  '((t :foreground "red"))
  "错误消息的面孔。")

;; 坏 - 冗余 -face 后缀
(defface my-package-error-face
  '((t :foreground "red"))
  "错误消息的面孔。")

4. 函数

Lambda 函数

使用指南:

;; 好 - lambda 用于本地使用
(mapcar (lambda (x) (* x 2)) '(1 2 3))

;; 坏 - lambda 在钩子中(使用命名函数)
(add-hook 'emacs-lisp-mode-hook
          (lambda () (eldoc-mode 1)))

;; 好 - 钩子中使用命名函数
(defun my-elisp-mode-setup ()
  "Emacs Lisp 模式设置。"
  (eldoc-mode 1))

(add-hook 'emacs-lisp-mode-hook #'my-elisp-mode-setup)

永不硬引用 Lambdas:

;; 坏 - 硬引用 lambda
(mapcar '(lambda (x) (* x 2)) list)

;; 好 - 未引用 lambda
(mapcar (lambda (x) (* x 2)) list)

;; 好 - 函数引用(尖引号)
(mapcar #'(lambda (x) (* x 2)) list)

不包装现有函数:

;; 坏 - 不必要的包装
(add-hook 'prog-mode-hook
          (lambda () (linum-mode 1)))

;; 好 - 直接函数引用
(add-hook 'prog-mode-hook #'linum-mode)

函数引用

使用尖引号作为函数名:

;; 好 - 函数引用有助于字节编译
(mapcar #'upcase '("foo" "bar"))
(add-hook 'text-mode-hook #'turn-on-auto-fill)
(global-set-key (kbd "C-c f") #'find-file)

;; 可接受但较不优化
(mapcar 'upcase '("foo" "bar"))

;; 坏 - 未引用(仅适用于交互命令)
(global-set-key (kbd "C-c f") 'find-file)

参数限制

避免太多参数:

;; 坏 - 太多位置参数
(defun create-user (name email age address city state zip country)
  ...)

;; 好 - 使用 plist 或 alist 作为多个参数
(defun create-user (name email &rest properties)
  "创建用户,带 NAME 和 EMAIL。
PROPERTIES 是 plist,可能包括 :age, :address, :city 等。"
  (let ((age (plist-get properties :age))
        (address (plist-get properties :address)))
    ...))

;; 好 - 使用 cl-defun 带关键字参数
(cl-defun create-user (name email &key age address city state)
  "创建用户,带 NAME 和 EMAIL 和可选属性。"
  ...)

5. 宏

何时使用宏

仅当函数无法工作时使用宏:

;; 好 - 宏需要特殊求值
(defmacro with-timing (label &rest body)
  "执行 BODY 并以 LABEL 打印执行时间。"
  (declare (indent 1))
  (let ((start (make-symbol "start")))
    `(let ((,start (current-time)))
       (prog1 (progn ,@body)
         (message "%s: %.2fs" ,label
                  (float-time (time-since ,start)))))))

;; 坏 - 函数可工作于此
(defmacro double (x)
  `(* 2 ,x))

;; 好 - 改用函数
(defun double (x)
  (* 2 x))

宏设计

首先提取示例:

;; 在编写宏前,写示例用法:
;;
;; (with-temporary-buffer
;;   (insert "content")
;;   (buffer-string))
;;
;; 然后实现以匹配期望用法

使用 Declare 用于元数据:

;; 好 - 带 declare 的完整宏
(defmacro with-my-mode (buffer &rest body)
  "在 BUFFER 中执行 BODY,带 my-mode 启用。"
  (declare (indent 1)
           (debug (sexp body)))
  `(with-current-buffer ,buffer
     (my-mode 1)
     (unwind-protect
         (progn ,@body)
       (my-mode -1))))

偏好语法引用(反引号):

;; 好 - 反引号带解引用
(defmacro my-when (condition &rest body)
  `(if ,condition
       (progn ,@body)))

;; 坏 - 手动列表构造
(defmacro my-when (condition &rest body)
  (list 'if condition
        (cons 'progn body)))

6. 文档

文档字符串

函数文档字符串:

;; 好 - 完整文档字符串
(defun my-package-process-file (filename)
  "处理在 FILENAME 的文件。

文件被读取、处理,结果被返回。
如果 FILENAME 不存在,发出错误信号。

返回处理后的内容作为字符串。"
  (with-temp-buffer
    (insert-file-contents filename)
    (buffer-string)))

;; 好 - 文档所有参数
(defun my-package-create-entry (name email &optional age)
  "创建带 NAME 和 EMAIL 的条目。

NAME 应是非空字符串。
EMAIL 应是有效电子邮件地址。
可选 AGE 应是正整数。

返回创建的条目作为 plist。"
  ...)

变量文档字符串:

;; 好
(defvar my-package-timeout 30
  "网络操作的超时秒数。

此值由 my-package 中的所有网络函数使用。
设置为 nil 以禁用超时。")

;; 好 - 自定义变量带类型
(defcustom my-package-auto-save t
  "是否自动保存文件。

当非 nil 时,文件在修改后自动保存。"
  :type 'boolean
  :group 'my-package)

首行摘要:

;; 好 - 首行是完整句子
(defun my-function (arg)
  "处理 ARG 并返回结果。

关于函数行为的附加细节。
更多解释如果需要。"
  ...)

;; 坏 - 不完整首行
(defun my-function (arg)
  "处理 ARG 和
返回结果。"
  ...)

注释

注释级别:

;;; 节标题
;;; 用于文件的主要节

;;; 注释:
;; 包描述和用法示例

;;; 代码:

;; 顶级注释
;; 用于函数前的解释

(defun my-function ()
  ;; 函数内注释
  ;; 用于解释代码块
  (let ((x 10))  ; 边距注释用于内联解释
    x))

注释最佳实践:

;; 好 - 解释为什么,而不是什么
(defun my-function (items)
  ;; 首先排序项目以优化查找性能
  (let ((sorted-items (sort items #'string<)))
    (process-items sorted-items)))

;; 坏 - 冗余注释
(defun my-function (items)
  ;; 排序项目
  (sort items #'string<))

;; 好 - 自文档化代码代替注释
(defun process-active-buffers ()
  (let ((active-buffers (get-active-buffers)))
    (mapc #'process-buffer active-buffers)))

;; 坏 - 需要注释解释
(defun process ()
  ;; 获取活动缓冲区
  (let ((bufs (get-bufs)))
    ;; 处理每个
    (mapc #'proc bufs)))

注解关键词:

;; TODO: 添加远程文件支持
;; FIXME: 这与 Unicode 字符冲突
;; OPTIMIZE: 缓存结果以提高性能
;; HACK: package.el 中的变通方法
;; REVIEW: 这是最佳方法吗?

;; 好 - 带首字母和日期
;; TODO(user 2025-01-15): 实现异步版本
;; FIXME(user 2025-01-15): 处理空输入的边缘情况

7. 加载和自动加载

模块结构

正确文件页脚:

;;; my-package.el --- 简短描述  -*- lexical-binding: t; -*-

;; 版权所有 (C) 2025 您的名字

;; 作者:您的名字 <your.email@example.com>
;; 版本:1.0.0
;; 包-需要:((emacs "27.1"))
;; 关键词:便利, 工具
;; URL: https://github.com/user/my-package

;;; 注释:

;; 包的更长描述。
;; 用法示例。

;;; 代码:

(defun my-package-do-something ()
  "做某事有用。")

(provide 'my-package)
;;; my-package.el 结束于此

Require 与 Load

使用 require 用于依赖:

;; 好 - 幂等、安全
(require 'subr-x)
(require 'cl-lib)

;; 坏 - 非幂等
(load "subr-x")
(load-library "cl-lib")

自动加载 Cookie

使用自动加载用于面向用户的命令:

;; 好 - 自动加载交互命令
;;;###autoload
(defun my-package-start ()
  "启动 my-package。"
  (interactive)
  (my-package-mode 1))

;; 好 - 自动加载模式定义
;;;###autoload
(define-minor-mode my-package-mode
  "切换 my-package 模式。"
  :lighter " MyPkg"
  (if my-package-mode
      (my-package--enable)
    (my-package--disable)))

;; 坏 - 自动加载内部函数
;;;###autoload
(defun my-package--internal-helper ()
  "内部助手。")

8. 词法绑定

总是启用词法绑定:

;;; my-package.el --- 描述  -*- lexical-binding: t; -*-

;; 词法绑定更快且更直观
;; 总是用于新代码

词法绑定的好处:

;; 使用词法绑定,闭包工作正确
(defun make-counter ()
  "创建计数器函数。"
  (let ((count 0))
    (lambda ()
      (setq count (1+ count))
      count)))

;; 用法:
;; (setq counter1 (make-counter))
;; (funcall counter1) => 1
;; (funcall counter1) => 2

9. 列表操作

使用 dolist 用于迭代:

;; 好 - 清晰且惯用
(dolist (item items)
  (process-item item))

;; 坏 - 冗长
(mapc (lambda (item)
        (process-item item))
      items)

不使用 mapcar 用于副作用:

;; 坏 - mapcar 创建未使用列表
(mapcar #'process-item items)

;; 好 - dolist 用于副作用
(dolist (item items)
  (process-item item))

;; 好 - seq-do 用于函数式风格
(seq-do #'process-item items)

10. 交互命令

正确交互规范:

;; 好 - 带提示
(defun my-package-open-file (filename)
  "在 my-package 中打开 FILENAME。"
  (interactive "F打开文件: ")
  (find-file filename))

;; 好 - 多个参数
(defun my-package-replace (from to)
  "在当前缓冲区中将 FROM 替换为 TO。"
  (interactive
   (list (read-string "替换: ")
         (read-string "为: ")))
  (save-excursion
    (goto-char (point-min))
    (while (search-forward from nil t)
      (replace-match to))))

;; 好 - 使用前缀参数
(defun my-package-insert-n (n)
  "插入 N 份某物。"
  (interactive "p")
  (dotimes (_ n)
    (insert "文本")))

11. 错误处理

适当发出错误信号:

;; 好 - 描述性错误
(defun my-package-get-user (id)
  "通过 ID 获取用户。"
  (or (gethash id my-package-users)
      (error "用户未找到:%s" id)))

;; 好 - user-error 用于用户错误
(defun my-package-save ()
  "保存当前数据。"
  (interactive)
  (unless (my-package--has-changes-p)
    (user-error "无更改保存")))

;; 好 - 带 condition-case
(defun my-package-load-file (filename)
  "安全加载 FILENAME。"
  (condition-case err
      (with-temp-buffer
        (insert-file-contents filename)
        (read (current-buffer)))
    (file-error
     (message "无法读取文件:%s" (error-message-string err))
     nil)))

最佳实践摘要

编写 Emacs Lisp 代码时:

  1. 总是启用词法绑定 在文件头
  2. 前缀所有公共符号 带包名
  3. 使用 defcustom 用于用户可配置变量
  4. 添加自动加载 cookie 到交互命令
  5. 编写全面文档字符串 用于所有公共函数
  6. 偏好内置函数 而非重新发明功能
  7. 使用 declare 在宏中用于缩进和调试
  8. 优雅处理错误 带信息性消息
  9. 遵循命名约定 一致地
  10. 保持函数专注 于单一职责

常见模式

包模板

;;; my-package.el --- 简短描述  -*- lexical-binding: t; -*-

;; 版权所有 (C) 2025 您的名字

;; 作者:您的名字 <email@example.com>
;; 版本:1.0.0
;; 包-需要:((emacs "27.1"))
;; 关键词:便利
;; URL: https://github.com/user/my-package

;;; 注释:

;; 更长描述和用法示例。

;;; 代码:

(require 'cl-lib)

(defgroup my-package nil
  "我的包自定义组。"
  :group 'convenience
  :prefix "my-package-")

(defcustom my-package-option t
  "启用某些选项。"
  :type 'boolean
  :group 'my-package)

;;;###autoload
(defun my-package-command ()
  "主命令。"
  (interactive)
  (message "Hello from my-package!"))

(provide 'my-package)
;;; my-package.el 结束于此

资源