Amon's Blog

猛猛如玉

sjy

四季怨 🎵

樱红柳绿春光转,
山碧水墨夏意暖。
云雨秋风携冬来,
犹望美人恨生晚。

qss

青衫湿 🎵

登高车马深,
倚楼山水昏。
每见尾气起,
犹思炊烟升。
挥汗十余载,
无悔付青春。
愿求她长伴,
青衫易功勋。

中美贸易战来袭:
A股:我跌
美股:我意思一下

贸易战势头加剧:
A股:我继续跌
美股:我跟跌

贸易战恐慌:
A股:我大跌
美股:我再意思一下

双方有意磋谈:
A股:我还跌
美股:该涨了吧(一次上涨抹平跌幅)

双方会谈:
A股:我涨一点意思一下
美股:涨涨涨,创新高

特朗普出尔反尔:
A股:我暴跌(一次下跌抹平涨幅)
美股:咋回事,我涨

贸易战卷土重来:
A股:我猛跌,创新低,夭寿啦夭寿啦
美股:你冷静点,我涨了

贸易战云里雾里:
A股:我继续跌,下探底部在哪
美股:不好意思,我又创新高了

贸易战不知所终:
A股:怕了吗,信不信我还是跌
美股:来人啊!救命啊!这里有个神经病啊……

pic

妻子在给女儿换衣服,
丈夫顶着蓬松的头发四处寻找,
“梳子呢,你带走了吗?”
“嗯,在包里”
丈夫转身去行李里翻找。

妻子换罢衣服,
过来从一堆行李里利索地拿起了梳子。
丈夫接过梳子去镜前梳头发。

“你为我梳一次头发吧”
“不会”
“你梳了那么多次了,给我梳一次吧”

妻子走来走去地收拾着东西,
丈夫拿着梳子跟在后面,
拗不过,妻子拿过梳子。

这是她第一次为他梳头,
零星的白发藏在一头并不浓密的头发中间,
她用手抚握住前面的头发,
用梳子轻轻地往后梳。

等到几束头发都梳到后面,
他递上捏在手中的皮筋,
她拿起皮筋将握在手中的头发扎起来。

她欠身准备继续去忙,
他拉着她的手,
不舍放开。

他凝视着镜中的自己,
还没等她们离开,
他便有些不舍了。
他对着镜子咧嘴扭曲着五官,
想记起些什么。

他换上鞋子,准备出门。
她唤女儿,
“跟爸爸来个临别的拥抱吧”
女儿马上黏了过来,
他俯下身子抱着女儿。

“明天这个时候你们就在老家啦”
“爸爸,你不跟我们回去吗”
“爸爸在这里,等你们”

他再往下俯了些,
然后起身离去。

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,不包含新功能的开发和功能变更。如有相应需求,可追加相关协议。

最后总结

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

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

0%