Chaofan

For the next train

作者: qcf

  • Siri Shortcut 初体验

    Siri Shortcuts 始于之前的 Workflow。实话说,自己当时对 Workflow 的印象属于鸡肋,由于 iOS 系统的限制,很多功能做不到,也需要在 Widget 去启动。相比 JSBox 的直接明了,功能确实差了不少。而自己却连要用 JSBox 做什么都没想明白,更勿谈 Workflow 了。因此,尽管苹果收购了 Workflow,我也没有对这个应用抱有太多注意力——手机上能做到的所谓效率和自动化实在太有限了,在这上面刻意追求有些错了方向。同样的,还有 IFTTT 这样的应用,不过它的着重点和 Workflow 似乎不太一样,有许多 Applet 是完全不依赖本机的。(比如每个 Google Voice 用户都会尝试的保活 Applet)

    今年的 WWDC 推出了 iOS 12,其中将原来的 Workflow 和 Siri 整合到了一起,作为一个新的系统功能叫做 Siri Shortcuts。当然,还是需要额外安装。我一直觉得,Siri 是个有些无用的东西。(当然了,所有的语音助手在这一点上都没什么根本区别,而 Siri 的语音合成还明显不如微软的 Cortana)今年 Google 在开发者大会上用语音助手自动点餐的 Demo 震撼了许多人。而苹果终于选择放下曾经的固执,朝着另一个方向,在 Siri 有根本性的突破之前,走一走 Amazon Echo 的路子,让用户自己定义自己的语音助手。

    把 Siri 和 Workflow 结合起来,意味着你可以自定义语音指令,驱使手机去帮你做一些事情。这无关所谓真正的「智能」,不过这确实让它变得更加实用了。另外,Workflow 变成 Shortcuts 之后,显得更加官方化,有更多苹果生态下的 App 开发者主动为它提供了接口,这也使得它的功能边界更拓展了一些。

    这里是个有趣的例子,我们想要检查一个人有没有发布新的微博。不想每次都点开,甚至像我这样把微博 App 删除了的人连特别关注都没得设置。我们想用 Siri 做到这一点。就以@带带大师兄为例吧。

    新建一个叫做「孙狗微博」的 Shortcut,在里面随便设置一个喜欢的颜色和图案。

    New Shortcut

    点击进入编辑内容。首先我们需要一个信息源。Shortcuts 有 RSS 的组件,但微博并没有给我们提供官方的 RSS 接口。或者可以在自己的一台 VPS 上写一个脚本定时刷新内容然后暴露一个 API 出来,但这样也太麻烦了。Shortcuts 支持在给定页面上运行一段 JavaScript 代码并返回结果,这个不错。不过这里发现了更好的选择,即微博的 API。这个 API 是从网页调试工具里发现的,带带大师兄的微博内容在这里,以 JSON 格式呈现。

    我们需要的字段主要有两个,一个是最新一条微博的发布时间,一条是最新一条微博的内容。方便的是,微博已经帮我们处理好了发布时间的可读性,近在 3 小时前发布的微博,结果会显示为 3 小时前而不是具体的时间。

    JSON Format

    好了,我们现在可以使用 Shortcuts 的 URL 组件,将字符串「转换」为 URL 对象。然后用「Get Contents of URL」获取内容,注意是 GET 方法。这个的结果就是这个 API 返回的字符串了。

    URL Component

    接下来需要获取指定位置的数据,在 data/cards/2/mblog 中。我们先将之前的字符串利用「Get Dictionary from Input」转换为 Dictionary,然后一步一步拿到数据。注意,这里的 List 索引是从 1 开始的。

    Get Dictionary from Input

    mblog 这里的时候,因为我们要用两次,所以先保存到一个变量中,用「Set Variable」,这个组件的输出就是变量的值。

    Set Variable

    于是乎就可以把时间先念出来了,用「Speak Text」。我手机的语言是英文,所以这里的「Language」不能为默认,而是要指定成中文,不然 Siri 会念不出来。如果系统语言是中文的话应该不存在这个问题。

    Speak Text

    接下来也一样,先取出之前保存下来的 mblog 的值,因为 JSON 里的原结果是 HTML,所以要先转换一下,不然 Siri 就会把标签符号一个一个念出来了。话说,这里还有富文本、HTML 和 Markdown 的相互转换工具,很有用。

    https://i.e7s.me/4a510892-b64b-4e5c-a17a-41bacc500192.jpeg

    最后我们在设置里指定一个语言指令,比如「孙狗发微博了吗」。

    Text Command Content

    看起来运行得不错。

    Demo

    自 iOS 12 正式版发布以来,有很多应用都专门加入了针对 Siri Shortcuts 的更新。在选择组件的界面,我们也可以看到有许多应用提供的接口,包括我手机并没有安装的 Ulysses 和 Drafts。结合应用的 x-callback-url,能做的事情可以很多,只要有足够的想象力。当然,像我们这里这样简单的流程,已经需要比较复杂的定义和操作。如果真的有更复杂的需求,还是用 JSBox 和 Pythonista 这样的应用好一些。不过,目前这两个应用还不支持从应用返回结果,只能执行特定脚本然后返回输入。希望未来他们能够做得更好。

    最后,我把这个 Shortcut 放到了 iCloud 上

  • 高考小记

    不知不觉,已是高考结束的第四年了。当初一起参加高考的同学一个个都开始分享自己穿着学士服的毕业照。有些还认得出,有些已经换了个样子。大概知识的冲刷都让大家变得有了内涵。不过我们几个男生,不管讲究的还是不讲究的,从照片看上去还是一个样。倒也只能安慰自己,男性往往成熟得更晚一些。大二的时候还会看到有同学自信地穿着不合身的西装,似乎现在都意识到了,暗示自己是学生会干部,也不过是在朋友圈里多拿几个赞而已。

    对于四年前那场考试及其前后的经过,记忆早就变得模糊,也不知道是身体机能在下降,还是时间真的可以抚平痕迹,不光是自己作留下的短暂伤痕。刚上大学那会,大概每隔个礼拜睡觉就会遇到「回去高考」的噩梦,有时候还附带走错考场这样的大礼。后来慢慢就没有了。直到最近,偶尔又开始陷入这个奇怪的梦境中难以脱身。联想自己最近的心理状态,我才意识到梦的逻辑未必那么简单。辛苦也好,不公平也好,高考都不是一场单纯的灾难,也不是压力的重点和释放的起点,十二年以来的课堂在我们心中种下了强烈而又时常不自知的暗示——高考是一场改变,或者说,革命,如果你愿意把人生看作一段历史的话。它可能将你带上高峰,也可能推你坠入深谷。安居乐业的人民是不会革命的。所以当自己不满现状的时候,高考之火又会在熟睡的深夜燎原。尽管高考早已结束,但它实在是和「改变命运」四个字形成了过于深刻乃至本能的心理关联。也因如此,许多人高考期间并不沉闷忧郁——大学能有高三苦吗?其实,中考前我也有过类似想法。

    在网络上,以高考为关键字搜索,去除大量有关辅导和招生信息有关的材料,会发现它连带着太多人的故事。大片的文字叙述了不同的高考经历,以及他们的人生如何因为这总和不到十个小时的考试而转折。再想一想肯定有更多的人没有讲出自己的经历,不禁窒息。同样具备大量信息量和故事性的关键字,我只能想到一个,即汶川地震,但沉重许多。汶川地震十周年的时候,我在网上看到一个视频,是地震发生时,成都某高校一学生在宿舍内拍下的场景。我无法确信他那位躲在桌下喊「老婆我爱你」的室友是否已经和同样的人结婚,不过我有足够理由认定,他们今天都还活着。他们现在在哪呢?这问题值得思索,因为当我写作时在图书馆坐在我旁边的同学,我也有足够理由认定他十年后还活着。我会去想他的故事吗?或者说,许多时候我们也不会思考,自己所处的位置,十年前是谁在做什么呢?高考如是。如果当初一念之差,我猜对了砾石到底是在大河的凸岸还是凹岸,是不是我真的就不在这里了?

    不过也不用这么惆怅。尽管我的去向像个随机事件,我还是相信无论我的结果如何,2018 年的社会还是今天这个样子。所以,即使高考结果相异,我大概只是在另一个地方逛 V2EX,帮另外的人做网站而已。其实更叹息的,是时间的消逝远远快过自我的体会。说好的大好时光,说好的未知远方,到今年 6 月都应该画上句号。一切历史都是当代史,谈古无非为了讽今,如果自己对现在的自己满意,「此间乐,不思蜀」的话,怎么会追忆当年呢?

    尽管在高考后的几天乃至几周里,许多人都会纠结自己那道简答题是不是正确,机读卡有没有填错。但是,再过一段时间就会发现,真正在乎高考题目的,只有又从头开始教高一的那群老师。高考体系的某种荒谬也在于此——如果它真的应该重要到决定你的人生,又怎么会至于过段时间你就忘得干干净净?好像也不全是,每次把碎玻璃扔到垃圾桶的时候,我都会想起那个突降暴雨的上午,想起迄今为止吃过最舒服的糖醋排骨。

    有时候,不高考的人反而比高考的人更认为这是场残酷的修行。想一想,在大学里,越来越难以回到考前所在的奇妙的平衡状态,又紧张,又有稳固知识支撑的自信。在事情变得随意,前途变得迷茫,心情变得焦虑的今天,应该很多人都会回想起几年前,有远虑无近忧的惬意下午吧。每一件小事好像都很有趣,都让自己想起来不虚此行。

    不过,我还是不同意「高考是人生的必修课」这句话。混淆因果是人的本性。我们所怀念的、叹息的、回忆的,都是高考这个体系下一群十多岁的少年聚集起书写的故事,不是四张考卷本身,不是匆匆依赖一个分数决定这件事本身。教育问题也是社会问题,不存在全面完美的答案。上大学之前不能理解什么叫「教育资源」,后来意识到,是资源就是稀缺的。有很多人,可能跟我一样,批评高考批评应试教育但还认为这个体系会继续存在,原因是,被坑了才发现大学教育问题更多,更需要资源,更应该改变。

    所以,偶尔有那么一瞬间也会回忆高考,回忆高中。别以为我是自虐,我只是想重来。

  • 说说隐私

    隐私很重要。每个人都知道。可什么才算隐私呢?小时候和同学课上传的小纸条、学期结束给老师的评语、自己拍摄的照片……给定一个事物,我们往往能对它属不属于隐私作出判断。但是对于「哪些是我们的隐私」,我们好像并不如「我家里都有哪些人」清楚。

    好吧,按照惯例,先来看看隐私一词有无相对准确权威的定义。

    任何人的私生活、家庭、住宅和通信不得任意干涉,他的荣誉和名誉不得加以攻击。 人人有权享受法律保护,以免受这种干涉或攻击。

    此定义来自于《世界人权宣言》,亦算作是各国政府制定相关法律时需参考的材料。宣言起草于 1948 年,时值二战结束,各国都已承受过人权被肆意践踏的惨烈后果。然彼时尚无成体系的数字信息存储,更无如今社会无孔不入的网络通信。因此在数字时代,隐私的外延已经有了发展变化。而巨头和权威对个人数字化隐私的监视,也让更多的人重新思考如何用新的方法保护隐私。

    数字化时代的特点

    隐私的本质保护的是一种信息,数字化时代的信息生产、保存、传播方式都有了显著变化。因此要分析数字化时代的隐私,先考虑一下数字化时代信息的特点。

    • 零复制成本。说信息没有复制成本可能不准确,不过用旧的眼光打量现代的信息复制,就会发现实在是太容易了。基本上,只要能获取到一份源数据,复刻出千份万份然后分发,不过是点点鼠标就可以解决的问题。当然在商业用途上,已经有了包括 DRM 以及 Netflix 防截图、中心服务器验证等技术在内的版权保护方案。然而个人的信息与这种商业批量销售的数字内容存在本质区别。
    • 传播路径长。一个信息要从一处到达另一处需要经过活跃在互联网协议栈不同层的中间节点,这些节点都有可能篡改、保留信息。
    • 终端结构复杂。多数人生产信息和连接互联网使用的都是个人电脑及移动计算设备(平板电脑、手机),这些设备大部分由特定商业公司开发并封闭源代码,不排除留有后门的可能,并且存在被恶意程序攻击的危险。
    • 数据在云端。出于同步方便或者本地储存空间的限制,越来越多的人选择将数据存储在服务商提供的云存储空间当中,并利用其提供的额外功能进行分享、编辑等操作。这些数据通常并不能保证被严格加密和保护。
    • 社交化蔓延。同传统软件是纯粹的生产力范畴相比,数字化时代的软件往往带有社交属性,一个人在社交圈子中几乎不可能完全避开这类软件。

    而在国内的网络环境下,信息的保存和传播还具备一些额外特征。

    • 匿名性弱。现行几乎所有国内网络产品账号都需要直接或间接绑定手机号码,而手机号码又严格实名。部分网络应用还要求提供额外的身份证及真人照片。
    • 体系封闭。许多国内服务商对 Web 网页端抱有抗拒的态度,部分服务甚至完全不提供网页版本。
    • 容易终止服务。许多云端服务的用户都面临着服务终止的危险,费用难以退回尚是小事,数据导出的周期也很短,甚至出现无法追回数据的情况。当然,国外也有许多例子。不过由于国内网民的消费习惯,个人云服务往往只是作为互联网巨头的边缘产品,少有专做云服务的企业,因此这些服务被「战略性放弃」的可能性比较高。
    • 泄漏风险高。国内的个人信息泄漏和买卖已经形成了一条完整的灰色产业链,用于推销、诈骗等领域。如果个人相对私密的信息泄漏且形成闭环,造成的社会风险极高。

    个人资料的保护

    信息本身是没有意义的,要被特定的人利用之后才能体现出特别的价值。每个人在生活当中都会产生或获取大量的信息,而这些信息的特性也各自不同,因此不太可能一视同仁地去保护。

    信息保护的本质

    那么,要保护,首先需要厘清保护信息这件事的内涵是什么。

    • 保证信息不能丢失
    • 保证信息不让不需要的人获取到
    • 在需要时,能够将信息安全地传递给另一方

    第一点其实没有什么好说的,在不考虑存储介质物理特性的情况下,保证信息不丢失的惟一做法就是备份。从 RAID 1 到 RAID 5 再到普通的硬盘复制,都是行之有效的备份方式。而对于云服务的使用者来讲,一个稍靠谱的服务提供商在数据冗余这方面都会比普通用户更加重视。只要不是在有意删改数据的情况下,一般不需担心数据丢失的问题。而像 iCloud 一样的云服务多数用作同步,在每台设备都留有一份副本,完全丢失的风险更小。

    这里需要考虑到数据是否属于常改动内容。如果纯粹出于归档的目的,放在自建阵列或者备用盘中更为稳妥;否则,就很不方便了。

    第二点和第一点实际上存在冲突。如果每一个介质都有一定概率被其他人窃取到数据,那么多个备份会让这个概率大大增加。好在信息技术为这一点提供了一条方便的方式,那就是加密。主要的操作系统都提供了可靠的磁盘加密方式,保证磁盘被第三方窃取之后,在没有密钥的情况下,数据不会被获取。现代的文件系统也提供了对单个文件或目录加密的功能。考虑到可移植性,也可以用 ZIP 等压缩格式带有的加密方式或者利用 OpenPGP 软件进行密钥加密。合理运用这些加密方式,基本可以抵御存储器意外丢失造成的数据泄漏风险。当然,早期 Google Chrome 隐私模式会提醒用户「注意你身后的人」。如果想让数据在被检查的时候还能匿迹隐形,做法无非是将内容混淆在普通的文件当中。这不是本文想探讨的主题。

    第三点本质上也是第二点的延续,不过面临的情况更加复杂,需要的应对方法也更加多。说到底,要解决这个问题,方法依然是加密。常规场景下,网络使用者接触到的无非是应用层级别的加密,以 HTTPS 为主。而新版本的 iOS 对于应用的网络请求强制要求使用 TLS 加密。对于浏览网页来说需要注意的就是不要胡乱添加根证书,适当时候需要删除本地中保存的不合适的根证书,以及及时更新浏览器。这里推荐 HTTPS Everywhere 这个浏览器插件,目前在 Chrome 和 Firefox 上都能找到。而如果你架设了一个网站,也请尽量申请一个免费的 TLS 证书,并配置好服务器,避免心脏出血等漏洞。另外一个场景可能是,在某些情况下,需要模拟 TLS 在一些不安全的应用上(比如电子邮件)传输加密的信息,解决这个问题的工具就是 OpenPGP 了。在网页端或者服务器端可以使用 OpenPGP.js 库实现生成密钥、加密、解密、签名等操作。在本地则可以使用 Gnu Privacy Guard (GPG) 完成这项任务。如果使用 Thunderbird 作为电子邮件客户端,可以利用 Enigmail 插件自动完成邮件的加解密和签名。这样就可以做到端到端加密了。不过对于电子邮件,需要注意的是标题是不会被加密的。

    一个重要问题是,在上述讨论的第三点中,与自己通讯的人是否是安全的?这实际上是很多人对加密技术产生的误解。加密只能保证你的信息安全地从你本地传输到了另一个地方不被第三方破解或监听,但你没有任何方式确保这个通讯者不会出卖你的信息。这实际上和加密技术没有任何关系。而在某些时候,这个信息是不得不交出去的,这是后面要讨论的内容。

    将隐私信息分级

    尽管对于普通人,一些简单的额外加密就可以保护自己的信息免受各种风险威胁。然而对不同的信息进行分类考虑和存储仍然是有必要的。一些数据泄漏可能仅会让你接到几个推销电话,而另一些则事关身家性命。

    • 极度危险并私密的信息。这些信息和数据不应该被放到网上,应该经过严格的加密后储存在安全的本地存储设备上。如果有特别的危险,这些数据甚至只应该牢记于心,或用只有自己才知道的方式混淆于普通文件中。
    • 私密但需要同步的信息,比如照片。这些数据可以放在自建的私有云服务上并经过加密存储,并且同步到备份硬盘或者加密后同步到网络服务商提供的对象存储服务中。如果你信任某些厂商(且认为他们短时间内没有倒闭的风险),可以将非极度隐私的内容放上去,毕竟它们的服务更加稳定,功能当然也更丰富。如果你只想做归档存储的话,用前面说的方法打包加密,几个地方都扔一份,也不危险。
    • 不能完全公开但不危险的工作资料。实际上存储这类资料要求的更多是稳定性以及分享、协作等功能。国内外都有相当多这类服务,或许跟着公司里大家的选择走是更令人愉快的选择。当然,如果你的工作本身就对信息安全要求很高,那还是认真按照更严格的规定操作吧。
    • 仅需要合适存储的资料。比如说你的学校今年的招生报告,跟工作和生活毫无关系。这些材料可以多份存储,哪里方面哪里来。

    当我们谈论隐私以及与其相关的一系列安全话题时,我们实际上更多想聊的是「私密但需要同步的信息」。

    iCloud 的替代品

    很少有苹果用户能够完全避开 iCloud 对生活的侵入。而 iCloud 引发的一系列事件让一部分人对 iCloud 的资料安全性不再信任,转而寻找其他的云端存储方式。这里聊聊几个常见的选择。

    • NextCloud (ownCloud 续作),如果你有过自己在 VPS 上搭建 WordPress 博客的经历,那么自己搭建 NextCloud 也不是什么难事。作为一个私有云,NextCloud 的功能实际上已经非常丰富了,除了最基本的文件存储之外,还支持分享、文件在线编辑、聊天、照片同步、笔记、日历等功能。手机端的 App 只需要输入自建服务 URL 和账号密码即可使用。总体来讲 NextCloud 还是很安全的,缺点在于需要自己搭建服务器,有一定的技术基础,速度可能也没有大公司服务的速度快。同时手机端照片管理的功能远不如 iOS 自带照片应用强大,如何安全地备份数据也是个令人头疼的问题。
    • OneDrive,其实连同前身 SkyDrive,OneDrive 已经运营有一些年头了,只不过免费容量一降再降实在让人不满,连笔者之前购买 Windows Phone 手机赠送的空间都砍掉了。OneDrive 现在和 Office 365 计划有联系,并且 OneNote 笔记本内容实际上也存储在 OneDrive 网盘当中,所以暂时不用担心它倒闭。毕竟是微软出品,OneDrive 上在线 Office 文件的编辑功能自然是绝佳,微软也试验过一系列智能照片管理的功能。Windows 10 已经集成了部分 OneDrive 支持。如果用过的话就会知道,最大的问题就是速度太慢。然后是空间太小(如果买了 Office 365 倒不存在)。手机端 App 的使用体验比 NextCloud 好,不过赶不上 Office 系列其他产品手机端的水准。
    • Google 云服务,Google 提供了包括照片、云端硬盘、文档等多款 App 来涵盖手机端云应用的功能,体验还是很不错的。微软和 Google 都是优秀的 iOS 应用开发商。免费的 Google 云盘存储空间是 15 G,值得一提的是,如果同意照片采用「高画质」而非「原文件」存储(也就是可能会稍微损失一点质量),照片的存储空间是无限大。Google 在照片应用中使用了大量人工智能技术,例如搜索 sunset 就会在上传过的库中筛选出夕阳景色的照片。根据 Google 的习惯,照片这类服务存在被砍掉的可能,不过参考 Google Code,真到了时候,还有足够机会迁移。最大的问题是在国内访问不便。
    • Dropbox,笔者几乎没有使用过 Dropbox 云盘和同步功能。Dropbox 的免费空间为 2G,收费扩容的价格也比较高。Dropbox 的独特之处在于它的增量同步和共享功能。许多第三方应用也支持通过 Dropbox 同步。和 Google 云服务一样,Dropbox 在国内访问不便。

    其他诸如 Amazon Drive 之类的服务在国内还不是特别成熟。而国内公司的云盘和同步服务质量不太令人满意。国内网盘服务普遍不盈利,朝令夕改,容易出现网盘关闭,数据难以找回的情况。在认真考虑到隐私状况的情况下,这些网盘在工作和娱乐上还是可以轻度使用或者做某种程度的备份。

    信息安全之外

    中学的物理题情景总是发生在去除各类难搞的干扰因素之后形成的「理想状态」中。现实往往比理想状态复杂很多。隐私保护亦是如此。信息安全的知识不能确保每个人都能够生活在安全和隐匿之中。即使信息以安全的形式传输和保存,个人隐私依然存在风险。

    • 不得不将信息公开或发送给不可信任的对方
    • 信息被针对性地破解或被胁迫解密数据

    正如 Ian Murdock 在自由软件界是名震一方的领袖,在现实生活中仍然难逃被轻易击毙的命运一样,在隐私保护的领域,很多事情已经超越了网络技术本身的范畴。信息使人获利,给人带来危险,都是因为信息映射着现实世界。

    大隐隐于市

    隐藏自己的身份,比公开身份然后将自身安全押在加密上明智很多。毕竟,加密防君子,混淆防小人。细究前面说到的信息,也可以略作分类。

    • 可以选择不发布的匿名信息
    • 可以选择不发布的实名信息
    • 必须发布的实名信息

    第一种情况某种意义上也是一种理想状态,除非使用了繁复可靠的安全措施,同时监视者不愿投入过大的资源破解。加上随处可见的手机号绑定提示,多数时候网络账号对应的情况是第二种。不过这当然不是非黑即白的两个极端,如果真的停用一切网络服务,日常生活会面临极大的不便,朋友也会觉得自己无趣。如果你足够无聊,甚至可以打造多个账号来学习明星打造自己的「人设」。保证安全的关键在于谨记「凡在网上的,必留下痕迹,不可撤销」。

    第三种情况就很复杂了。日常办证办卡总会留下点记录,法律不健全的情况下,隐私无法确保安全。令人不安的是,这类信息不像网络账号可以自由创建多个,而是和人身份强绑定的。老司机会告诉你开房时用护照,戴口罩,用现金。不过更重要的,可能是认真评估这类信息泄漏带来的负面后果。比如接到诈骗电话,比如被人将真实身份和网络账号关联起来公开信息……就像防御 XSS 攻击。许多人都喜欢给自己取一个独一无二的特殊 ID 作为各类网络账号的用户名来标识自己,这很棒,只要它不关联到现实身份,或者被人连线挖出某些信息时不会觉得尴尬。

    信任

    熟悉 TLS 协议的朋友都清楚证书机制是如何工作的,一条信任链必须存在一个用户绝对信任的起点。如果这个起点不值得信任,那一切安全都无从谈起。对于隐私或数据,安全也建立在一层层信任机制之上。一个 iPhone 用户需要信任苹果公司不会无底线出卖其数据。请注意,这跟 iCloud 无关。如果往阴谋论方向深究,iPhone 完全有可能不按照用户设定,悄悄地发送数据到苹果服务器上。这一切建立在对苹果公司的信任上。就像一个人如果不信任银行,也不会去银行开户存钱。(除非他无从选择)对不同公司不同的信任度,决定了何种私密等级的信息可以被存放在这家公司的设备或服务中。可怕的其实是,这些公司事实上的可信赖程度比大众直觉上的信任度低。FSF 的观点看似极端,可往往最终都是对的。

    谨慎信任,保持匿名,时刻加密。

    延伸材料

  • 记2017年开源年会

    说起来开源年会过去都已经两周了。写参加某项活动的经历似乎像是小学生写作文,不过还是有必要的,毕竟这也算一个自己一直以来向往的事情。每个人对于「开源」的认知都是不同的,这类活动也是,各取所需。说白了,也可以用这种观点来看待上课。现在的课程,一类已会,一类不会,到头来还是没什么长进。

    会议的流程和其他技术会议类似,签到入场后门口一排赞助商在为自己的产品打广告。场内演讲的时间很紧凑。有些内容感兴趣,有些内容又有点鸡肋。噢,有送可乐的,很实在。

    上午第一场报告是一个国内学者做的,大致讲了一下开源和云计算相关方面的趋势和问题。没有什么亮点,或者说没有本人的关注点,所以印象不深刻了。后面是 GNOME 基金会主席的演讲,跟技术无关,说了一下在当今桌面软件依然具有独到的重要性,以及鼓励大家能够多参与到像 GNOME 这样的项目当中去。当然,我是一个 KDE 爱好者。(如果就 Linux 的这群桌面系统而言)

    我个人对桌面软件开发知之甚少,因此不太清楚这样的桌面系统的组件的复杂程度和难度。有时候只是单纯地觉得,作为自由软件社区的开发者,靠自发组织是难以维系像桌面软件、浏览器这类众口难调,又期待用户体验的程序了。不同的人看待开源社区自然有不同的视角。除了开源软件本身蕴含的技术价值,开源软件的开发模式本身已经成为一种神奇的现象。传统意义上认为的需要精密严格管理才能完成的项目居然在如此松散的组织条件下也能做到,还能显得生机勃勃。当然,现在的开源生态,包括不同项目各自使用的商业模式,恐怕也是 Richard Stallman 当年未曾想到的。有人将开源软件视作道德标准,有人以质量作为开源软件的核心优势,有人则把开源当作能更好完成项目的开发模型。不得不说这就是开源世界的魅力。其实开源并无什么必然准则,开放扩张自然带来无穷的生命力。

    后面一位演讲者也来自外国,是 Apache 基金会的前副总裁 Niclas Hadhman 先生。这位老哥如果生在中国,那就是个不折不扣的愤怒青年。一上来先解释说自己不想讲一些重复的东西,大家有兴趣可以去看之前的讲稿。中间介绍了 Apache 基金会的一些项目。(说起来 Apache 基金会的项目除了起初的 HTTP 服务器之外好像都是 Java 世界的产物。)后面指出了中国人或者中国开发者在参与开源项目过程中的一些问题,或者说和国外迥然不同的地方。其一是中国人甚好微信,选择用微信来完成一切事情。我个人对于微信的看法其实是相对负面的。在早期年轻人用微信更多是为了好玩,有人会将自己的微信号公开在网上。而现在微信成了社交关系中不可避免的纽带,也影响到了在中国的外国人。

    微信像是一种工作关系的延伸,特别是在工作生活界限不明确的环境当中。我不喜欢微信这个软件本身的很多功能设计,尽管它和它的爱好者们喜欢以「简洁」等词标榜自己。社交软件背后一定是承载着特别的年龄人群的。即使是像微信或者 FaceBook 这样全民级别的软件,内部也隐式地根据不同的年龄层分化出相对离散的用户群,并且产生着独立的社交文化。话说回来,微信的确是个不错的支付软件。尽管在可以用银行卡的地方,我觉得不用输密码的闪付更方便一点。(如果他们不执意让我签字的话)微信的很多问题在于,它是一个运转良好的通讯软件,但它与 QQ 都只是腾讯生态下的核心平台而已。它们是应用,而不是一个开放的协议。微信不让我导出聊天记录,新进群看不到之前发言,微信不能接入机器人,更不支持插件。(不久前腾讯似乎以反垃圾信息为由关闭了微信网页版,真实目的不明,不过我想在网页版下可以很方便地利用浏览器插件实现端到端加密,尽管目前尚未耳闻有人这样做)

    后面的批评包括了对于中国文化的常见看法。比如中国人笃信权威,但是开源社区里没有一个绝对权威。不过我认为这其实是来自于这些中国参与者对开源文化的不了解。日常生活中的会议目的是求同存异,开会各方最后的目的是达成共识然后开始某项进程。而开源社区很多时候不是这样,去参与一个项目的目的很可能就是 Just for fun 或者使自己用着更方便一点。在这种背景下,达成共识是好事但不是必需之事。(苹果毁掉 KHTML 这样的事是道德范畴,个人做不到也不太可能去做)所以无法达成共识的时候,那就分裂。开源社区并没有限制个人在遵守许可证条款前提下分裂项目的权利。如果自用,那无所谓;如果有不同意见,等待自然选择即可。他还提到了开源社区中英语中心的观点,认为这不利于开源的发展。(抱歉,这一点记忆不是很清晰,可能不太准确)也有人认为在 GitHub 上用中文交流可耻。我个人的观点没有那么激进,也不清楚 GitHub 有没有相关的限制。Ruby 社区的邮件列表中也专门有日语板块。对于语言问题不妨用更实用主义的态度,如果一个软件的主要用户和贡献者都是中文使用者,那用中文交流甚至注释都并无大碍。如果项目涉及到来自多个文化的参与者,至少为交流内容提供一份英文副本是有助于它的传播的。这是一个沟通成本的问题,和中文本身没关系。请不要将中文附带上额外的政治意义,也不要因为国内不负责任的译本盛行而认为中文缺乏表达力。坦白讲,以许多程序员(也包括我自己)的语文素养,完全没有资格质疑中文和其他语言在表达能力上的差距。

    演讲中还有一点,就是来自中国的 Apache 开源项目多是由企业捐赠,而不是个人爱好者的开发结果。有朋友表示是中国程序员普遍压力比较大的原因。我认同这一点。不过还有一个原因是之前国内还没有形成一种程序员的黑客文化。现在看来已经好很多了。当然,一些基础设施类的项目还少有国内独立程序员的参与,这大概也跟技术积淀有关。况且国外许多有价值的开源项目都出自大学之手,而国内大学的教师普遍在干什么,明白了吧?

    不得不说 Niclas 真是一个有意思的人。后面还有一场短的演讲题目叫「软件宅神进化史」。指出现在软件开发走错了路,没有试图用更少更好的人来解决问题。印象最深刻的有两点。一是他强调一个团队人数一定要小,五个人的团队开发效率几乎大于五十个人的团队。二是有听众提问,人工智能是否会取代程序员的工作,他说「大概是会的,不过离今天还很远,至少二十年吧,而那个时候我已经不在了,所以我并不想考虑这些问题」。实习、软件工程课本和学校项目的经历已经有力地说明了第一点的内容。过多的人员配置真的会带来极大的负担和沟通成本。学校的老师恐怕也只是出于偷懒的心态要求诸如八人或者十人的队伍。

    下午有四个分会场,看个人兴趣跑来跑去。当然比较困了,听有些演讲的时候都睡着了。挑一些有印象的来说吧。每个分会场有各自的主题,大致是关于明星项目、草根项目、开源社区、开源治理。开源社区那一场的主持人是一位来自台湾现在在华为的工程师。开场的演讲也由他带来,聊了聊两岸三地开源社区的发展和不同,不过主要是关于台湾。台湾的政府部门也支持过开源项目,不过后来终止了。印象深刻的是在台湾,开源已经发展到超越软件从业者本身的一个事业,转变成一种社会运动,典型的例子如台湾社区发起的 g0v 零时政府项目。这才是体现了开源本质精神的项目。一则推动信息开放,二则使人人可参与。每一个人,都需要对信息有更多的了解,不仅仅是作为一个使用者的角色。屡屡爆出的信息安全事件(比如「永恒之蓝」)和曝光出来的地下产业,都是证明。

    开源社区分会场后面有一场姜军带来的有关 RubyConf China­ 的演讲。很实用主义,提到了筹办一个技术会议过程中的种种花费和问题。晚宴时坐在姜军旁边聊了几句,才知道他是大学肄业。明年的 Ruby 大会已经向松本行弘和 Aaron Patterson 发出了邀请函,在上海举办。挺期待,虽然那个时候应该不是 Ruby 程序员了。

    另外两场演讲,一是如何用开源盈利,二是关于开源运用中拿来主义的心态。内容都比较中规中矩。开源事业能发展到今天,的确也不只是个人开发者的义务劳动能够涵盖的成果,许多商业公司也为开源项目贡献了代码,不少开源项目也找到了盈利模式从而活了下去。典型的例子包括 MySQL 的双重授权,RedHat 的服务收费,一些软件包提供商业的高级版,Google 靠其他业务变现等等。讲者强调了两点:做开源不要忌讳谈钱;所有盈利模式的基础都是用户数量。另一位讲者从文化的角度分析了企业不愿意向上游贡献,不愿意付出资源到开源事业的原因。个人看来,这也跟环境有关。大环境不尊重知识,法律对版权的保护也不到位,外行指导内行,就是这样的结果。不过在可见的未来,这是可以改变的。

    后面有一位法律人士在谈 GPL 的传染性问题。很遗憾没有听到。在今天,GPL 协议几乎快到了「人人喊打」的程度,也是令人唏嘘。如果我写了一个自己用的小程序,我会选择用 GPL 发布,因为它代表了自由软件的纯正价值观。但是如果我的目的不是自己日常使用,而是想推广它,恐怕就会考虑 Apache、MIT 或者 BSD 一类宽松的许可证了。作为个人实质上很难去猜测 GPL 或者自由软件基金会的未来,因为 GNU 和 GPL 项目在整个开源世界里所占的比例越来越小,受关注也越来越少。但是没有人能够否认 GNU 带来的工具链在整个体系中的核心地位,大家也几乎都承认是 GPL 保护了 Linux 这么多年来的顺利发展。自由软件的概念也慢慢被开源软件所取代。在软件世界发生的事情不禁使人联想到现实世界:理想主义者作为开拓者推翻了旧的世界,但最后却是由资本和权力接管了世界。靠义务劳动和热情是无法完成如此巨大的事业的。现时开源软件已经是任何软件开发者和公司包括微软都避不开的一个话题了。我们有足够的理由相信开源世界明天会变得更好。然而现实不仅仅是源代码。新的科技引出了新的问题,个人隐私比以往任何一个时代都危险。庆幸还有 Richard Stallman 这样的义士继续为自由与开放而奔走呼号。

    最后的晚宴不错,第一次吃黑胡椒牛排炒年糕。下午的茶歇也很舒服,对蛋糕的认识有了改观。晚上的活动结束之后,遇到一个用友的工程师,扫地僧一样的角色。他对我说了一句话。

    「你今天对开源社区付出的,明天社区会十倍、百倍地回报于你。」

    我想是的。

  • 我为什么讨厌狼人杀?

    昨天早上在知乎上看见有朋友分享这个「你为什么讨厌狼人杀」的问题。点进去之后我非常惊讶——居然在政治、社会话题之外还有这样的让我感觉「解气」的问答。说大家是落井下石也好,不会玩也好,总之无法否认,这个风靡街头巷尾的游戏,是像任何一个明星一样,有不少人对此反感的。

    我记得我最开始接触这个游戏的时候还是在初中。那时对杀人游戏和狼人傻傻分不清,印象里反正是那些认识很多朋友的外向同学出去玩的标配。玩得不多,「天黑请闭眼」还是记得住的。一直到刚上大学那会,每次玩这个游戏似乎大家都傻乎乎的,如果是陌生人那就随便票,如果是认识的一起玩,当然就是把大家最爱开玩笑的对象投死了。

    其实我真的也不知道狼人游戏是怎么在不长的时间内转变成现在这个样子的,就像我不知道如今微博上一个小有名气的明星转发评论赞数都能上万了一样。狼人和狼人杀有什么区别我不清楚,不过我猜想这名字或多或少都得跟三国杀有关系。说起来,随便一家小的桌游吧藏有的游戏都能摆满一整个柜子,可是大多数人出去玩能想到的恐怕还真只有三国杀和狼人。UNO的话,其实真不太想跟不熟的人玩这个。跟前两者比起来,这个游戏的标准规则存在感实在是太弱,变种又太多,有冲突就挺尴尬的。不过好在它节奏快,而且各自为战,运气成分强,玩起来还是算轻松愉快的。

    回到原题。头一次接触这种「高端」的狼人游戏是去年夏天给一个朋友送行的时候。一帮还算熟的人去桌游吧玩狼人,我当时倒是爽快地答应了。第一把如往常,大家一开始就把东道主给票死了。第二把的时候就被叫出去和陌生的老玩家一起玩。头一回见着这样的分析我是很震撼的,还在一边听一边掏出手机查那些接头暗号一样的术语到底是什么意思。第一轮,很自然的,因为我都不认识,所以随便挑了一个指。结果就是这样随便一指,在第二轮搞得高玩们分析了半天,还纳闷为什么大多数人都喜欢装预言家。很有趣,但是也挺无聊的。那天晚上后来又经历了几次随便过然后被票,也有分析了一波带了下节奏的。回去之后跟其他同学聊天的时候把那天晚上的见闻吹嘘了一番,毕竟我的确是震惊不已。后来我再也没有玩过狼人杀,也的确不想再玩。

    其实在我看来重点不在于什么黑话多不多。仔细分析一下可以发现狼人杀是一个「推进感」很强的游戏。这个词是我自己采用的。推进感是什么?狼人杀可以说不需要什么硬件设施,就几张牌抽身份而已,要搬上网络不能更简单,基本上属于一个纯语言类的游戏。既然没有硬件设施,游戏的进行全靠每个玩家的发言作为载体。如果每个玩家都不说两句话,这个游戏几乎就无法进行。这样的逆向淘汰也就是早期狼人深受交友爱好者喜爱的原因,因为他们更爱说话,没有太多顾忌,尽管那时还没有这么多套路。如果你是一个喜欢发言并且掌控节奏的人,这个游戏当然能带给你别致的体验。但是对另一部分玩家来说,这就是巨大的压力——为了游戏的正常进行,你不得不发言,强行分析。和日常辩论不一样的是,这样的发言充斥着谎话。我当然知道游戏中的谎言无法代表玩家现实生活中的人品,但是不能否认,有不少人无法这样面不改色地说谎。就我个人的感受而言,这个游戏过于紧迫,不像我在玩游戏,而像是我在被这个游戏和其他玩家玩。

    竞技类游戏也是这样吗?大概是的。不过如果你不是职业玩家的话,完全可以娱乐一点。而且嘴炮水平和实际能力完全不挂钩,有时候不会让人那么反感……而狼人杀,现在大有一种「图个乐呵」的玩家被所谓的高手绑架的趋势。更令人不悦的是,乐在其中的人一方面在游戏过程中强调它的竞技性,反感那些不认真玩的玩家或者是坑队友的新手;另一方面又把这个游戏和社交挂钩,默认所有人都应该像自己一样充满节奏感。如果我和人出去玩的时候拒绝一起玩狼人,恐怕是会被扣上「不合群」的帽子的。电脑游戏的话,至少别人拉我去上网的时候我说我不会玩英雄联盟玩玩其他的好像也不是什么严重的问题。

    长大了真的会发现,人和人之间的价值观可能会有巨大的差异。于我个人而言,我真的不太喜欢游戏过程中的竞技性。初中一开始我也和同学一起打篮球,后来的Dota什么的我也玩过。为什么总是进行不下去呢?配合少也好,水平次也好,玩游戏真的就图个热闹,这是价值观的问题。所以我选择不参与,不干扰好胜玩家的正常游戏。我认为这是一种尊重。我不玩狼人杀,我喜欢被动一点硬件多一点的游戏,我会对没玩过的桌游感兴趣。我说了许多我不喜欢狼人杀这个游戏的理由,也许在读这篇文的人一条都不认同,但你没有资格反驳我。因为我没有干扰你的游戏,我也不会因为一个人的爱好就随便下结论。我们当然也可以成为很好的朋友,但未必就要用这种令我不悦的方式。

    我很怀念高中的时候周五偷偷玩七大奇迹把科技树点到底,也怀念大富翁下得几千上万生死一线间,一起玩UNO被加了十六张大家哄堂笑我也很激动。回头看也许这才是面对面游戏本质的乐趣。

    可我不喜欢狼人杀。准确地说,是这个以社交名义绑架你我的狼人杀。

  • 关于大学老师上课的一些看法

    好像很长的时间都没有好好写过文章了,也不知道放在这里会不会有人发现。之前约定自己,每天或者每周更新这个博客,可惜这件事到现在并没有做到。说实话,其实就是自己懒,未能规划好时间。说到这里,的确是有点羡慕那些平日上课按部就班的同学了。也许他们的实际「水平」不如另外一些人,不过每天跟着老师的进度走,自己完成作业,也按时交,不怕点名,感觉大概是很棒的。这个学期期末的时候要交数据结构的课程设计,那些作业本可以很早以前就写完然后上交的,然而总是觉得这不对那不对。题目只要求用命令行的界面,但是怎么看怎么觉得有点不太能上台面;但是用GUI做吧,用什么库呢?好像也不好看,带个东西也臃肿得很,要求叫可执行文件,还得麻烦一下用Windows的同学。所以就一直拖着。一直到Deadline前,才意识到这个东西实在不能再拖下去了,于是就赶紧三下五除二赶好。你别说,赶作业那段时间虽然时间紧,但是感觉还挺好的。之前的C语言大作业、Cocos的游戏作业,都是这样的,有别人推着走的感觉会很稳。不像自己学东西,没有什么自制力,说好的自己要写编译器,拖拖延延到现在还是没个头。

    不过呢,这次去北京参加大型主机的比赛,整个过程中开了不少的会,也在北京玩了一把,收获确实是不少的。每天或者每几天review一次的感觉相当麻烦,不过真的可以保证某个项目的稳定推进。之前想的是,对自己制定一个完整的规划,自己照着这个规划来,每一步完成一个什么。但是一个可行的规划就必须要求制订者对这个项目和工作效率有基本的认识,多数时候这就是意味着丰富的经验。对于个人学习的项目来说,这种做法多半是不可行的。所以不如「走一步看一步」,但是要求一定得强硬,这比较适合假期一个人的时候学习。又说回这个编译器的想法,该写的是一定要写的。可以不忙着设计语言,就照着C11的标准来就行。实现语言的话,可以用Ruby可以用C++,乃至OCaml。我还计划大三的时候开门课讲编译原理呢,估计那时候就真的得用C++来搞了。说来也是惭愧,这学期选了门编译原理,结果好像就没听几次课(老师懂肯定是懂的,但是上课真的无比蛋疼),下周就要考试了还不知道做了多少准备。

    说到老师的授课方式,好像进入了这篇文章想要说的正题。讲真,第一次在这个博客上如此激动的想写那么一点东西下来。其实回想自己大学生活的过程,可以很明显地感觉到对自己的要求在一步一步地变低。说大一的时候不谙世事也好,说是因为想转专业积累绩点也好,那个时候听课的确是认真的。也不知道是不是我的错觉,老师也比现在要走心一点。第一节西方哲学史的课堂上,韩潮老师看着几个迟到的同学说:「你们现在才大一,不要这么早就像高年级的同学一样了啊。」对他的课印象比较模糊,因为我总是想睡着,可就像高中的物理课一样,每次真的要倒下去的时候,脖子又会坚强地抬起来。这样往复,还真的能坚持几十分钟。政治课也是认真听的,下课一没事就去图书馆看看书。那个时候心里没底,但生活却是最规律的。

    来软件了之后,直到大二,我似乎明白了一个道理:大部分专业都会把讲课最令人舒服的老师放到大一来开课,可能是让大家至少不要「堕落」得那么快吧。都说物理像高数一样,不过那时候我的高数课上得还是挺开心的,困归困,基本都不玩手机,按时交作业,身边也没有同学可以抄。那时候想的是,大学的学习也不过如此嘛。哎,人就不能过早下定论。

    怎么讲呢,之前我也是从来没想过挂科的。就连物理上,也是期末抢救了一番之后,成功保住了一个及格。虽然是同一个老师,可物理下就没有那么幸运了。大概是我太水吧,真的一点不会,平时也不想听。最后期末复习看书的时候,真的有一种想杀人的感觉——我真的,从小到大都没有这样窘迫过,为什么一定要这么为难我?为什么?恶性循环嘛,所以到今天,我的物理挂了三次。第一次挂科的感觉真的是很难受的。在同济,挂科的人每学期一定不会少,找老师求情的也不会少,但是像我一样把自己的光荣事迹放到校内公共平台上,同时质疑物理课开课必要性的,恐怕真的没几家了。说真的,在网上狠狠同别人吵了一架之后,物理对我造成的伤害反而没那么大了。我的概率论好像也挂了。看来到这个时候,就是我的问题了。或者说,早就习惯了人文的复习方式,一下这样「动真格」,是的确不适应。

    好像又说远了。各种有大学生的论坛里,吐槽授课教师上课水平低的绝对不是一两例了。可不知道有一些人到底是真糊涂还是装糊涂,强行立了一个逻辑,说「学生水平还不如老师,所以没资格评判老师的上课水平」。这就让我想起了高中时候的某同学。我说,我觉得汪东城长得不帅啊。他讲,「你自己照照镜子看看你有他帅么」。事实上学生当然是有理由指出老师上课的不足之处的,因为学生就是老师授课的对象,恐怕最有资格来做评教这件事。可惜一个老师来上某门专业课未必是出于普及知识拯救学生的信念,而是无奈受迫所致。这就导致许多同学毕业以后,不记得自己上过多少有干货的专业课,反而对某些选修课文采飞扬的老师印象颇深。你当然可以说,真知都是枯燥的,仿佛老祖宗讲的良药苦口一样。可为什么现在的不少药,外面都得加个糖衣吗?复杂、系统的知识当然需要花费大量的时间理解和练习,但是这绝对不是任课教师不认真备课,不认真思考课程改革方案的理由。当然这不只是任课教师的锅,学院的思想僵化,领导独断专行,都是共同原因。不过在下确是认为这些不太受学生欢迎的老师,应该反思反思,自己有没有落实好这个「培养计划的最后一公里」的角色。

    知乎上跟教师有关的话题,尤其是大学教师的话题,我时常觉得不堪入目。大清亡了都一百多年了,女权主义都要成热点话题了,为什么众人对教师职业的看法还停留在那个时代?老旧的尊师观念、苏联式办学传统和官僚体制下僵化的大学培养制度共同诞生出了我国目前本科教育这个怪胎。很多人讨论问题的时候都是在想当然。同济有没有严格的老师?有。但是这些老师被学生骂到死了吗?没有,反而受一代代的学生尊重,每年选课的时候都会成为抢手货。因为这些老师的严格,不是目的,而是达成教学目标的一种手段。学生通过老师的严格,最后获得了真正的知识和成绩,所以才会反过来感谢老师。有些老师口口声声「其实你成绩怎么样和我有什么关系」,实质上就是在为自己的不负责任找借口。学生不听课,不代表你老师就可以瞎干了啊。

    但这个问题并不好解决。所以在可见的大学生活里面,学习这档子事情,还是只能靠自己。自觉是不一定靠得住的,正如一个人只能溺水而死,不能憋气而死一样,要适当创造一些「自己无力改变」的短时条件,比如上课不带手机和电脑。或许效果会好很多,至少我可以自己看书。

    所以还是想给下学期好好定个安排,我记得以前也定过,常变常新嘛。

    • 物理一定要过,否则以后的课程安排会受很大影响
    • 实现一个C语言编译器,后端生成nasm汇编代码,实在没时间用LLVM也可以,要在今年GSoC之前完成
    • 至少把《经济学的思维方式》《纳粹德国:一部新的历史》和《死亡》读完
    • 下学期上课尽量不带手机和电脑,每次上课坐前1/3排
    • 买一对哑铃,或者每周去健身房三次
    • 读《Ruby源码剖析》这本书,尽量向今年GSoC的Ruby项目靠拢,大三可能没那么多机会了
    • 有空的话,深入学习OCaml,看一下《近世代数》
    • 去一个江浙沪之外的地方旅游
    • 每周至少更新一次这个博客

    一开始的语言可能有些激动,请原谅我这个人许多时候的自以为是。

    祝好,送一声迟到的新年快乐和早来的新春快乐。

  • Ruby on Rails开发入门手记

    上个月的博文里说过,由于要开发同济权益的二手书管理系统,所以用PHP写过这样的代码。第一个版本是纯PHP撸成的,没有用到任何的外部框架,路由也是纯手写.htaccess文件实现的,一共就四个文件,负责分发请求,最后交给处理数据库的文件来输出对应的JSON信息。现在看来,这大概叫强行RESTful吧,哈哈。话说回来呢,当时写的时候,甚至还有点基本的ORM思想,不过觉得太麻烦就没着手完成这个。后来才知道这个模型有个高大上的英文缩写叫做ORM。当然,这个版本的代码,模块性一点都不好。五百多行,基本没法维护。

    后来寒假的时候,确实是觉得这个太不行了,需要找个框架重写,顺便学习一个。用哪个框架呢?知乎搜索了一下,就用CodeIgniter吧,相对轻量,又确实有用。这个框架是MVC架构的,但是也没有实现完整的关系对象模型,仅仅是封装了方便的数据库查询器而已。这个版本,自我感觉代码结构好多了,不过好像和前端交互的API还是没有改。

    到了四月初的时候,听说软件学院要搞一个Android应用开发比赛,所以呢,后端可能还得改。这次大家坐在一起开了个会把需求相对完整地定了下来。不过,还没等得及下手,一切又变了。应用还没开始做呢,就又变卦了。这次是得改变架构,网页和后台不分离。大概就剩一个月的时间了。怎么搞呢?用Ruby on Rails吧。听说,这玩意很快啊。

    要说之前,Ruby这个语言我就没有碰过。虽然它和Python我都不熟,不过其实还是用Python相对多一点。但是不行啊,硬着头皮也得上。所幸Rails的文档写得确实不错,我做这个网站的过程中很大程度参考了Ruby on Rails的中文指南。如果有朋友对Rails这个框架感兴趣,非常推荐这一系列文档。同时,也听说Ruby China在国内的技术社区里面是最有氛围的一个了。

    Rails无非也就像是其他MVC的Web框架一样,在目录下通过命令生成一个新项目,然后运行服务器,打开看到一个正确运行的页面。这就标志着我们的开发工作正式开始了。当然由于Rails这个框架比较复杂,生成的代码也比较多,所以新建一个控制器还不能像CodeIgniter这种框架一样直接手动新建个controller文件就好了,需要用到generate命令。敲下命令之后,发现Rails确实爆炸,自动帮我生成了RESTful风格的路由和一堆代码。

    由于朝RESTful看齐的特点,直接在Rails的路由文件routes.rb里像这样写:

    Rails.application.routes.draw do
    
      resources :books
      resources :users
      resources :appointments
    
      root 'welcome#index'
    end

    寥寥几行代码,Rails就帮我们生成好了书、用户、预约三种资源的所有路由,已经定义了网站根目录对应的控制器方法。

    说到生成,Rails还有一个更厉害的东西,叫做「脚手架」。利用它,可以一键生成控制器、视图和模型的代码。虽说这些代码直接拿来用不太现实,不过也可以作为初学者学习的重要参考。

    Rails的脚手架用法如下:

    rails g scaffold books title:string price:decimal

    这样就快速创建了一个叫做books的资源,包含title和price两个属性。脚手架为我们快速创建了路由、控制器、视图、模型,甚至测试文件。脚手架生成的代码虽然完整,但是往往不能完全满足我们的需求,我们也未必喜欢它给我们强加上的一堆网页样式。我们可以基于脚手架的代码来修改,不过更多的时候只是用它当作例子来学习,看看标准的Rails代码是个什么样子。

    Rails严格区分了资源的单复数。一个资源在作为Model层的时候是单数的,比如Book;而在View层和Controller层的时候是复数的,比如books_controller.打开Rails为我们创建的这个controller,我们可以发现七种RESTful的操作已经预先定义好了,并且自带html和json两种模式。很迷的是在许多方法里只有一行语句甚至没有语句,那么Rails是怎么渲染View的呢?

    实际上,Rails会自动找到与controller里的方法同名的模版文件(Rails默认用的是erb)然后渲染。当然有些方法是不需要HTML模版的,比如POST、PUT这些HTTP请求使用的controller就不需要特别的模版,用得更多的是重定向。当然,我们可以手动选择要渲染的模板。即使用render方法。

    Rails的模板是怎么写的呢?因为Ruby语言不靠缩进来标记语法层次,所以Ruby可以轻松地嵌入到HTML模板文件当中,这一点比不得不自造模板语言的Django高到不知哪里去了。这类模板也就是所谓的erb。Rails可以在设置里选用其他的模板,比如haml乃至markdown。

    被渲染的erb模板能够正确地加载调用它的控制器里的所有实例变量。什么意思?比如像这样:

    # controllers/books_controller.rb
    class BooksController
      def index
        @time = Time.now
      end
    end
    
    # views/books/index.html.erb
    <p>Current time is <= @time %>.</p>

    所谓的带@符号的变量是Ruby的一种语法,称为实例变量,表示类的数据成员。如果我们去掉这里的@符号,把time定义成局部变量的话,模板渲染的时候就会报错了。

    我们把Rails的视图层和控制器都极简地介绍了一下。尽管没说几句话,但是已经基本可以构建一个可以运行的网站了。这都要归功于Rails的良好封装和Ruby语言的自由度。现在我们来看看之前被忽略掉的Model层。实际上,Rails更加鼓励把更多的逻辑写到模型层里面。(Rails还需要我们为controller写什么代码吗?)对于书这种资源,我们也能在models目录下发现对应的代码文件book.rb,注意到book用的是单数。

    class Book < ActiveRecord::Base
    end

    咦,这个Book里什么代码也没有,就一个空类嘛,有什么用?

    别急,如果我们在Controller里面想要查询关于Book的信息,非常简单:

    def get
      @book = Book.find_by_id params[:id]
    end

    有趣,这个find_by_id是哪来的?噢,忘说了,Ruby里面方法调用跟Perl一样不需要括号噢。

    哈哈,这就是Ruby语言的威力,它可以把字符串和代码相互转换,并且可以在运行时动态定义新的方法,甚至覆盖旧的方法。Ruby看到我们find_by_id这个方法时,不会急着报错,而是去寻找有没有「当方法找不到时的解决方案」,而Rails在这里为我们提供了一个。它会去数据库的结构里查找id这个字段,然后返回给我们想要的结果。也就是说,如果我们的Book表里新增了一个字段叫ISBN,那么我们可以直接find_by_isbn了,什么也不用改。

    这只是Rails的数据库子包ActiveRecord强大功能的冰山一角。另一个值得一提的地方是它的关联机制。比如这样:

    class Article < ActiveRecord::Base
      has_many :comments
    end
    
    # 注意单复数
    class Comment < ActiveRecord::Base
      belongs_to :article
    end
    
    # Rails会去寻找Comment表里article_id这个字段
    some_article = Article.take
    some_article.comments # 我们已经得到了此文章对应的所有评论

    这里对article_id这个字段名的假定体现了Rails的一个重要原则:「约定大于配置」。相比于Java繁复的各种XML配置文件,不知道Rails的这种风格有没有让你觉得清爽呢?

    拖了六个月,说了这么多,其实也就是Rails最基本的一点点。不过足以让许多人认识到Rails框架和Ruby语言的魔力了。整个Ruby社区很有趣的一点就是,从Rails的插件到各个方面的库,作者都会以这个库的使用方式「看起来像英语」为荣,比如:

    every 3.hours do
      sleep 2.minutes
    end

    得益于独特的语言文化和语言的强大能力,这样的DSL在整个Ruby社区到处都是!

    文章很短,不过希望你能从中感受到用Ruby编程的魅力。就像DHH说的,成为百万富翁以后才发现,开兰博基尼还不如写Rails来得快乐。(虽然我没开过)

    以下有两个页面可供想学习Ruby on Rails的你参考:

  • 不受待见的程序员

    程序员正在遭受来自这个社会的,同对医生一样的误解。为什么大众会误解医生呢?或者说,为什么医生与患者间的关系开始变糟了呢?行医救人的职业古已有之,尽管那时尚无「白衣天使」这种搪塞个人追求的名字。然而工业时代来袭,全世界的居民都接受了经过塑造的现代生活方式、谋生手段,且几百年而不绝,这种改变仍在快速进行之中。物资丰富、日新月异的年代,每个人都有权获得更好的生活。为了区分这个先后缓急,才有了资本、技术、劳动作为收入分配的依据。医术自古即是一门技术,但那时技术还算不得资本,更何况是「士农工商」的年代?

    现今的人不能理解社会系统、国家机器运作的复杂性和脆弱性,他们仍习惯于以小农时代的思维模式打量这个世界的运转。也正由于此我们能够时常看到身边人关于社会管理、国际关系等的的种种呓语。正如和你大谈高层秘辛的出租车司机不必深究汽车原理便可参与营运一样,现代社会允许其成员未曾窥过其逻辑即可自由地享受生活。简言之,现时的医生早已不同于古代,就医过程的参与者也不仅是患者与医者双方。用想当然的逻辑思考规整化的医疗体系,自然是会出问题的。

    程序员就更可怜了。如果说医者的职业目的大众还算心中有数的话,对程序员,就真算一片空白了。「开发软件」?软件是什么?即使是日常用到软件的人,也不清楚这些司空见惯的程序的复杂程度(「2000块给我做个淘宝」)——从这个角度讲,社会和软件倒有相似之处。须知,大多数人对职业的认识几乎完全是从个人经验出发的——比如律师就是耍嘴皮子,设计就是随便画画。在建筑行业整体遭冷的今天,仍有不少的重庆人相信搞桥梁是最好的职业之一,因为他们一辈子见得最多的就是桥。更何况,在风口可以吹猪上天的今天,程序员引来了诸多行业的羡慕与嫉妒。又有大批对计算机几无敬畏与热爱的人入行以工程师自居。这些拿着半吊子水平的Java就大谈而谈各路本质论的人继续伤害着程序员在大众眼中的观感,误解自此不可避免。

    说「这个世界还未能体会程序员带来的美和智慧」有些过了。对于我这个不爱车的人来说,我也没法感受搞汽车的人乐趣在哪。不过我清楚汽车是什么,做什么,大概是个什么样子。我也明白现今驾驶、乘坐的体验是上百年一代代工程师的努力带来的。可……把「汽车」换成「软件」呢?这确是教育的问题。今天又越来越多的人把杨永信的网瘾治疗当作是一个笑话,但不要忘了在2016年还有大量的人想在互联网上再来一次卢德运动。可悲又可笑。

    未来当然会有越来越多的非程序员学会编程并用以解决自己领域的问题,也当然会有程序员丢掉饭碗,正如原来的电话接线员。实际上,如果现在的程序员穿越回二三十年前:「不会手写键盘驱动来接受输入,你也配叫会编程?」技术的进步是为了解放人类,使其能考虑更高层次的问题。国内僵化落后的计算机普及教育,却会使更多的外行视编程为洪水猛兽,也无法正式程序员这个职业。

    做一个程序员有哪里好羞耻的呢?在「劳心者治人而劳力者治于人」的文化环境中,程序员的火热本身就是对「读书无用论」的重击。我很期待在未来看到工作到六七十岁的老程序员,那是社会理念更进步的表现。想一想,还有多少专业的人有资格以纯粹的兴趣看待专业问题呢,并拥有如此低的创造成本呢?

    全世界程序员,联合起来!

  • cJSON源码分析-实现

    好了好了,这篇文章拖了真久啊,活活三个月。不过之前承诺要经常更新博客的,所以先把这个烂尾的坑给填了。

    之前提到了cJSON这个C语言写成的JSON解析库的接口,也就是头文件里的内容。这一次我们来分析一下实现。上车吧。

    还是按照源代码的逻辑走。我们发现cJSON最开始有一个全局的字符指针ep,以及一个用以返回ep的函数。可以看出,这个ep是用来存储错误信息的。这种实现是C语言的常用手法,即把一些状态用static的方式隐藏在单个文件中,并实现一些函数当作接口。

    static const char *ep;
    const char *cJSON_GetErrorPtr(void) {return ep;}

    下面是一个大小写无关的字符串比较函数。看后面的源码可以发现这个函数用在了JSON对象名的查找当中。实现没有什么特别的难点,所以就不提了。顺便说一句,在C++里要实现这个大小写无关比较,可以用STL里的一个方法叫做lexicographical_compare,搭配lambda表达式可以轻松达到目的。

    作者把负责动态内存分配和释放的函数直接「填」成了标准库的malloc和free,像C++的allocator一样。如果担心内存碎片的问题,可以自己再当个二道贩子,实现一个内存池,不过那有点背离本文的主题了。作者自己实现了一个版本的strdup,里面也是用这个用户可以更换的cJSON_malloc进行内存分配的。

    新建JSON和释放JSON的工作不难,前者就是内存分配的问题,后者就判断一下JSON的type,如果是基本类型就直接释放,如果是数组或者对象就递归删除(类似二叉树)。

    目前好戏来了。第一个函数是解析数的。写过词法分析器的就懂,这个用自动机很容易描述,不过这里实在是没有什么好的能在电脑上绘制自动机图的工具,所以用列表表示这个过程,看看应该能体会。

    1. 开头有负号吗?有就记下来,向前走。
    2. 第一个数字是0吗?是就前进,反正默认的结果都是0.
    3. 这是小数点以前的部分,一位一位地循环解析就好了,到第一个非数字的字符为止。
    4. 有点并且点后面有数字吗?有就把小数部分也解析了加上去。
    5. 有E或者e吗?有就算10的幂次方。

    简单吧?我们接着往下走。看到一个…奇怪的函数。

    static int pow2gt (int x)
    {
        --x;
        x|=x>>1;
        x|=x>>2;
        x|=x>>4;
        x|=x>>8;
        x|=x>>16;
        return x+1;
    }

    看见这个函数心里大概会想——什么鬼?名字看不懂,内容也看不懂。唔,不过看在它参数和返回值都是简单的int类型,不妨写个小程序测试一下结果。(限于篇幅省略结果)跑完之后我们猜测,这个函数的目的大概是返回一个不小于x的2的整数次幂。(对2的整数次幂还不敏感吗?)那我们来根据代码验证一下。

    先略过这个减1的过程,看看位运算。我们假设整数的二进制形式从右向左,最后一个值为1的位是第n位,那么运算的过程是:

    1. 首轮,第n位右移1位,经过按位或运算,第n和n-1位(n右边那一位)确保为1.
    2. 第二轮,类似地,n和n-1都右移2位,所以n-3到n都是1.
    3. 第三轮,同样,此时n-7到n位都可以确保为1.
    4. 第四第五轮后,从1到n位都是1了。右移到16截止是因为这里的整数只有32位。

    最后往这n位连续的1上再加个1,就是1后n个0,即2^n了。起来这个过程有点故弄玄虚的意思,因为我们也可以用循环的方式来解决。不过这里作者巧妙利用了整数位数的限制,用五次位运算达成了对任意整数都有效的效果。为什么要减1呢?因为不减1再加回去的话,对一个已经是2^n的数进行运算会得到2^(n+1),不符合我们的预期。实际上,这样的位运算技巧在《高效算法的奥秘》和《深入理解计算机系统》中都有相关的阐述。

    这个函数有什么用呢?搜索一下就会发现。它只用在了一个地方,就是下面这个ensure函数。继续追踪可以发现,这个函数包括下面的update以及printbuffer这个结构体,都是用来存储缓冲区的。在缓冲区里面空间成2倍地扩大。不过我们的重点在字符串解析和内部的数据结构。

    到这里,我们回过头来整理一下思路。JSON的类型有6种,而操作又都有解析和输出两种。

    • null和bool,由于bool只有两种固定的值,所以对于这两者,输出和解析都是简单的strcmp、strcpy就可以。
    • string,解析本身难度不是太大,去掉两端引号中间的就是字符串内容。但是有两个(或者说就是一个)问题,一是要注意反斜杠’\’开头的转义字符,二是字符串涉及到utf16到utf8的转换。输出的话不是什么太大问题。
    • number的解析前面已经说过了,浮点数输出要考虑一下精度,IEEE754标准和utf8都是坑。
    • array和object都是递归解析。如果要按格式输出,缩进是一个问题。

    唔,好尴尬,写到这里突然不知道怎么继续了。在这里贴一下主要的解析函数parse_value的代码。

    /* Parser core - when encountering text, process appropriately. */
    static const char *parse_value(cJSON *item,const char *value)
    {
       if (!value)         return 0;   /* Fail on null. */
       if (!strncmp(value,"null",4))   { item->type=cJSON_NULL;  return value+4; }
       if (!strncmp(value,"false",5))  { item->type=cJSON_False; return value+5; }
       if (!strncmp(value,"true",4))   { item->type=cJSON_True; item-&gt;valueint=1;    return value+4; }
       if (*value=='\"')               { return parse_string(item,value); }
       if (*value=='-' || (*value>='0' && *value<='9')){ return parse_number(item,value); }
       if (*value=='[')                { return parse_array(item,value); }
       if (*value=='{')                { return parse_object(item,value); }
       ep=value;return 0;  /* failure. */
    }

    跟我前面的分类一样,很清楚了。至于空格,这个函数在每次被调用之前都会先调用一次skip函数,用来跳过空白的:

    /* Utility to jump whitespace and cr/lf */
    static const char *skip(const char *in)
    {
        while (in && *in && (unsigned char)*in<=32)
            in++;
        return in;
    }

    查看ASCII码表就可以知道32之前的基本都是不可见的控制字符或者空白,这里的条件判断真是简单粗暴。

    parse_array的过程已经说得比较清楚了,就是不断地跳过空格、读取逗号、再跳过空格、读取一个新的对象的循环……直到遇到反方括号。前面那个指针ep的用途也明白了,就是指向读取失败的地方。parse_object类似,只是每次循环还要插入一个读名字的过程。

    输出部分没有什么特别值得提的地方(其实是懒),要注意的就是输出array和object的时候需要控制一下缩进。总的来说,cJSON的代码逻辑就是这个样子。阅读这样「接地气」的代码,好处在于能够快速学到很多这门语言的最佳实践,但是繁杂的工程细节也会让人厌烦。好在大一些的项目往往在抽象上做得更好,方便我们抽丝剥茧,寻得新知。

  • 夏夜怨记

    大概真的有很久没有认真写过一篇日志了。

    拖延症想来真是可怕的一件事,从高考、到生日、到搬嘉定,一直想记录下这些时刻,然而最后都因为各种原因忘掉了。

    本来说,这次回家,要好好看书,复习物理,认真把之前没做的事情做完。可没想到睡着睡着这暑假就睡过快一半了。从现在开始大概还来得及,但是真的有机会让我意思到「快来不及」吗?

    从去年七月底学籍变更开始算起,转到软件学院基本上算整整一年了。说没学到什么东西,那是假的。但是能不能合上当初对于大一的预期呢?恐怕这里不是个问号,甚至都应该是感叹号了。

    大一开学第一门课好似一记闷锤,重重地把暑假群里的欢声笑语打得不知所踪。我本是个好为人师的人,也因此,有不少人认识了我。虽说常常觉得会有人因为此对我颇有微词,但是更多的时候笑一笑,只是觉得帮助别人挺好的,足矣。

    说Deadline是第一生产力,这话我同意一半。Deadline只能带来产品,带不来作品。除了紧迫感以外,要创造出作品必须的还有一种「渴望」。这样的「渴望」是发自内心的,但对于常人而言,要利用它创造作品,恐怕这期限得是无穷远加上一个epsilon,所以它和期限交织在一起,就是促发我们为作品拼命的动力。

    既然半年混一混就过去了,那再来半年似乎就更不是问题了。下半个学期过得更加无趣和无所事事。Cocos已经足够不能带给人成就感了,更何况一个拖了许久需求一删再删的半成品游戏。年级群也在变得越发沉闷,只有俱乐部还能带给人些许慰藉。

    其实说到底,这些问题都是自己没有规划没有坚持造成的。规划是简单的,大不了发现有问题再改。难的是坚持。你说每天写一篇日志容不容易?好像也不难。但是甫一打开后台,脑子里各种毛病就来了。觉得浪费时间也好,质量不高也好,一天天就混吃等死推过去了。吹牛的时候我老喜欢提自己初中就学编程的事,然而到今天有太大正面影响吗?还真的没啥。

    这并不需什么天才,那个年纪就是能自学那种程度的知识和技能。问题是环境是否容许,例如放学后去办公室搞到午夜才回家,同时学业成绩不能太差。

    在知乎上看见Milo谈小孩子学编程的时候说的话,深以为然。如果时间能够给我再一次机会,我真的会做出不一样的选择吗?

    在软件这一年,有收获,有遗憾,但这段经历比不上大一一年带来的震撼。回头看自己一直以来的读书经历,好像也能总结出一个道理:

    永远不要试图框定自己的界限。否则,你总会发现外面还有所遗漏。