0221 - 我是如何把 Klib 带到这个世界的

这里不但有「*Klib 的诞生过程」

还有「独立开发者」的真实写照,

万字长文,深度干货,请谨慎继续…


历时 50 天、横跨 2 年、经过 258 小时的纯手工打造,Klib 终于来到了这个世界。

这两天 Klib 刚刚发布,收到很多用户的好评。赶着手热,记录下 Klib 的诞生历程吧。

Klib 是全新的 Kindle 标注、笔记/书摘管理工具,macOS 平台。

缘起

2016 年底的时候,我在思考接下来做什么项目,并没有定下来,只是有个大概的方向:做个稍微大些的,以及技术上的需求,就是做一个有界面交互的。因为之前做的 i 系列工具(iPiciTimeriPasteiHosts)都是非常垂直的,并且都是菜单栏工具,我想让自己有所突破

正巧,当时也给自己定了读书计划,就是要经常读书,并且读完后要在博客上写文章。主要自己是独立开发,获取信息的渠道本来就不多,就要用这种方式强迫自己不断更新。而要写文章,很重要的内容就是读书时的书摘。我习惯于用 Kindle 读电子书,于是,很直接的需求就是,把 Kindle 中的标注、笔记复制出来,用于写文章。

而我大致搜索了下当时 macOS 平台下的标注管理工具,发现并没有趁手的。于是,就开始了程序员最喜欢做的事:造轮子

时间,正好是 2017 年元旦,详见 当天日记。顺便说一句,我有每天写日记的习惯,已经坚持几年了,也是为了逼迫单打独斗的自己,能够保持思考、反省

设计

这里借用设计这个名字,只是用以涵盖产品前期的各种思考。确实是有各种思考,如市场环境、商业模式、竞品分析、名字、Logo、做成什么样子、交互、如何发布、怎么运营、时间节点、等等。时间有些早了,这里简单回忆下几个比较重要的点。

定位

我一向倾向于做简洁的产品,这个工具也是如此。我最核心的需求就是导出 Kindle 中的笔记,最直观的衍伸就是能持久保存、定期回顾这些笔记。好了,就是这些吧,再多就难做的精简、好用了。重复+小结一下:

定位:精简的、macOS 平台、Kindle 笔记管理工具

功能集:

  • 导出 Kindle 中的笔记
  • 持久保存笔记
  • 方便回顾

名字好定,因为我开发的工具都是 i 开头的(iPiciTimeriPasteiHosts),这个自然也是。再加上是 Kindle 相关的,于是就叫:iKindle. 当然,你知道最后不是这个名字;这个坑,以后再填。

Logo 也很容易呀,你看我之前产品的 Logo 就知道了:

iKindle 的 Logo 一定是类似下面这个样子的:

当然,你知道,后来也改了。这是后话。

交互

有了前面的铺垫,就可以考虑具体的交互。

交互一定要直观、符合直觉。开始时,大致的想法是这样的(幸亏我还保留了当时的截图):

左侧是书列表、右上角是书中笔记列表、右下角是选中笔记的内容、方便编辑。后来当然是不断演变了。

先是书

我一开始就没打开按作者来区分,因为我本人并没有追作者的习惯;并且,即使追,也没必要非得按作者分类,搜索不就可以了。

但是,太多书放在一起,就有了排序的问题。按书名、最后阅读时间、还是别的?其实你会发现,如果书多,再怎么排序都没用。那干嘛不让书少点呢?有了,如果只看最近的几本书,比如 3 本,还需要考虑排序吗?那些更早的书,归档起来不就好了,之后能搜索到就行。

于是,这就诞生了 iKindle 独有的创意:区分当下正在阅读的书、和之前已经阅读的书。形式上就是把已读的书放在一起,可以折叠隐藏。恩,挺棒的!

然后是笔记列表

程序员的思考,很直观的就是把所有属性都列出来,比如笔记内容、类型(是标注、还是笔记)、位置、添加日期。这用列表很容易实现。

可问题是,除了笔记内容,后面这些属性会占用大量屏幕内容,却又不重要,只要在我需要的时候能看到就可以了。

于是,那就把它们都隐藏起来好了,隐藏到简介里,这样笔记列表就只显示纯粹的笔记内容。恩,挺棒的!

再然后就是阅读、编辑当前笔记

你会发现,在最原始的设计中,这部分也占据了大量屏幕、却不得已有大量空白。并且,对于我实际的笔记来看,很多都是一句话的,在列表中就几乎可以展示了,不再需要额外的这个区域。再并且,真正编辑的需求并不大(最多的就是在标注时,前后有不需要的内容、符号),大部分时间只要看就行了。

于是,这部分也被我干掉了。那如果笔记确实很长怎么办呢?我自然想到了 Finder 的快速预览。OK,那就搬过来吧。恩,挺棒的!

下一个就是搜索

这个并不需要特别设计,跟系统的保持一致好了,不一致反而不好。恩,挺棒的!

最后,就是整体的交互

我一看,经过上面的改进,这不就是个系统「提醒事项」的样子吗?好吧,那就做成「提醒事项」的样子。恩,挺棒的!

开发

设计差不多了,就可以码了。我开发的顺序一般是三步走:数据结构、堆 UI、完成业务逻辑。

数据结构

数据结构又主要为三块:Kindle 本身数据结构、内存中数据结构、持久化数据结构。

Kindle 本身数据结构

好吧,先吐槽一句:Kindle 的数据结构真垃圾!至少是开放出来的数据结构真增加,就是一个纯文本,类似于以下的内容:

1
2
3
4
5
史蒂夫•乔布斯传(Steve Jobs:A Biography) (沃尔特•艾萨克森 (Walter Isaacson))
- 您在位置 #5676-5677的标注 | 添加于 2015年8月22日星期六 下午1:33:54

一家妥善经营的公司能够大量催生创新,远胜于任何一个有创造性的个人。
==========

看起来还行是吗?那再来一些多语言版本的:

1
2
3
4
5
6
7
- Your Highlight on Location 1-6 | Added on Thursday, January 5, 2017 4:20:05 PM
- Ihre Markierung bei Position 6-7 | Hinzugefügt am Mittwoch, 1. Februar 2017 12:21:36
- Votre surlignement à lʼemplacement 9-12 | Ajouté le mercredi 1 février 2017 12:51:12
- Tu subrayado en la posición 33-35 | Añadido el miércoles, 1 de febrero de 2017 12:42:10
- 位置No. 39-42のハイライト |作成日: 2017年2月1日水曜日 13:00:33
– Ваш выделенный отрывок в месте 45–49 | Добавлено: среда, 1 февраля 2017 г. в 13:12:38
- 您在位置 #38-39的标注 | 添加于 2017年1月5日星期四 下午4:07:22

单单是识别其中的日期,就有种想 shi 的冲动:要获取日期文本、识别格式、确定 Locale;另外,还不知道日期所在时区。

这里我做一个小机巧:数据结构的解析能力可以动态更新。也即,在二进制程序不变的情况下,可以从云端获取数据的最新解析方式。这样,当用户反馈给我不支持的格式时,我只要更新云端的解析能力,客户端就都可以无痛、后台式更新了。为自己这个设计点赞!

内存中数据结构

这个没太多好说的,定义书、笔记的各个属性即可。

一个主要的机巧在于:如何识别笔记的唯一性?前面的文本中可以看到,笔记并没有所谓唯一 ID 之类的东西。既然没有,那就把笔记整体、或整体的 Hash 值作为唯一 ID 好了,反正笔记的数据量并不大,一个人一生的笔记,可能也没有 iPhone 拍的一张图片体积大。

持久化数据结构

这一步,主要在结构化存储、和非结构化存储中纠结。前者数据规整,适合笔记存储,但扩展性差。后者优缺点大致和前者相反。

最后,我还是选择了结构化存储。具体的,就是 SQLite. 一方面笔记类数据实在是太规整了;另一方面,抱着学习的态度,反正我哪种都不熟悉,随便拉一种出来练手吧。

还有一个考量就是:数据结构的公开性。数据是用户的、宝贵的,如果使用私有的二进制数据结构,万一哪天要停止维护了,用户就没有任何办法可以解析这些数据。而使用公开的 SQLite 则不怕,任意一款 SQLite 工具都可以查看用户自己生成的数据。这样做,对用户是负责任的。

堆 UI

这部分倒没有太多可说的。

一方面就是一些核心控件的用法,如 NSOutlineView, NSTextView, NSPopover, NSSearchField. 其中,Source List 模式的 NSOutlineView 是有些坑的,会出现诸如 UI 刷新不及时、图标丢失等现象。有遇到类似问题的朋友,可以找我聊聊。

另一方面就是界面的细节,如字体、字号、颜色、留白、间距、等等。这方面我倒是偷懒了,因为基本就是「复刻」系统「提醒事项」

实现业务逻辑

打通数据流

比如,选择右侧书时,右侧笔记列表能相应变化;编辑笔记时,数据能持久化;查看笔记时,能读取正常的数据;等等。是不是听起来都像是废话?恩,就是这么回事,做对就行了。关键是 代码结构要好,不要有复制代码、严重耦合之类的问题。

用户引导层面

比如,开始时引导用户手动导入、进行过程中引导用户如何操作。这部分我基本没做太多,因为希望程序可以做到无需引导。不过,引导用户导入这一步,做的确实不好,还要再改进。

外围的一些功能

比如,账户系统、日志及反馈系统、等等。这里就不再铺开了。

测试

单元测试

其实,单元测试是在开发一开始就进行的。尤其是对数据结构部分,要充分测试。如果这部分没问题,只要程序能跑起来,基本就没有大问题。如果这部分有问题,改 Bug 够喝一壶的。

并且,要在定义数据结构、完成新功能那一刻,立即完善单元测试。因为这时候是对代码最熟悉、最清楚、也最愿意写单元测试的时候。错过这一刻,实在是太可惜了。

完善测试用例

和单元测试一样,测试用例不是在产品开发结果后补齐,而是在每开发完一个功能逻辑后,立即完善测试用例。道理一样,因为这个时候是对逻辑理解最透彻、清晰的时候,千万不能错过。

版本测试

单元测试跑一遍,心里就有底了。再加上前面完善的测试用例,版本测试无非就是照着用例跑一遍。

我并没有做自动化测试、持续集成,主要是涉及 UI 的自动化测试并不好做,而且投入很大。对于我这种小项目、独立开发者而言,手动跑跑测试用例,可能是性价比最高的。

值得介绍的一点是,推荐使用虚拟机进行版本测试。事先建立好干净的系统、生成镜像。每次测试时,只要将虚拟机恢复到指定的镜像即可。再者,就是可以在当前系统(如 macOS 10.12)中,测试其它版本的系统(如 macOS 10.11)

上架

自己辛苦做出来的东西,当然是希望越多人用户越好。要触到更多的用户,最直接有效的办法就是 上架 Mac App Store (MAS) 了。

内测、公测

在上架前,要保证自己的产品的可用、好用的,最好的办法就是,先让一部分用户用起来,也就是内测、公测(以下统一称公测)。

特别要强调的是,公测的目的并不是发现 Bug,这是你自己要做好的事。公测主要有以下目的:

  • 检验市场:也就是说,这个产品是不是有人需要、有多迫切需要,之前假想的市场是否存在、有多大,等等
  • 检验设计:如果有人要,那自己做出来的东西是不是他想要的?哪些地方设计的比较好,有没有致命的缺陷?
  • 检验质量:因为自己能覆盖的情况是有限的,还是要靠真实用户的实际使用来最终检验质量。

我的内测主要是在之前的用户群中、V2EX 中,在此特别感谢参与公测的朋友!

Mac App Store

好吧,终于来到 Mac App Store (MAS) 这个大坑。说 MAS 是大坑,主要是以下方面:

沙盒限制

沙盒会限制很多接口、系统权限的使用,对程序设计、产品交互有明显的影响。

比如,除非用户手动操作(即用操作本身来授权)沙盒不允许产品访问用户的文件系统。这就使用 iKindle 无法在一开始时直接自动导入当前连接的 Kindle 设备,而必需由用户手动操作。这就需要很恰当的引导。而一旦使用过一次,后续就不再需要用户手动操作。这也是为什么 iKindle 后续可以自动导入。

还有一个很关键的:即使在开发过程中打开了沙盒限制,苹果在审核时还是可能以沙盒为理由拒绝。虽说苹果准备了齐全的文档来说明这些限制,但一般开发者明显不能完全领会。就像你不可能读完所有法律以后再迈出家门第一步,一定碰到问题再解决问题更实际。好在,这次我没碰到这个问题。

名字

好吧,来说前文的坑。也许你会奇怪,为什么直到「沙盒限制」用的还是「iKindle」这个名字?是不是打错了?没错,确实是叫 iKindle.

但是,苹果不允许在 App 名字中出现 Kindle 这个字样。于是,只能改名了。这真是个痛苦的过程。一方面,想出一个好名字并不容易,我纠结了 N 久(具体可见我的博客),最终写了 Klib (Kindle library) 这个名字。

另一方面,所有的东西都是跟名字相关的,比如代码、App 名称、文案、截图、等等。一旦名字改了,所有这些都要改。这是极其繁琐的事,我再也不想做第 2 次。

比如 Logo,变成了下面的样子:

发布、推广

终于,在被拒了 2 次后,Klib 终于顽强上架 Mac App Store.

接下来的问题就是,怎么让更多人知道 Klib,也即,如何发布、推广?

Product Hunt

在这一环节,我最没有办法的,就是 如何在海外推广。毕竟咱们是中国人,很难打入敌人内部,更别说在外国产生影响力。

怎么办呢?我目前找到最直接有效的方法,就是在 Product Hunt 中有个成功的发布。这样国外的媒体也会在 Product Hunt 中找较好的产品进行报道。所以,Product Hunt 成了 Klib 走出国门、走向世界的关键

Product Hunt 的发布有很多可说的,这里挑几点重要的来说:

  • 发布时间
    • 从星期几的角度,每周二是最合适的。因为周二 Product Hunt 的用户量最多,也有充足的时间进入 Product Hunt 的 Weekly Report. 不过,考虑到 Klib 是小众产品,不太可能进入当天的 Top 1. 根据「鸡头与凤尾」的逻辑,我选择美国的周一来发布。
    • 从几点的角度,一种做法是在凌晨 00:00 发布,这样可以充分利用 24 小时进行传播;另一种做法是在早上的时间发布,因为这里媒体人、评测者刚刚起床,可以给他们更多爆光。考虑到我并没认识国外的媒体人,还是 00:00 发布吧。因为时区的原因,Product Hunt 会在北京时间下午 4 点切换昨天、与今天的产品列表。
  • 名字、标语
    • 名字就是 Klib 了,重要的是标语。要简洁、要突出产品特色、要抓住用户眼球。
  • 截图
    • 如果有必要,可以制作动图,虽然我挺讨厌一旦有多个产品都使用动图,会让界面闪啊闪,显得很 Low;但没办法,这确实是能抓用户眼球。并且,Product Hunt 的 Twitter 账户也有专门转发动力的。
    • 基本上,我把把 Mac App Store 的截图重用了。
  • 发布后,立即在首条评论中添加更多信息。如产品的进一步介绍、未来的规划、联系方式、等等。
  • 转发、扩散。也就是让更多的人点进来看,投票、讨论。尤其是大咖的转发会很有效。我最大的成绩是 Ben Tossell (Community Lead of Product Hunt) 参与了讨论。可惜,他并没有转发。
    • 不过,不要一味的拉票,Product Hunt 有自己的排名算法。
  • 及时回复留言 。用户的留言,要立即回复(恩,晚上基本可以不睡了,盯着)。并且,可以将用户的回复转发到 Twitter 上,并表示感谢。

最终,Klib 在 Product Hunt 上获取 200+ 点赞,10 几位国际用户参与讨论,并 入选当日 Top 10,基本满意。

用户群

Mac App Store 上的好评、留言,自然是非常重要的。而 最有可能给你好评的,就是你之前产品的用户。我一开始没有经营自己的用户群,现在回想起来深觉可惜。好在,现在慢慢有了 微信群Telegram 群也有了一些非常友善的用户,感谢你们!

媒体

媒体的爆光自然必不可少,他们的影响力是个人无法比拟的。最好是:

  • 平时就与媒体朋友保持联系,不要临时抱佛脚;
  • 上线前发体验版本,给他们预留时间准备文案;
  • 上线时及时通知,方便他们安排发布时间;也可以提供兑换码,方便对方做活动。
  • 发布会,也要帮忙转发,并表示感谢。

其他影响力

动用所有可以动用的影响力,让 Klib 触达更多的人。比如让朋友帮忙转发及好评,微博转发抽奖,朋友圈,等等。

其他

定价

定价是门玄学,太高没人买、太低自己不划算。目前 Klib 采用的是免费 + 内购(一次性买断)的方式。免费是为了方便更多的人使用 Klib、降低门槛。虽然我很想继续尝试订阅模式,无奈用户对订阅始终接受程度不高,且 Klib 本身的属性也不太适合订阅模式,于是采取买断式。

趁这个机会,表达下个人观点:买断对开发者并不友好,订阅才符合实际。毕竟通常开发者需要对产品进行持续改进(这并不是说当初发布的产品有问题,而是产品本身有改进),却不能从改进中获得收益,这并不健康。伤害的最终可能还是用户自己的利益,因为开发者一旦坚持不下去而不更新,用户只能使用有待改进的版本。

定价目前是 ¥50,首发限时半价优惠,即¥25,入手即赚。

首发的意义

目前,Klib 是 macOS 平台、首款上架 Mac App Store 的 Kindle 笔记管理工具。这种唯一性是很有意义的。因为没有直接竞品,甚至有一定的定价权。

对用户而言,首发更容易使产品在这一品类中形成定位,后来者要想抢走在用户心智中的定位,就不容易了。

单日销量的意义

如果单日销量可以使 App 出现在排行榜的前面,会带来一些附加的流量,因为别的用户可能因为榜单的原因多看两眼。

首发第 2 天(第 1 天 App Store 更新太慢,没有数据),Klib 在中国区、Utilities 这一品类中,Free 排名 22、Grossing 排名 14,算是不错的成绩了。

之后

目前,Klib 算是成功上架 Mac App Store,也收到了很多用户的反馈。接下来,我会继续改进 Klib,如增加多看支持、与 Amazon 中的笔记同步、与 Kindle 其他平台的笔记同步、导出至 Evernote、等等。当然,我会非常谨慎地加功能,保持 Klib 的精简。

致谢

真的有很多人需要感谢…

  • 感谢家人对我做独立开发者的支持(主要是接受不赚钱…)
  • 感谢用户对我的信任
  • 感谢朋友的帮助(比如 Kindle Mate 作者 提供多语言支持、Allen 帮助制作 Logo、61 帮忙搞定微博短链)
  • 感谢媒体的报道(点名最先报道的 少数派
  • 感谢所有点赞与批评的朋友
  • 感谢苹果(虽然 MAS 有坑)
  • 感谢 Kindle
  • 当然,感谢 CCTV

最后

希望 Klib 能让大家多读点书、记点笔记,哪怕只是一点点改变,也是 Klib 的莫大荣幸。