我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。

22 岁的我在想些什么

蛋黄总说我过去二十几年过得顺风顺水,想想也是,虽然有小的波折,但并没有什么让我后悔的事情。我觉得这当然有运气的成分,但和我个人的选择也是分不开的。

在大家抱怨学校生活时,我就想找一个彻底的方式来摆脱学校的限制,于是后来我退学了;在大家抱怨工作不称心的时候,我就想着一定要去一家能让我开心地工作的公司,于是我找到了番茄土豆和 LeanCloud;现在我仍在听着大家抱怨着生活中的种种,于是我也在准备着一个能够彻底摆脱这些问题的计划。

对于这个选择我的心情非常复杂,既有期待也有不安;既怪自己行动得太晚了,但也明白是遇到了蛋黄才让我有了勇气和决心来准备移民。在我们刚刚在一起的时候其实就已经有了这个想法,也在陆续做着准备,在过去的一年中,整个路线逐渐清晰了起来,我们也越来越期待成功的那一天,这将会是我下一个重要的选择。

国内就总喜欢黑高福利国家人们不好好工作、人力成本高的要死,黑民主国家办事效率低、办不成大事。但我现在越来越觉得,这也许才是一个稳定的国家的常态,充分保障个人的自由、甚至说保障人们「不求上进」的自由,平衡各方的利益,这样才能带来稳定。

随着一开始的「矜持」被放下,这一年中我们吵得确实比刚在一起的几个月要多得多。但我和蛋黄已经为未来做了这么多规划了,虽然大大小小的分歧吵闹不断,但我还是相信我们会一直走下去。年初我在知乎上回答了一个 关于完美女朋友的问题,也为自己找到了「完美女朋友」而高兴 —— 当然我现在也是。但还有一点我比较关心的是,蛋黄还没找到工作,我觉得工作是生活中很重要的一部分,我希望她也能找到一个愿意为之投入,也能获取合理的收入的工作。另外我觉得人在开始工作之后可能对于很多事情的看法会有一个比较大的转变,对这种转变我还是期待更多一点,我觉得那时她会更能够理解我的一些想法。

在 LeanCloud 工作三年了,期间也有人问我要不要考虑其他的工作机会,我想了一下,继续留在 LeanCloud 大概是因为这里的工作既「稳定」又「充满挑战」。这么说也许看似矛盾,但我说的稳定是指,这里的工作全部围绕着 LeanCloud 这个云平台 —— 更具体来说是围绕着云引擎,它本身已经是一个相对稳定的系统了,有着不小的用户量,需要以严肃的态度去对待每一个改动;充满挑战是指围绕这个大的方向,还是可以做很多有趣的尝试的,工作的方向和内容并没有受到限制,而且我会感觉到我个人的决定,对公司的产品是有着直接的影响的。

Play Cards: 探索通用的游戏后端方案

当我们公司决定推出一个「多人在线游戏后端解决方案」的时候,我其实很疑惑会有一个「通用」的方案可以解决所有在线游戏的后端需求么?

于是在前一段时间,我尝试开发了一个「多人在线卡牌对战游戏」,支持了「斗地主」游戏规则的一个子集,可以在 play-cards.leanapp.cn 访问到,源代码在 jysperm/play-cards。不过请不要指望能够匹配到其他玩家 —— 你需要自己开三个窗口,通过左右手互搏的方式来体验游戏,下面是一个演示视频:

https://streamable.com/belpq

动作同步和状态同步

其实这个游戏的关键就是在多个客户端之间来同步数据、实现联机游戏,为此我先了解了一下业界总结出的两种同步模型:「动作同步」和「状态同步」。

在动作同步(帧同步)中:

  • 客户端发送操作,服务器只转发客户端的操作
  • 游戏逻辑主要在客户端运行(通常客户端需要掌握所有数据)
  • 延迟低,适合 RTS、MOBA、FPS
  • 可以让所有客户端有一个完全一致的时间轴

游戏状态的计算必须是确定的,不能有随机数,这样才能保证不同的客户端在应用相同的一系列动作之后能够得到相同的状态(游戏画面)。

在状态同步(C/S 同步)中:

  • 客户端发送操作,服务器转发计算后的游戏状态
  • 游戏逻辑主要在服务器运行(可以只向客户端发送部分数据)
  • 易于反作弊,适合 MMORPG、回合制(卡牌)游戏

因为游戏逻辑需要运行在服务器,所以服务器程序还是必不可少的。

选型

因为这是一个实验性质的项目,因此我想同时实现这两种同步模式来进行对比。在这里我挑选了一个回合制、属于非对称博弈的卡牌游戏作为游戏的内容。

我首先实现了动作同步,在动作同步中服务器几乎不需要做什么事情,只是作为一个「消息服务」来转发客户端的动作。于是我在这里使用了 LeanCloud Play 来完成这个消息转发的工作,每局游戏对应 Play 中的一个 Room,Play 允许客户端在房间中向其他玩家广播消息。

然后我又尝试将这个游戏改为了状态同步 —— 我引入一个运行在服务器上的特殊客户端(称为 MasterClient)。这个客户端中同样连入 Play 的消息服务,其中运行着完整的游戏逻辑,它将其他真正玩家的动作作为输入,然后输出游戏状态给其他真正玩家的客户端来展示,防止客户端作弊。

动作和状态

前面我提到业界已经总结出了两种同步模式,即动作同步和状态同步,「动作(Action)」和「状态(State)」这两个概念会贯穿这篇文章。

动作即玩家对于游戏的「输入」,可以是按键、点击,或者更抽象的动作,例如在这个游戏中我定义了两种动作 —— 出牌和放弃出牌:

type GameAction = PlayCardsAction | PassAction

interface PlayCardsAction {
  action: 'playCards'
  player: Player
  cards: Card[]
}

interface PassAction {
  action: 'pass'
  player: Player
}

状态即用来表示整个游戏局势所需的所有数据,例如在这个游戏中:

export interface GameState {
  players: Player[]
  playersCardsCount: PlayersCardsCount

  myCards: Card[]

  previousCards: Card[]
  previousCardsPlayer?: Player
  currentPlayer?: Player

  winer?: Player
}

游戏抽象

有了动作和状态的概念,我们就可以对游戏进行一个抽象了,我设计了一个 Game 类,是对一局游戏整个生命周期的封装,这个类将会同时运行于客户端和服务器:

// 事件:action(当前玩家的动作)、stateChanged(游戏状态变化)、error
class Game extends EventEmitter {
  constructor(seed: string, players: Player[])

  // 获取游戏状态(供 UI 调用)
  public getState(player: Player): GameState
  // 设置游戏状态(状态同步时),会触发 stateChanged 事件
  public setState(player: Player, state: GameState)

  // 当前玩家执行动作,会触发 action 事件
  public performAction(action: GameAction)
  // 应用其他玩家的动作(动作同步时)
  public applyAction(action: GameAction)
}

在客户端中,当 UI 捕捉到用户的输入时,执行 Game.performAction,动作(Action)的执行会改变状态(State),触发 stateChanged 事件,UI 收到这个事件后根据新的游戏状态来重绘 UI。

至于其他游戏逻辑则主要是关于「一组牌能够管得上另一组牌」的判断,在此不再罗列。

结构

在这个游戏中,我们将消息转发(由 LeanCloud Play 提供)视作一项服务、视作一个中心。所有玩家的客户端都连接到消息转发服务上,同时每局游戏我们还需加入一个运行在服务器上的 MasterClient 来提供特殊的管理能力。

为了将两种同步模式做成可简单替换的,我其实将服务器程序作为了一个必选组件(master-client 目录),但这个服务器程序在动作同步中只是负责创建房间(可以移到客户端),并不参与游戏逻辑。

我的代码分为 3 个部分:

common
├── game.ts
└── types.ts
browser-client
├── app.tsx
└── client-sync.ts
master-client
├── server-sync.ts
└── server.ts
  • common 部分会同时运行在服务器和客户端,包含游戏的核心逻辑
  • browser-client 是运行在浏览器中的客户端,包含 UI
  • master-client 是运行在服务器端中的 MasterClient

动作同步

我首先实现的是动作同步(client-sync.tsserver-sync.ts 中的 actionSyncController),这种模式下客户端发送动作(Action),服务器只转发动作,游戏逻辑主要在客户端运行,客户端掌握所有的数据(包括其他玩家的手牌)。

客户端的工作:

  • game.on('action') 时(表示用户在 UI 上进行了一个动作),通过 Play 将动作广播给其他客户端play.sendEvent(action)
  • play.on('customEvent') 时(表示收到其他客户端广播的动作),应用其他玩家的动作 game.applyAction(action)

而服务器端几乎没有什么工作,只是帮助客户端创建一个房间而已。

状态同步

在状态同步(client-sync.tsserver-sync.ts 中的 statusSyncContorller)中,客户端发送动作(Action),服务器运行游戏逻辑后,转发计算后的游戏状态(State),游戏逻辑主要在服务器运行,客户端只做展现,只知道自己的手牌。

客户端的工作:

  • game.on('action') 时(表示玩家在 UI 上进行了一个动作),通过 Play 将动作单独发送给 MasterClient play.sendEvent(action)(需加参数 {receiverGroup: ReceiverGroup.MasterClient}
  • play.on('customEvent') 时(表示收到 MasterClient 广播的最新状态),从服务器覆盖游戏状态 game.setState(state)

MasterClient 在服务器的工作:

  • 为每局游戏创建一个 Game 对象。
  • play.on('customEvent') 时(表示玩家进行了一个操作),在游戏对象上执行动作 game.performAction(action)
  • game.on('stateChanged') 时,给每一个玩家发送最新的游戏状态 play.sendEvent(state)

复用代码

在完成这个实验性质的项目后,我们需要来思考一下,哪些代码是可以复用的。

  • Game 类为一个游戏的过程提供了一个非常基本的抽象(即通过动作去改变状态),也为数据同步提供了基础的支持(action 事件和 stateChanged 事件)。
  • MasterClient 中对房间的管理是通用的,并且还有很大的改善空间,比如支持多实例允许并实现负载均衡等。

我将我使用的这种开发多人在线游戏的方式称之为 MasterClient 模式,它的好处是:

  • 在服务器和客户端之间复用大部分的游戏逻辑 如果你像我一样使用 JavaScript 的话,那么可以在服务器和客户端运行完全相同的代码。
  • 单机游戏 => 动作同步 => 状态同步 的迁移过程非常平滑 只要一开始能够区分好动作和状态,那么这个迁移的过程中只需改动少数代码。
  • 服务器端的游戏逻辑和消息转发服务解耦 消息转发服务可以更加稳定;MasterClient 则可以更快速地迭代。
  • 符合开发者直觉 至少我作为一个游戏开发的小白是觉得挺符合直觉的

Play & Client Engine

在我完成这个项目的过程中,我也不断地与公司的同事保持着沟通,经过几个月的努力,LeanCloud 在 Play 基础上发布了 Client Engine —— 一个用于托管 MasterClient 的容器平台(类似于云引擎),同时我们也提供了一个项目骨架,集成了我前面提到的功能,帮助开发者实现服务器端逻辑:

  • 游戏抽象 提供了一个类似的 Game 类来帮助开发者管理房间和玩家、填充游戏逻辑,在 Play 的基础上提供 RxJS 风格的高层次 API 来操作游戏动作和状态。
  • 负载均衡 提供了一个 GameManager 类来管理房间的创建,允许 Master Client 以多实例的集群模式运行,以便进行横向扩展,消除容量瓶颈。
  • 平滑部署 当你部署新版本的时候,旧实例会等待已有的房间完成游戏再退出,你可以在任何时候部署新的版本而不必担心影响用户的游戏。

我的这个项目早于 Client Engine 成型,是对通用游戏后端的一个实验。如果你希望编写类似的游戏,请阅读 Client Engine 的文档,并基于 Client Engine 的脚手架来进行开发,而不要直接基于本项目修改。

小结

本文探索的是一种「通用游戏后端」的解决方案,在这个小项目中我们用到了两个 LeanCloud 的服务:Play 和 Client Engine。

  • LeanCloud Play 扮演的是一个「消息转发服务」,它会维持与所有客户端(包括 Master Client)的长链接,允许客户端之间广播或单发消息。
  • LeanCloud Client Engine 提供了一个可信的服务器端环境来运行客户端 —— 在本文的例子中是 MasterClient,可以将游戏逻辑运行在服务器端来实现反作弊。

借助这两个服务,我们将对游戏服务器的开发需求降到了最低,只需编写运行于服务器端的游戏逻辑即可,而不必关心链接的保持、消息的转发和服务器环境和扩容等问题。

自由软件和开源许可证

本文整理自 彩排 中的一期播客。

什么是自由软件

我总结了三点自由:

  • 出于任何目的、以任何方式 使用该软件的自由(包括商业用途,一些商业软件限制你不得进行破解、不能用于犯罪活动就是在限制你使用的方式或目的)。
  • 修改软件的自由,当然修改软件的前提是你要能够得到源代码。
  • 重新分发的自由,不仅是分发原始版本,你也可以分发修改后的版本。

只有符合这三条才可以算自由软件,有一些只开源代码(可能是为了安全审计,或者只限非商业用途)的软件并不能算自由软件。

我们经常看到的 GPL、Apache、MIT 都属于自由软件许可证,这些许可证会在前面的三项自由之外附加一些限制条款,但这些限制条款与自由软件中的自由并不冲突。

Copyleft 的 GPL 家族

在自由软件许可证中,有一类特殊的被称为 Copyleft 的许可证(Copyleft 是一个与 Copyright 相对的词,代表着一种具有「传染性」的自由),其中最知名的就是 GPL 家族了,我们接下来会聊到其中的 GPL、LGPL 和 AGPL。

Copyleft 的特点在于它有一种理想主义的野心 —— 它希望这个世界上所有的软件都是自由的,因此这些许可证都是具有「传染性」的。它们在自由软件的基础上附加了一个最主要的限制条款:在重新分发 Copyleft 的软件时,必须授予用户同等的自由、也必须附加同等的限制条款。在实践中一般就是说,你修改一个 GPL 的软件后,你再发布的时候也必须是 GPL,其他人修改了你修改后的软件,也必须是 GPL,这就是所谓的传染性。

自由软件社区中有很多使用 GPL 许可证的软件,例如我们熟知的 Linux、WordPress 等等。说到 WordPress,现在有很多公开出售 WordPress 主题或插件的社区,但按照 GPL 的许可证,主题或插件中的 PHP 代码显然属于衍生作品,必须使用 GPL 许可证来发布(而 CSS 和 JavaScript 可以通过单独以其他协议发布来规避),所以理论上,任何一个人购买了 WordPress 主题,那么他都可以随意地再次出售这个主题(这是 GPL 授予他的权利),在 2013 年我便就此话题写过一篇 文章

GPL 中的衍生作品

我们再来看 GPL 的限制条款,即「再分发衍生作品时必须授予用户同等的自由」,这里有两个词可以单独拿出来讨论一下,「分发」和「衍生作品」。

所谓 分发 就是说你把这个软件(包括修改后的版本)发送给其他人使用,例如你开发了一个桌面软件,当你给用户提供下载时,这就是一种分发行为;当你提供一个库(无论是源代码还是二进制版本)也是一次分发行为;甚至包括浏览器中的 JavaScript 也算分发行为 —— 因为用户的浏览器需要从你的服务器下载 JavaScript.

GPL 为了保证用户有修改软件的自由,要求你在进行分发时同时提供源代码。而分发可能是公开的,也可能是小范围的,如果你在网络上公开发布一个软件,那么你也必须公开发布源代码;但如果你只是私下发给一个朋友,或在公司内部分发一个内部软件,那么你只需要向这个朋友,或者在你的公司内部提供源代码。所以说「分发」和「提供源代码」的行为是挂钩的,你向谁分发就向谁提供源代码。

所谓 衍生作品,GPL 认为只要你的项目中用到了 GPL 的代码,都可以算衍生代码,无论你是把 GPL 代码作为一个主要功能,还是用来做一个非常细枝末节的功能。而且 GPL 感染的对象是你的整个软件,也就是说只要你的项目用了 GPL,那么整个项目都被感染成了 GPL,你在进行发布时需要提供整个项目的源代码。

这样听起来 GPL 的确很流氓,那么具体到条款上,GPL 什么情况下会感染你的项目呢?我总结了一下,除非你采用以下三种隔离方法中的一种,将 GPL 代码与你的项目进行了隔离,否则都会被 GPL 感染:

  • 内存地址空间隔离,即如果你的代码和用到的 GPL 代码不共享内存地址空间,例如分别属于两个进程(操作系统帮你做了内存地址空间隔离),或者分别属于内核态和用户态那么就不算衍生作品,例如 Linux 并不会感染运行在 Linux 上的程序。
  • 代码和数据隔离,即如果你的代码和 GPL 的代码是分别作为代码和数据被看待的,那么不算衍生作品。例如用 GCC 编译你的程序是不会感染的,因为在编译时,你的程序是作为数据被看待的;同理,用 Emacs 编写代码也不会感染被编写的代码。
  • 指令计数器隔离,这个是针对虚拟机的一种例外,像 JVM 这样的运行环境,实际上是虚拟了一个 CPU 去执行其上的 Java 操作码,这个虚拟的 CPU 实际上有着和物理 CPU 不同的指令计数器(即 CPU 用来记录当前执行位置的寄存器),因此 JVM 不会感染运行在其上的 Java 代码。

注意这里的例外并不包括静态链接和动态链接,因此如果你静态链接一个 GPL 的库也是会感染你的整个项目的;至于动态链接则目前还存在争议,GNU 极力认为动态链接也在感染的范围内。

LGPL

除了 GPL 还有一个叫 LGPL 的许可证,它是一个比 GPL 宽松一些的许可证,LGPL 中将衍生作品的范围减少到「同一个编译单元」,允许用户在使用了 LGPL 的静态链接库时,仅将 LGPL 代码所属的编译单元开源,所谓编译单元在 C 这样的语言里就是一个文件,若干个编译单元会在最后被链接到一起,形成一个可执行文件。

但如果推广到一些并没有明确区分编译和链接环节的基于虚拟机的语言(例如 Java 或 JavaScript)中,则还有一些争议,一般是认为一个包属于一个编译单元。

LGPL 被广泛地使用在依赖库中,例如 Qt 和 FFmpeg 都使用 LGPL 许可证,但实际上作为 GNU 之父的 Richard 后来并不建议大家使用 LGPL,而是建议大家直接使用原版的 GPL。

他在一篇题为「Why you shouldn’t use LGPL for your next library」的文章中说,非自由软件的开发者往往都有很多金钱上的优势,我们这些自由软件的开发者必须团结起来以便得到某种优势来和他们对抗。如果我们发布的库都使用原版的 GPL,那么我们这些自由软件开发者将获得一种巨大的优势 —— 我们可以使用这些库,但那些非自由软件开发者却不能。

所以后来 Richard 将 LGPL 的全称从 GNU Library License 改为了 GNU Lesser License,并且不建议大家在新的项目中使用。

GPL 在法律上的现状

GPL 在法律上的现状并不乐观,很多时候 GPL 是一种君子协定,很多人经常是基于道德,或者很多公司是基于声誉的考量才去遵守 GPL。虽然 GPL 许可证本质上是一种合同,将代码的使用权有限制地授予愿意遵守这个限制的人。但 GPL 的主要诉求是「开源代码」,而即使赢得诉讼,(至少在中国)法院也不会帮你强制执行「开源代码」这一判决;因为通常法院只能强制执行经济类的判决,如果你要求经济上的赔偿的话,因为 GPL 中并没有写明不遵守许可证的情况下的罚金,因此你可能需要证明被告不开源这一行为给他带来了多大的利益或者给你造成了多大损失,注意并不是他使用你的代码得到的利益,而仅仅是不开源这一行为的利益。

尤其很多开源项目并没有精力去打官司,一部分开源软件可能只是会在网站上登出一个「耻辱柱」的页面,列出哪些项目或公司使用了他们的代码却没有遵守 GPL 许可证,例如 FFmpeg 的网站

AGPL

接下来我们来介绍 GPL 家族的另一位成员 AGPL,AGPL 是一个更严格的 GPL 协议,代表性的项目是 MongoDB —— 一个非常知名的非关系型数据库。考虑到现在是互联网时代,很多公司会将 MongoDB 之类的开源软件进行一些修改或封装,然后提供基于 MongoDB 的云服务来盈利。于是就有了 AGPL 许可证,AGPL 将分发的行为扩展到了「通过网络提供服务」,之前的 GPL 对于服务器端应用是没有什么约束的,因为服务器端应用并不会发布给最终用户,但如果你使用了 AGPL 来通过网络提供服务,那么你也需要开源你对其的修改。

就在发文前,MongoDB 宣布新版本将会切换到 SSPL,这个协议是对 AGPL 的进一步细化,规定了若提供主要价值来自于 MongoDB 的云服务,就必须开源能够让用户运行起来这个服务所需的所有组件,包括用户界面、API、自动化、监控、备份等组件。

非 Copyleft 许可证

前面花了很多篇幅去介绍 GPL 家族,的确他们是自由软件中最有趣也最优代表性的部分。但现在一些新的开源软件往往会选择一个非 Copyleft 的许可证,例如 Apache、BSD、MIT 等等,不过这些协议的条款相对简单,一句话便可概括。

其中 MIT 许可证仅仅要求用户进行署名,所谓署名是你必须将用到的自由软件代码的许可证附带在你分发的每份软件中,并且让用户能够看到,例如在很多软件的某个隐蔽的角落,你可以看到一个叫「开源许可证」或「法律信息」的按钮,点开可以看到它用到的所有自由软件的许可证。

当然你也就可以连署名都不要求的话,你可以选择直接将你的作品释放到共有领域,例如 SQLite(一个嵌入式关系型数据库)就直接被发布到了共有领域,你可以用它的代码做任何事情,连署名都不需要。

BSD 会稍微严格一些,除了要求署名之外还要求用户不得使用其名字用作宣传的用途,例如你基于一个 BSD 协议的软件发布了一个修改后的版本你必须为它起一个新的名字,和原有项目做出明确的区分,以免误导其他用户。

而 Apache 许可证在 BSD 的基础上还要求你在发布修改后的版本时明确注明对哪些文件、做了什么修改。

这里需要注意的一点是这些许可证的授权往往是 不可撤销 的,一旦你曾经以某个许可证发布了一个项目,那么任何人都可以按照你当时发布的那个版本的许可证去使用你当时发布的那个版本的代码。当然你可以从某个时间点开始以新的许可证来发布代码,但其他人依然可以选择遵守旧的许可证去使用旧的版本。这一点是写在大部分自由软件许可证中的条款,是出于保护自由软件代码的使用者的考虑,否则如果你可以随时收回你的授权,是没有任何人敢去用你的代码的。

Creative Commons

前面我们提到的的都是针对于代码的许可证,但有的时候我们也希望授予文本作品的读者一些自由。我接下来介绍的这个「创作共用许可证」就是这样的一种许可证,通常缩写为 CC.

CC 许可证将文本作品的作者常见的几种诉求总结成了四点:

  • BY 署名
  • SA 相同方式共享,所有的衍生作品都必须使用同样的许可证,类似于 GPL 的要求,但因为文本作品的衍生关系相对简单,所以可能并没有太多人关注它的传染性
  • NC 禁止商业使用
  • ND 禁止演绎

这四种诉求可以被组合到一起,例如我的博客选用了 CC 署名、非商业使用、相同方式共享发布。也就是说你可以非商业的目的自由地使用和分发这篇文章,需要提供指向 caipai.fm 的链接,如果你修改了文章则必须也允许其他人以相同的方式转载和修改。

CLA

我们还会看到一些软件同时是自由软件(比如 Qt 和 MySQL),也提供了商业授权,如果你购买了他们的商业授权,那么你并不需要遵守自由软件的许可证,这是如何做到的呢?

首先,一个作品的作者是有权决定如何把这个作品的使用权授予其他人的,他可以即发布一个面向所有人的自由软件许可证的版本,当然这个自由软件版本其实也是有限制的;他也可以以单独的、有着完全不同的限制的方式来把使用权授予其他人,商业授权就是这样实现的。

但很多社区开发的自由软件,例如 Linux,一旦有来自社区的贡献者参与了进来之后,最初的发起者就没有权利去修改许可证或发行商业版本了,因为这时已经没有人拥有这个软件的所有的代码的所有权利了,因为这个软件的不同的部分的代码来自不同的贡献者,他们只是以这个项目的自由软件许可证发布他们的代码,所有人都必须遵守这个自由软件许可证,而显然将一个 GPL 许可证的自由软件改为其他非 Copyleft 许可证是不行的,因为这违反了 GPL 许可证的要求。

所以在实践上,很多社区开发的软件会要求贡献者在提交代码之前签署一份协议(通常缩写为 CLA),这份协议的主要内容有两点:

  • 贡献者授予软件的维护者有关此次贡献的代码的永久的、无限制的使用权(而不仅仅是按照自由软件许可证授权)。
  • 贡献者声明自己拥有所贡献的代码的相关权利(而不是从另外一个不兼容的许可证的项目、或非开源项目中拷贝的)。

如果一个社区开发的软件一开始就这么做,那么第一条保证了软件的维护者始终拥有对所有代码的没有限制的使用权,这样项目的维护者才有可能修改许可证或者发行商业授权;而第二点我个人认为只是为了在出现著作权纠纷时,规避一些法律上的问题。

拓展阅读:

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2024-04-08.