继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

我开发了一款工具,终结在有害开源项目上的时间浪费

慕侠2389804
关注TA
已关注
手记 451
粉丝 56
获赞 159

动机

在向多个开源项目贡献代码后,我发现其中一些项目存在严重问题。许多维护者在提交 Pull Request 时并不提供帮助,你最终只能独自应对自动化代码审查,只为在 GitHub 个人资料上显示一次提交。这次,我决定不再构建个人项目(如用 TypeScript 构建自己的 HTTP 服务器构建让生活更轻松的 CLI 工具),不再向随机开源仓库贡献代码,也不再埋头刷 LeetCode 题目,而是希望为开源社区创造真正有影响力的工具。我决定构建 repo-health 在线演示,帮助贡献者选择能够成功参与的项目,并了解开源协作中的最佳实践。

技术栈

  • 前端:Next.js 16, React 19, Chakra UI
  • 后端:tRPC, Octokit, Zod
  • 数据:MySQL (Prisma), Redis

选择这个技术栈是为了熟悉当前行业标准工具,了解它们在实际应用中如何协同工作。

第一个挑战:明确产品定位

开始时,我只是简单展示 GitHub 数据,自认为很酷,直到向朋友和大学生展示后才意识到问题。我发现,即使花费大量时间构建功能或修复重大 bug,如果无法解决实际问题,这些努力就毫无意义。因此,在编写代码前,我决定先深入思考产品的核心价值。为此,我给自己设定了两周期限,集中精力完善这个产品。

缩小项目范围

我决定专注于解决开源贡献者面临的实际问题,特别是 GitHub 上的有害沟通环境,以及维护者指导缺失导致的自动化代码审查困境。


核心功能:项目健康评分

系统采用混合方法,结合基于规则的确定性算法和定性的大语言模型(LLM)评判模块,全面评估项目健康状况。

评分算法(0-100 分)

基础健康评分采用自定义加权平均算法,灵感来自标准化 CHAOSS 指标。权重根据现代健康项目的特征进行调整:

分数 = (0.3 × 活跃度) + (0.25 × 维护度) + (0.2 × 社区) + (0.25 × 文档)

  • 活跃度 (30%): 提交频率、更新及时性、独立作者数量
  • 维护度 (25%): 问题响应时间、未解决问题比例、仓库年龄
  • 社区 (20%): 星标和复刻数量的对数尺度
  • 文档 (25%): README、LICENSE、CONTRIBUTING 文件的存在性

大语言模型调整

基于规则的算法常将"功能完备"项目误判为"已死亡"。为此,我增加了大语言模型评判层。

技术实现:

我设计了辅助逻辑层,让大语言模型分析仓库的目的(通过 README 内容和文件结构)。当模型检测到指标可能产生误导时,可以在±20分范围内调整最终分数。这是为了弥合原始数据与现实语境之间的差距。

目前 MVP 使用 GPT-4 Mini,因其成本效益和响应速度优势。这有助于验证方法可行性,为后续优化奠定基础。

  • 示例: 稳定工具库,6个月内无提交
  • 算法判定: "陈旧/已遗弃"
  • LLM 调整: 识别为"已完成/稳定",给予+20分稳定性加分

实现代码片段:

// 将计算分数输入 AI 提示词,请求调整
prompt += `
  "scoreInsights": {
    "adjustment": {
       "shouldAdjust": true, 
       "amount": 20, // 范围: -20 到 +20
       "reason": "这是处于维护模式的稳定工具库,低活跃度符合预期", 
       "confidence": "high"
    }
  }
`;

PR 指标分析

为解决开源协作中的沟通缺失问题,我构建了 PR 指标分析功能。贡献前需要了解:

  1. 响应速度: 平均合并时间(小时还是月?)
  2. 协作质量: 是与真人协作还是对抗机器人审查?
  3. 成长生态: 新贡献者是否会持续参与?

高效数据处理(后端并发)

为快速获取统计数据,采用 Promise.all 并行获取数据:

// 并行获取开放 PR、已关闭 PR 和模板检查
const [openPRs, closedPRs, template] = await Promise.all([
  fetchPRs(octokit, { owner, repo, state: "open" }),
  fetchPRs(octokit, { owner, repo, state: "closed" }),
  checkPRTemplate(octokit, { owner, repo }),
]);

留存率比数量更重要。使用 Sankey 图可视化贡献者流动路径:

// 贡献者流动可视化
const data = {
  nodes: [
    { id: "首次 PR", color: "#58a6ff" },
    { id: "二次贡献", color: "#3fb950" },
    { id: "常规贡献者 (3-9)", color: "#a371f7" },
    { id: "核心团队 (10+)", color: "#f0883e" }
  ],
  links: [
    {
      source: "首次 PR",
      target: "二次贡献",
      value: funnel.secondContribution + funnel.regular + funnel.coreTeam
    }
    // ... 流动映射逻辑
  ].filter((link) => link.value > 0)
};

经验教训:安全扫描器的取舍

最初基于 GitleaksTruffleHog 的灵感,构建了完整的密钥检测功能。

实现方案:

  • 正则模式匹配: 22个行业标准模式检测已知密钥
  • 随机性检测: 数学方法识别类似密钥的高随机字符串

放弃原因:

虽然安全扫描器是很好的工程挑战,但偏离了核心使命。为保持项目聚焦社区指标,最终决定移除该功能。软件工程的核心是解决实际问题,而非单纯的技术挑战。


智能问题分析

问题分析是了解项目活跃度的有效方法。我实现了多个指标为贡献者提供深度洞察:

  • 平均关闭时间: 衡量项目真实响应速度。短期平均关闭时间(如2天 vs 6个月)表明健康状态
  • 热门问题: 自定义算法优先展示近期更新(48小时内)、高参与度和安全相关讨论
  • 高价值问题: 突出"老旧但重要"的功能请求,适合作为首次贡献选择
  • 贡献难度评分: 基于文档质量、文件范围和测试要求的难度评级(0-100分)

项目结构与文件-问题映射

作为前端新手,这次开发推动我深入前端领域。重点解决新项目探索的复杂性难题。

解决方案:LLM 驱动的结构分析

系统递归获取完整文件树结构,输入大语言模型进行分析:

// 递归获取 GitHub 文件树
const { data } = await octokit.git.getTree({
  owner,
  repo,
  tree_sha: "HEAD",
  recursive: "true"  // 一次性获取完整结构
});

文件-问题映射

在 LLM 处理前,使用正则表达式扫描问题描述中的文件路径:

// 正则提取问题中的文件路径
const FILE_PATTERN = /[\w\-\/\.]+\.(ts|tsx|js|jsx|py|go|rs|java|cpp|c)/gi;
function extractFilePaths(text: string): string[] {
  const matches = text.match(FILE_PATTERN) || [];
  return [...new Set(matches)]; // 去重
}

项目树可视化

repo-visualizer 启发,实现递归构建层次结构:

// 从扁平列表构建层次结构
function buildHierarchy(files: FileNode[], repoName: string, maxDepth = 3): HierarchyNode {
  const root: HierarchyNode = { name: repoName, path: "", children: [] };
  files.forEach((file) => {
    const parts = file.path.split("/");
    let current = root;
    parts.slice(0, maxDepth + 1).forEach((part, index) => {
      let child = current.children?.find((c) => c.name === part);
      if (!child) {
        child = { name: part, children: [] };
        current.children!.push(child);
      }
      current = child;
    });
  });
  return root;
}

通过深度和文件数量限制确保可视化可读性。当前版本暂缓交互功能开发,优先保证核心稳定性。


活动模式检测

针对 Hacktoberfest 等时期的刷提交行为,构建基于 GitHub API 的模式检测系统。

可疑模式定义:

批量删除检测

// 检测异常删除模式
function detectChurnAnomalies(commits: CommitWithStats[]): PatternAnomaly[] {
  for (const commit of commits) {
    const total = commit.additions + commit.deletions;
    const churnRatio = commit.deletions / total;

    // 标记删除超过80%的提交
    if (churnRatio > 0.8 && commit.deletions > 100) {
      anomalies.push({
        type: "churn",
        severity: churnRatio > 0.9 ? "critical" : "warning",
        description: `删除${Math.round(churnRatio * 100)}%代码(${commit.deletions}行)`
      });
    }
  }
}

速射提交检测

// 检测提交轰炸
function detectBurstActivity(commits: CommitWithStats[]): PatternAnomaly[] {
  for (let i = 0; i < sorted.length - 4; i++) {
    const windowStart = new Date(sorted[i].date).getTime();
    const windowEnd = new Date(sorted[i + 4].date).getTime();
    const diffMinutes = (windowEnd - windowStart) / (1000 * 60);

    // 10分钟内5+次提交视为可疑
    if (diffMinutes <= 10) {
      anomalies.push({
        type: "velocity",
        severity: count > 10 ? "critical" : "warning",
        description: `爆发:${count}次提交(${Math.round(diffMinutes)}分钟)`
      });
    }
  }
}

风险等级

等级 分数 含义
A 0-10 正常活动
B 11-30 轻微异常
C 31-50 建议审查
D 51-70 可疑
F 71-100 需要严格审查

视角转变:维护者维度

初期仅从贡献者角度思考,但现实让我意识到维护者视角的重要性。通过阅读相关文章,我理解了开源维护的挑战:不合理的功能请求、企业无偿使用、用户毒性行为等。这促使我增加维护者维度的分析功能。

贡献洞察

分析被拒 PR 的失败原因,帮助贡献者避免重复错误。

垃圾信息检测

const SPAM_TITLE_PATTERNS = [
  /add(ed|ing)?\s+(my\s+)?name/i,
  /update(d)?\s+readme/i,
  /hacktoberfest/i
];

function detectSpam(pr, files): { isSpam: boolean; reason: string } {
  const isReadmeOnly = files.length === 1 && 
    files[0].filename.toLowerCase().includes("readme");

  if (isReadmeOnly && files[0].additions < 5) {
    return { isSpam: true, reason: "琐碎的 README 更改" };
  }
  return { isSpam: false, reason: "" };
}

自动化失败分析

type PitfallAnalysis = {
  prNumber: number;
  mistake: string;
  reviewFeedback: string;
  advice: string;
  category: "tests" | "style" | "scope" | "setup" | "breaking" | "docs";
};
类别 含义
tests 缺少或测试失败
style 代码格式问题
scope 改动过大或超范围
setup 环境配置问题
breaking 破坏性变更
docs 文档缺失

技术挑战与解决方案

缓存安全漏洞

初期对技术栈不熟悉,犯下缓存键重复使用的错误:

import crypto from "crypto";

export function getTokenHash(token?: string | null): string {
  if (!token) return "public";
  return crypto.createHash("sha256").update(token).digest("hex").slice(0, 8);
}

修复方案:

const tokenHash = getTokenHash(accessToken);
const cacheKey = `repo:info:${owner}:${repo}:${tokenHash}`;
场景 令牌哈希 缓存键示例
公共仓库 public repo:info:facebook:react:public
用户A私有 k3m7p2q9 repo:info:userA:secret:k3m7p2q9
用户B私有 x4y9z2a5 repo:info:userA:secret:x4y9z2a5

React Hydration 不匹配

// 修复方案
const { data: session, status } = useSession();

// JSX 实现
{status === "loading" ? (
  <Box
    w="100px"
    h="32px"
    bg="#21262d"
    borderRadius="md"
    opacity={0.5}
  />
) : session?.user ? (
  <UserMenu />
) : (
  <SignInButton />
)}

推荐阅读 The Perils of Rehydration 深入了解 hydration 机制。


未来规划

需要同时平衡贡献者和维护者视角,未来计划包括:

  • 重点提交展示: 过滤琐碎更新,突出功能添加和架构改进
  • 功能请求适配性检查: 识别不符合项目目标的请求
  • 资助平台推广: 突出 GitHub Sponsors 等资助渠道
  • 项目生命周期标识: 显示活跃、维护、归档等状态
  • 常见错误预防: 基于 CONTRIBUTING.md 和 CI 失败模式提供指导
  • 贡献者分级系统: 首次贡献者、初学者、技术专家等层级
  • 全面测试覆盖: 单元测试、集成测试和端到端测试

结语

这个两周内快速开发的项目,代码质量可能不是最优的,但快速迭代有助于验证核心想法。在实际开发中,我使用 LLM 辅助完成重复性编码任务,但所有核心创意和架构决策都来自深入思考。开源项目的价值在于持续改进,希望通过社区协作,共同构建对维护者和贡献者都有价值的工具。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP