一个故事:从想法到产品
毫无疑问,新冠疫情在很多方面都深刻的影响了我们的生活。不论是出门必须戴口罩,在户外保持社交距离,还是外出回来要使用6步洗手法洗手,又或者由于远程办公而形成的物理隔离带来的心理上的负面影响等等,这些都深远的影响了我们的日常。
我今天要分享的一个故事正是与此相关:我是如何在维州宣布进入灾难状态的一个多月后,不得不全天几乎24小时呆在家里的情况下,从零开始进行一个iOS App开发并最终上线的故事。而这一切自然要从原始的需求分析开始说起。
原始需求分析
这个需求其实由来已久。在平时购物的时候,为了避免花费太多时间在超市,很多人都会随身携带一个购物清单,像下面这张图上列出来的那样:
大家平时买东西也可能有类似这样的纸质的购物清单吧。这种购物清单便于携带,随用随扔,非常方便。不过有几个小的缺点:一是买完这次之后,下次再去买的时候很可能卡片不知道丢到哪里去了;二是如果不配合笔一起使用的话,很可能要检查多次清单才能确定某样商品是否已经买过。此外,纸片上的信息毕竟有限,有时候你想要描述类似这样的需求:在永辉超市买甘竹牌豆豉鲮鱼,不要错买成鹰金钱,就会发现纸和笔可能不够用了。
我希望有一个手机App可以帮我管理这些清单。因为手机基本上是我出门必然会带的设备,手机本身可以作为纸笔Todo的替代品,而且有图片作为参考可以快速定位并找到我需要的商品。
另外一个典型的购物场景是:我每次购物往往会去多个超市,有的超市新鲜果蔬比较好,但是价格偏贵;而另一家则百货齐全,有时候水果不太新鲜;另外有一些商品比如豆腐乳,胡椒粉之类只能去亚洲超市才能买到。这时候我希望这个App可以提供分组,我可以在某家超市买完所有想要的商品之后,再换一家买剩余的。
灾难状态的封城在某些方面促进了这个想法的实现:首先被关在家里不能外出的我平白多了很多时间,用这段时间来学习一项新的技能显然是可行的;其次每次购物必须速战速决,尽量避免在人多的地方待太长时间而增加感染新冠的可能性;最后每个家庭每天只能有一个人出去购物,需要将清单尽可能细化然后实施(软件可以在某种程度上简化这个过程)。
当然,苹果自带的Notes
或者Reminder
都有类似的Todo管理,但是并不专门面向买菜。我设想的这个应用专门用来管理购物清单。在实施方面,我不打算从Hello world
这样一步步从Swift的基本与反学起,而是先确定要做的App的主要功能,然后带着问题一边学习一边实践,最终把它演进成一个公开发布的工具。
实施
现在我终于到了我有一个价值千万的idea,就差一个程序员的阶段了。而巧的是,我就是个程序员。唯一的问题是,我的Swift
和移动App开发经验约等于零(虽然多年前在项目间隔中参加过张帅的Android和黄磊的iOS workshop,但是时间隔得太久已经基本上还回去了)。此外,一个产品还有很多杂项,比如界面设计,图标,legal相关,App发布流程等等,这些对我来说完全都是未知数。
在实践中学习
通常来说,我比较喜欢通过循序渐进的方式来学习,从简单的例子开始,逐渐学习各个主题(如何设计界面,如何连接数据库,如何使用网络等等),这种方式的好处是基础会比较扎实,而且很多细节都可以得到充分练习。不过缺点是战线太长,往往需要耗时数月而没有实际产出。也因此如果过程中被一些其他事情耽搁,就很难再连续起来。
另一种方法是结果导向,然后用结果反过来驱动需要学习的内容。比如你想要开发一个本地的图片编辑App,那么肯定需要学习访问摄像头,访问本地文件系统等,而无需考虑网络和数据库。这种方式的优点是可以提供实时反馈,并且时刻有明确目标。
同样的,这种方法也有一些缺点:
- 会花费很多时间在
Google
和Stackoverflow
上 - 很多资料都严重过时了,很多
object-c
的资料,以及很多swift
老版本的资料 - 大部分人分享的内容,都比
Hello world
级别稍微复杂一点点,但是基本上不可能直接应用到你的应用中
因此在这个项目里,我尝试将两者结合起来。比如在第一个milestone
里,我通过结果导向的方式,仅仅浮光掠影的学习必要的知识点,比如列表视图,iOS里的MVC,navigation之类。主要课程就是参考youtube
上众多的入门教程:通过一个简单的例子,手把手的写一个Todo之类。这个过程可以熟悉编辑器,快捷键的使用,Swift
的简单用法,界面设计器的使用等等。
这个阶段之后就可以开始“野蛮”编码了,开始的时候并没有特别的章法,就是照着例子写就好了。如果进行的顺利的话,很快就会有一个简单的原型出来,但是过程中会遇到海量的问题。小到如何添加图标,配置颜色,大到两个view
之间如何通信,如何同步数据。不过作为一个有工程经验的程序员,大部分问题都可以通过Google+Stackoverflow
解决。实在自己找不到答案的,还有很多热心的同事可以请教。
如果说我有一丁点成绩的话,无非是把别人用来思考变量名的时间花在了尝试Google关键字上了而已。
– 鲁迅
过了这一个阶段之后 - 有了一个可以工作的原型 - 就可以好好的打磨它了。于是我开始系统的学习udemy
上的课程,比如如何使用使用protocol
模式,如何使用MVVM
,使用segue
的正确方式等等。这时候可以适当的做一些重构和修一些简单的bug
。
下图是从第一个原型到当前最新版本的用户界面的演进示意。从一开始的原生的TableView
到定制的单元格,再到对数据按照超市信息分组并展现,每一次都比之前略复杂一些,而且每次的修改基本上都是基于客户的反馈来改进的。
此处的一个建议是:如果时间充裕,还是建议从基础学起,循序渐进,这样的方式可以确保基础比较牢固。而如果时间有限,又害怕被别的事情分心,那就可以边做边看,用到了再学,等建立起正向反馈之后再回过头来查漏补缺。
项目管理
在实施过程中我发现:一个可视化的,有迹可循的故事板非常有用。当然比这个故事板更加重要的是你需要制定一些规则并确保执行。比如,所有的卡片都应该从Backlog开始(即所有的需求都要经过仔细分析),开始前需要和stakeholder确认优先级,在开发过程中需要及时收集反馈,有明确的DoD(definition of done)等等。简而言之,尽量将其当成一个正式的项目来运作。
另外我还发现,之前在客户那里见到的诸多反敏捷的实践,我自己也往往会再犯一次。比如不及时更新卡的状态,临时插入Doing任务或者不限制在制品数量,不及时从stakeholder那里收集反馈,验收条件不明确等等。不过后来就好很多了,我尽量让其变得正式一些,而这个过程也确实可以帮助我更加聚焦在高优先级,更具价值的(而不是更fancy)的功能上。
这个大约是最简单,投入成本最小,又可以产生很好效果的实践之一了。特别是如果被一些临时任务中断之后(比如过了一个周末,或者项目上很忙晚上要学一些其他资料),一个故事板可以快速的帮你建立上下文并快速进入状态。
上线
经过了大约两周之后,我注册了Apple developer账号,并提交了第一个原型。第一个原型包含了三个列表(一个商品catalogue,一个待买列表,一个推迟列表)结果被无情reject
了,原因是……功能太简单。仔细分析之后,我发现功能虽然不算单调,但是界面太过于原始,很多界面元素也需要调整。于是有花费了一周的时间来调整设计,比如字体的选择,字号,色彩对比等,以及实现了共享列表功能(可以将你的购物清单通过Airdrop或者微信等发给别人)。第二次提交之后,3个小时后就审核通过了。
V1的开发大约耗时3周时间,每天晚上学习并编码2-3个小时左右,周末会稍微多一些。随后V2也差不多2-3周,除了功能开发之外,还有些周边的定义,比如screenshots
海报的设计和实现(详见下一小节)。不过我最近发现To Buy在iOS 14上有个bug(原因是iOS自身对NSFetchedResultsControllerDelegate
的更新上有个同步的bug,如果你正好知道解决方法的话,请不吝赐教,非常感谢),所以如果你使用的以后发现什么异常的话,请反馈给我。此外,所有代码都在Github上,如果有感兴趣的同学也可以一起来完善(野生iOS程序员写的代码,请轻拍)。
To Buy应用
这个应用叫To Buy: grocery shopping list,目前已经在App Store上线了。这是我的第一个从界面设计,到编码实现,到架构及运维(虽然目前主要用户只是我和我老婆),以及图标和海报的设计,文案的编写等完全端到端的产品。虽然没什么用户,不过作为第一个App,我自己还是挺满意的。
特性列表
再经过了几个版本(当前版本V2.5.0)的迭代之后,现在的To Buy的主要功能就是:维护一个购物清单。用户可以:
- 通过拍照/相册里的照片,文字等来添加一个要买的商品
- 商品会按照超市分类,这样我就可以在A超市买所有果蔬,在B超市买日用等
- 可以把历史上买过的商品存到字典里,方便下次购买
- 可以将购物清单分享给另一个人(比如通过Airdrop)
- 可以把Apple自带的Photos/照片中的照片直接添加到To Buy中
- 所有数据会同步到iCloud,即跨设备可以完全同步
在迭代中,我发现了一个很有意思的现象:不论我当初如何笃定某个特性的设计,它的最终形态都会和开始的时候想法相去甚远。比如最早的想法是个内置一个大而全的商品字典,然后用户从这个字典里选择商品。但是最终发现这样的操作效率很低,特别是在手机上操作的时候。
最终To Buy
演化成可以通过照片/摄像头添加,然后快速编辑等。还有一些功能,比如一键分享你的清单给别人,让TA帮你买东西等,也是在使用者发现的新的功能点。甚至,最开始的三个独立的页签也被简化成了一个页面。
一些心得
在这个从想法到实践再到产品上线的过程中,我有了一些对做一个产品(即使是一个很小的,只是为了解决我自己生活中一个小问题的产品)的新的认识。总结一下,大致可以分为这样一些方面,希望可以对你也有用。
不要想太多
和对一切都充满兴趣的新人程序员比起来,有经验的程序员往往有一个功利心太重问题。他们更倾向于分析学习某项技术的投入产出比,该技术的前景等等。诚然,在学习之前做一些功课来确保自己的时间不会白费当然无可厚非,不过需要注意的是:不要花过多的时间在考量本身上。首先这个对比本身就会占用一定时间;其次,在做了各种理性客观的对比之后,有可能会忘记自己的出发点(甚至产生已经掌握了该技术的错觉);最后,很多问题是表面上看不见的,只有在动手解决问题的时候才会意识到其中涉及细节之深广。
解决方法就是不要想太多,在做了初步调研之后,不妨动手用这些技术来解决一个具体的问题,并通过解决问题来学习并和该技术进行交互,从而得到更好的提升。另外,解决问题带来的成就该又回驱动你进一步的探索。
不要追求完美
正如人们常说的那样:Perfect is the enemy of good。这一点在做一个产品的时候尤为重要,不论是看起来更加健壮的架构风格,还是完善的自动化测试,又或者正确的使用MVVM模式,这些的优先级都应该是略低于产品的主要功能的。如果想要设计一个完美的架构在开始进行编码的话,我怀疑几乎不可能有那一天的到来。此外,追求完美可以是一个渐进的过程,我们可以从具体的问题开始简单实现,逐步的将其变得完美。
我们当然需要不断的迭代并打磨产品中的各个细节,有时候甚至需要逐像素的调整一些视觉元素,但是优先级更高的是将主体搭建起来,然后再考虑局部的优化。
如果不确定哪种方案,选择简单的
在选择某个技术方案时,尽量选简单的方案。如果使用文件系统就满足需求,那就不要用数据库;如果本地计算就足够,不要选择网络。在工程中,每一个额外的集成点都会有很多难以预料的失败几率。而这些失败几率会累积并放大,形成更大的,几乎必然的的错误/失败。
换句话说,延迟对架构的决策,直到万不得已。另外,我们总是有机会回过头来在做调整的,也正因为如此,在做调整时也始终选择比较简单易行的方案,这样即使要重写的话也更容易。
细节中的魔鬼
在这个过程中,另一个重要的心得是很多高大上的、振奋人心的功能事实上并不会花很多时间,反而是在一些不起眼的功能诸如i18n
,或者accessibility
,从异常中恢复等等辅助性功能上会花费很多时间。
比如在实现dark mode的时候,我们需要考虑背景色,高亮色,阴影,文字对比度等等,而且需要在各个场景下都即可用又好用,还不能让用户感受到设计的存在。这些隐藏的细节会花费大量的时间和精力,甚至需要很多重复的纯体力的劳动,但是你仍然需要像接受其他部分的特性那样接受它们。
而要做好一个产品,你需要静下心来仔细打磨那些边缘的,支撑性的功能。这些看似出力不讨好的特性,就像是在让你感觉到饱的之前吃掉的那些馒头一样不可或缺。在做计划时,也需要将这些因素都纳入考虑范围。
小结
道德经有云:合抱之木,生于毫末。任何一个复杂的产品都起始于一个简单的想法。而在将一个想法逐步转换到产品的过程中,即使像To Buy这样一个简单的业务,也会有很多值得反思和琢磨的细节。
首先,除了明确目标之后的坚持不懈之外,还需要有意识的将这个目标breakdown
成小的、可度量的任务,并赋予优先级并逐步的实现。在上面的情况中,我自己恰好是stakeholder之一,学习iOS开发是项目的一个重要目标,因此这个目标也会参与到优先级的排列。这一点在很多的客户项目中则并不总是现实情况。
其次,在实现细节中,大可以在有了初步设想之后就大胆的做实验,以粗糙但目标明确的方式实现原型,然后再以此原型为基础逐步完善。对于拿不准的设计或者技术方案,也不用过度考虑,选择相对简单易于实现的选项。大部分复杂性则可能当你深入到足够细节中之后才逐步浮现,此时的决策反而较事先的详细规划更为可靠和可行。
此外,将整个实现的过程看成一次/一系列实验的另外一个好处是,你通过调整不同的参数来观察组合的结果,并动态的修正目标,最终到达一个比较贴合实际的成果。退一步来说,不用将最终结果看的太过重要,不妨将其作为一次有趣的学习过程,至不济也可以为你下一个想法到产品的过程提供可以依据的经验。