惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
GbyAI
GbyAI
人人都是产品经理
人人都是产品经理
T
Tor Project blog
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
爱范儿
爱范儿
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
Vercel News
Vercel News
美团技术团队
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
L
LangChain Blog
IT之家
IT之家
博客园_首页
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
T
Tailwind CSS Blog
M
MIT News - Artificial intelligence
P
Proofpoint News Feed
Hacker News: Ask HN
Hacker News: Ask HN
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
量子位
Project Zero
Project Zero
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
C
CERT Recently Published Vulnerability Notes
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
Know Your Adversary
Know Your Adversary
L
Lohrmann on Cybersecurity
C
Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
I
InfoQ
P
Privacy & Cybersecurity Law Blog
Spread Privacy
Spread Privacy
Martin Fowler
Martin Fowler
腾讯CDC
S
Security @ Cisco Blogs
F
Fortinet All Blogs

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
读:整洁代码的几个通用原则——从 Go 生态看起 - 暗无天日
2026-05-01 · via 暗无天日

最近读了一篇 Go 整洁代码的长文,内容覆盖函数设计、错误处理、接口设计和包架构。虽然代码是 Go 写的,但里面的原则大部分不限于特定语言。本文提炼几条最有通用价值的,示例改用 Python。

屏幕规则(Screen Rule):函数不超过一屏

原文提了一个很实在的质量指标:一个函数应该能在开发者屏幕上完整显示,大概是 30-50 行。超过了就该考虑拆分。

30 行不是什么 magic number。关键是一个函数如果滚动才能看全,它做的事情就超出了人能一次性理解的范围——你不可能一边看后面的逻辑一边记住前面的状态。

拆分的依据是单一职责。看个反面例子:

def process_user(user_id: int) -> dict:
    if user_id <= 0:
        print(f"无效用户 ID: {user_id}")
        return {"error": "invalid"}
    db = sqlite3.connect("app.db")
    cur = db.cursor()
    cur.execute("SELECT id, name, email FROM users WHERE id = ?", (user_id,))
    row = cur.fetchone()
    if row is None:
        return {"error": "not found"}
    user = {"id": row[0], "name": row[1], "email": row[2]}
    if user["email"]:
        domain = user["email"].split("@")[1]
        user["is_active"] = domain in ["google.com", "microsoft.com"]
        user["email_domain"] = domain
    db.close()
    print(f"用户 {user_id} 处理完成")
    return user

这个函数既做校验、又管理数据库连接、又执行查询、又做数据增强、又打日志。拆开之后,每个函数只做一件事:

def get_user(user_id: int) -> dict:
    validate_user_id(user_id)
    user = fetch_user(user_id)
    enrich_user(user)
    return user

def validate_user_id(uid: int):
    if uid <= 0:
        raise ValueError(f"无效用户 ID: {uid}")

def fetch_user(uid: int) -> dict:
    db = sqlite3.connect("app.db")
    try:
        cur = db.cursor()
        cur.execute(
            "SELECT id, name, email FROM users WHERE id = ?", (uid,)
        )
        row = cur.fetchone()
        if row is None:
            raise KeyError(f"用户 {uid} 不存在")
        return {"id": row[0], "name": row[1], "email": row[2]}
    finally:
        db.close()

def enrich_user(user: dict):
    if not user.get("email"):
        return
    user["email_domain"] = user["email"].split("@")[1]

拆开之后每个函数不超过 10 行,可以独立测试,读代码的人不需要同时追踪数据库连接和数据增强两件事。

早返回代替嵌套

嵌套是降低代码可读性的头号杀手。原文管它叫"厄运金字塔":

def send_notification(user_id: int, message: str) -> str:
    user = find_user(user_id)
    if user is not None:
        if user.get("email"):
            if user.get("active"):
                if user.get("notifications_enabled"):
                    result = send_email(user["email"], message)
                    if result:
                        return "发送成功"
                    else:
                        return "发送失败"
                else:
                    return "通知未开启"
            else:
                return "用户未激活"
        else:
            return "邮箱为空"
    else:
        return "用户不存在"

每一层缩进都是一个心智负担。修复方式是用早返回(guard clauses),把异常情况提前排除:

def send_notification(user_id: int, message: str) -> str:
    user = find_user(user_id)
    if user is None:
        return "用户不存在"
    if not user.get("email"):
        return "邮箱为空"
    if not user.get("active"):
        return "用户未激活"
    if not user.get("notifications_enabled"):
        return "通知未开启"
    if not send_email(user["email"], message):
        return "发送失败"
    return "发送成功"

两种写法逻辑完全一致,但早返回版本不需要追踪括号匹配,读起来平坦通畅。

用上下文管理器保证清理

Go 有 defer 关键字,保证函数退出时执行清理操作。Python 没有 defer ,但有更好的替代:上下文管理器( with 语句):

def read_config(path: str) -> dict:
    import json
    f = open(path)
    try:
        data = f.read()
        config = json.loads(data)
        f.close()
        return config
    except Exception:
        f.close()
        raise

def read_config(path: str) -> dict:
    import json
    with open(path) as f:
        return json.load(f)

with 语句比 defer 更简洁: defer 只在函数退出时触发,而 with 的清理范围精确到代码块,生命周期更清晰。

接口越小越好

原文的一个核心观点是接口应该小。Go 标准库的经典例子是 `io.Reader`(一个 `Read` 方法)和 `io.Writer`(一个 `Write` 方法)。一个方法一个接口,组合使用。

Python 的协议类(Protocol)也是同样的思路:

from typing import Protocol

class Readable(Protocol):
    def read(self, size: int = -1) -> bytes: ...

class Writable(Protocol):
    def write(self, data: bytes) -> int: ...

def process_data(src: Readable, dst: Writable):
    data = src.read()
    dst.write(data)

接口小有三个好处:容易实现(一个方法而已)、容易测试(mock 一个方法比 mock 十个简单得多)、容易组合(小接口拼成大接口,而不是大接口拆小)。

接口定义在消费方

传统面向对象编程的思路是提供方定义接口,使用者去实现它。Go 反过来:你作为调用者,定义你需要什么,然后在调用处把接口写出来。只要传入的对象有你要的方法就行,不需要它显式声明"我实现了这个接口"。

Python 的 Protocol 也支持同样的思维:

from typing import Protocol

class UserFetcher(Protocol):
    def get(self, user_id: int) -> dict: ...

def greet_user(db: UserFetcher, user_id: int):
    user = db.get(user_id)
    return f"你好,{user['name']}"

class PostgresDB:
    def get(self, user_id: int) -> dict:
        return {"name": "张三"}

class RedisCache:
    def get(self, user_id: int) -> dict:
        return {"name": "李四"}

print(greet_user(PostgresDB(), 1))
print(greet_user(RedisCache(), 1))

实际输出:

你好,张三
你好,李四

`PostgresDB` 和 `RedisCache` 都不知道 `UserFetcher` 的存在。它们只是恰好有个 `.get()` 方法,而 `greet_user` 只关心这个。想换实现时,不需要改接口定义,也不需要改调用者的代码,换实现就是换一个参数而已。

你可能会想:这不就是 duck typing 吗?确实是,但有个关键区别。纯动态 duck typing(无类型标注的 Python、Ruby、JS)在运行时才检查对象有没有对应方法,传错了只能等线上炸。Go 和 Python 的 Protocol + mypy 都是在编译/类型检查阶段就确认传入的对象满足接口要求——既有 duck typing 的灵活性,又有静态类型的可靠性。这是 duck typing 的进阶形态,保留了灵活、去掉了惊吓。

方法链(Builder 模式)

有些对象的构造过程参数很多,原文展示了用 builder 模式解决这个问题。这是 Go 版的 Functional Options 模式,用 Python 的方法链是同样的思路:

class QueryBuilder:
    def __init__(self, table: str):
        self._table = table
        self._columns = ["*"]
        self._where: list[str] = []
        self._order_by: str | None = None
        self._limit: int | None = None

    def select(self, *columns: str):
        self._columns = columns
        return self

    def where(self, condition: str):
        self._where.append(condition)
        return self

    def order_by(self, column: str):
        self._order_by = column
        return self

    def limit(self, n: int):
        self._limit = n
        return self

    def build(self) -> str:
        sql = f"SELECT {', '.join(self._columns)} FROM {self._table}"
        if self._where:
            sql += " WHERE " + " AND ".join(self._where)
        if self._order_by:
            sql += f" ORDER BY {self._order_by}"
        if self._limit is not None:
            sql += f" LIMIT {self._limit}"
        return sql

sql = (QueryBuilder("users")
       .select("id", "name", "email")
       .where("active = true")
       .order_by("created_at DESC")
       .limit(10)
       .build())

实际输出是一条完整的 SQL 查询语句:

SELECT id, name, email FROM users WHERE active = true ORDER BY created_at DESC LIMIT 10

关键设计是每个设置方法都返回 `self`,这样调用可以不断串联下去,代码读起来就是在描述要做什么。

小结

这篇文章最有价值的地方不是某个具体的 Go 技巧,而是背后贯穿的思路:代码首先是给人读的,其次才是给机器执行的。屏幕规则、早返回、小接口、清理自动化——这些东西跟具体语言关系不大。不管用什么语言写代码,这些原则都能用上。