2015年8月27日星期四

也谈Shadowsocks的“关闭”风波

之前已经在Google+上零星地发表过一些看法。本文总的来说可以看作是对这些零星看法的一个汇总和系统化。已经看过的朋友可以无视。
另外,标题之所以对“关闭”二字加引号,是因为这种开源项目从严格意义上来说没有什么关闭不关闭的。项目已经被许多人fork过,Release也都还在,就算最新源代码被删了又怎么样呢?也就只是做给国安局的人看看吧。

言归正传……

这次Shadowsocks作者被盖世太保警告,然后GitHub上的项目代码被删,在IT界造成了不小的震动。除了愤怒与哀叹,还有以下几个问题/观点也被抛出:
  • 认为没什么人对Shadowsocks作贡献。
  • 把GPL开源转闭源违反协议。
说起来,这两个问题,其实是开源项目的问题,与GFW无关。原作者在删代码之前,也曾言辞激烈地作过批评。现在的这两个看法,基本上也延续了原作者的思路。不过,我倒是有一些不同的看法。

首先,要说说对开源项目的贡献这种事情。
在程序员(开发人员)看来,对开源项目作贡献,通常意义上意味着需要修改/提交代码。至少,也得明确地指出Bug的所在,可能还得需要有对应的解决方案或手段。这样,才能算是对项目作出了贡献。
但是,在我看来,那些只是默默地使用着Shadowsocks,用来配合自己的VPS完成翻墙的人,他们都对这个项目有贡献。如果测试出了错误,当然应该指出。但是还有更多没有测试出错误,没有遇到错误的使用者,他们同样可以被视为是测试人员之一。一个庞大的用户基数,是保障一个软件变得越来越成熟的基础。如果没有什么人用,即使自己或一个小团队测试并修正了很多Bug,也不能确定它是不是真的足够稳定耐用。如果有很多人使用,但是没有什么人报告Bug,那只是说明了作者水平不错,并不能说明别人不贡献。
那么,如果一个开源项目做得不好,差劲,问题多多。别人用了之后拔腿就跑,还是不肯修改或报告错误,这是不是就能说明“天朝那么多牛B哄哄的程序员不肯作贡献”了呢?
答案还是“否”。开源项目,是有自己的生态系统的。如果一个项目大家都跑,那它就应该消亡。用脚投票,也是一种贡献,不能因此责怪别人“不作贡献”。只有完全不用,这就真的是“不作贡献,但你难道把这种事情怪到卖菜小贩头上?
在我看来,之所以Shadowsocks原作者会觉得没什么人帮他改代码,恰恰在于他把这个项目做得太好用了。大家用起来都很舒服,自然不会有什么人想要去改。会想去改的人,就更可能是那些“爱折腾”却又不具备相关能力的人,所以作者才会觉得有压力。
换句话说,对于真正有能力改动代码的人,以及要求不高的人而言。Shadowsocks一开始就已经足够好了。这就过滤掉了一大群人。“劣币驱逐良币”这个比喻用在这里可能不是很恰当,不过我相信大家懂我的意思。对于一个用户数够大的软件而言,沉默的人占大多数,这是一个好事。原作者可能还没意识到这个情况。

GPL转闭源这个的确违反了许可协议,这没有什么话好说。我是在想另外一件事情:GPL到底保护了什么?
GPL要求后继项目也必须公开源代码,并且也必须采用GPL。这保证了项目代码会永远开源下去。但问题是,如果你采用GPL许可证,你是想保护你的思想还是你的代码?
由于要求比较苛刻,可能会有更多的人不愿意从GPL项目中fork,特别是那些想商业化的公司、团队或个人。这样一来,你的代码还是原来的代码,没有人来夺走,但你的代码其实是你思想的结晶,否则它们只是一堆敲击键盘而成的无意义的文字而已。如果没有人愿意fork,那你的思想也就无法被继承并发扬。
如果我想让自己的思想发扬光大的话,我会选择让自己的代码进入Public Domain。并且,只提供思路及核心代码,包括一个最简实现。至于项目最后包装成啥样,有兴趣的就自己动手。改动代码也好,只是加个壳也好,做成商业应用去卖钱也好,免费分发也罢,都可以。
本来推墙这种事情,就是需要五花八门,三教九流一齐上。你管它是志愿军还是雇佣兵。反倒是如果仅此一种工具,就很容易被GFW对付。提供一种可行的思路,比做出一个完美的实用产品,可能更合适一点。
这样的好处就是,原作者的压力小了,甚至可以不需要去维护。不需要去考虑产品化,不需要在UI上费脑筋,不需要为了不同用户的一些无关大局的小需求改来改去。这些事情,如果需要,就自然有人会去做。作为原作者,可以把精力放在更有意义的事情上——如果你有那个能力的话。

2015年8月19日星期三

蹊跷的LNK1104错误

最近在给公司开发下一代产品,所以比较忙,也没有太多的事情写Blog。不过昨天在技术上遇到一件不大不小的事情,也许值得拿出来说一下。

这个下一代产品是从头全新开发的,为此我提供了一些基础库的代码,给到项目组的其它Project使用。其中有一个是用的动态链接库的形式。为了方便其它Project使用,我把如下的预编译宏写在了头文件中:
#pragma comment(lib, "XEngine")
这样,所有需要包含这个头文件的代码,就不需要自己去搞清楚到底要链接哪个lib文件了。

但是,同事在拿到代码做全新编译的时候,出现了LNK1104错误。Cannot Open File XEngine.lib。同事当然一头雾水,Baidu上搜了一下之后,一头埋进去检查硬盘坏道去了。

我这边一开始没有问题,当把发布目录中的XEngine.lib删除了之后也出现了同样的问题。源代码对编译结果产生了依赖,这当然是不对的。问题当然出在那句预编译宏上,只不过代码的编写时间过去有点久了,我一时没想起来。
正好这个DLL具有导出函数,因此VC自动生成了相关的代码,只需要把前面的预编译宏放在这里面就可以了:
#ifdef XENGINE_EXPORTS
#define XENGINE_API __declspec(dllexport)
#else
#define XENGINE_API __declspec(dllimport)
#pragma comment(lib, "XEngine")
#endif
这样就解决这个问题了。如果没有这些自动生成的代码可以利用,也可以参考着自己写一个。