本篇文章给大家谈谈宝宝起名字软件排名,以及宝宝起名字生辰八字免费的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
随着线下演唱会复苏,票务软件被用户使用的频率也在大幅度提升,那么目前市场上常见的票务软件里,大多都遵循什么样的设计策略?不同的票务软件之间又存在着哪些设计或功能上的差异?一起来看看作者的分析拆解。
现在,偃旗息鼓半路夭折的音乐节演唱会,也终于可以如火如荼地燥起来了,热门演唱会抢票难一直是一个难题,票务类平台众多,音乐迷们又该如何选择一个靠谱的票务平台,本文就带领大家分析一下top前五的在线票务平台大麦、秀动、猫眼、摩天轮、票牛进行分析和拆解,希望对大家有所帮助。
一、阅读须知全文共约11000字,深度阅读本文大约需要30分钟。
本文所有观点不代表公司,仅个人观点;本文中的所有观点没有任何产品偏向,仅站在一个用户的角度来分析产品。
第一类:UI/UX体验设计师:跳出设计执行层,去思考票务产品背后的设计策略,提升产品设计分析能力;第二类:产品/运营经理:通过全面的产品设计拆解、用户逻辑分析,获取产品设计参考;第三类:票务行业从业者:通过首页分析与拆解,获取竞品逻辑参考。
二、行业介绍指为消费者提供演唱会、话剧、音乐剧、体育赛事等领域的门票售卖平台。目前已形成以一级专业票务机构为主、二级票务机构为辅,演出机构、演出场馆票务共存的多元化格局。
一级专业票务机构走单一定价模式,不溢价,不打折,其作用主要是出票、验票、核票,还有进场馆的查验等,保障票的真实性,很多平台都会要求主办方签订独家合作协议,确保出票的准确性,保证不会出现同一张票被卖两次的风险,其模式为自营,同时聚合了大量演出、赛事主办方或出品方,有较强的演出资源储备和获取能力,在行业内处于主导地位。
目前大麦是一级票务平台的头部,以体育赛事为例,大麦是体育赛事最大的系统服务提供商,已服务过9000多个项目,3万多场比赛,这部分的市场份额占到了国内可市场化票务的70%以上。秀动、猫眼也属于一级票务平台。
二级票务平台定价是以市场需求为导向的动态定价机制,票会溢价,也会打折,它整合了一级票务平台以及各票务代理公司的众多资源,相当于票务版本的携程,可以根据市场需求来灵活定价,但与主办方联系不够紧密,在行业话语权较低。目前“摩天轮”是二级票务平台的绝对头部,在细分市场占有率超50%。票牛属于二级票务平台。
1)对主办方
将票分发给一级票务平台,靠谱有保证,用合理的价格回馈粉丝。
将票分给二级票务平台,价格根据市场调节,实现利润最大化。
2)对消费者
从传统的线下购票,到线上购票平台,买票更便捷,可供的选择更多;如果在一级售票平台售罄的情况下,可以去票源更多的二级平台抢到票,靠谱的平台会验票保真,并且交易均有平台介入,相较于资质不靠谱的黄牛,资金有保证。如果遇到突然票多余的情况,还可以去如摩天轮、西十区这样的平台去转票。
主办方将艺人的表演作为商品进行发售,在申请批准、准备好场地后,开始卖票,他们会将20-30%的票分发给一级票务平台定价发售,通常来说这些价格是较为合理的,用来回馈粉丝用户,剩下的70%-80%的票会分发给二级票务平台,通过市场机制调整价格,如遇到供不应求的情况,价格抬高,也就是冷门折扣,热门溢价,以此实现利益最大化。
2023年3月1日根据点点数据搜索关键词“票务”,排行前五的app为“大麦”“摩天轮”“秀动”“票牛”“猫眼”;根据易观千帆截止2020年6月30日的数据,“大麦”的月活166.60万,日活14.76万,“摩天轮”的月活18.79万,日活1.18万,“秀动”的月活10.82万,“票牛”的月活6.33万,“猫眼”的月活303.43万,日活39.58万。
1)产品更新频次
更新频次越高,说明产品更新换代速度越快,适应市场变化的能力越强,企业的发展就越有保障。根据点点数据提供的娱乐票务app更新频次数据的显示,大麦的更新频次为:3.4周/次、摩天轮3.7周/次、秀动3.2周/次、票牛近一年没有更新、猫眼3周/次,从数据可看出猫眼的产品更新频次最高,票牛相对更新频次最低。
2)平台slogan
对于任何产品或者企业来说,除了logo外, 一句朗朗上口的slogan能够加深用户对品牌的印象, 从slogan上也能看出品牌的重点信息:
大麦:去现场,为所爱 (可以看出大麦主打的是现场文化)
摩天轮:90%的演出票都打折(主打折扣)
秀动:感受音乐,就在现场 (可以看出秀动主打的是现场文化)
票牛:看演出,上票牛
猫眼:娱乐看猫眼(猫眼囊括电影、景点等等更为宽泛的娱乐,现场娱乐仅仅是一部分,并不是主打)
三、首页拆解1)框架结构
首页各家的框架大同小异,整体分为:顶部操作区、运营区、内容分类入口区、细分内容推荐区、Feed流内容区、底部操作区这六个区域组成。
2)首页页面与品牌色
品牌色可以代表一个App的整体调性,给用户一个初步的印象,对于票务软件的现场文化娱乐,它的受众青少年群体居多,年轻活力是这些APP普遍想要传达的气质。
根据国内第三方数据公司易观的《2018中国现场娱乐票务市场年度综合分析》,“Z时代”正成为现场娱乐的消费主力,24岁以下人群占比攀升至14.8%,30岁以下人群占比达到42.4%。
所以,票务平台十分相似地,都采用了鲜艳的红色调作为品牌主色,这种带有活力的红色能符合品牌的受众的年轻化特征,能够很好的营造娱乐现场的氛围。
1)布局
顶部导航栏,在软件界面中承担着重要的角色,很多关键的信息和操作都是在此获取和执行的,它也是用户浏览界面最先注意到的地方。
这几款软件中,顶部导航栏主要由“城市定位+搜索框”组成,“大麦”和“摩天轮”将消息中心放在顶部右侧,票牛将演出日历放在顶部右侧。
2)搜索
除了秀动采用的是一个搜索图标作为搜索入口之外,另外的四个app都采用了视觉上更重量化的搜索框样式。
大麦:首页展示“扫描框 + 搜索按钮 + 定时滚动搜索热词”,进入搜索界面后,搜索框中显示点击时候的滚动热词,保持固定不变,如果不输入任何内容直接搜索,出来的是此时的热词结果,手动输入关键词后,需要手动点击搜索,才能完成搜索操作;搜索界面提供“搜索历史”和“热词榜”。
秀动:首页展示“搜索图标入口”,点击搜索图标进入搜索界面后,搜索框固定暗示词“演出、周边、众筹、视频、乐迷”,输入关键词后,需要手动完成搜索操作,才能显示搜索结果;搜索界面提供“历史搜索”和“音乐风格关键词”。
猫眼:首页展示“定时滚动搜索热词”,点击搜索框区域,进入搜索界面后,搜索框中显示点击时候的滚动热词,保持固定不变,如果不输入任何内容直接搜索,出来的是此时的热词结果,手动输入关键词,会实时自动完成搜索操作;搜索界面提供“电影、影人、影院、资讯”分类入口和猫眼榜单。
摩天轮:首页展示“固定搜索热词 + 搜索框下方罗列热搜关键词”,点击搜索框区域,进入搜索界面后,如果不输入任何内容直接搜索,出来的是热词结果,手动输入关键词,会实时自动完成搜索操作;搜索界面提供“历史搜索”和“热门搜索”。
票牛:首页展示“固定搜索热词 + 搜索框下方罗列热搜关键词”,点击搜索框区域,进入搜索界面后,如果不输入任何内容直接搜索,出来的是热词结果,手动输入关键词,会实时自动完成搜索操作;搜索界面提供“历史记录”和“搜索推荐”。
思考:搜索功能是票务软件的重点吗?
艺人的名字:这个是主要输入的内容,很多用户是冲着喜欢的艺人的演出而来。
演出类型:用户多数会在首页的内容分类入口中,选择自己中意的演出类型,而不是手动输入演出类型。
演出的城市:用户会在顶部操作区的城市切换按钮中直接选择,而不是在搜索框中输入。
演出的具体名字:除非是营销做的很好的演出名字,如某某音乐节,用户从某社交媒体听说,然后打开票务软件搜索音乐节名字,否则一般是在内容区看到。
所以综上所述,搜索功能并非票务软件的重要模块。
3)证照信息
有一个点值得注意,大麦、猫眼、摩天轮将“证照信息”放在了顶部操作区,虽然字比较小,但是也足够引起注意,证照信息里提供了营业执照、营业性演出许可证等等合规性文件,大大加强了平台的可信度,秀动将证照信息放在“我的-设置-关于秀动-关于我们-资质许可”中,而票牛暂时没找到任何证照信息。
在社交媒体上,我们常常可以看到某些乐迷粉丝被黄牛骗钱,大家对于黄牛的反感是刻在骨子里的,所以一个票务软件的合规合法性能够迅速地建立起平台和用户的粘性和信任,证照信息的露出是很重要的。
运营区是APP引流、吸引用户点击的地方,在票务软件中,运营区主要就是推荐演出/折扣的banner区域,仅以首页首屏比较,各运营区占比情况如下,我们看到,摩天轮的运营区占比最多,有30%,大麦的运营区占比最少,仅有10%。
亮点:
猫眼的运营区藏的很巧妙:
1)传统下拉刷新,用户看到的是一个刷新的循环小动效,而猫眼的下拉刷新会打开一个电影海报,就像是小孩子打开一扇门,发现了一片森林,打破用户心理预期,给用户新奇的体验,将运营区隐藏在下拉刷新中的方法,很巧妙。
2)在Feed流区域之前,会展示一个电影海报的局部,当用户连续下拉或者上滑时,就像是手执镜头一样上下扫动,通过这样的方式能够看到一个完整的海报,是很新奇的体验,在寸土寸金的首页,开拓了运营区域。
票务软件中,演出类型入口是一个重要的功能,这是人们做决策的第一个考虑因素,正如人们吃饭之前,会思考吃中餐西餐甜点上海菜川菜还是云南菜。
最常见的做法是用“图标+文字”的方式在首页罗列常见的演出类型,进入次级页面后进行筛选,筛选时提供的演出分类会更详细,筛选时,可以根据地点,时间,价格,热度等等因素筛选出用户喜欢的具体演出。
我们看到,猫眼提供的活动分类最多,有15种,秀动提供的活动分类最少,只有7种,而且往往首页会露出较为热门的演出类型,不会全部露出。
根据米勒法则,建议首页露出的数目不超过9种,这样既提供了多样的选择,又不会增加用户的选择记忆负担。露出的类型常常是时下流行的livehouse、剧本杀等等,像体育赛事这样的冷门活动常常是隐藏在筛选条件种,不进行露出的。
票牛对于露出的演出类型进行视觉上的差异化,究竟有没有必要?
我们看到,另外四款软件,首页露出的演出类型的视觉样式是一致和统一的,票牛这里的上下两行是十分不同的视觉样式,上行给白色图标配颜色差异的圆形背景底板,视觉重量更大,与下行的形状、颜色拉开了差距,更吸引用户的注意力。
也许票牛是想要突出强调上面的热门演出类型,但是这里容易引起用户的疑惑,明明都是演出类型,为什么要做差异化?而且视觉上的不一致带来一种不舒服不和谐的感觉。
这里将图标样式统一,是更好的做法。
票务平台的受众众多,有些可能是想要定一场本周末的活动,有的想带着孩子看一场适合亲子的演出、有的想看一场今天的演出、有的想抢一些优惠折扣力度大的演出,每个人购票时的考虑因素不同,所以细分推荐演出是很有必要的。
这五款软件中,大麦、摩天轮、票牛的细分推荐种类最多(12-13种),秀动的细分推荐种类最少(仅仅只有1种),在所有细分推荐中,时间是各大App考虑比较多的因素:演出日历、24小时演出、七天精选/本周好戏、今日必抢、即将开售、周末约、近期热门、月度看点。
大麦(13种):抢票播报站、福利站、演出日历、大麦演出榜、演出攻略、必看演出、即将开售、预售早鸟、你想看的、24小时演出、为你推荐(Feed流)、高分榜、周末约。
秀动(1种):精选演出。
摩天轮(13种):近期热门、七天精选、月度看点、超值精选、节目榜单、艺人榜单、热门场馆、演唱会、话剧歌剧、爱看展、音乐会、体育赛事、为你推荐。
猫眼(7种):今日必抢、七日精选、月度看点、精选榜单、品牌馆、大咖新动态、为你推荐(Feed流)。
票牛(12种):本周好戏、近期热门、歌剧话剧、绝美舞剧、艺术展览、Live看现场、心动音乐会、欢乐喜剧、推荐(Feed流)、榜单、合辑、大牌。
百科中对于Feed的定义如下:
“a web feed (or news feed) is a data format used for providing users with frequently updated content. Content distributors syndicatea web feed, thereby allowing users to subscribe a channel to it”
Feed流是将用户主动订阅的若干消息源组合在一起形成内容聚合器,帮助用户持续地获取最新的订阅源内容。
大麦、猫眼、票牛都在首页底部设置了信息流区域,用户可以不断的下拉刷新获取更多的内容。
我们看到,大麦、票牛的Feed流都使用了长卡双列流,因为它的内容是海报图片为主,视频内容为辅;而猫眼首页使用的是短卡为主、长卡为辅的双列流,因为它的主要内容是电影视频,海报内容较少,但是它的“演出/玩乐”模块的内容Feed流使用也是长卡双列流。
大麦的信息流由BGC(大麦官方推荐内容、大麦榜单)、UGC(用户看完演出后的感想)、引流运营(厂牌推荐)组成,BGC内容占据主导。
票牛的信息流由BGC、UGC组成,BGC内容占主导。
而猫眼的“演出/玩乐”Feed流是BGC内容+营销引流区域;首页是由UGC+BGC构成,没有营销模块。
通过比较这5个APP,我们发现大多数底部图标的切换方式都是由单色线性图标变为渐变色的面性图标,第1个和第5个图标都是“首页”和“我的”;“消息”都放在第四个位置,并且会以角标的形式告知消息数量;大麦的“现场”、秀动的“发现”、票牛的“发现”都是社区UGC内容,用户分享自己参与的演出活动,鼓励用户发帖;摩天轮有一个“转票”功能,这是其他的APP没有的底部操作区,说明转票是摩天轮主营的业务,这也是由平台性质决定的。
用户体验亮点:
首页因为有Feed流的存在,用户在不停的下滑获取新的内容时,在传统模式下,如果想要回到首页的顶部,则需要手动滑到顶部,比较费时间,很不方便,大麦、摩天轮和票牛都提供了首页下滑时,将图标改为 “回到顶部”,点击一下就能回到顶部,十分方便。
在图标中加入品牌IP,加强用户对品牌的认知和记忆点,我们看到大麦、猫眼和票牛都有融入品牌IP的因素,猫眼和票牛直接将品牌IP作为首页图标,大麦则是用一种润物细无声的方式,“我的”的形象和大麦IP形象“麦可疯”的脏辫发型是一致的,在其他地方也采用了洋葱头的形象,十分和谐一致。
不同的Tab做差异化:大麦为了突出会员中心的存在,它的颜色和其他图标做了差异;票牛为了凸显首页和用户社区,于是将他们进行面积上的放大,并且将原本的发现图标变为“发布”,引导用户主动发帖,丰富社区内容。
可改进的点:
大麦的首页下滑时,图标改变了,但是下方的字没有改变,仍然是“精选”,图标的含义不清晰,用户不易发现此处图标的作用,意识不到点击后能回到顶部,建议改变后的图标文字改为“回到顶部”。
秀动的“票价”被激活后,改为“刷新”的图标,究竟有没有这个必要呢?通常来说,大家会通过下拉拖拽进行刷新,这是很常见的操作了,而通过改变图标的方式,虽然很新颖,但是多少有点迷惑,能不改变就尽量不改变。
四、重点拆解对于一些提供座位的演出形式如演唱会、话剧,不同的座位对应不同的票档,一个清晰明确的指引选座方式很重要,秀动和票牛不支持选座,都是选择票档后随机分配座位,只能保证同一个订单多张票座位连坐,但是不能选择座位,另外三款软件是支持选座的,我们来看看:
1)大麦
大麦是通过区别CTR的文字,如果有座位可选的演出,底部的CTR按钮为“选座购买”,如果演出没有座位提供,那么底部的CTR按钮为“立刻购买”。
大麦是将用户的决策因素分步骤交给用户,第一步是选择日期,然后是选择场次和票档,在这一步,如果用户对日期反悔了,不用返回上一步,顶部提供了切换日期的选择,这一点非常好;在日期场次和票档确定之后,进入舞台座位的平面图,不同的票档用不同的颜色标注出来,在这一步,如果用户对票档反悔了,用户可以直接点击上方的图例,切换票档,然后选择座位。
2)摩天轮
如果有座位可选的演出,底部的CTR按钮为两个“选座购买”+“立刻购买”,并且用黄色和红色对两个按钮做视觉上的差异化;如果演出没有座位提供,那么底部的CTR按钮为“立刻购买”。
摩天轮的决策是在一个页面中完成的,在选座界面中,顶部可以选择时间和场次的组合,底部提供选座和选票面两种购票方式,对应的其实就是“选座购票”和“立即购票”,当你选择平面图中的某一块区域,选座Tab下就会展示这个区域中的可选择座位,右上角的价格优先和位置优先会自动帮你选中对应的座位;如果你选择“选票面”,那么你就只能选择票面,座位是随机分配的,不能选座。
有一个很矛盾的设计点:
在第一个页面中,将选座购买和立刻购买这两种方式差异化,两者是“或”的关系,用户只能二选一,但是到达下一级页面时,选座和选票面看起来是“和”的关系,用户在选完座位后,这个选票面看起来是下一步用户需要完成的事情,但是用户如果真的选择了,那么之前选择的座位的记录就消失掉了,用户在切换几次后才发现,原来这两个选择只能二选一,可是反复做的选择很费时间。
反过来想,既然在下一层页面中,选座和选票面是可以切换的,那么第一个页面中将两个选择分开究竟有没有必要呢?
选座方式的优化:
只展示“选座购买”按钮。
在点击“选座购买”后,进入一个单选界面,让用户选择:1)选择座位 or 2)直接选择票面(随机分配座位)?
后面的界面只展示对应的Tab,不同时展示,其他的不变。
3)猫眼
如果有座位可选的演出,底部的CTR按钮为“选座购票”;如果演出没有座位提供,那么底部的CTR按钮为“立即购票”。
猫眼购票流程如下图所示,点击按钮,先选择场次,然后选择楼层区域(注意此时选择票档的图标是没有任何反应的,仅仅会显示一些额外的优惠信息,并不会对对应的座位区域进行高亮),这一步骤中倘若对场次进行反悔,可以直接在顶部切换场次,点击区域后,进入选座界面。
此时你点击图例后,画面会移动到对应的颜色区域,但是并没有做特别的视觉变化,比如常见的将其他颜色的饱和度变低,凸显出选中区域,而且当用户选中某个票档的座位,对应的图例没有任何变化,这一点我认为是可以做改进的。
选座方式的优化:
我们看“选择区域”这个界面:
上面的票档的图例此时是没有任何作用的,仅仅作为信息展示,告知用户此次演出有几个票档,但是用户容易误认为点击图例后,就会展现对应的区域,然后选择座位,这一步容易分散用户的注意力;
再来看颜色,580票档的颜色和一楼区域的颜色是相同的,380票档的颜色和二楼区域的颜色是一样的,那么这容易让用户认为,一楼的票价是580,二楼的票价是380,同时其他票档的颜色却没有,用户会产生大大的疑惑,选择区域后,用户才发现,原来刚才的颜色和楼层是没有关系的,图例在后一个界面才发挥了作用。
优化方法:
在选择区域界面,将图例的透明度降低50%,看起来是disable状态,这样用户不会误点击。
将一楼和二楼的区域用明度的方式进行区分,或者用不同的符号进行标注,避免和图例颜色起冲突。
当点击图例时,其他颜色区域的透明度降低50%;反过来,当选择某个座位时,对应的图例应该切换成选中状态。
可选座/不可选座的信息展示:
可以选座或者不可以选座,这个信息需要清晰地给到用户,我们看看这几个App是怎么做的吧:
大麦:在演出列表界面,卡片标签“可选座”;在详情页首屏,在演出时间地点下方展示“可选座”标签,底部按钮为“选座购买”。
秀动:不支持选座,根据票档随机分配座位。对于不能选座这个信息,秀动仅仅在详情页面标注出来,不容易发现。
猫眼:在演出列表界面,卡片标签“选座购票”;在详情页首屏,在演出时间地点下方展示“可选座”标签,底部按钮为“选座购票”。
摩天轮:在演出列表界面,卡片标签“可选座”;在详情页,无标签提示,但是底部按钮,比不可选座新增了一个,非常明显了。
票牛:列表和详情页都没有不可选座的提示,知道订单支付也没有任何不可选票的提示。
从第一眼视觉上看,摩天轮和票牛的打折信息是最为显眼的,一方面他们本身的信息展露的较少,单个演出的展示面积很大,所以折扣信息相对的面积也更大更容易被注意到;另一方面,这是由平台属性决定的,摩天轮和票牛都属于二级票务平台,冷门折扣,热门溢价是他们的特征,所以明显的折扣信息也是很必要的。
拿Feed流中比较典型的BGC内容卡片为例,看一下内容的组成成分,必需因素由图片+标签+标题+日期+价格组成,有的还有评分和星级展示。
我们看到大麦的标签有三个位置可选:图片的左下角,价格的右边,价格的下边;猫眼的标签位于标题上方;票牛的标签有两个位置可选:图片底部(宽度与图片同宽),图片左上角。
图中,白色标签是必需标签,红色为可选标签。
体验优化点:
1)在卡片内容中,价格应该是最重要的因素,从视觉上看,大麦的价格因为环绕着众多的标签,显得不那么突出;对比猫眼和票牛,他们的标签离价格都比较远,不受干扰,又是位于最底部的,更容易引起注意。
2)猫眼的评分机制是从1到5星中进行选择(5星代表10分),然后将分数进行平均后得到了类似9.9分这样的分数。所以在猫眼的卡片中的星级就有点多余了,这个星星在这里更多是一种icon的作用,在比较宝贵的内容区多多少少是浪费空间了,可以考虑以标签的形式存在。
3)猫眼的价格中“数字”和“起”用了同样的颜色,它们的差异化还不够,可以学习下大麦和票牛的做法,将“起”的字号和字重继续缩小,颜色变成灰色。
4)大麦的最底部的标签很难阅读,不符合无障碍规范,建议调整一下颜色。
标签的位置和形式对用户注意力的影响:
1)票牛将类别标签用透明黑底白字的方式放在图片的左上角,其实是不太容易引起注意的,其一,黑白色不跳脱,不容易一眼看出来,其二,海报常常比较花里胡哨,人们容易忽略图片上的内容,而去直接看卡片底部的文字,但是这里这样子使用,是符合场景的,因为类别不需要引起用户的注意,是一个中性的语义。
2)票牛的位于图片底部的标签由于其占据面积大,对比度高,位置又处在用户的阅读动线上,所以十分的抓取注意力。
3)猫眼的标签放在了标题上方,用鲜艳的红色标注,刚好这落在了用户的视觉动线上,用户从上往下看的第一个便是标签,所以这里的标签是非常抓取用户注意力的,很难被忽略。
4)大麦的类别标签和评分放在图片的左下角,因为距离标题很近,所以相较于放在图片左上角,它吸引的注意力更多,评分用黄色突出,在暗色的背景下尤为明显。
5)我们注意到大麦的左下角也有一个标签,这个标签的对比度比较低,又是浅黄色,和另外两个标签比,视觉重量一下子弱化了,不过它的位置位于底部,能稍稍引起一些注意力。
社区可以更好的连接粉丝,增加用户粘性,维持社区用户活跃,通过社区进行更广范围的传播和推广,根据用户在社区的讨论,迭代和改进产品功能,根据用户的兴趣点寻找新的机会。
目前,大麦的“现场”,票牛&秀动的“发现”,都是专门的用户社区模块。
目前,社区中的内容大致可以分为:
官方组织的活动;
用户拍摄的活动现场;
粉丝团;
官方的视频特辑和刊物;
用户关注的内容;
热门话题入口。
大麦:演出动态、星映话、现场志、观赏团、热门圈子、推荐、想看&想玩、关注。
秀动:关注、广场(特别活动、推荐话题、附近场地、骨灰乐迷)、现场(热门、同城)、视频(秀动特刊、推荐、独立现场、摇滚现场、民谣现场)。
票牛:热门(活动报名、拍客现场、每日奖励、小十播报、独家探班、全部、音乐、戏剧、展览、亲子、幕后花絮、圈子广场)关注。
优化:
除了上述内容,有一个痛点是我逛小红书的时候发现的,有许多类似这种招募队友的帖子,有很多时候我们想要看一场演出,但是却找不到同伴一起,多少会失去点购票的动力,如果在票务软件的社区能放一个找同伴的模块,会不会让用户更有购票的积极性呢?
突然遇到事情,没办法观看演出的情况时有发生,人们想要退换票或者是转赠甚至转卖票,通常来说,人们会去找到平台客服,以寻求帮助,一个明显的客服入口也很重要。
1)大麦
分别在6个场景中有客服入口:购票详情页底部、首页和“我的”右上角消息通知中的“观演智能助理”、票夹中的票详情底部常见问题、“我的”中的“我的服务”、设置中的“客服电话”以及“意见反馈”中的“客服”,非常方便用户与客服建立对话。
退换票规则:若演出详情中未提到不可退票,则都是可以退的。退票会根据距离开演的时间收取不同的手续费。
转赠规则:可以进入大麦app票夹中,找到对应的电子票查看是否有转赠按钮,如果有转赠按钮代表可以操作转赠,如演出支持转赠,操作后一般被接收人需在两小时之内接收此电子票,否则电子票将退回原账户。实名制演出被转接收人,转赠受限,仅支持身份证证件接收。如果没有转赠按钮代表门票不可以操作转赠,同时客服也无法帮忙转赠,(如果演出是三证合一实名制入场,他人使用您的门票是无法入场的)转送功能一般在演出前24小时关闭,【具体规则以演出详情页为准】。每张票限转送1次。
2)秀动
分别在3个场景中有客服入口:购票详情页的右上角、底部Tab“消息”中的客服入口、设置中“在线客服”。
退换票规则:当演出开始时间(演出开始时间前后一个小时调整除外)、艺人、场地变更,可申请退票,其它情况下不支持退票,如无法正常观看,还请自行处理,任何情况下均不支持换票。
转赠规则:进入秀动app的票夹中,找到对应的电子票查看是否有转赠按钮,如果有转赠按钮代表可以操作转赠。如演出支持转赠,操作后一般被转赠人会即刻收到转赠的电子票。如电子票已绑定观演人是无法进行转赠的。
如果没有转赠按钮代表门票不可以进行转赠, 如果演出在购票详情页有明确要求三证合一实名制入场,他人使用是无法入场的)。
转赠功能在演出前均是可以转赠的,每张票限转赠1次,一经转赠无法撤回。
3)摩天轮
分别在3个场景中有客服入口: 购票详情页的右上角、“我的”中的“联系客服”、“设置”中的“客服电话”。
退换票规则:对于明确标记不支持退换的票品,摩天轮平台将无法提供退换票服务。对于可退票的演出,会根据距离开演的时间收取不同的手续费. 用户自身原因发起的退票都是会产生一定退票手续费,节目延期或取消则不需要手续费。
转票规则:可以使用app界面“转票”功能自行转卖,价格随缘。订单状态“已提交”可以在app最底下位置的“转票”的功能“我的转票”进行取消或重新修改价格。订单状态“买家响应”可不发货48小时后自动取消或联系平台客服操作取消。不支持转赠。
4)票牛
分别在3个场景中有客服入口:购票详情页底部、“消息”中的“在线客服”、“我的”中的“帮助与客服”,非常方便用户与客服建立对话。
退换票规则:一旦票品售出,除因不可抗因素导致演出取消或延期(不收取任何手续费,7个工作日内将款项原路退回到客户的账户中),其他订单不支持无理由退换。
转票规则:没有强制实名入场的票可以转赠。
5)猫眼
目前只有一个地方可以找到客服入口,并且只有电话形式,没有在线的形式:“我的”-“设置”-“关于”-“猫眼客服电话”。这个入口藏的比较深,用户不容易找到。
平台上目前没有看到具体的退换票规则。
总的看下来,大麦的客服入口最多,有6个,用户在购票前或者购票后遇到问题,非常容易找到客服沟通;猫眼的客服入口最少,仅仅只有一个,并且入口隐藏的太深,不容易发现,用户出现问题但是找不到客服解决时,就很容易放弃购票,或者购票后造成很差的用户的体验。
各个票务平台的规则也大同小异,由于票的特殊性,一般不支持退票,即使可以退票,除不可抗力因素外,都是要收取一定的手续费的。
摩天轮的转票功能是个亮点,给买了票但是无法观看演出的用户一种新的选择,并且背靠有资质的平台,资金更有保障。
五、总结从个人观点,大麦的综合体验是最好的,无论是选座方式,还是首页布局合理性、细分推荐、客服入口数量、运营区域占比,都把控得十分到位。
本文通过对两类票务平台,总计5个APP,从首页布局与重点功能的分析与拆解中,我们发现相似的地方,也发现针对同样的问题,不同的思考角度和解决方案,也许能为我们未来遇到问题,提供一些思路和方向。
本文由 @自来卷夏忆 原创发布于人人都是产品经理。未经许可,禁止转载
题图来自Unsplash,基于 CC0 协议
该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务。
好名受用一生,名字测吉凶查询,不要小看这三两个字,有的名字给人以大富大贵,诚实可信,玉树临风的联想,有的名字给人以文静雅致,温柔尔雅的馨香,有的名字助人在事业上大展宏图,李宗瑞的老豆就是改名后富贵逼人的。下面跟随恋星座小编一起来看看吧。
名字吉凶查询
一、取名必知条件
1、 准确的出生时间(包括年、月、日、时)
2、 性别
3、 姓氏
4、父母姓名(可缺)
二、 取名年份具体步骤及方法
第一步:列出四柱命局,根据四柱找出用神,喜神。
第二步:根据姓氏排列各种五格数理,组合变化,同时,将四柱用神、喜神之五行转化为姓名学所规定五行之数理加入其中,按重要程度、先后顺序补在人格、地格、总格、外格,找出最佳配置。另须注意,是补姓名五行数理,用偏旁,字意来补命局之喜用神次之,切记,重要的是数理(数理所代表的五行)。
A、 在此步中,还需调整好天人地三才配置,尽量取吉配置且五行相生为佳。五格全吉最好,实在不好配,四格吉也可。
B、在此步中,选吉祥数理人名,女孩还需避开孤、独、寡及过刚之数理。
C、 一般用三字姓名,即除去姓氏(包括复姓),名字为两字的,如"黄启伦、温雅迪。避免使用二字姓名(1、减少重名;2、二字姓名,外格均为2凶数,且五行数理较偏重某五行,一般情况均不太吉祥)。随着时代的发展,起四字姓名也是一个新的趋势,应大力推广。
第三步:根据第二步可知名字第一字与第二字的笔划数为几,然后根据繁体字(以《康熙字典》或《取名专用大字典》为准)上(第一字)下(第二字)各排列两行,每行十几字即可。
第四步:将上下两排字相互组合几对,兼顾字义、字形及音韵几方面,并避开不良谐音,粗谷不雅等组合,选出最佳组合3~4个,供自己或客户选择。
第五步:选定小孩新名后,应将新名与此小孩四柱,均抄在一张白纸上,贴在小孩床头,大人可以多叫多念,以使新名尽早产生作用。小孩长大后,也要多写多用,作用便更加明显。
名字吉凶查询要点
1、字义要吉祥、力避粗俗、冷静、洋化字体,避免字体克害。
2、字音要悦耳,如意、阴阳平衡,富于节奏感。
3、字型要搭配协调,富于变化,便于书写。
4、要结合家风,事业及父母长辈对子女的期望。
5、有的家庭以字排辈,可防止与祖上重名,重字、重音现象。
6、根据男孩女孩的不同性别特征起名,应体现其性别特征。
7、依孩子出生前后情况取名,如当时的气候特征或出生前后有无特殊的事件
8、以出生时间取名。考虑年、月、日、季节、时辰、属相等。
9、隐寓理想抱负、兴趣爱好和目标追求,心愿寄托在名姓之中,寓意聪慧、刚毅、豁达、华贵、优美、脱俗、福寿。
从字形上判断五行名字吉凶查询
形,就是字形,指字的形体结构。许多汉字在字形上包含了木、火、土、金、水,这可以直接看出来。这是最古老最可靠也最广为人们接受的一种汉字五行划分法,原理源于汉字的象形文字特性,但最大的弊端是很多字形不包括木、火、土、金、水这五种偏旁部首的汉字难以明确划分其五行属性。
属木的:本,朴,未,术,朱,机,村,材,桥,榕,楠,植,森,棠,集,椿,楚,榆,寨,槟,榜,模,柏,相,栋,松,林,枫,标,樵,桐等。
属火的:灿,灼,灵,炀,炜,炬,炫,荧,炳,炼,炽,烁,耿,烽,焕,焱,琰,焯,煌等。
属土的:圣,圭,至,地,坚,均,坦,垣,城,培,基,堂,增,墨等。
属金的:鉴,鑫,钢,钧,钮,铜,铭,锡,铿等。
属水的:泉,淼,冰,汀,汇,江,汉,汝,沉,泽,泓,波,济,洁,派,润,涛,浩,深,鸿,淇,澎,潭等。
生辰八字就是出生的年、月、日、时,跟我们的姓名是相依相伴的关系,根据八字取的姓名体现一个人的气质。下面就一起来看看生辰八字姓名测试吧!
根据生辰八字姓名测试
生辰八字与姓名的测试
姓名与八字也是互为表里,相依相伴的关系。人体通过姓名来体现自己的气质内涵,而姓名则借助人体来张扬自己的个性特征。简单来说八字就象树干,而姓名何尝不象树叶 树干既是身体,而树叶则是衣服。八字也好比是人体,是主干,而姓名则好比是衣服,是树叶。衣服与人体是互为表里、相依相伴的关系。
起名就是根据生辰八字喜用,为其量体裁衣,配制一件得体而漂亮的新衣。根据生辰八字分析或策划名字,就是“有的放矢”了。
姓名是为命服务的,如果没有八字的参照起出来的名字对命是起不到好的改变作用的,起名时只有先分析出八字的喜用神,五行等基本要素才可以进行下一步姓名数理的配置。现在几乎所有的人在给孩子起名字的时候都是按照八字喜用五行这一套程序来起名,已经很少有人不根据生辰八字起名的了,只有参考了八字起出来的名字才可以称得上是合格的好名字。
生辰八字姓名测试姓名和运气
人们常说:一切都是命运的安排”,那么,什么是命运”呢?命运”是否可以改变?
命运”是命”和运”的合体,命”是先天的,运”则是后天的,故谓先天命”,后天运”之区别。人的出生年、月、日、时,也就是俗称的生辰八字,当人呱呱坠地之时,就已经确定下来,所以说生辰八字是与生俱来的,任何人也无法改变,就是先天命”。人的名字是可以经过人为的手段进行选择,也就是命理当中可以改变的部分,这就是后天运”。当人出生时,其星座、命盘、血型、四柱八字就已经确定了。先天不足者何以更命?可行者,唯姓名也。先天吉命何以锦上添花,趋吉避凶?可行者唯姓名也。
中国的象形字源于自然界的物”,其本身就有生命之灵气。当文字成为名字,公司的名称时,常常寄托人的企求和希望,更暗示着天对人,对公司的信息诱力。若将名字和公司名称仅作区别符号”来认识,似乎名”与运”没有任何联系,何以不见刘A、李E、张一、赵三?即使是孙甲、周乙之类的姓名也难见一二呢?名”能影响运”,这种客观观点古已有之。《说文》有名自命也”。先贤尹文字有形以定名,名以定事,事以一名”之说,可见,名似虚而实,即实且传神。千百年来,姓名命理学流传至今,为广大群众所信,所受,自有其道理。
盲目否定,常会误已误人,即使不信者,姑且听之,宁信其有,勿信其无。
人们对空间的认识经历了从一维空间到三维空间的过程,近来,科学界提出了四维空间的假说,在未得到充分证实之前,谁又能说清其存在与否?大自然中又有多少人们尚未破解之迷?在人们只知一维空间之时,三维空间岂不是也太玄了吗?
名”能影响运”,是根据数”、理”与音”的威力。名称与文字由点和线组合而成,即数、理之根本。换言之,文字即理、数,为灵之表现,表现为文字所构成的名称则具有影响人命运的威力。古人有天数”,数之所定”,天数难逃”等等,可见数”即命运。命运即数”。世人经过努力,而终不成功,或在幸福中突遭凶变,此等人之姓名必有缺陷!若有人问:姓名若能影响人之命运,则同姓名之人,其命运必同,为何实际上有差异?这并非是理论上的矛盾,实为影响人的命运,除姓名之外尚有四柱八字配之关系。所谓命运者,实乃命与名之合体,相互作用。八字好比人之身体,姓名则好比精神,精神旺则身体壮,精神弱则身必弱,精神长存则身生,精神枯竭则身死,此即姓名与命运之关系。
姓名的暗示具有极强的,不可抗拒的信息诱导与支配力,这种信息诱导力足以支配人生的命运。因此,姓名凶者,常导致病弱,其所以皆归于失败,有情人难成眷属,劳碌奔波困苦生活。此类凶名者,当速解破以早享天福。现在虽幸运儿,若有凶名者,亦须破解,方可逢凶化吉,以免遭遇将来之恶运。本名虽劣,若惯用的名称或别号、绰号为吉者,从人称呼上改名,不必变更本名以免多费手续。
综合而言,凶名招祸,吉名天相,因此不论其先天八字命运好坏都一定要选一大吉利的名字以补救或辅助,方能获致人生所求之富贵长寿。
古代姓名历来看重姓名的形、音、义”,取名时,在数”、理”大吉的基础上,再选形、音、义”大吉者,岂不是更吉吗?
在此,奉劝网友诸君,宁信其有,勿信其无”,即或不信,姑且做一翻姓名测字,再做出决定,切不可轻下结论,于人于已皆无损,大家一乐尔。
根据生辰八字测名字
一.给孩子起名字要注意的原则:
1、音韵的灵动力:名字念起来会产生声音的磁场,好的名字悦耳,不佳的名字会形成干扰,不少艺术家或作家都会另外取一个名字或另取字号,如:张大千(原名张员)。好名字不仅会令人印象深刻,自己也能认同。
2、避免谐音:父母若打算自己帮宝宝取名字,要注意名字念起来是否有不雅的谐音,以免日后徒生困扰。如:吴理茂、曾豪孝、雨玲(雨淋)、尉琮(喂虫)。最好是名字取好后,多念几遍,看看听起来是否流畅。
3、注意字义:父母若想为孩子取名字,必须先了解字的意义,因为有些字并不常见,或者换了旁侧的部首,却意义不佳,所以最好在取名字前,查阅康熙字典确定字义。
4、生辰八字:有些是参考宝宝的生辰八字来帮宝宝取名,即利用生辰八字来了解宝宝的先天命格,如:是否阴阳协调、五行均等,再借着取名来调和、改善宝宝的运势。帮宝宝命名可以依照八字命盘、再参考格局、笔画,最后找出合适的字义。比如由生辰八字得知宝宝个性任性刁蛮,建议可用,如:理、德、修、维,来修饰孩子的个性。
5、生肖: 不少父母在取名字时,常会因生肖来选择字,如:龙年时男生常取名为龙。或者某些生肖,适合用某些字,如:蛇喜欢待在小洞,蛇年出生的宝宝,可以选择:哲、启、善、唯等字。
6、单名:一般而言,单名好记且响亮,但是就单名欠缺地格、外格,除非命格好者,否则不适合使用,或者可再取字或号作为辅助,如:孙文,字中山;李白,字太白。
7、笔画的八十一灵动:可分别算出五格(天格、地格、人格、外格、总格)的八十一灵动,并观察其五行的相生相克关系。总格24、25、29、31、32、33、35(这样的笔画数格局很不错,但人生有较多波澜重叠)37、39、41、45、47、48(这样的笔画数是不错的格局,有这样笔画数的人,大都能克服困难,开创格局。)
8、三才五格: 五格是由姓名的笔画中算得,五格的笔画可算出相应的五行,接着即可了解五格及五行间相生相克与吉凶关系,取名字时,最重要在于五行是否协调,再者才是笔画好不好。三才是指天格、人格、地格间的组合并系。取名字时可参考康熙字典的字义和笔画,因为有些字必须以偏旁部首来算,才能得到正确的笔画。
二,取名的基本要领:
1,以父姓加固定的字派(有的家谱规定必须使用)加一个有特定意义的字;
2,以孩子父母的姓加上一个有特定意义的字,组成三个字的姓名;
3,以孩子父姓加上一个有特定意义的字,组成单名(容易造成重名);
4,以孩子父母的姓加上两个有特定意义的字,组成四个字的名。
三,取名时那些“有特定意义的字”可以是:
1,孩子出生的季节,如春天出生的用“春”字,秋天出生的用“秋”字;
2,孩子出生的日子,如国庆日出生的用“国庆”“建国”“建华”;
3,孩子出生的时晨,如早晨出生的用“晓光”“晨光”;
4,孩子出生的地点:如“京生”“津生”“沪生”“渝生”;
5,父母对孩子的期望:男孩用“健、英、伟”,女孩用“慧、娟、淑”等。
四,起名时需要特别注意的是:
1,尽量不用多数人都使用的常用字,以避免重名;
2,尽量不用多数人容易念错的生辟字,以避免难认;
3,尽量不用笔画繁多和字形不正的字,以避免难写;
4,尽可能使用好听的褒意诣音,不用不顺耳的贬意诣音;
5,尽可能使用多音调,即三个字避免同一声调,念起来响亮有起伏感;
6,在父母的姓字上多下功夫,或拆其字形,或用其字音,但要慎用叠音。
五,关于取名的具体建议:
1,买本相关的书,了解一些命名数理和生辰八字五行相生相克的知识。
2,如果凭想象实在想不出理想的字,不妨查查字典。
3,现在给孩子取名,最好是用四个字的名,即父母两人的姓加上两个字: 如男孩用“夫子”“健夫”,女孩用“慧子”“纯子”等,四个字的 名不易重名,别具一格,好处多多,将逐步流行。
4,一般来说,男孩取名用字应含阳钢之气:如“英”“强”;女孩取名用字应含贤淑之意:如“慧”“娟”。而有时男女反用则孩子长大后各方面都比较优秀。 总而言之,取名的总体原则是:好听、易记、画简、雅致、义深。
作者:marinewu,腾讯PCG客户端开发工程师
| 导语基于 Readability 的出题阅卷经验的一些不完全的具体的命名指南。
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
软件开发中一个著名的反直觉就是“起名儿”这个看上去很平凡的任务实际上很有难度。身边统计学显示,越是有经验的程序员,越为起名头痛。给小孩起名儿都没这么费劲,对吧,王浩然?
命名的困难可能来自于以下几个方面:
信息压缩:命名的本质是把类/方法的信息提炼成一个或几个词汇,这本身需要对抽象模型的准确理解和概括。
预测未来:类/方法的职责可能会在未来有变化,现在起的名字需要考虑未来可能的变动。
语言能力:缺少正确的语法知识,或是缺少足够的词汇量。本来英文就不是大部分中国人的母语,更甚者,计算机的词汇表不同于日常交流词汇表,有大量黑话。
不良设计:混乱的职责分布、不清晰的抽象分层、错误的实现,都会导致无法起出好的名字。在这个意义上,起名字其实是对设计的测试: 如果起不出名字来,很可能是设计没做好 -- 重新想想设计吧。
命名就像写作,会写字不等于会写作。而且,命名更多像是一门艺术[注](此处艺术的含义取自于 Knuth -- 命名会诉诸品味和个人判断。),不存在一个可复制的命名操作手册。
本文描述一些实用主义的、可操作的、基于经验的命名指南,并提供了一个代码词汇表,和部分近义词辨析。本文没有涉及讨论名字的形而上学,例如如何做更好的设计和抽象以利于命名,也没有涉及如何划分对象等,也无意讨论分析哲学。
命名原则命名是一门平衡准确性和简洁性的艺术 -- 名字应该包含足够的信息能够表达完整的含义,又应该不包含冗余的信息。
准确 Precision名字最重要的属性是准确。名字应该告诉用户这个对象/方法的意图 -- “它是什么” 和 “它能做什么”。 事实上,它是体现意图的第一媒介 -- 名字无法表现含义时读者才会阅读文档。
名字应该是有信息量的、无歧义的。以下一些策略可以增加名字的准确度:
可读最基本的语法原理,是一个类(Class/Record/Struct/... 随你喜欢)应该是一个名词,作为主语。一个方法应该是动词,作为谓语。 换言之,类“是什么”,方法“做什么”, 它们应该是可读的,应该是 [Object] [Does ...] 式的句子。
可读是字面意思,意味着它应该是通顺的,所以应该:
避免 API 中使用缩写就像是给老板的汇报中不会把商业计划写成 Busi Plan 一样,也不应该在公开 API 中使用一些奇怪的缩写。现在已经不是 1970 年了,没有一个方法不能超过 8 个字符的限制。把类/方法的名字写全,对读者好一点,可以降低自己被同事打一顿的风险。
creat 是个错误,是个错误,是个错误!
但是,首字母缩略词的术语是可行并且推荐的,如 Http, Id, Url。
以下是可用的、得到普遍认可的缩写:
configuration -> config
identifier -> id
specification -> spec
statistics -> stats
database -> db (only common in Go)
regular expression -> re/regex/regexp
未得到普遍认可的缩写:
request -> req
response -> resp/rsp
service -> svr
object -> obj
metadata -> meta
business -> busi
req/resp/svr 在服务名称中很常见。这非常糟糕。请使用全称。
避免双关再次说明:以上的说明是针对 API 名称,具体包括公开对象/函数名字、RPC/Web API 名字。在局部变量使用缩写不受此影响。
对类/方法的命名,不要使用 2 表示 To, 4 表示 For。
func foo2Bar(f *Foo) *Bar // BAD
func fooToBar(f *Foo) *Bar // GOOD
func to(f *Foo) *Bar // Good if not ambiguous.
合乎语法2/4 这种一般只有在大小写不敏感的场合才会使用,例如包名 e2e 比 endtoend 更可读。能区分大小写的场合,不要使用 2/4。
虽然不能完全符合语法(例如通常会省略冠词),但是,方法的命名应该尽量符合语法。例如:
class Car {
void tireReplace(Tire tire); // BAD, reads like "Car's tire replaces"
void replaceTire(Tire tire); // GOOD, reads like "replace car's tire"
}
关于命名的语法见“语法规则”一章。
使用单一的概念命名命名本质上是分类(taxonomy)。即,选择一个单一的分类,能够包含类的全部信息,作为名字。
考虑以下的角度:
例如,把大象装进冰箱,需要有三步 -- 打冰箱门打开,把大象放进去,把冰箱门关上。但是,这可以用单一的概念来描述:“放置”。
class Fridge {
public void openDoorAndMoveObjectIntoFridgeAndCloseDoor(Elephant elphant); // BAD
public void put(Elephant elphant); // GOOD
}
应该使用所允许的最细粒度的分类
避免使用过于宽泛的类别。例如,这世界上所有的对象都是“对象”,但显然,应该使用能够完整描述对象的、最细颗粒度的类别。
class Fridge {
public put(Elephant elephant); // GOOD.
public dealWith(Elephant elephant); // BAD: deal with? Anything can be dealt with. How?
}
简洁 Simplicity简而言之,名字应该是包含所有概念的分类的下确界。
名字长通常会包含更多信息,可以更准确地表意。但是,过长的名字会影响可读性。例如,“王浩然”是一个比“浩然·达拉崩吧斑得贝迪卜多比鲁翁·米娅莫拉苏娜丹尼谢莉红·迪菲特(defeat)·昆图库塔卡提考特苏瓦西拉松·蒙达鲁克硫斯伯古比奇巴勒·王”可能更好的名字。(来自于达啦崩吧)
在此,我提出一个可能会有争议的观点:所有的编程语言的命名风格应该是趋同的。不同于通常认为 Java 的命名会倾向于详尽,Go 的命名会倾向于精简,所有的语言对具体的“名字到底有多长”的建议应该是几乎一样的 -- 对外可见应该更详细,内部成员应该更精简。具体地:
public,如 public 类的名字、public 方法的名字 - 应该详细、不使用缩写、减少依赖上下文。通常是完整名词短语。
non-public,如类成员、私有方法 - 不使用缩写、可以省略上下文。下界是单词,不应该使用单字符。
local,如函数的局部变量 - 基本上是风格是自由的。不影响可读性的前提下,例如函数方法长度很短,可以使用单字符指代成员。
上述规则像是 Go 的风格指南。但是,并没有规定 Java 不能这样做。事实上,Java 的冗长是 Java 程序员的自我束缚。即使在 Java 的代码里,也可以这样写:
public class BazelRuntime {
public boolean exec(Command cmd) {
String m = cmd.mainCommand(); // YES, you can use single-letter variables in Java.
// ...
}
}
同样,在 Go 的代码中也不应该出现大量的无意义的缩写,尤其是导出的结构体和方法。
type struct Runtime {} // package name is bazel, so bazel prefix is unnecessary
type struct Rtm {} // BAD. DO NOT INVENT ABBREVIATION!
当然,由于语言特性,在命名风格上可能会有差异。例如,由于 Go 的导入结构体都需要加包前缀,所以结构名中通常不会重复包前缀;但 C++/Java 通常不会依赖包名。但是,上述的原则仍然是成立的 -- 可见度越高,应该越少依赖上下文,并且命名越详尽。
Google Go Style Guide 是唯一详尽讨论命名长度的风格指南,非常值得参考,并且不限于 Go 编程: https://google.github.io/styleguide/go/decisions#variable-names
一致 Consistency另一个容易被忽略的命名的黄金原则是一致性。换言之,名字的选取,在项目中应该保持一致。遵守代码规范,避免这方面的主观能动性,方便别人阅读代码。通常情况下,一个差的、但是达成共识的代码规范,也会远好于几个好的、但是被未达成共识的规范。
这个图我能用到下辈子: [xkcd 927]()
但是仅符合代码规范是不够的。如同所有的语言,同一个概念,有多个正确的写法。
考虑以下的例子:
message Record {
int32 start_time_millis = 1; // OK
int32 commited_at = 2; // Wait. Why not commit_time? Anything special?
int32 update_time = 3; // What unit? Also millis?
google.types.Timestamp end_time = 4; // WTF? Why only end_time is typed?
}
几种都是合理的(虽然不带单位值得商榷)。但是,如果在一个代码中出现了多种风格,使用起来很难预测。您也不想使用这样的 API 吧?
所以,在修改代码的时候,应该查看上下文,选择已有的处理方案。一致性大于其它要求,即使旧有的方案不是最好的,在做局部修改时,也应该保持一致。
另一个可考虑的建议是项目的技术负责人应该为项目准备项目的专有词汇表。
语法规则类/类型类类应该是名词形式,通常由单个名词或名词短语组成。其中,主要名词会作为名词短语的末尾。例如 Thread, PriorityQueue, MergeRequestRepository。
名词短语通常不使用所有格。如,并非 ServiceOfBook,也不是 BooksService (省略 '),而是 BookService。
接口接口的命名规则和类相同。除此之外,当接口表示可行动类型时,可使用另一个语法,即 Verb-able。例如:
public interface Serializable {
byte[] serialize();
}
public interface Copyable<T> {
T copy();
}
public interface Closable {
void close();
}
(Go 通常不使用这个命令风格。只在 Java/C++ 中使用。)
辅助类只在 Java(注1)中使用。一个类或概念所有的辅助方法应该聚合在同一个辅助类。这个类应该以被辅助类的复数形式出现。不推荐使用 Helper/Utils 后缀表示辅助类。尤其不推荐使用 Utils/Helpers 做类名,把所有的辅助方法包进去。如:
class Collections {} // For Collection
class Strings {} // For String
class BaseRuleClasses {} // For BaseRuleClass
class StringUtils {} // WORSE!
class StringHelper {} // WORSE!
方法注1: 客观来说,这适用于所有强制 OOP 的语言(所有强制把方法放在类里的语言)。但是除了 Java, 没有别的语言这么烦啦。
方法通常是谓语(动词),或是 谓宾(动词+名词) 结构。注意以上语法中,动词都在最前端。例如:
class Expander {
String expand(String attribute); // 主-谓
String expandAndTokenizeList(String attribute, List<String> values); // 主-谓-宾
}
除此之外,有以下特例值得注意:
访问器 Getter直接使用所 Get 的对象的名词形式,即 Foo()。不要使用 GetFoo()。
Java: 所有的 Getter 都需要一个 get 前缀是来自于过时的 Java Beans Specification,以及 Javaer 的思想钢印。
func Counts() int; // GOOD
func GetCounts() int; // BAD: UNNECESSARY.
断言 Predicate
断言函数指返回结果是布尔型(即真伪值)的函数。它们通常有以下命名格式:
系动词: 主-系-表即 isAdjective() 或 areAdjective() 格式,表示是否具有某个二元属性。类似于 Getter,可以省略系语,只使用表语,即: adjective()。
func IsDone() bool {} // OK-ish. But could be better.
func Done() bool {} // GOOD. Why bother with is/are?
func CheckEnabled() bool { // BAD. Nobody cares if it is "checked". Just tell the user if it is enabled.
return enabled;
}
func Enabled() bool {} // GOOD.
情态动词: 主-助谓-谓-(宾/表)
情态动词也是常见的断言形式。常见的是以下三个:
should: 查询当前是否应该执行给定的实义动词。
can: 查询当前类所在状态是否可以执行给定的实义动词。某些情况下,也可以使用第三人称单数作为更简洁的代替。
must: 特殊形式。不同于前两者,会执行给定的实义动词。must 表示执行必须成功,否则会抛出不可恢复错误 (throw/panic)。类似于 C++ 中常见的 OrDie 后缀。
func Compile(s string) Regexp, error // Returns error upon failure
func MustCompile(s string) Regexp // Panics upon failure
func (r Regexp) CanExpand(s string) bool // Whether s is legal and can be expanded
func (r Regexp) Expands(s string) bool // Whether r expands s, i.e. r can expand s.
func (r Regexp) ShouldReset() bool // Whether the state requires reset. Does not perform de-facto reset.
func (r Regexp) Reset() // De-facto reset.
表尝试: 主-maybe/try-谓-(宾/表)
上文 "must" 的反面,表示尝试性的执行,并且失败不会造成严重后果:
maybe 前缀用以表示指定的行为有前置条件,也在方法中执行。如果前置条件不满足,不会执行指定行为。通常不会出现在公开 API。
try 通常用于 Try-Parse Pattern,用于避免抛出异常。
void maybeExecute() {
if (!predicate()) {
return;
}
// execute
}
std::unique_ptr<DateTime> ParseOrDie(std::string_view dateTime);
bool TryParse(string_view dateTime, DateTime* dateTime);
第三人称单数
另一个常见场景是我们希望表示类拥有某些属性,但是使用助动词并不合适。如果前文描述,常见的选择是使用第三人称单数的静态动词(Stative verb)(注1) 表示类满足给定断言。
func (l *List) Contains(e interface{}) bool
func (r Regexp) Expands(s string) bool
一阶逻辑 First-order logic, Predicate Logic注1: 简单地说,静态动词是表示状态的动词,与动态动词(Dynamic verb)表示动作对应。或言“持续性动词”。
一阶逻辑量词也是常见的前缀:
all 表示所有对象满足给定要求
any 表示任意对象满足给定要求
none 表示没有任何对象满足给定要求
语法: <一阶量词><动词|形容词>
class Stream {
// Returns whether all elements of this stream match the provided predicate.
boolean allMatch(Predicate<? super T> p);
// Returns whether any elements of this stream match the provided predicate.
boolean anyMatch(Predicate<? super T> p);
// Returns whether no elements of this stream match the provided predicate.
boolean noneMatch(Predicate<? super T> predicate)
}
介词
介词经常与某些动词固定搭配,因此,通常可以省略动词,而只使用介词作为方法名称。
to: 转换至另一对象,等价于 convertTo。to 会产生一个全新的对象,通常不持有对原对象的引用。
as: 返回某个视图,等价于 returnViewAs。一个“视图(View)” 通常是对原有对象的另一角度的抽象,通常会持有对原有数据的引用,而不会产生新的数据。
of/from/with:构造新对象,等价于 createOutOf/createFrom/createWith。见下文“工厂模式”。
on: 监听事件,等价于 actUpon。见下文“事件”。
class Foo {
public List<T> toList(); // Convert to (Construct a new instance of) a new List. Creates a new list.
public List<T> asList(); // Return a List as a different **view**. Holds reference of the original reference.
static Foo of(); // Construct Foo as a factory method.
static Foo from(Bar); // Construct Foo from Bar.
Foo with(Bar); // Construct a new Foo by replacing Bar with new Bar.
void onClick(ClickEvent e); // Act upon click event.
}
参考资料:
https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html
https://journal.stuffwithstuff.com/2016/06/16/long-names-are-long/
https://journal.stuffwithstuff.com/2009/06/05/naming-things-in-code/
https://habr.com/en/post/567870/#names_in_engineering
https://medium.com/wix-engineering/naming-convention-8-basic-rules-for-any-piece-of-code-c4c5f65b0c09
https://github.com/kettanaito/naming-cheatsheet
[[Effective Java]] Item 68: Adhere to generally accepted naming conventions
下文按用途归类了常见动词和名词,并对同义近义词进行了辨析。
类/名词类继承JavaAbstract/Base
Impl
Default
Interface: 通常不需要额外的表示。不要加 I 前缀,或后缀 FooInterface。
Abstract class: 通常会添加 Abstract/Base 前缀以明确属性。这是因为 Interface/Impl 是常见的,Class 也是常见的,但是基于继承的抽象类是特殊的、应该予以避免的,应该给予特殊标记。
Implementation:
如果不实现接口,通常不需要任何特殊修饰符。
如果以 "is-a" 的方式实现了某个接口,那么通常实现会以 {InterfaceName}Impl 的方式命名。
如果一个类实现了多个接口,那么通常这个类应该是以作为主要目标的接口为基础命名。例如 class BazelBuilderImpl implements BazelBuilder, AutoClosable, Serializable。
如果一个接口有多个实现,通常会基于它们本身的实现特点命名,并且不使用 Impl 后缀。Default 通常用来命名默认的实现,即其它实现如果不存在会 fallback 到的实现。如果所有的实现都是平等地位,那么不要使用 Default 命名。
// https://github.com/bazelbuild/bazel with some fake examples
public interface SkyFunction {}
public abstract class AbstractFileChainUniquenessFunction implements SkyFunction {}
public class DefaultSkyFunction implements SkyFunction {}
public class BazelModuleInspectorFunction implements SkyFunction {}
public interface VisibilityProvider {}
public final class VisibilityProviderImpl {}
C++
C++ 的 interface 是通过抽象类不存在基类成员变量模拟。通常接口所有的成员函数都是公开纯虚函数。
使用 Impl 表示实现。
Abstract class: 通常会添加 Base 后缀以明确属性。这是因为 Interface/Impl 是常见的,Class 也是常见的,但是基于继承的抽象类是特殊的、应该予以避免的,应该给予特殊标记。
// levelDB
// includes/db.h
class DB {
public:
virtual ~DB(); // MUST!
virtual Status Delete(const WriteOptions&, const Slice&) = 0;
}
// db/db_impl.h
class DBImpl : public DB {}
// rocksDB
// Base class
class CacheShardBase {}
Go
Go 的 interface 从来不是用来做 "is-a" 定义的。Go 的 interface 契约通过 duck typing 满足。interface 应该在消费方定义,而非提供方。因此, interface Foo/struct FooImpl 不应该出现。
Go 也并没有抽象类,虽然可以将一个结构体嵌入到另一个结构体中。所以 Base/Abstract 也极少出现。
原则上,Go 的类关系更为简化,命名更强调意义优先,因此在命名时避免使用修饰性前后缀。
异常JavaException/Error
所有的异常扩展应该以 Exception 为后缀。所有的错误应该以 Error 为后缀。 对异常和错误的区别请参见 https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html
public class IllegalArgumentException;
public class OutOfMemoryError;
C++
C++ 的 exception 通常指语法特性,与 throw 对应,而 error 可以用来表示具体的异常错误。
// stdlib
std::exception;
std::runtime_error
Go
所有的错误都是 error。因此,所有自定义的对 error 的扩展都以 Error 作为后缀。
os.PathError
测试
Test
Java/Go/C++ 均使用 Test 作为测试类的后缀。
模块Module/Component
Module/Component 通常会在框架中使用。不同的语言/框架对于 Module/Component 有不同的定义。 在非框架代码中应该减少使用 Module/Componenet 等命名,因为可能与已有框架冲突,并且 Module/Componenet 过于宽泛而缺少实质意义。
Module/Component 是意义相近的词,都可以表示“模块”或者“组件”。两者虽然有细微的分别,但是框架通常都显式(即在文档中指定,或者通过框架语义约束)地把它们定义为框架语境下的某些结构层级。
总结,Module/Component 命名应该注意:
只应该在框架代码中使用。
Module/Component 应该在框架的语境中给出确切的定义。
服务Service
Service 通常用于作为 C-S 架构下的服务提供者的名称的后缀,如:
HelloService
但除此之外,Service 可以表示任何长期存活的、提供功能的组件。例如:
BackgroundService // Android 后台服务
ExecutorService // 线程池执行服务,也是服务
容器BAD: 不要使用 Svr 缩写。使用全称。
Holder/Container/Wrapper
Holder/Container/Wrapper 都表示“容器”,具有同一个意图:为一个类增加额外的数据/功能,例如:
添加某些语境下的元数据(Decorator 模式)
做适配以在另一个环境中使用(Adapter 模式)
通常的结构如下:
class ObjectHolder {
private final Object object;
// other stuff ...
public Object object() {}
// Other methods
}
这三个词没有区别。在同一个项目中,应该保持一致。
控制类Manager/Controller
Manager 和 Controller 是同义词。它们通常用来表示专门控制某些类的类。
这两个词有以下几个常见场景:
Manager 管理资源,如 DownloadManager, PackageManager。
Manager 通常特指管理对象的生命周期,从创建到销毁。
Controller 通常在某些架构中,尤其是 MVC (Model-View-Controller)。
即使如此,Manager/Controller 是无意义词汇,出现时充满了可疑的味道 -- 类应该管理它们自己。 Controller/Manager 多了一层抽象,而这很可能是多余的。 认真考虑是否需要 Manager/Controller。
辅助类Util/Utility/Utils/Helper/{ClassName}s
辅助类是强制 OOP 的语言(i.e. Java) 所需要的特殊类。通常它们是一些辅助方法的合集。
Java将与某个类型相关的辅助方法放在一个类中,并且以复数形式命名辅助类。如:
// Java std lib
public final class Strings {}
public final class Lists {}
public final class Collections {}
避免使用 Util/Utility/Utils/Helper。它们是无意义词汇。
C++使用全局方法。如果担心命名污染,将之置入更细粒度的 namespace。
Go使用全局方法。
函数式Function/Predicate/Callback
Function 通常表示任意函数。 Predicate 表示命题,即通常返回类型为 bool。 Callback 指回调函数,指将函数作为参数传递到其它代码的某段代码的引用。换言之, Function 可以作为 Callback 使用。因此,Callback 在现代函数式编程概念流行后,通常很少使用。
Java熟悉 Java 8 开始提供的函数式语义。如果可以使用标准库的语义,不要自己创建名词。 注意 Function 指单入参、单出参的函数。如果是多入参的函数,直接定义 FunctionalInterface 并且按用途命名,例如 OnClickListener.listen(Context, ClickEvent)。
// java.util.function
Predicate<T> // f(T) -> bool
Function<T, R> // f(T) -> R
Consumer<T> // f(T) -> void
Supplier<T> // f() -> T
C++
first-class 函数的标准类型为 std::function。
C++ 表示命名函数对象的惯用法是 fun。Stdlib 中会缩写 function 为 fun,如 pmem_fun_ref,因此与 stdlib 一致,在代码中不要使用 fn 或是 func 。
GoGo 通常使用 Func 或是 Fn 表示函数类型。
type ProviderFunc func(config ConfigSource, source PassPhraseSource) (Provider, error)
type cancelFn func(context.Context) error
在同一个项目中,应该保持一致。
作为参数时,函数不会特意标明 Fn,而是遵从普通的参数命名方式:
func Sort(less func(a, b string) int)
设计模式类换言之,函数是一等公民。
类/方法通常都按它们的行为模式来命名。恰好,设计模式就归类抽象了很多行为模式。所以设计模式提供了很多好的名字。
创建式Factory: 工厂模式。通常,使用工厂方法即可,不需要一个额外的工厂类。只有当工厂特别复杂,或者工厂有状态时再考虑使用工厂类。
Builder:构建者模式。一般来说 Builder 都是作为 inner class,如
class Foo {
static class FooBuilder {}
}
行为式Adapter: 适配器
在 GoF 中 Adapter 本来是将一个类封装以可以被作为另一个类型被调用,这样调用方不需要额外改变代码。这种用途通常被内化到容器上,见上文[容器类]部分。
在现代,Adapter 更多地被作为 数据类 -> 数据类的转化,如常见的 pb -> pb:
class ProtoAdapter<S, T extends Message> {}
Decorator:装饰器
在 GoF 中 Decorator 本来是将一个类作为抽象类,通过组合+继承实现添加功能。实际上现代的编程实践中往往通过直接提供一个容器的封装提供装饰功能,见上文 [容器类]部分。 所以 GoF 式 Decorator 并不常见,除非像 Python 在语法层面提供了装饰器。在 Java 中类似的功能是注解。
Delegation:委派模式GoF 中是非常基本的模式:由一个类负责接受请求,并把请求转发到合适的实例类中执行。
class RealPrinter {}
class Printer {
RealPrinter printer;
}
Delegate 非常常见,也提供了两个名字,请注意区分:
Delegate 是被委任的对象。
Delegator 是委任对象。
所以,通常情况下 Delegator 在命名中会更常见,类似于 Dispatcher。Delegate 更多作为一个类型或是接口被实现。具体的选择参见 [编排] 部分。
GoF 中 Facade Pattern 通常是指为子系统提供一个更高层的统一界面,屏蔽子系统的独有的细节。 在现实中,Facade 通常用来为非常复杂的类/系统定义一个较为简化的界面,如:
// proto, extremely complicated TripResponse
message TripResponse {
// ...
// ...
string last_message = 3279;
}
class TripResponseFacade {
private final TripResponse response;
Trip trip();
Endpoint source(); // Abstracted and processed
Endpoint target(); // Abstracted and processed
}
Facade 与 Adapter 的主要区别在于 Facade 的主要目的是为了简化,或者说更高层次的抽象,并且通常简化的界面不服务于专门的对接类。 Adapter 通常是为了一个特定的对接类实现。
注意 Facade 命名通常可以省略。仅当你的意图是明确告知用户这是关于某个类的外观时使用。
Proxy:代理模式GoF 中代理模式用来添加一层抽象,以对实际类进行控制,并添加某些行为(如 lazy/memoized),或是隐藏某些信息(例如可见性或是执行远程调用)。
Proxy 与 Facade 的区别在于 Proxy 通常是为了额外的控制/记录等行为,而非只是为了更高的抽象/简化。
注意 Proxy 作为代码模式时,通常不应该出现在命名之中。使用具体的 Proxy 的目的作为命名,如 LazyCar 或是 TracedRuntime,而非 CarProxy 或是 RuntimeProxy。
Proxy 还有另一个含义就是真正的“代理”,如代理服务器。在这种情况下,使用 Proxy 是合适且应该的。这也是另一个为什么代理模式不应该用 Proxy 命名的原因。
Iterator: 迭代器时至今日仍然最常见的模式之一。Interator 有以下两个术语,不要混淆:
Iterable: 迭代容器
Iterator: 迭代器
Visitor: 访问者模式访问者模式用来遍历一个结构内的多个对象。对象提供 accept(Visitor) 方法,调用 Visitor.visit 方法。
即使如此,Visitor 应该并不常见,因为它可以简单地被函数式的写法替换:
class Car {
void accept(Consumer<Car> visitor); // No longer need to define Visitor class.
}
Observer/Observable: 观察者模式
Observer/Publisher/Subscriber/Producer/Consumer
时至今日最常见的模式之一。和事件驱动编程(Event-based)有紧密关系 -- Oberservable 发布消息,所有注册的 Obeserver 会接收消息。 Publisher/Subscriber 也是类似的,它们的区别在于 Observer 模式往往是强绑定的 -- 注册和分发通常在 Observable 类中实现; 而 PubSub 模式通常有专门的 Message Broker,即 Publisher 与 Subscriber 是完全解耦的。
PubSub 与 Producer/Consumer 的区别是:
Publisher/Subscriber: 在事件流系统中,表示 1:N 广播/订阅。
Producer/Consumer: 在整个流系统中,专指 1:1 生产/消费。Producer/Consumer 也是 Pub/Sub 系统的组件(广播也是一对一广播的)。
有些系统(Kafka)使用 Consumer Group 表示 Subscriber。
所有的消息注册的模式由三部分组成:
Notification: 消息本身
Sender:消息发送者/注册
Receiver: 消息接收者
关于命名参见 [事件] 部分。
Strategy:策略模式Strategy/Policy
策略模式在 GoF 中用以指定某个行为在不同场景下的不同实现,作为“策略”。
Strategy 模式往往不是很显式。现代通常使用 Strategy 表示实际的“策略”,即对信息不同的处理策略,而不采取 Strategy 模式的含义。
在“策略”这个语义中,Strategy/Policy 没有区别。在同一个项目中,应该保持一致。
Command:命令模式命令模式在 GoF 中以类开代表实际行动,将行动封装,以支持重复、取消等操作。
Command 在现代编程实践中可以通过简单的函数式方案替换,如:
Function<T, T> command; // Java
std::function<const T&(const T&)> command; // C++
type Command func(T*) T* // Go
现代通常使用 Command 表示实际的“命令”,而不采取 Command 模式的含义。
Null Object 模式Tombstone
Null Object 模式不在 GoF 当中。它是一个用来代替 null 的 object,对其所有的操作都会被吞掉。 Null Object 主要是为了避免空指针。 合理的零值,例如 go time.Time = 0,也可以理解为一种 Null Object。
通常会有一个专门的对象表示 Null Object。可以借用 Tombstone 表示 Null Object。
Object Pool 对象池模式Pool
对象池模式不在 GoF 当中。它是将一系列昂贵的对象创建好放在一个池子中,并使用户通过向池子申请对象,而不再自己手动地创建/销毁对象。最著名的池化的例子是线程池,即 ThreadPool。
Pool 通常用来表示对象池子,例如 ThreadPool, ConnectionPool。
Arena
Arena 是指 Region-based memory management,是指一片连续的内存空间,用户在其中分配创建对象,管理内存。
前/后缀并发/异步Concurrent
Synchronized
Async
有时候我们需要特别标明一个类是线程安全的。通常这是特意为了与另一个线程不安全的实现做区分。典型的例子是 HashMap 和 ConcurrentHashMap。如果一个类只是单纯是线程安全的,那么通常不需要在名字里特意说明,在文档里说明即可。
例如:
/** This class is designed to be thread safe. */
class SomeClassThreadSafe {}
/** This class is immutable thus thread safe. */
class SomeClassImmutable {}
Concurrent 通常是用来说明该类是线程安全的前缀。Synchronized 是另一个在 Java 中可用的标明类是线程安全的前缀。但是,这通常说明这个类是通过 synchronized 机制来保证线程安全的,所以只在 Java 中使用。
另一个常见的场景是同一个方法有两种实现:同步阻塞和异步不阻塞的。在这种情况下,通常会命名异常不阻塞的方法为 {synchronizedMethod}Async,例如:
public T exec();
public Future<T> execAsync();
如果一个异步的方法并没有对应的同步方法,通常不需要加 Async 后缀。
在 Go 中,如果一个方法是意图在其它协程中异步执行,不需要加 Async 后缀。
缓存/惰性Cached/Buffered
Lazy
Memoized
名词辨析:
Cached 表示获取的对象会被缓存,保留一段时间,在缓存期间不会重新获取。
Buffered 与 Cached 同义。
Lazy 表示这个对象会被在第一次调用时创建,之后一直保留
Memoized 通常表示执行结果会在第一次计算后被记忆,之后不会再重复计算
注意 Buffered 不应该与 Buffer 混淆。 Buffer 作为名词专指“缓冲区”。
注意 Cached 不应该与 Cache 混淆。 Cache 作为名词专指“缓存”。
Cached/Buffered 应该在项目中是一致的。 Cached/Lazy/Memoized 取决于对象是被获取的,还是创建的,还是计算获得的。
不可变性Mutable
Immutable
Mutable 显式地声明一个类是可变的,Immutable 显式地声明一个类是不可变的。 通常情况下,类似于并发安全性,是否可变应该在类文档中说明,而不应该在类名中,显得臃肿。只有当一个类同时有可变/不可变版本时,可以使用 Class/ImmutableClass。
存储/数据/处理数据类Object
Data
Value
Record
Entity
Instance
上面几个都可以用来表示一个表示数据的类。但是这些词是典型的“无意义词汇”,如果把它们从名字中删除,仍然可以表示完整意义,那么应该删掉。
class CarObject {} // Bad
class CarEntity {} // Bad
class CarInstance {} // Bad
class Car {} // Good
class MapKey {}
class MapValue {} // OK. Couldn't be shortened.
class LoggingMetricsData {} // Bad
class LoggingMetricsValue {} // Bad
class LoggingMetricsRecord {} // Bad
class Logging Metrics {} // Good
class DrivingRecord {} // OK. Couldn't be shortened.
Statistics/Stats
表示“统计数据”。 Stats 是公认的可用的 Statistics 的缩写,Java/C++/Go 均可。
存储Storage
Database
Store
DB
Cache
Verbs:
- save/store/put
Storage/Database/Store/DB 都可以作为“存储服务”,即广义上的“数据库”(不是必须是完整的 DBMS)。 其中,在 C++/Go 中 DB 是常见且可接受的。在 Java 中通常使用全称。
项目内应该选择一个术语保持一致。
save/store/put 在数据库类中是同义词。同一个项目中应该保持一致。
数据格式Schema
Index
Format
Pattern
名词辨析:
Schema 借用数据库的概念,指数据的结构模式。
Index 借用数据库的概念,专指数据的索引。
Format/Pattern 通常是泛指的“模式/格式”概念。实际出现时,Format/Pattern 往往和字符串相关,如 Java 使用 Pattern 表示正则表达式。在非公共代码中,Format/Pattern 通常过于宽泛,应该考虑选用更细化的名词。
哈希Hash/Digest/Fingerprint/Checksum
Hash/Digest 哈希是一种将任何数据映射到一个较小的空间的方法。映射通常被称为哈希函数(Hash Function),映射值通常被称为摘要(Digest)。
Hash(Data) = Digest
Checksum 出自编码论,可以理解为一种特殊的哈希函数,用来检查文件的完整性。换言之,如果一份数据出现了任何变动,Checksum 应该期待会改变。(但是 Checksum 实际上并不要求唯一性,见 Fingerpint)
Fingerprint 类似于 Checksum,但是 Fingerprint 通常更严格,它通常要求最少有 64-bit,使得任何两个文件只要不同,几乎(概率意义上接近 2^-64)不可能有同一份指纹,即唯一性。(但是 Fingerprint 的定义不要求密码安全性即 cryptographic)
所以 Checksum 只是作为文件变更校验,而 Fingerprint 可以作为数据的唯一标记。
在命名时,优先使用 Fingerprint/Checksum,或其它特定指定用途的术语。当以上均不合适时,回退到更泛化的概念,即 Digest。
流式编程Stream
Source/Sink
Pipe/Piped
流式编程通常有自己的专有词汇表。具体地:
Stream 表示流式
Source 表示数据源(输入),Sink 表示 数据汇(输出)。
动词词汇表,如 map/reduce/join/filter/iterate/do/window/key/evict/peek/trigger/slide/...
原则是:选择你的团队里最常使用的流式处理系统所使用的词汇表。
状态State/Status
很讽刺地,很多人认为这两个词有区别,但是他们认为区别的点各不相同。见下文参考文献。笔者倾向于认为它们其实没什么本质区别。
鼓励使用 State 表示状态。因为 HTTP 和 RPC 已经占用了 Status 这个术语,为了避免误解,使用 State 表示自定义状态。
参考:
https://stackoverflow.com/questions/1162816/naming-conventions-state-versus-status
https://softwareengineering.stackexchange.com/questions/219351/state-or-status-when-should-a-variable-name-contain-the-word-state-and-w
鼓励使用 State: https://google.aip.dev/216
计数Num/Count/Size/Length/Capacity
Num/Count 表示数量,但不强制是某个 collection 的长度。推荐使用 Count。
Size/Length 表示容器1的当前容量。遵循语言惯例,通常使用 Size。
Capacity 通常表示容器的最大容量。
方法/动词动词是句子的精髓。选择精准的动词是代码可读性的关键。 本章对动作做了分类,并且提供了部分备选。如果动词有反义词,它们会被聚合在一个词条中。 本章的词汇有两种:
动词
以执行一个动作为主的某些行为类,即 -er 模式,如 Producer。 -able 模式,如 Writable 是类似的,因为不再赘述。
创建/提供Producer/Provider/Supplier/Generator/Constructor/Factory
Builder.build
Verbs:
create/from/of/with/valueOf/instance/getInstance/newInstance/getFoo/newFoo
get/peek
make/generate
创建/提供名词辨析:
Producer/Supplier/Provider 同义词,都表示“提供一个对象”。这些提供者可能是惰性的(Lazy)。实例未必由这些提供者创建(虽然通常是)。
它们对应的动词是工厂方法的常见命名,即:
create/from/of/with/valueOf/instance/getInstance/newInstance/getFoo/newFoo
推荐在项目中使用同一种命名。推荐使用 of/from,更短。
Generator 通常专指某些需要经过计算的、特殊的对象,例如 ID。
对应的动词是 generate,强调全新生成。
Constructor 通常是指一个复杂对象的构建器,不是指构造函数。它通常用于比 Builder 更复杂的构建 (Builder 通常不会附带逻辑)。
Factory 是专职的工厂类。当工厂方法较为复杂,需要抽出,或者有状态的工厂时使用。
对应上文工厂方法的常见命名。
Builder 见前文 [Builder 构建者模式]
动词辨析:
get vs peek
get 是广义的“获取”,在绝大部分场景下适用
peek 也是“获取”对象,但是这里强调的是对原对象无副作用。在函数式编程中会用来作为不破坏数据流的旁路操作。
create vs make vs generate
同义词,表创建。推荐在项目中保持一致。
消费Consumer.accept/consume/poll
消费名词:
Consumer 是最常见的“消费者”,通常表示某个数据流的终端消费方。
对应的动词是 accept 或是 consume,遵守所使用消息队列框架的命名风格,否则,项目内保持一致。
poll 特指数据是通过轮询(poll),即 Consumer 通常主动获取消息,而非被推送(push)后处理。
注意区分轮 xun 中文的歧义:
poll 翻译为轮询,指一个客户端间断性地向外进行获取数据的行为策略。
round-robin 翻译为轮循,指以单一的方向循环接受信息/资源的分发策略。
注意轮询是 poll 不是 pull,虽然后者直觉上是“拉取,但 poll 强制间断性地主动地采样/获取数据,是正式的计算机术语。
查找Verbs:
- find/search/query
同义词。推荐在项目中保持一致。 具体地,这几个词实际上有细微的不一致。通常情况下它们可能有以下区分:
find 查询单个结果,search 查询一列符合条件的结果
find 表示“找到”,即终态,search 表“搜索”,即行为。
query 表示“查询”,类似于 search,但是暗示可能会有更高的成本。
但是,不要做这种程度的细分,大部分人认为它们是同义词。
参考 https://stackoverflow.com/questions/480811/semantic-difference-between-find-and-search
拷贝Verbs:
- copy/clone
同义词。遵循语言惯例。
Java 使用 clone。 Go/C++ 使用 copy。
添加Verbs:
- add/append/put/insert/push
动词辨析:
append 专指添加到列表末。
insert 强调可以插入到列表的任何位置。
add 是通用的列表添加方案,add(E) 等同于 append,add(index, E) 等同于 insert。addAll 用于批量添加。
put 通常用于列表之外的添加场景。如 map, iostream。
push 仅用于某些数据结构,如栈。
对于自定义的可添加 api,应该贴近底层的标准库的数据结构所使用的动词。作为泛用的添加,使用 add。
更新Verbs:
- set/update/edit
同义词。在代码 API 中使用 set,在 RPC API 中使用 update。
删除Verbs:
- remove/delete/erase/clear/pop
动词辨析:
remove/delete/erase 是同义词。严格来说,remove 指移除,即暂时从容器中取出放置在一边,delete/erase 指删除,即将对象整个清除。但是在日常编程中不需要做这种区分。通常,代码 API 中使用 remove(或依语言惯例),RPC API 中使用 delete 作为标准方法。
clear 通常表示 1) 清理列表,等效于 removeAll 2)清理状态,即恢复类到初始化状态。
pop 只在栈/队列等数据结构中使用,通常会返回对象并从数据结构中移除。
编排Scheduler/Dispatcher/Coordinator/Orchestrator/Delegator
- Verb: schedule/dispatch/orchestrate
Scheduler/Dispatcher 均借用于操作系统概念。
名词辨析:
Scheduler: 通常 Scheduler 用于分发中长期 Job。换言之,Scheduler 通常涉及到资源分配。
对应动词为 schedule
Dispatcher: 通常只负责接受事件,采用某些固定的策略分发任务,例如 round-robin。不涉及资源分配。
对应动词为 dispatch
Coordinator: 通常作为 Scheduler/Dispatcher 的同义词。鉴于其模糊性,推荐使用更细化的 Scheduler/Dispatcher
对应动词为 coordinate
Orchstrator:执行比简单的分发,即 scheduler/dispatcher 更复杂的任务/流程编排。通常,这些任务有前后依赖关系,会形成一个有向无环图。
对应动词为 orchestrate。 Orchestrator 的输出通常是工作流,即 Workflow。
Delegator: 专指委任。虽然形式类似,但是 Delegator 强调单纯的委任。参见 [Delegation: 委派模式]。
对应动词为 delegate,但通常不会使用。
检查/验证Validator/Checker/Verifier
- Verb: validate/check/verify/assert
Validation/Verification 的明确区分来自于软件测试。
Validation 通常指对产品符合用户/顾客预期的验证。外部用户会参与。
Verification 通常指产品合规/符合给定规范。通常是内部流程。
在程序中,不沿用这种区分。通常:
Validator 用于输入检测
Verifier 用于运行的不变量检测
具体地:
check 用于输入校验。 validate 用于复杂的输入校验。
assert/verify 用于不变量验证,尤其在单元测试中
public void process(String s, ComplicatedObject co) {
checkNotNull(s); // check
validateComplicatedObject(co); // validate
}
@Test
public void testProcess() {
process("ss", co);
Truth.assertThat(...); // assert
verifyZeroInvocations(co); // verify
}
执行/操作
Task/Job/Runnable
Executor/Operator/Processor/Runner
- Verb: exec/execute/do/process/run
名词辨析:
Runnable 是泛用的“带上文的可执行代码块”。
Task 粒度比 Job 更细
Job 通常是耗时更长的任务
但是,推荐不做区分,认为它们都是同义词。使用 Task 或者 Job 作为类名。
名词辨析: Processor/Executor/Operator 是从计算机架构借用的概念。
Executor: 常见。通常对应 Job/Task
对应 execute。 exec 是可接受的公认的缩写。
Operator: 通常对应某些具体的操作类。更多使用本义,即操作符。
对应 do。
Processor:更多在文本文档(work/document processor)、数据处理(data processor) 语境下使用。
对应 process。
Runner: 通常对应 Runnable
对应 run
但是,推荐不做区分,认为它们都是同义词。日常编程中,使用 Executor 作为 Job 执行器。
开启 vs 关闭toggle/switch/enable/disable/turnOn/turnOff/activate/deactivate
二元状态的开启关闭。上述全是同义词。
在项目中保持统一。注意比起 toggle(bool) 和 switch(bool),更推荐分离的 enable/disable。
读取 vs 写入Reader/Prefetcher/Fetcher/Downloader/Loader
- Verb: read/get/fetch/load/retrieve
Writer/Uploader
- Verb: write/upload
Lifecycle:
- open/close
名词辨析:
Reader 通常是从 stdio/文件/其它 Source 中读取。
对应动词 read
Fetcher 通常是从远端拉取数据
对应动词 fetch
Downloader 类似于 Fetcher,但是通常内容是文件等 blob,而非结构化数据
对应动词 download
Prefetcher 强调预热拉取,通常是拉取到缓存中。
对应动词 prefetch 或是简单的 fetch
Loader 是泛用词汇,表示广义的“加载”。通常可以表示上述的任何一种。
对应动词 load
Retrieve 是 Fetch 的同义词。
具体地,fetch/load 是有语义的细微差别。但是,不需要做具体的细分。
优先使用 read/fetch/download,当均不合适时,回退到 load。
序列化 vs 反序列化Serializer
- Verb: serialize/pack/marshal
Deserializer
- Verb: deserialize/unpack/unmarshal
动词辨析:
pack 指打包,将数据打包为一个不可拆分的(通常是不透明的)对象
serialize 指序列化,将数据转换为可以被存储/传输的(通常是二进制)格式。
marshal 强调意图 -- 将一个对象从程序 A 转移到程序 B 中。
但是,不需要做这个区分。可以认为它们都是同义词。按语言惯例使用:
C++: Serialize
Java: Serialize
Go: Marshal
Python: Pack
注意反序列化是 deserialize, 比 unserialize 更常见。 但 pack -> unpack, marshal -> unmarshal。
https://en.wikipedia.org/wiki/Marshalling_(computer_science)
https://en.wikipedia.org/wiki/Serialization
转换Applier/Converter/Transformer/Mapper
- Verb: apply/convert/transform/map/to/translate
可以认为它们都是同义词。在项目中应该保持一致。 严格来说,Mapper 更多指同一数据的两种形式的双向映射,例如数据库存储和运行时对象。 在 Applier/Converter/Transformer 中,Applier 最为常见,因为源自设计模式。 Mapper 在框架中较常见。
匹配Filter/Matcher
- Verb: query/filter/match
可以认为它们都是同义词。 在项目中应该保持一致。
事件Event
Listener/Notifier Verbs: notify
Observer/Observable Verbs: observe
Handler Verbs: handle
Publisher/Subscriber
Publisher/Consumer
在 [Observer Pattner: 观察者模式] 中已经解释。
Observer 是正宗的观察者
Listner/Notifier 通常可以用来作为 Observer/Observable 的同义词。但是 Listener 也可能表示其它含义,如 TraceListener,视框架而定。
Handler 也是同义词。它与 Listener/Observer 的区别在于,它表示唯一的事件处理器。而 Listener/Observer 可能有多个。
Publisher/Subscriber: 在事件流系统中,表示 1:N 广播/订阅。
Producer/Consumer: 在整个流系统中,专指 1:1 生产/消费。
见 https://stackoverflow.com/questions/42471870/publish-subscribe-vs-producer-consumer
文本处理Regex/Pattern/Template
Pruner/Stripper/Trimmer
Formatter/Prettier
Resolver/Parser/Expander
- Verb: compile/parse/resolve/expand
- Verb: format/split/separate/merge/join
通常,一个程序中有 20% 的代码在处理字符串。所以与文本相关的内容非常多。这里没有列出全部。
“模板”名词解析:
Regex 专指正则表达式。另一个常见的缩写是 Regexp。应该与语言保持一致。C++ 使用 Regex。Go 使用 Regexp。
编译使用 compile。 正则本身就是一种 formal language,因此使用 compile 是正统。
匹配对应动词为 expand/match/match
Pattern 在 Java 中表示正则表达式。虽然 Pattern 可能通指“模式”,但是通常不在编程中使用。
编译使用 compile
对应动词为 match/split
Template 指模板,通常不是正则形式的,而是简单的匹配替换模板,如 HTML 模板。
对应动词为 expand
“修剪”动名词解析:
Pruner.prune: 指清理掉过时的、不应存在的内容
Stripper.strip: 指清理掉多余的、过度生长的内容
Trimmer.trim: 泛指修剪,使其更好看。
但是,Prune/Strip/Trim 在编程中通常认为是同义词。它们通常情况下:
Strip/Trim 指去掉头尾的多余空格
Prune 可能会进行其它的裁剪
语言可能会为之赋予特殊含义,例如在 Java 11 中,Trim 会清理掉所有的普通空格,而 Strip 会清理掉所有的 Unicode 空格。
https://stackoverflow.com/questions/51266582/difference-between-string-trim-and-strip-methods-in-java-11
“格式化”动名词解析:
Formatter.format 是将对象进行格式化。通用名词。
Prettier.pprint 专指将数据整理为便于人类可读的的输出格式。典型的例子是 Python 的 pprint。
“解析”动名词解析:
Expander.expand 通常用于 DSL/Schema 解析,专指将某些 DSL 展开,如变量替换,展开 glob。
Parser.parse 类似于 parse,但强调将文本进行句法解析,形成格式化的中间层表示。借用了编译器术语。
Resolver.resolve Resolve 通常指从人类可读的定义(可能有歧义或不精确)向机器可读的定义(精确的、可解析的)的转换。例如,域名 -> ip 的解析,依赖包的版本号的解析(打平)。(!) resolve 不同于 expand/parse 的文本解析。这是一个相同中文不同英文的易混淆例子。
生命周期Lifecycle
Initializer/Finalizer
Verb:
- init/setup/prepare
- pause/resume
- start/begin
- end/terminate/stop/halt
- destroy/release/shutdown/teardown
生命周期解析: 一个对象的生命周期,称为 Lifecycle,通常有以下流程:
创建。通常由语言特性支持,不属于生命周期管理范围。
初始化:init。init 是 initialize 的全称,通常用来初始化一个类到可用状态。应该尽量避免创建之外的额外初始化步骤,一个对象应该尽可能在创建后就处于已初始化状态。额外的状态会让这个类更难正确使用。
setup/prepare 是 init 的同义词。应该在项目内统一,推荐为 init。setUp 通常在测试中使用,用于作为每个测试用例设计的前置步骤。
init vs prepare: 具体地细分,init 的语义通常指“在类生命周期层面处在正常可执行状态”,prepare 的语义通常指“在业务层面做好准备”
开始: start/begin。通常用于这个对象正式开始正常工作,即切换到 running 状态。在切换到其它状态之前这个类会一直保持在 running 状态。
start/begin 是同义词。通常使用 start 作为动词“开始”,使用 begin 作为列表的头。
暂停: pause。pause 应该使得类暂停运行,从 running 状态切换到 paused 状态。这段时间这个类应该不再工作。
恢复:resume。resume 与 pause 是成对的。 resume 会恢复 paused 到 running 状态。通常,pause/resume 可以无限次随时切换。
停止:stop/end/terminate/halt。停止类运行,与 start 对应。通常情况下,一个类 stop 意味着不会再重新启动。通常情况下,停止状态的类应该拒绝任何请求。
stop/end/terminate/halt 是同义词。不要做区分处理,在项目中保持一致。
销毁:destroy/release/shutdown/teardown/exit。彻底销毁对象。此后,对象不再处于可用状态。
destroy/release/shutdown/teardown 是近义词。具体地:
destroy 强调销毁对象。
release 强调释放资源。
teardown 通常与 setup 对应。
exit 通常指程序退出。
shutdown 是通用“彻底关闭”的动词。当 destroy/release 不合适时,回退到 shutdown。
使用 gracefullyShutdown 表示优雅关闭。这通常意味着是前几个行为的集合:停止服务、释放资源、刷新缓冲区、销毁对象。
计算Calculator
Verb:
- compute/calculate/calc
使用 Calculator 而非 Computer 表示某个运算的执行器。Computer 虽然也是“计算器”,但是在代码语境下有歧义。
compute/calculate/calc 可以认为是同义词。如果是 Calculator,使用 calculate。其它情况下,使用 compute。
元数据(配置/环境/...)Option/Config/Configuration/Setting/Preference/Property/Parameter/Argument
Context/Environment
Info/Metadata/Manifest/Version
配置名词解析: 这个有类似的名词辨析,但是它们在编程时通常认为都是“配置”的同义词。它们还会出现在用户界面,尤其是 Settings/Options/Preferences。
在编程的角度,Option/Config/Configuration 是同义词,均表示配置。惯例使用 Options 作为子类定义一个类所需的配置,尤其是作为依赖注入时。
使用 Property 表示单个属性, 而且通常是 k-v 结构。换言之,Option/Config 通常由多个 Properties 组织。只有当 Property 是动态属性时,才定义特殊的 Property 类,否则,在 Option 中定义具体的域表示 Property。
struct Options {
int fur_layer_count; // Good
int fur_layer_count_property; // Bad! Property unnecessary
struct ColorProperty {
int a;
int r;
int g;
int b;
} // Bad! Prefer Color.
ColorProperty color;
}
参数解析:
Parameter:通常表示在接口处定义的参数
Argument:指实际传入接口的参数
例如:
func foo(param string)
foo(arg)
https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter
上下文名词辨析:
Context 指上下文,通常用于在 API 之间传递与一次执行相关的信息。在 RPC 处理中非常常见,例如 https://pkg.go.dev/context 。
Environment 指环境。这个名词从系统环境变量而来。通常,这表示在程序启动后保持稳定的环境数据,不随所执行的内容(如 rpc 请求)变化而变化。
元数据辨析:
Info 泛指信息。而元数据相当于特定的“关于数据”的信息。
Metadata 标准用语,专指元数据。避免使用 Info 代表元数据。
Manifest 专指文件清单,描述一个模块中的文件/功能/其它组成结构的列表。Manifest 来自于货运术语,Ship Manifest 用以列出所有的船员和船队所有的船只。
Version 专指程序的版本元数据,例如 TrpcVersion。如果一个类专指版本,使用 Version 是最精确合适的。