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

推荐订阅源

P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
Security Latest
Security Latest
P
Privacy International News Feed
T
Threat Research - Cisco Blogs
H
Help Net Security
T
The Exploit Database - CXSecurity.com
Know Your Adversary
Know Your Adversary
博客园_首页
S
Securelist
S
Schneier on Security
G
GRAHAM CLULEY
Cisco Talos Blog
Cisco Talos Blog
V
Visual Studio Blog
博客园 - 叶小钗
C
Cybersecurity and Infrastructure Security Agency CISA
有赞技术团队
有赞技术团队
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
量子位
L
Lohrmann on Cybersecurity
Hugging Face - Blog
Hugging Face - Blog
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
SegmentFault 最新的问题
The Hacker News
The Hacker News
罗磊的独立博客
博客园 - 司徒正美
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 【当耐特】
Microsoft Security Blog
Microsoft Security Blog
K
Kaspersky official blog
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
L
LINUX DO - 热门话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
V2EX
V
Vulnerabilities – Threatpost
AWS News Blog
AWS News Blog
小众软件
小众软件
Project Zero
Project Zero

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
代码的仪式
2023-09-07 · via hsfzxjy 的博客

我们常能在英文社区看到 coding ceremory 一词,或译为 代码的仪式。Stack Overflow 上有个问题 What does “low ceremony” mean?,作者曾如此提问:

In the Trac Main Features page https://trac.edgewall.org/wiki/TracFeatures, Trac is said to emphasize “ease of use and low ceremony”. Can someone please explain what “ceremony” means in the context of software usage?

low ceremony 与 ease of use 作并列短语,可见在程序开发的语境下,代码的仪式不是一个褒义词——过多的仪式并没有好处。用户 Rowan Freeman 则作此回答:

Low ceremony means a small amount of code to achieve something. It means you don’t need to set up a lot of things in order to get going.

如其所述,代码的仪式是完成一个功能所需要的额外准备。仪式越少,准备工作越简洁,完成起来也越容易。

代码仪式被称为仪式,正如古代祭祀的舞蹈,传统庆典的繁文缛节,其对完成目标贡献甚微,却又是不可或缺的步骤。复杂的仪式冗长而乏味,我们偏偏还得忍受其枯燥,如履薄冰,完成得分毫不差——这也解释了为什么大多数人都不喜欢代码仪式。

不同的代码仪式

依照呈现的形式,代码的仪式可以分为 编写仪式 和 运行仪式 两类。

编写仪式

编写仪式指完成功能我们所需编写的额外代码。其中最基本的一类源自编程语言对程序完整性的要求。每种编程语言会要求用户以特定的方式编写代码,如此方能产生可运行的程序,如 C/C++ 需要定义 main() 函数,旧式 Java 需要声明带有 public static void main() 方法的类等等:

#include <stdio.h>

int main() {
printf("Hello world\n");
return 0;
}
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
main :: IO ()
main = putStrLn "Hello, World!"
SECTION .DATA
hello: db 'Hello world!',10
helloLen: equ $-hello


SECTION .TEXT
GLOBAL _start

_start:
mov eax,4
mov ebx,1
mov ecx,hello
mov edx,helloLen
int 80h


mov eax,1
mov ebx,0
int 80h

可见不同编程语言对“程序完整性”的要求有繁有简,短者如 Python 基本没有要求,长者如汇编语言(NASM)需要手动标注代码段与数据段。但这类仪式基本是一次性的,只需在项目伊始完成,较于漫长的开发工作简直不值一提。

编写仪式也可能源于软件框架的规范要求。软件框架简化了大型程序开发的流程,针对特定领域需求构建好了程序的蓝图,事无巨细皆有覆盖。开发者借助框架开发,就好比对照蓝图搭积木,只需编写上层业务相关代码即可。在简化开发的同时,软件框架通常会束缚代码的组织形式。若以复杂性为轴,我们可以绘制一幅软件框架的光谱。框架的对代码组织形式的约束依其在光谱上的位置而大相径庭。

光谱的一端是轻量级框架。它们有着精简的内核与宽松的规范约束。大多数轻量级框架并不强求规范的目录结构,开发者只需添加少量仪式以完成必要的程序初始化。以知名 Web 框架 Flask 为例。作为轻量级框架的代表,Flask 只负责做好与网络协议相关的工作,给予开发者十足的自由决定更上层逻辑的表现。借助 Flask,仅需寥寥数行代码即可构建一个最简单的 Web 程序:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"

某种意义上,这类框架可以被看作领域特定语言 1,如 Flask 像是基于 Python 为 Web 开发设计的一门“语言”。以语言作类比使我们能套用前文对编程语言仪式的分析,将此类框架的仪式看作其寄主语言仪式的延伸。上面所示的代码中,from flask import Flaskapp = Flask(__name__) 等套路化的代码,即是源自虚构的“Flask 语言”对程序完整性的要求。

光谱的另一端是重量级框架。相较于轻量级框架,重量级框架会向上层业务延伸自己的责任边界。它们的设计者通常更精通领域的业务,了解常见问题,通晓最佳实践,并尝试将这些经验融入框架中以服务开发者。

但事实上业务需求是多变的,设计者需要让框架足够灵活、足够可定制,方能满足现实中开发者的需求。灵活可定制,随之而来的代价便是框架日趋复杂。设计者或是抬高框架的抽象程度,应用设计模式使各种模块易于组合拆解;或是提供眼花缭乱的可配置项,迎合每一个潜在的需求点。前者使样板代码(boilerplate code)膨胀,后者使最佳实践难写——这些都是重量级框架仪式的体现。

我们还是以 Python Web 框架举例,但这次是著名的 Django。和 Flask 相比,一个典型的 Django 项目需包含多个文件,并以特定的结构组织在一起。其目录结构和部分文件内容如下:

├── manage.py
├── mysite
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── polls
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py
3 directories, 13 files
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent.parent






SECRET_KEY = "django-insecure-kqi@xv-ay@y!c2es$5m)b&uulbt-l8g28kw-)o2$&w#@2uvx#&"


DEBUG = True

ALLOWED_HOSTS = []




INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "mysite.urls"

TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]

WSGI_APPLICATION = "mysite.wsgi.application"





DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}





AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]





LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True





STATIC_URL = "static/"




DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
from django.contrib import admin
from django.urls import path

urlpatterns = [
path("admin/", admin.site.urls),
]

Django 提倡以模块切分业务逻辑,每个模块置于单独的目录,称为一个 app。本例中目录 polls/ 便是一个 app,其下的各文件分别定义了所属 app 的数据模型、视图和路由等。Django 奉行所谓的 MTV 模式,即将数据的逻辑和展示分开,使程序更加易于维护。MTV 模式便是 Django 为可维护性引入的额外抽象,其代价是项目结构的复杂化与业务无关代码的增多。而为了更高的灵活性,Django 在 settings.py 中提供了多个配置项。用户可通过调整配置项的值,以满足业务的特定需求。此二者相合便是 Django 开发的编写仪式。

运行仪式

运行仪式是将所写代码执行起来所要付出的额外努力。编写仪式是代码跃于纸上的成本,运行仪式则是代码脱胎成型的代价。 最常见的运行仪式莫过于“程序编译”,这也是人们通常评判运行仪式的焦点。

程序编译即将代码经由某种手段转移为目标代码。编译型语言,如 C/C++、Rust、Go 或 Java 等,其代码须经此工序方能运行。目标代码通常是机器码,但也不尽然,如 Java 是编译为 JVM 的字节码,牺牲一定的效率以换取跨平台能力。

许多人讨厌编译,害怕编译。这一方面是因为编译在代码编写和程序运行中间强插了间隙,加剧调试的延时与焦虑。但最主要的还是编译工序的繁杂与报错的晦涩。C++ 便是个典型例子。其编译的仪式源自语言的设计缺陷——模块概念的缺乏使编译工序冗繁,语法的异常复杂使编译报错晦涩难懂。编译的复杂性还可能源自业务本身的复杂性。如在 Android 开发中,你几乎不可能脱离 Android SDK 完成程序的编译——这是由交叉编译的复杂性所决定的。

程序编译曾是编译型语言的特需,但近年来,解释型语言中也出现了这种需求,其中以 Web 前端开发尤甚。Web 前端程序的运行环境有特殊性。浏览器以 HTML、CSS 以及 Javascript 三种语言的源代码作为最直接、最底层的输入,极大简化了程序的编写。但另一方面,这三种语言结构松散、抽象能力有限,同时在不同平台上标准不一,这迫使人们引入框架和工程化手段以提高大型程序的开发效率和可维护性。人们用表现力更强的“方言”(如 Typescript)编写代码,用更合理的文件结构和抽象程度更高的框架(React、Angular)组织代码,再通过工具编译成为浏览器所能执行的三种语言。这种模式在初期而饱受诟病,后因工具链的不断完善而逐渐被人们接受,直至今日成为了大多数项目的标准配置。

然而,就算是摆脱了编译梦魇的其他解释型语言,在开发过程中也会遇到其他运行仪式。Python 开发者常被依赖安装问题所困。Python 程序免除了程序编译的步骤,却只是把编译期的问题延至运行期。当程序运行时,你需要保证其所依赖的每个模块都被正确安装在正确的位置,并且有着正确的版本号。达成这点并不容易,有时它要求操作系统内要有某个老旧的库,但这个库又被其他程序所依赖,不得轻易修改(早期深度学习开发者应该深有体会)。此种依赖模式还使程序变得难以分发——因为分发对象的机器上也要有对应的依赖。依赖问题催生了 virtualenv、Anaconda 乃至 Docker 等策略的应用,但这愈发加重了 Python 运行仪式的繁杂。

讨论与分析

许多代码仪式其实是迫不得已的产物。这一方面源于业务本身,另一方面源于人。

业务复杂性是代码仪式的最直接成因,决定了其复杂性的下限。如前面所述的 Android 开发必须有 Android SDK 支持,是因为生成一个可执行的 apk 文件就是有这么复杂。另一个例子是 Python 时代前的深度学习开发,人们被各种库的编译与运行时的问题折腾得不轻,是因为 CUDA 编程本身就有这么复杂。

但另一方面,我们还要考虑人的因素。大型程序编写是人分工管理的艺术。人与人的能力不同,效率不同,让项目的有效开发与可维护性成了头疼的问题。此时,框架的设计者或是项目的领导者需提高基础代码的抽象层次,以实现对项目逻辑的拆解,更高效地进行团队协作。这也是为什么大型项目或框架会需要许多样板代码或是胶水代码。重量级框架的意义是积极的,它着眼处理的是大型程序的问题。如果你觉得它们很繁琐,可能是你和它们的目标人群并不完全契合,你需要找寻更轻量级的解决方案。

让人有效协作的另一个手段是压制个性,简单来说便是让所有人都尽量写出质量差不多的代码。这可以从两方面入手。一是使用工具制定代码规范,强迫每处代码遵从一致的风格。Web 前端常用的 ESLint、Python 常用的 pylint 和 autopep8 便是为此服务的。这类工具增加了代码的仪式,但保持了代码的可维护性。另一个方案则是采用尽量简单的编程语言。Java 之所以被企业广泛采用,一部分原因是其语法足够简单,开发者无法写出个性差异巨大的代码,从而能维持项目的一致性。然而,“语法简单”与“抽象能力不足”是一体两面的。语法简单的 Java 在许多时候会显得捉襟见肘,以致需要各种设计模式补偿抽象能力的缺陷,这进而又使代码仪式复杂化了。

在考察代码仪式时,解决方案的灵活性也是个有趣的因素。前文说有着诸多配置项的重型框架会增加代码仪式,那内核紧致的轻型框架又如何呢?轻型框架收缩了责任边界,让社区去构建多种多样的插件以满足上层业务的需求,是否兼具低代码仪式与高灵活性呢?

历史上曾有多种微内核高扩展性的解决方案。Flask 是 Python 后端开发方案中的此类代表。Flask 很简洁,同时有着海量社区插件。但当人们想利用插件构建稍微复杂的程序时,却总会觉得如鲠在喉。一方面是因为插件良莠不齐,总是要斟酌许久才能选中一个特定功能的插件,而且这个插件还不能让人百分百满意。另一方面是各插件之间通常保持着绝对的独立,想在插件间做交互往往要写更多的胶水代码。人们逐渐发现,想利用在野的插件组合出任意需求是很难的,很多时候不如自己从头重写,或是使用一站式的重型框架。与此类似的还有前端的 Webpack——在其鼎盛时期,你在网上可以找到 1000 种编译 ES6 的方案,但每一种都有些小瑕疵。

人们渐渐发现选择众多并不总是一件好事。选择带来争端与标准的割裂,于高效开发是无益的。人们需要一种独断的(opinionated)、大家长式的解决方案,减少选择提高效率。这种解决方案必须瞄准领域的某个最佳实践,有且只能有一个,同时隐藏多数可选项,只提供少数或完全不提供配置项。这种模式看似违背自由竞争的意志,却能切实地减少不必要的代码仪式,提高效率。用 Python 写 Restful API 时,人们更倾向于使用 FastAPI 而不是 Flask;构建前端时,人们发现 Vite 比 Webpack 更省心。这种思潮也深刻影响了近几年开发工具的设计思路。新兴语言如 Go、Rust,其官方都会提供足够好用的包管理器、构建工具以及格式化器等,从源头掐断各种争纷,让用户专注于程序的设计。

尾声

事实上,代码仪式是一个宽泛非正式的概念。人们总是在抱怨某某代码仪式的复杂,但人们没有系统地讨论过。本文无法面面俱到地讨论各种仪式的表现、成因和应对,只是结合现实中的例子粗略地讲解了一下。愿读者能有所收获。


作者:hsfzxjy
链接:
许可:CC BY-NC-ND 4.0.
著作权归作者所有。本文不允许被用作商业用途,非商业转载请注明出处。