Chris Pietschmann 最近在 GitHub 上翻到了一个奇怪的存档页面——那是 Planet Source Code(PSC)的代码索引,上面赫然列着他在 2002 到 2004 年间提交的几十段 VBScript、ASP 和 JavaScript 代码。二十多年前的自己突然跳到眼前,他写了一篇怀旧长文,引发了一波老开发者的集体回忆。
这不仅仅是一段个人回忆。PSC 的兴衰折射出代码分享方式的根本变迁:从"贴代码"到"推仓库",从单文件到项目,从浏览下载到协作迭代。
Planet Source Code 是什么
PSC 在 1999 年左右上线,定位非常直白——一个代码片段的发布与浏览站点。开发者把一段自认为有用的代码贴上去,附上说明和分类标签,其他人搜索、下载、评分。站点按语言分区:VB、Java、C/C++、ASP、JavaScript……每个分区还有"周最佳代码"评选。
在那个年代,这几乎是普通开发者发布代码的唯一可行渠道。个人网站 Hosting 不便宜,FTP 空间有限,SourceForge 只接纳正式的开源项目。一段 50 行的 VBScript 工具函数?没地方放。PSC 填的就是这个空档。
它的交互模式可以概括为:
- 发布者:把代码复制粘贴到网页表单,选语言、写描述、提交
- 浏览者:按语言/关键词搜索,看到代码后复制到本地,给个评分
- 反馈:评论区留言,没有 diff、没有 fork、没有 issue
这套模式在今天看来笨拙,但在 2000 年前后,它就是最接近"开源社区"的东西。
前 GitHub 时代的代码分享生态
PSC 并不是孤例。那个年代有一整套代码分享基础设施,各有侧重:
| 平台 | 定位 | 交互方式 |
|---|---|---|
| Planet Source Code | 代码片段发布与评分 | 粘贴提交、复制下载、评论 |
| SourceForge | 正式开源项目托管 | CVS/SVN、下载 tarball、邮件列表 |
| CodeProject | 技术文章+附带代码 | 长文教程、代码下载、论坛讨论 |
| Snipplr / dzone snippets | 小片段收藏 | 粘贴、标签、收藏 |
| 个人网站/FTP | 自由发布 | 手动上传、链接分享 |
| Usenet / 邮件列表 | 讨论中附带代码 | 纯文本贴代码、手动复制 |
这些平台有一个共同特征:代码是静态的发布物,不是活的协作对象。你下载一段代码后,改了什么、修了哪个 bug,原作者完全不知道。改进版本要么重新发布一个新条目,要么就淹没在评论区的某条留言里。
从"贴一段代码"到"推一个仓库"到底改变了什么
GitHub 带来的不只是 git 托管。它改变了代码分享的几个根本假设:
1. 代码从"成品"变成"过程"
PSC 上你看到的是一段最终代码。GitHub 上你看到的是 commit 历史——每一步尝试、每一个回退、每一次重构。代码变成了有时间维度的活文档。
2. 改进从"重新发布"变成"fork + PR"
在 PSC,如果你改进了某人的代码,你要么自己发一个新条目,要么在评论区贴修改版。两条代码之间没有结构化的关联。GitHub 的 fork/PR 让改进直接回流到源头。
3. 发现从"分类浏览"变成"网络效应"
PSC 的发现机制是人工分类和评分排序。GitHub 的发现靠社交网络——你看到某人 star 了一个项目,依赖链把相关项目推到你面前,趋势算法放大活跃项目。
4. 代码从"片段"变成"项目上下文"
PSC 的代码是剥离了环境的纯片段。GitHub 上哪怕是一个小工具,也有 README、依赖声明、构建脚本、测试。你拿到的不只是一段逻辑,而是可运行的项目。
实践:给自己的旧代码建一个本地"时光胶囊"
PSC 的存档之所以能引发共鸣,是因为很多开发者手里都有散落各处的旧代码——硬盘里的 .vbs 文件、旧邮件附件里的脚本、甚至纸质打印的源码。与其等某个平台偶然存档,不如自己动手建一个本地索引。
下面是一个 Python 脚本,扫描指定目录下的源码文件,生成一个静态 HTML 紓览页面——本质上就是一个小型 Planet Source Code,只不过数据在你自己手里:
#!/usr/bin/env python3
"""codeshelf.py — 把散落的旧代码文件整理成一个可浏览的本地 HTML 紓引"""
import os
import sys
import hashlib
from datetime import datetime
from pathlib import Path
# 支持的源码扩展名及对应语言标签
LANG_MAP = {
".vbs": "VBScript", ".bas": "VB6", ".cls": "VB6",
".js": "JavaScript", ".html": "HTML",
".asp": "ASP", ".php": "PHP",
".py": "Python", ".pl": "Perl",
".c": "C", ".h": "C Header", ".cpp": "C++",
".java": "Java", ".sh": "Shell",
".sql": "SQL", ".ps1": "PowerShell",
}
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>Code Shelf — {title}</title>
<style>
body {{ font-family: monospace; max-width: 960px; margin: 2em auto; background: #1e1e1e; color: #d4d4d4; }}
h1 {{ color: #569cd6; }}
h2 {{ color: #dcdcaa; margin-top: 2em; }}
.meta {{ color: #608b4e; font-size: 0.85em; }}
pre {{ background: #252526; padding: 1em; border-radius: 4px; overflow-x: auto; }}
.lang-tag {{ background: #264f78; padding: 2px 8px; border-radius: 3px; font-size: 0.8em; }}
a {{ color: #4ec9b0; }}
</style>
</head>
<body>
<h1>📦 Code Shelf — {title}</h1>
<p class="meta">生成时间: {timestamp} | 文件数: {count}</p>
{entries}
</body>
</html>"""
ENTRY_TEMPLATE = """
<h2>{filename} <span class="lang-tag">{lang}</span></h2>
<p class="meta">路径: {relpath} | 大小: {size} bytes | SHA256: {hash}</p>
<pre><code>{content}</code></pre>
"""
def sha256_of(text: str) -> str:
return hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()[:16]
def build_shelf(scan_dir: str, output_file: str = "codeshelf.html"):
scan_path = Path(scan_dir).resolve()
entries_html = []
count = 0
for filepath in sorted(scan_path.rglob("*")):
if filepath.suffix.lower() not in LANG_MAP:
continue
if filepath.is_dir() or filepath.stat().st_size > 100_000:
# 跳过目录和超过 100KB 的文件
continue
try:
content = filepath.read_text(encoding="utf-8", errors="replace")
except Exception:
content = filepath.read_text(encoding="latin-1", errors="replace")
relpath = filepath.relative_to(scan_path)
lang = LANG_MAP.get(filepath.suffix.lower(), "Unknown")
entries_html.append(ENTRY_TEMPLATE.format(
filename=filepath.name,
lang=lang,
relpath=relpath,
size=filepath.stat().st_size,
hash=sha256_of(content),
content=content,
))
count += 1
full_html = HTML_TEMPLATE.format(
title=scan_path.name,
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M"),
count=count,
entries="\n".join(entries_html),
)
out = Path(output_file)
out.write_text(full_html, encoding="utf-8")
print(f"✅ 已生成 {out},收录 {count} 个文件")
print(f" 用浏览器打开: file://{out.resolve()}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python codeshelf.py <扫描目录> [输出文件名]")
print("示例: python codeshelf.py ./my_old_code ./shelf.html")
sys.exit(1)
build_shelf(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "codeshelf.html")
运行方式:
# 把你散落各处的旧代码复制到一个目录
mkdir -p ~/my_old_code
cp ~/archive/2002/*.vbs ~/my_old_code/
cp ~/archive/2003/*.asp ~/my_old_code/
cp ~/old_scripts/*.js ~/my_old_code/
# 生成浏览页面
python codeshelf.py ~/my_old_code ~/shelf.html
# 打开查看
open ~/shelf.html # macOS
# 或 xdg-open ~/shelf.html # Linux
生成的页面按文件名排列,每个文件标注语言、路径、大小和 SHA256 摘要。你可以随时追加新文件再重新生成——比 PSC 的粘贴提交更可控,数据也完全在你手里。
如果想更进一步,可以把 shelf.html 和源码目录一起推到一个 GitHub 仓库,加上 README 说明年代和背景,这就是一个自建的代码存档。
我们失去了什么,又该保留什么
PSC 的消亡不是技术落后那么简单。它身上有一些 GitHub 没有完全继承的东西:
低门槛的片段分享。GitHub 的最小单元是仓库,哪怕一个单文件工具也要有仓库结构。PSC 让你贴一段 30 行的函数就能发布,这个门槛差对初学者和随手分享意义重大。今天 GitHub Gist 部分填补了这个空档,但 Gist 的发现机制远不如 PSC 的分类浏览和评分排序。
人工评选的质量信号。PSC 的"周最佳代码"是人工评选的,带有编辑判断。GitHub 的 Trending 是算法驱动的,偏向已有关注度的项目。两种信号各有盲区,但人工评选能挖掘冷门但高质量的贡献,这是算法容易漏掉的。
面向学习的代码浏览。PSC 的核心体验是"逛"——按语言翻、看评分、读代码、学技巧。GitHub 的核心体验是"用"——找到项目、clone、装依赖、跑起来。前者是学习姿态,后者是工程姿态。两者都需要,但今天的平台明显偏工程。
如果你手里有值得保留的旧代码,别等平台偶然存档。用上面的脚本或类似方法,自己建一个本地索引,再推到 GitHub 做长期保存。二十年后翻出来,你会感谢今天的自己。