0%

cover

很久没走过这趟路,村前往东南的路,一条鲜有人走的路。

它算不上一条路,因为根本没有一条路。这趟路通到东南水塘上的菜园,它需要经过稻场、穿过一条条田埂、跨过排水渠,再爬上水塘埂就到了。如果绕着水塘和边上的菜园走上半圈,翻过菜园后面的一座高土埂,就可以来到隔壁村屋后。

这趟路除了附近田地的庄稼人和菜园的主人,一个寡居老人,几无人走。加上隔壁村与我们两村之间少有来往,据我所知几无亲戚或通婚之类,所以这趟路鲜有人走,又因位置偏僻加上没有可通行的路,附近村走亲戚都路过不了这里。

二姑嫁到了隔壁村的隔壁村后,我们一家便成了这趟路的少有的过客。小时候,每逢去二姑家,翻田埂、过菜园、爬上水塘,站在村前东南高处,面朝西北回望着村庄。它像一块沉睡的石头,安静地枕在风中、睡在雨里,向回望者诉说着它的不舍。

作客归来,穿过隔壁村庄从土埂下来,又一次站在村前东南高处,遥看到村庄,她四周被田地和池塘包围着,东边是高地,西边是洼地,南边是平原田地,北边是山。她蛰伏着,默默欢迎归来的人们。

当我走进村庄,拥抱了村庄,村庄拥抱了我,将我裹进怀里,露出她的一块块肌肉和血管。“不识庐山真面目,只缘身在此山中”,村庄静静的诉说着她的不一样。


很久没有走过这条路,站在东南,面向西北望着村庄。现在我回到这里,村庄也在这里,我凝视着村庄,村庄也凝视着我。

很久不见!
很久不见。
我回来了。
你回来了。
你在等我?
你在等我。
我在外地,如何等你。
你在等我。
你知道我会回来?
你会回来。
我没有想过回来。
你会回来。
你了解我?
我了解你。
呵,你看着我从小到大十几年,应该了解我。
我了解你。
谁了解你?
我了解我。
这么多年,没人了解你?
我了解我。
我不想回来,我长大了,为了远离你,去远方,再也不回来。
你会回来。
等到落叶归根?
落叶归根。
那时你还在么?
我还在。
可能你会拆掉,重建,甚至废弃?
我还在。
一直在这里?
在这里。
现在是在哪里?在梦里?
在梦里。


我站在东塘埂上望着,东头小金家起火了,隔壁大飞门前看到了熟悉的人群,村庄有了久未拥有的热闹。我背着东西,和几人一起往东南逃离。

在梦里?小金年前已经搬去新盖的新农村楼房里,拜年时我去过,300来平的独栋三层小别墅,小金媳妇与以前住在拥挤的民房里完全不一样。大飞一家搬到镇上已有几年,大飞门前怕是多少年没有人群聚集过了。

村庄凝视着我,
你会回来,
都会回来,
我见证了你祖辈们的生老病死,你们,也不例外。


醒来时,深圳三点,外面月色渐弱,远处大楼点亮的光,路灯下偶尔疾驰的车辆。
此时村庄应该是漆黑一片,天色将明,所有一切都沉睡着,除了村庄。
哪里是梦,哪里又是现实呢。

Date Histogram时区:

遇到一个按时间统计数据不一致的bug,怀疑是时区的问题,设置下时区即可。

查询语句中:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"query": {},
"aggs": {
"dateAgg": {
"date_histogram": {
"field": "@timestamp",
"interval": "1d", //1s/1m/1h/1d/1w/1M/1q/1y
"format": "yyyy-MM-dd",
"time_zone": "+08:00" //时区设置
}
}
}
}

Java 中:

1
AggregationBuilder aggregationBuilder = AggregationBuilders.dateHistogram("dateAgg").field("@timestamp").dateHistogramInterval(DateHistogramInterval.DAY).format("yyyy-MM-dd").timeZone(DateTimeZone.forID("Asia/Shanghai"));

参考资料:官方文档


Date Histogram统计后再去重

查询语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"aggs": {
"dateAgg": {
"date_histogram": {
"field": "@timestamp",
"interval": "1d",
"format": "yyyy-MM-dd",
"time_zone": "+08:00"
},
"aggs": {
"distinctUserIdAgg": {
"cardinality": {
"field": "user_id", //去重字段
"precision_threshold": 1000 //精度,范围0-40000
}
}
}
}
}

Java中:

1
2
CardinalityAggregationBuilder userIdAggregationBuilder = AggregationBuilders.cardinality("userIdAgg").field("user_id").precisionThreshold(40000);
aggregationBuilder.subAggregation(userIdAggregationBuilder);

参考资料:官方文档


根据分数区间统计

例如要统计班级学生考试分数哪个区间的人最多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"query": {},
"aggs": {
"scoreAgg": {
"histogram": {
"field": "score", //根据哪个字段划分区间
"interval": 10, //划分区间大小
"order": {
"_count": "desc" //排序
}
}
}
}
}

参考资料:官方文档


根据分数区间统计后再去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"query": {},
"aggs": {
"scoreAgg": {
"histogram": {
"field": "score",
"interval": 10,
"order": {
"_count": "desc"
}
},
"aggs": {
"userIdAgg": {
"cardinality": {
"field": "user_id",
"precision_threshold": 1000
}
}
}
}
}
}

Fielddata is disabled on text fields by default. Set fielddata=true on xxx in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memor

这个错误的官方解释在这里: Fielddata can consume a lot of heap space, especially when loading high cardinality text fields. Once fielddata has been loaded into the heap, it remains there for the lifetime of the segment. Also, loading fielddata is an expensive process which can cause users to experience latency hits. This is why fielddata is disabled by default.

解决方案:

1
2
3
4
5
6
7
8
9
curl -XPUT 'http://{ip}:{port}/{index_name}/_mapping/{type_name}' -d '
{
"properties": {
"{field_name}": {
"type": "text",
"fielddata": true
}
}
}'

  • type_name:ES中type的值
  • field_name:要top统计字段的名字

参考资料:官方文档

cover
换 Disqus 才一年,因为被 Q,本来就清静的博客更清静了,索性再也看不到任何评论,忧伤。

最近又看到一个讨论国内社会化评论系统的帖子,兴之所致接入 Gitment。

网上有不少教程,可以按照教程一步步操作。说说我的踩坑过程:


1. 更新主题

我的博客采用的是 hexo + hexo-theme-yilia,首先更新了hexo-theme-yilia主题。

2. 配置信息

发现hexo-theme-yilia主题自带支持Gitment,如下

1
2
3
4
5
gitment_owner: #      #你的 GitHub ID
gitment_repo: '#' #存储评论的 repo
gitment_oauth:
client_id: '#' #client ID
client_secret: '#' #client secret

创建一个 OAuth application,并获取相关信息填入其中。

3. 部署博客

进入博客文章详情,可以看到文章底部已经有评论模块了。

4. 初始化评论

可以自己点进博客手动点击初始化,也可以使用自动初始化 Gitalk 和 Gitment 评论

遇到一个蛋疼的问题是当文章链接过长的时候(超过 50 个字符),会报Validation Failed错误。

解决办法:

  • 如果是手动初始化的,可以考虑在 Gitment 配置里面用page.date代替url,这样就不会超出 50 个字符了。
  • 如果是使用自动初始化的 ruby 脚本,可以改下代码,思路有几种:
    1. 将 url 截取不超过 50 个字符
    2. 将 url md5 加密或者 Base64 编码
    3. 用时间来代替(估计比较蛋疼,涉及时间格式转换)
    4. 设置博客的permalink,保证 url 长度在 50 个字符以内
    5. 其它大胆的想法…
      我采用的是第 1 种方案。

FBI Warning! FBI Warning! FBI Warning!

因为 Issue 需要和文章对应,改完代码后,还需要在博客源码里面改下 Gitment 的配置,使两处生成的值一致。

Ruby 脚本修改一处:

1
req.body = { body: url, labels: [kind, url[0,48]], title: title }.to_json

Gitment 配置修改一处:

1
2
var gitment = new Gitment({
id: "<%= url %>".substring(0,48),

为方便还晕着的,再贴下原来的代码以便区分:

原 Ruby 脚本:

1
req.body = { body: url, labels: [kind, url], title: title }.to_json

原 Gitment 配置:

1
2
var gitment = new Gitment({
id: "<%=url%>",

5. 大功告成
6. 后续问题
  • 之前旧的评论不能导入:理论上可以实现,解析 Disqus 导出的评论文件,然后添加到文章的评论内。看有无必要了。
  • 后续每次添加新文章,都需要手动初始化下评论:同样可以使用脚本初始化,或者发布文章部署时自动调用以初始化。

参考资料:

Gitment:使用 GitHub Issues 搭建评论系统

自动初始化 Gitalk 和 Gitment 评论

Gitment评论功能接入踩坑教程

周五,天晴,午后。

15:00 股票收盘后,放弃盯着泛绿的持仓,拿起水杯咕咚灌下几口,时光恍惚间变得慵懒了些。

QQ 闪来一句消息,老家的表弟分享一条游戏抽奖链接。往上一条消息,是过年时候他群发的祝福。表弟差我十二岁,素来不甚亲近,我生他未生,他生我少年,等他少年我已结婚生子成年。


表弟分享游戏里的抽奖链接,
我心想:这小家伙,放假玩游戏啦。
回复道:“。。”
表弟:“!”
“小家伙,回家了吗?”
“对”
“在干吗呢”
“打游戏”


“嘿!怎么又在玩游戏?”。我本想问这一句。

但那一刻忽然想起了,十几年前的初中,周五放学后,裹拥着人群出了校门,和几个同村的小伙伴一路迎着夕阳,边玩边走回到了家。放下书包望着一周未摸的小霸王,趁机耍把魂斗罗。不一会,小伙伴溜来,拉着要在门前趁着天色尚亮玩一波。

几天不见的柳树又长出了新叶,田野里庄稼发的更绿,脚下的土地因为久无玩耍的我们而显得有些冰凉。

家家烟囱里升起了晚饭的炊烟,窗户里飘出了香味。过不一会,回家吃饭的叫喊声此起彼伏,小伙伴依依不舍道别。


我不该问这么扫兴的问题,十几岁的我,讨厌被说教、讨厌被上课,只想沉浸在自己的小小世界里。

“拍张家里的照片看看天气如何”
过了些会,表弟发来如图。
51
水边的树,茂密的枝叶遮住了五月的天空,天空上满布白云。


已是五月,该回家看看了。

cover_img

前注

本文原创作者Amon Xu,转载请注明作者与链接,谢谢:D

”16 点会议室开会,讨论接下来到 6 月份的工作计划。“
一阵默默无言。
……

今天是大哥上班最后一天,大家都想和大哥多呆上一会。
”大哥,什么时候走?“ —— ”一会,等 Boss 签字。“
“现在走吗?我送你。” —— “还要一会。”
“要不要一起去开会,最后一次。” —— “我不用了,你们去吧。”
……

会议正进行时,大哥在微信群里发了一个红包:祝大家工作顺利。彼时,会议室唾沫横飞、激扬文字,窗外四点钟的太阳依旧强烈,大哥开车飞快驶往广州,视野两边的一切都迅速被甩在身后。深圳,再见!

前一天夜晚,大哥请我们吃散伙饭,一路欢声笑语,饭桌上举杯痛饮,像我们刚认识时吃结伙饭一样。大哥和最后一次组队王者荣耀,大哥依旧超神 Carry 全场,连下三城。这是我们送给大哥最后的胜利,也是大哥送给我们最后的胜利。

再往前几天,大哥举行了交接工作会议,会议上他讲解了满满一大篇的交接文档,以及部分核心业务逻辑的思维导图。涉及核心业务逻辑、环境配置及部署、数据算法对接、中间件模块说明等。基本囊括了大哥进入公司一年多的重要成果。

时间再往前,过年刚来没几天,大家尚沉浸中假日的气氛里、陷入假期综合症中不能自拔,惊闻大哥要撤退了,大家甚至没有做好上班的心理准备,突然受到新年第一暴击,皆哑然叹息。

回想大哥入职一年有余,短短时间,已然成为团队核心主力之一,这也是为何“大哥”称号流传开来。上年度(2017)年终总结大会,大哥荣膺“攻坚奖”,以此表彰大哥上年度的工作成果。

大哥初入职,彼时团队正值项目重构期:将后台架构由 Golang 迁移到 Java。当时搭了一套 Java 微服务架构,大伙皆摸着石头过河。大哥坐在离我不远的位置,开始了熟悉业务和代码。

因为项目旧数据都存放在 MongoDB,新架构采用 PostgreSQL + MongoDB,大哥首先接触的是这个数据迁移的需求:将部分数据由 MongoDB 导入 PostgreSQL,并编写了部分 DAO 访问数据库的接口。

与其说是“重构”,倒不如说是“重写”,整个重构需要将原 Golang 所有的业务代码用 Java 重写。因为赶时间,加上大家是摸着石头过河,前期很多业务代码写的不甚严谨,写 Test Case 的习惯也没有流行起来。大哥编写了部分接口的 Test Case,并将这个习惯发扬。

在项目重构还在进行的时候,已经出现重构‘重构‘的情况,大哥为整理、优化代码提供诸多帮助和建议,比如抽离了 DAO 层业务的共享代码,使不同 Rest 层项目访问 DAO 层简便了诸多。

项目重构中,为了快速验证新平台架构,本地部署验证的都很匆忙。我们当时采用比较原始的 Git + Maven 部署方式,因为采用微服务架构,有大小十几个项目,每次部署苦不堪言。大哥后来编写几个 Shell 脚本,实现一条命令快速部署多个项目,部署项目从此实现鸟枪换炮。

大哥也承担了部分核心业务逻辑的编写。重构后项目年初终于上线,大哥在上线部署过程中发挥了重要作用,这也导致了在后续的工作中,他甚至承担了部分运维的工作。

上线后,逐渐发现了一些性能的问题,大哥优化完善了项目核心基础数据和统计数据的缓存方式,以及改造了部分业务流程的计算和缓存逻辑。

再往后,大哥一手编写了各个服务的 Dockerfile,以及 Maven 配置 Docker,通过 Docker Compose 编排各种服务,实现线上部署服务 Docker 化。

随着迭代的不断进行,大哥也对业务和代码愈加熟悉。因为产品的特殊性,PM 们经常脑洞大开做一些富含’战略性’、‘创造性‘的新功能,大哥承担了这些新功能的后台职责。亦由于产品的广性,需要做大量兼容性的工作、特殊性的功能、擦屁股般的需求,这些需求也源源不断的流向了大哥的名下。

彼时,大哥俨然已成为团队后台的核心人物之一,大哥的称号也流传越来越广。

大哥是典型的 90 后,表面文静内心火热,话虽不多人挺利索。

大哥喜欢追各种日漫,就连和大嫂去日本旅游时,狠狠地过了一把动漫粉丝的瘾。当他发出和各种动漫形象合影的朋友圈时,大家纷纷调侃大哥宅男。

大哥对游戏也钟爱有加。有一阵子,我们几人组队打王者荣耀,在我们猥琐发育时,大哥擅长打野位,每每 Carry 我们,成为一个靠谱的队友。

网易吃鸡新出时,我们自然也成为第一批尝鲜者。我成了一个勤劳的快递员,而大哥,无不冲在最前、攻城掠地、收割人头,继续扮演着中国好大哥角色。
game_img

大哥也早早的进入了养生了模式,保温杯、枸杞茶,天气稍微变冷就注意保暖,每每被调侃很虚。

PM 最擅长两件事情:一是脑洞大开做一些富含’战略性‘、’创造性‘的新功能;二是为这些脑洞填土,要么修修补补,要么重新推翻重来,然后未来某个时刻又想回到从前。

大哥难当,大哥更难当。不知何时起,大哥也被这些吃屎般的需求,每每逼的从座位上站起身,绕着座位背后的梁柱而行。每逢此时,我都很想和大哥说一句:QTMD 产品!转而低头做各种“ ”的需求。

我料想大哥应该不会久留了,但没想会这么快,我原以为大哥至少会再呆上半年的。大哥是在拿“攻坚奖”前后提出辞职的,这个决定无论是前还是后,背后都是几个艰难的决定。

大哥入职有一阵我才知道,大哥并非初入公司,早在读研时已在公司实习过,后来去了百度和网易。

大哥走后,据说大哥离开的原因是遇到天花板了,我们当然知道,大哥本来抱着学习的心态进入公司,一不小心就触摸到了天花板,这里面既有技术上的,也有产品上的,既有可以改变的,更多的是无力改变的。

海阔凭鱼跃,天高任鸟飞。大哥,祝你飞的更高!

cover_img

前注

本文原创作者Amon Xu,首发于云沃客平台,转载请注明作者与链接,谢谢:D

上一篇: 复盘 | 初次接项目的血与泪,扎坑了老铁

上篇文章主要讲踩坑。初次接项目,在项目开始前夕遇到的一些坑,并提供了一些个人建议。最后说到原型的迟迟交付、需求方对接人员的变更、硬件开发团队人员离职等等,各种因素都在拖累项目的进度。

当然,项目中也不是只有坑,也有一座高山。今天就继续回顾接下来项目过程,讲讲如何爬这座山。

既然选择了远方,便只顾风雨兼程。
                                —— 汪国真 《热爱生命》

万事开头难

中间因为需求整理、原型设计、经历长假一连串的折腾,等到原型最终交付出来,距离合同签订时间已经过去了一个月。如果按照合同上的交付时间来算的话,也意味着我们开发时间已经少了一个月。

合同上写明自签订日期开始项目正式开始,直到 xxxx 年 x 月 x 日。因为前期准备工作耽误了太多时间,如果就这么下去的话,项目风险很大,且会违反合同交付时间。基于此,我和对方人员沟通一下,明确是因为前期需求整理和产品原型设计耽误太多时间导致整体进度 delay,并确定整体进度预计会后延多少个工作日,这一切都拟定一个补充协议。

千里之行,始于足下

项目要开始,自然不能一股劲儿干,做好开发计划,不打无准备之仗。我们采用敏捷开发模式,所以下面这些内容很多是基于敏捷开发模式。

消化需求

首先,组织大家阅读消化项目需求。需求的理解非常重要,我见过许多开发人员,没有理解需求,上手就是一个字:干,然后开发过程各种问题,手忙脚乱地开发,还抱怨时间少、进度赶,结果自然开发效率和质量难以乐观。

仔细理解项目需求,正是磨刀不误砍柴工。设计人员理解了,可以更好的设计用户交互;开发人员理解了需求,可以避免后续开发中遇到因为需求不明而导致的逻辑问题;项目经理理解了,可以更好地把控项目的进度和细节,也更有效率地和团队人员沟通,不至于因为中间出现问题项目经理表示一脸茫然。

任务拆分

在理解需求之后,我们开始进行任务的拆分。任务拆分中,基于几个原则:

  • 任务粒度原子化。 最小粒度确保开发目标清晰,不涉及其他任务,可以更好的评估任务复杂度和开发估时。
  • 目标为独立个人。避免单个任务产生人员依赖,如有需要多人参与的任务,可以独立划分给相关人员。
  • 任务需要有优先级。如有强依赖,可定义好前置依赖。

举例:用户登录的功能,需要怎么拆分?UI设计一个任务、前端登录一个任务、后端登录一个任务?说是一个登录功能,里面可能包含:

* 账号密码登录(手机号、Email、用户名)
* 手机号快捷登录
* 第三方登录(微信、微博、QQ等)
* 其他方式登录(如卡号卡密登录、SSO登录等)

手机登录需要涉及短信 SDK(注册申请、后台接入 SDK)、获取验证码、发送频率限制、黑白名单等等;Email 涉及 Email 发送、验证;用户名涉及敏感词检测、唯一性检测;第三方登录需要涉及第三方 SDK(注册申请、后台接入、前端接入)。

所以你看,任何任务的拆分,都应该首先从业务角度出发,列出到底有多少种应用场景和可能性,然后逐个拆分细化。只有不断的将任务一点点地解剖到骨肉分离、细节毫发毕现,才能避免因为经验和时间的问题,没有看到一些暗藏的功能和细节,导致开发过程中处处踩雷。

时间评估

任务拆分后,评估时间的时候,开发人员一般会比较乐观,另外一般项目也因为赶进度压缩时间,我的建议是在条件允许的情况下,尽可能预留充裕的开发时间和缓冲时间。长期地压缩开发时间快速开发和赶进度,会压缩开发人员深度思考的能力,当然最终影响到的还是产品的质量。

外包项目中,时间评估上尽可能细致周全,将需求整理、原型设计、UI 设计、架构设计、项目搭建、功能开发、测试联调、文档整理交付、交付验收、项目上线等时间都区分开(因项目、需求而异会有不同)。

任务拆分和评估时间后,所有的信息都输出到项目管理工具上,然后制作燃尽图。即使没有项目经理也可以实现成员自我管理。如有项目经理,也可以基于此整理出一个开发排期表和甘特图。

Let’s go!

如果一个外包项目合同已经签订下来,项目正式启动,那么就早些动手吧。避免拖延症,尽早地让团队齐心协力,向着完成项目的目标前进。尤其是业余时间做外包项目,有各种理由和原因迟迟不行动,最终耽误的时间还是会由自己埋单。

这次外包也遇到这个问题,因为前期准备工作耽误时间太久的缘故,项目整个处于 blocked 状态,过了一段时间询问相关开发人员进度,得知还没有开始。

技术选型

技术选型要贴合业务场景,外包项目更要如此。当业务初期时,技术要灵活,以便快速验证业务模式,就是说要能灵活地改改改;当业务处于稳定期,技术要稳定,不能拖技术后腿,就是说业务正常了系统不能整天出 Bug 或者服务器没事就提出一个问题;当业务处于维护期时,技术要讲究妥协,就是说不能还花费大量精力折腾项目,维稳就好。

总之,技术选型都是为了业务服务。比如因为对方有 iOS/Android 两端 App 的要求,在仔细评估需求后,决定采用 Native + React 模式开发,这样开发效率提高了,开发成本也降低了。

时间管理

时间管理上,因为是业余时间做外包,所以本职工作和外包上的时间花费一定要平衡好,不能因为外包耽误本职工作,当然也不能将外包置之一旁。

每个人有自己的时间管理观念和舒适的状态,比如我,我个人比较喜欢的状态是早起写一阵代码,如果是工作日的话大概是一个多小时,上班时间专心处理公司事情,下班回家后继续处理外包工作一两个小时,这样每天可以有将三个小时左右的时间处理外包任务。夜晚务必早睡,这样可以保证第二天的状态,不影响本职工作。如果是周末的话,早上会运动,洗澡吃饭后精神倍爽,一口气可以持续工作到中午。

外包过程中务必找到自己最习惯的节奏和最舒适的状态,本职工作和外包项目一定不能顾此失彼。当然更重要的是,个人的健康状况,毕竟身体是革命的本钱。

项目管理

前面提到,敏捷开发会采用一些项目管理工具,如Tapd、Tower、Trello、Teambition等。基本上当一个团队有了经验后,都可以使用这些工具实现自管理。但因为是初次接外包,和团队成员磨合也是一个挑战,加上每个人个性不同,和外包项目的特殊性,作为项目管理人员需要在项目管理上投入一定的精力和时间。

项目管理千差万别,项目管理人员也千差万别。记得以前公司有过两任完全不同风格的项目管理人员,一个是每天晨会出现一次,其它时间见不到人,基本没什么存在感;另一个是每天像班主任一样跟在团队成员屁股后面催进度,结果导致团队产生了严重的依赖性,在他突然离职后,项目基本处理停滞状态,团队成员也完全无法适应。

我的想法是,团队成员可以有不同的时间管理方式,和投入方式,但是务必保证有持续性的产出。所以,在项目前期,我们便花一部分精力搭建了 CI 系统持续集成,后台和前端人员在实现功能提交代码后,可以选择自动发布项目或者一键发布,工作成果开发团队和外包需求方都可以查看。使用 CI 后,项目的进度就一目了然,不会处在一个黑盒里面。CI 的意义是以最小的精力,实现最大的价值。

外包项目如果周期很长,会是一个持久性的拉锯战。因为团队成员都在一个城市,所以我们除了日常的开发和沟通外,定期会组织大家坐在一起工作,这样面对面地沟通交流,将之前因异步沟通无法解决的问题提出来解决。比如租个酒店房间,大家周末一天或者两天时间呆在那里,没有其它的干扰,可以全身心地投入到项目中。一般这样的一天,可以有几天的产出。当然这样高专注度、高强度的工作也会使人比较疲惫,所以不必经常这么做。

当然主要还是大家日常开发、沟通,所以养成线上及时同步开发进度、共享开发中存在的问题很重要,我们当时是每日在微信里面沟通进度,然后如果 CI 有更新,大家可以相互体验测试,发现问题,有问题直接在微信里沟通,如果落实下来,直接输出到问题管理系统里。

在整个开发过程中,可以将重要的、难度比较大的任务优先解决,这样防止后续时间不充足的情况下,解决难度大的问题没有太多的精力。

里程碑交付

里程碑交付也是外包项目中关键的一环,在之前任务模块拆分和评估时间后,项目经理可以根据进度安排里程碑交付,在一定周期内按时按量交付已有功能模块,避免项目周期持续太久客户接收不到产出。尽早的交付,可以验证一些功能,也可以暴露一些问题,甚至了解客户的验收喜好标准等等。

例如,一个购物网站,可以根据功能划分为商品模块、会员模块、订单模块、购买支付几大模块,然后依据优先级分期交付一些模块。

记得之前在云沃客上接项目时,项目里面会有里程碑的历史记录,详细记录了每一个阶段的任务、截止日期、金额和状态,每一个阶段都会有交付工作和申请结款两种操作,将里程碑线上化,工作者和需求方都能清晰地查看到每个阶段的工作状况和历史交付产品。

交付验收

辛苦一段时间,最终将项目交给客户手里,因为前面已经有 CI 系统和里程碑交付,所以对客户的验收标准是有一些心理准备。项目中的一些问题,也尽量会在开发阶段就沟通处理掉,争取不在最后交付验收时遗留问题。最终交付给客户一定是包含完整功能、能稳定运行的系统。

关于后续维护,开始签订合同时已经约定好免费维护周期,这期间只包含处理线上问题和解决 Bug,不包含新功能的开发和功能变更。如有相应需求,可追加相关协议。

最后总结

项目到此也结束了,回过头看看,整个项目最花费时间精力的地方,还是开始时和对方沟通整理需求,因为相隔两地,线上沟通效率又不高,花费了大量的时间和精力在业务层面。

当然,现在一般的外包项目都是需求很明确,只需要将之由需求层面实现出来就好,这样让专业的人做专业的事。所以建议大家想图省心的话,还是上正规的外包平台(比如云沃客)接活,因为做一个外包项目要当项目经理、产品经理、开发人员,真的太累了:(

cover_img

前注

本文原创作者Amon Xu,首发于云沃客平台,转载请注明作者与链接,谢谢:D

谈起外包经历,我的第一次外包源自前两年某天陪着女友逛商场时,接到一个朋友的电话,朋友兴高采烈地跟我介绍一个大项目:需求不多、钱不少,难度不大、口气不小,我一听心动了,原以为要赚一笔 easy money,后面再看看,这次外包踩了大大小小不少的坑,遂想好好记录一下。

前期沟通

电话的第二天,和外包项目需求方简单沟通后,他们发来十几张 App 界面的样例,大概是些软硬件结合、通过 App 界面展示硬件信息和数据统计,以及相关信息的 CRUD Demo,功能不多不过开发时间也有限,要求在月底前做完 App Demo 与后台系统,赶着参加一个会议展示。对方多次强调项目的优势:正处于风口、资源配置各方面都齐备,除了…没有软件技术团队,目前只有硬件团队,软件这边只有零星的两三个,但不堪重用。

Tips:

这里我犯下了第一个错误,我以为只是一个Demo完事,但这背后是一个完整庞大的项目,项目大小、类型和复杂度的错误评估,使我没有很好地把控全局和考虑整个项目的细节,导致后面引发了很多问题。

在评估一个项目时,我们通常会低估项目的复杂度,而高估自己处理某些琐碎细节的能力。

组建团队

项目要进行,一个人是搞不定的,因为涉及到 各端 App、Web以及后台,于是我首先找了一个靠谱的后台开发朋友,然后等项目快正式开始前,再一起寻找和确定其它小伙伴。

Tips:

外包合作过程中,优先找靠谱、技术扎实、有责任心的人,外包项目大多技术不复杂,但因为协作方式的特殊性,大多是异地异步办公,需要有强烈责任心的人。不然项目开发时,经常找不到人,或者沟通缺乏反馈就很被动了。

项目报价

谈到项目必然会谈到钱,关于报价这块,对方很开放的有两种合作方式,一种是技术入股的形式,另一种是按照外包的方式报价。我想着因为是第一次合作,采用第二种方式最为保险,毕竟落袋为安嘛。

由于是第一次接外包没有经验,心里很忐忑,赶忙去网上查一些外包报价的方式和注意事项,最终决定根据团队人员工作的日薪,乘以一个系数,报给了他们。不出所料,他们觉得贵了,整个合作就僵持在那里。介绍项目的朋友答应去斡旋,然后…没了下文。

Tips:

不同外包项目的公司、项目背景不同,遇到技术入股这事得慎之又慎。当然现在外包平台很多,一切都基本流程化、正规化了,直接是项目与钱的交易,这种问题也会越来越少。

按照故事的正常节奏,我的外包初体验夭折了。大概两周后,事情出了转机,对方的负责人打来电话说要当面沟通一下。然后技术负责人和老总一并赶了过来,扯了半天介绍了项目的背景、公司及技术团队的情况,我意识到了这个项目不只是一个 Demo 这么简单。最后约定另找时间详细沟通需求,以及评估报价。

等到沟通完需求要报价的时候,对方想要一个打包价格,而不管每人每天的算法,又扯到这个项目很大,会分几期开发交付,第一期想让双方以磨合的姿态来合作。意思是你们也别想着开高价了,我们第一次合作先便宜点,磨合一下摸摸底,觉得不错的话后面合作再谈。

因为我也是第一次接外包,缺乏经验,在这个磨价的过程中,脑子一热不小心就答应了对方的要求。等到协商完毕确定好报价,发现只有第一次给出的每人每天报价的一半,才意识到我们还是图样图森破。

Tips:

这里是第二个错误,报价过程中要尽可能坚持自己的报价条件和底限,如果对方说出最低价格这种话,绝不能给出一个自以为的最低报价,不然就容易弄成菜市场的讨价还价,最终会被磨的和自己预期差距很远,可以跟对方认真沟通,谈钱一定不能图省事。价格贵也是质量的保证,可以象征性地少一些,但务必控制范围。

签订合同

不管怎么说,既然给出了报价,本着学习涨姿势的态度,咱就干吧。需要拟订合同时,没看到合适的,最终在网上找了一个软件外包开发合同模板,大致改了一下,将就用着。

关于外包合同有很多需要注意的地方,这里就只简单说一点:合同的条款一定要一条条地过,确保自己能完全掌握和理解每一条的内容及背后的含义,确保不要对自己埋有坑,当然也最好不要坑对方。

Tips:

当然现在外包行业发展越来越成熟,外包流程和项目也越来越规范,也诞生了像云沃客这种成熟的众包平台,甚至不再需要合作双方私下签订协议,服务方和需求方都能把精力专注于项目上,而把背后的一些琐碎之事和问题交由平台来规范管理,省心很多。

签合同远赴对方公司,中午正热时坐了个顺风车过去,下了车一看太阳都快下山了,高楼不见了,眼见之处都是低矮的民房,大爷大妈懒洋洋地支起了小吃摊,第一感觉是从深圳到县城了。对方是一个传统的公司/工厂,这意味着什么互联网、软件开发等等都可能是对牛弹琴,如果对方没有一个专业懂行的对接人员,这个项目的进展将会非常艰难,后面的事情也正出乎我所料。

Tips:

尽可能详细地了解对方公司、项目情况及相关人员背景,如果出现对接人员素质与项目不相符的情况,尽早向合作方提出疑问,把问题抛向对方,不要让这种问题影响项目的进度和后续工作的开展。

合同签完,需要再次详细沟通需求和评估开发计划,我和团队同伴远赴对方公司开会。沟通需求的过程中对方少不了加需求,甚至是一个独立的模块,相当于工作量莫名就多了几分之一。对方含糊其词,说这是一个非常重要的模块,没有这个模块就不是一个完整的系统,当初以为这是默认大家知道的事情云云。好在先前拟订合同的时候,我把主要功能和相关模块都写在了合同的开发内容一款里面,赶忙把合同拿给对方看,对方哑口无言,后面继续沟通是加时间、加人力还是精简功能。

Tips:

拟订合同时,一定要写清楚开发内容和主要功能,尽可能详细准确,避免后续因为添功能、改功能扯皮,毕竟口说无凭、白纸黑字才是硬道理。

项目开始

合同签完,按照合同约定对方需要先支付 30% 的项目款作为一期款,因为这些都是明确写到合同里,整个付款过程中很利索,唯一的问题是对方需要提供发票,后面找了朋友公司代开搞定。

软件增值税票税点一般是 6%,税费也会是一笔不小的支出。最好在报价时沟通好税费及发票相关事宜。

Tips:

最好等到预付款 or 第一期项目款到账后再启动项目,避免不必要的麻烦。

报价时将税费和发票考虑进去。现在众包平台也大多解决了这个问题,用户不必再操心这个。

项目准备

等到相关流程都走完,需要对方提供产品原型的时候,对方硬是石滚碾不出个屁来,憋了很久什么东西也提供不出来,我们艰难地跟他们普及了设计稿和原型稿的区别后,他们疑惑地表示:这种东西不是应该由你们来搞定吗。只好边跟他们说清楚,边给对方提供几个原型示例和原型工具。

回过头看看,整个项目过程中对方除了给出一个非常粗糙的概念需求文档,任何文档输出都没有,在前面沟通需求时提出让对方把相关需求文档整理给我们,他们表示这种东西都在自己脑子里没有时间整理。

没有输出的文档,后续的工作便没有了依据,而所有的依据,也只是在详细沟通需求的时候,我们自己整理的需求列表文档。

Tips:

文档的输出非常重要,详细的需求文档与设计文档是后续项目开发中的必备利器,没有这些,整个项目成了巧妇难为无米之炊,而且这些也会是项目开发完毕验收的标准之一。

项目前期

项目还没正式开始,对方又出幺蛾子了,对方对接人员由技术主管变更为另一个下级技术负责人,估计他们内部都没有仔细沟通过,就直接让我们和他对接,上来第一句便是找个时间沟通下需求,这边不太清楚细节。拜托,细节都在你们老大那里了,求我们心理阴影面积…

所有的输出文档只有在我和第一任对接人沟通需求时,整理的需求列表文档,这意味着它是经过第一任对接人陈述并由我们消化整理的,而第二任对接人如果再以它为参照的话,这里面的需求理解因人而异,项目变数更多、前景堪忧。想到这些,我们只好再次奔赴过去详细沟通需求。

Tips:

项目对接人的变更算是一个意料之外的问题,也更显前面所述的文档的重要性。越快越早地形成详细清晰的文档直接决定了项目后续的走势和进度。

在等原型的这段时间,风雨飘摇的项目又出了新纰漏:原本协商好的我们只需要负责软件系统开发(包含各端 App、Web 管理系统、后台系统),对方负责硬件生产及硬件系统开发,后来他们硬件开发人员离职,想把硬件系统开发这一块也交由我们。我们想都没想,就直接拒绝了。

Tips:

尽管接下硬件这块又有钱赚了,但这不是我们团队的强项,需要另找专业人员,相当于给团队和项目增加风险和不确定性。专注于做自己擅长的一面,不为团队和项目累加风险和不确定性,也是一种责任心。

写在最后

还没写到项目正式开始,就已经罗罗嗦嗦一大篇了,后续记录一下项目开发过程中的坑和教训,未完待续,欢迎交流。

距离发布上一篇文章已去了三月有余,中间也偶尔良心发现,想着该写点什么了。但是最终理智战胜了良心,加上中间疯狂的老鼠赛跑,以及经历各种生死惊奇,然后就…

我现在挖了这么些坑:

  • 《六月论战》
  • 《蚀》
  • 《坟》
  • 《一个女孩去了城市》
  • 《离去的人》

加上之前一起挖的,

  • 《双世》
  • 《寻找十字架》
  • 《废弃的城》
  • 《某码农意外去世了,我接手了他的代码…》
  • 《拜手机神教》

更久之前的就算了,实在填不起来了。
试试填吧…

很久没有写文章了,第一太懒,第二没时间。连买来的体重称都几天没见到了。

今天在论坛上看到有人问这三个问题,想了想便回复如下,不知所云。

Q:你打算一直做程序猿吗?

A:看怎么理解“做”这个词,以及理解“程序猿”的涵义,甚至如何定义“一直”。

“做”是仅仅代表着职业,还是代表着某种身份。

“程序猿”是只要在IT行业干着就算“程序猿”吗,不在IT行业但是业余编程算不算。

“一直”有多直?“永远”有多远?这个问题交给哲学,交给佛学。

At last, 编程是我的兴趣爱好,但不是生活的全部,也不是生命的全部,所以“程序猿”不会是我“一直”的“职业”,而是我某种身份:很多种身份之一。

Q:如果将来转行,你打算做什么?

A:农妇山泉有点田,空闲之余思考哲学与科技。大隐中隐隐不住,房价太高,只能小隐隐于野了。

Q:如果没打算转行,那目前您感到的最大的压力是什么?

A:人类自我膨胀的速度与社会进步的速度不成正比的压力。

理解上句很简单,宇宙空间这么大,人类居然害怕没有住的地方,将一辈子的生命付诸到买那不到一百平方米上。

嗟夫!这和一只可怜的飞蛾,为了贪图眼前的光明,义无反顾、毫不思考地扑向火苗有何异处。