CountDownLatch 和 CyclicBarrier 保证线程同步

CountDownLatch

// 创建 2 个线程的线程池

Executor executor =

Executors.newFixedThreadPool(2);

while(存在未对账订单){

// 计数器初始化为 2

CountDownLatch latch =

new CountDownLatch(2);

// 查询未对账订单

executor.execute(()-> {

pos = getPOrders();

latch.countDown();

});

// 查询派送单

executor.execute(()-> {

dos = getDOrders();

latch.countDown();

});

 

// 等待两个查询操作结束

latch.await();

 

// 执行对账操作

diff = check(pos, dos);

// 差异写入差异库

save(diff);

}

 

CyclicBarrier

// 订单队列

Vector<P> pos;

// 派送单队列

Vector<D> dos;

// 执行回调的线程池

Executor executor =

Executors.newFixedThreadPool(1);

final CyclicBarrier barrier =

new CyclicBarrier(2, ()->{

executor.execute(()->check());

});

 

void check(){

P p = pos.remove(0);

D d = dos.remove(0);

// 执行对账操作

diff = check(p, d);

// 差异写入差异库

save(diff);

}

 

void checkAll(){

// 循环查询订单库

Thread T1 = new Thread(()->{

while(存在未对账订单){

// 查询订单库

pos.add(getPOrders());

// 等待

barrier.await();

}

});

T1.start();

// 循环查询运单库

Thread T2 = new Thread(()->{

while(存在未对账订单){

// 查询运单库

dos.add(getDOrders());

// 等待

barrier.await();

}

});

T2.start();

}

CountDownLatch 和 CyclicBarrier 是 Java 并发包提供的两个非常易用的线程同步工具类,这两个工具类用法的区别在这里还是有必要再强调一下:CountDownLatch 主要用来解决一个线程等待多个线程的场景,可以类比旅游团团长要等待所有的游客到齐才能去下一个景点;而CyclicBarrier 是一组线程之间互相等待,更像是几个驴友之间不离不弃。除此之外 CountDownLatch 的计数器是不能循环利用的,也就是说一旦计数器减到 0,再有线程调用 await(),该线程会直接通过。但CyclicBarrier 的计数器是可以循环利用的,而且具备自动重置的功能,一旦计数器减到 0 会自动重置到你设置的初始值。除此之外,CyclicBarrier 还可以设置回调函数,可以说是功能丰富。

本章的示例代码中有两处用到了线程池,你现在只需要大概了解即可,因为线程池相关的知识咱们专栏后面还会有详细介绍。另外,线程池提供了 Future 特性,我们也可以利用 Future 特性来实现线程之间的等待,这个后面我们也会详细介绍。

继续阅读

发表在 java | 标签为 , , , | CountDownLatch 和 CyclicBarrier 保证线程同步已关闭评论

在阿里做了五年技术主管,我有话想说

 

阿里妹导读:在历史文章《如何成为优秀的技术主管?》中,阿里巴巴高级技术专家云狄从开发规范、开发流程、技术规划与管理三个角度,分享对技术 TL 的理解与思考。

 

 

 

今天的文章,他将继续深入探讨这一话题,从管理的角度分享技术TL的核心职责,主要包括团队建设、团队管理、团队文化、沟通与辅导、招聘与解雇等,希望与大家共同探讨、交流。

 

 

 

背景

 

 

 

互联网公司的技术团队管理通常分为2个方向:技术管理和团队管理,互联网公司的技术TL与传统软件公司的PM还是有很大的区别,传统软件公司的PM更多注重于对项目的管理包括项目任务拆解、项目进度以及风险等。对于多数互联网公司而言,技术TL更多的职责不再局限于项目角度,而是对业务与技术都要有深入的了解,就像黑夜里的灯塔,能够引导和修正团队成员前进的航向。综合技术和业务角度去深度思考问题,具备一定的前瞻性,并在技术领域投入持续的学习热情,向团队成员传道,补齐短板,提高整个团队的战斗力。

 

 

 

技术TL职责不仅需要制定日常规范,包括开发规范、流程规范等,推动规范的落地,以公有的强制约定来避免不必要的内耗,另外一多半的时间可能花在了开发任务分解分配、开发实践、技术架构评审、代码审核和风险识别上,剩余的时间则花在为了保障系统按时交付所需的各种计划、协作、沟通、管理上。

 

 

 

管理大师彼得·德鲁克说:“组织的目的,就是让平凡的人做出不平凡的事。”然而,不是任何一群平凡的人聚集到一起,都能做出不平凡的事。甚至一群优秀的人聚集到一起,也可能只是一个平庸的组织。大到一个国家,小到一个团队,任何一个卓越的组织,都必须有一个卓越的领导者。领导者是一个组织的灵魂,领导者在很大程度上决定了组织所能达到的高度。

 

 

 

阿里有句土话“平凡人、非凡事”,技术团队同样如此,管理者的战略眼光、管理方法、人格魅力等,都会给团队的工作结果带来决定性的影响。

 

 

 

其实每个公司、每个团队的背景不太一样,从管理学的角度探讨一些问题,没有统一标准的答案,本文中一些观点仅是个人观点,更多从我个人成长为技术TL一些观点理念,同时我也是吸取了前辈们一些优秀的管理理念,包括我最为尊敬的通用电气CEO杰克·韦尔奇、苹果CEO乔布斯、Intel CEO格鲁夫,国内我最推崇的技术管理者robbin(丁香园的技术副总裁)。

 

 

 

 

 

 

 

团队建设

 

 

从2014年开始带这块业务技术团队,至今有5个年头。回想起来,团队管理中所有能遇上的问题都遇到过了,其中的磕磕绊绊数不胜数,完全是在实践当中吸取教训,团队建设这块在这里和大家简单分享一下,当然这里面也有做得不够好的地方。

 

 

 

在阿里每个人都能感受到拥抱变化,基本上每年组织架构都会调整,甚至有些团队每半年都会调整一次。14年我也算是被分配到这个团队负责这块业务,这块业务是集团收购一家子公司的业务,整个团队文化和技术体系与阿里有很大的差异化。一般来说新官上任三把火,新的技术TL空降之后往往会大肆招人,快速推进改革,而且有些技术TL喜欢把原来的一些旧将搬进来。

 

 

 

当时我没有急于这么去做,没有招过一个新员工,而是立足于稳定现有的团队,主要基于以下原因:

 

 

 

团队和业务了解不够深:对于目前的团队的人员以及业务,我不够了解,不清楚这里面有哪些坑和陷阱,一旦初战不利,领导的信任度被透支,在公司恐怕难有立足之地,更不用谈论改造团队,发挥自己的才能了。

 

 

 

流程与制度:针对团队现状存在的一些问题,我初步判断并不是人的问题,很多问题是一些组织、流程、制度上的问题。我认为只有好的制度才能造就好的团队,在没有解决现有团队的痼疾之前招聘新人,不但不会带来新的生产力,反而会造成团队的混乱,应该先打下一个好的根基,再招人,才能事半功倍。

 

 

 

团队安全感:不想让团队现有的成员感觉一朝天子一朝臣,担心自己在团队中会被边缘化,成为弃儿。另外一方面能够让现有团队心理比较安全,可以安心地好好工作,不至于发生更多的动荡。

 

 

 

经过了几个月的摸底了解,大概清楚当时团队存在的一些问题和原因:

 

 

 

业务配合不规范:产品、运营、研发部门之间配合没有建立合理的工作流程,比如对于产品需求的PRD评审没有标准,对于运营需求没有量化指标,大家都是疲于奔命做需求,导致大家的积极性不够高。

 

 

 

 

跨团队协作混乱:跨部门之间的工作配合毫无规范可言,部门之间相互推诿,随便什么业务人员都会随时给研发人员下命令,长此以往,伤害了研发团队的积极性。

 

 

 

 

针对以上问题,我主要把协作流程规范梳理了一番,制定了相对合理、规范的产品合作流程,同产品同学约法三章,明确了PRD输出的标准和规范,运营的业务需求也统一由产品输出,杜绝一句话需求。同产品、前端、UED、QA团队的协作统一标准流程,下游对上游依赖方输出的工作必须有明确的标准规范,口头说的统统无效,拒绝合作。

 

 

 

针对跨团队协作乱的情况,我特别想说明一下,由于研发部门不是直接创造收入的业务部门,而是承担业务部门的服务者角色。作为一个服务者,往往站在一个被动和弱势的位置上,很容易被业务人员举着收入的大棒指挥你无条件的服从。业务部门人员随便指派任务,随意变更需求,团队同学无所适从。这样一来,部门内部无论怎样合理的计划都会被外部的力量轻易打破,让团队同学无所适从,导致大家的工作积极性不高,喜欢互相推卸责任。久而久之,员工就产生了自我保护意识,凡工作尽量往后退,凡责任尽量往别处推,不求有功但求无过。

 

 

 

为打破员工养成的这种自我封闭的保护意识,鼓励员工更加积极主动做事情,我能够做的就是把这些责任都扛在自己身上,亲自去协调每项工作,让团队成员没有后顾之忧,让团队同学相信我可以搞定他们担心的事情,出了任何问题我可以来背锅,给自己的团队创造一个相对宽松和自由的工作空间,保护团队不被外部的各种杂事伤害到。

 

 

 

 

 

 

 

 

团队管理

 

 

人往往会高估自己而低估别人,很多管理者都会觉得手下交上来的工作做得不够完美,这里考虑不周那里做的啰嗦,但很多时候你只是看到了他人不擅长的地方,或者只是对方和你的出发点不同给出了不同的解决方案而已。很多时候,我们并不如自己想象的那么强。管理者在充分理解一些管理的理念之后,不断地在实际的管理工作中去实践并收集反馈和迭代,这样才能够形成自己的管理风格,并找到最适合当前团队的管理方法。

 

 

 

作为一个团队的管理者,通常会有两种风格管理策略,简要概括为集权式的管理风格、放权式的管理风格。

 

 

 

集权式管理:管理者的风格是偏细节的,定义清晰的工作目标,并且把工作目标分解得非常细致,让手下的团队能按照整个计划步步为营往前推进,这是一种风格,相对来讲比较集权。

 

 

 

可以说我带这个团队的第一年是这种风格,我甚至会参加每一次需求评审,无论需求大小,会和研发同学一起去写代码,对研发团队我会做详细的code review,亲自带领研发团队做技术交流和分享,参与技术讨论确认架构方案,这样以来和大家建立起了充分的信任。

 

 

 

放权式管理:定义大的目标,把握大的方向,做关键性的决策。但是并不深入每个细节去管控手下团队的执行细节,以结果为导向。

 

 

 

我到这个团队一年后,业务流程已经清晰的建立起来了,骨干员工在业务上能够完全领会并且达到我的要求,这个时候放权可以充分调动团队的自主性和创造性,多数技术人员他们喜欢被领导,不喜欢被管理。

 

 

 

以上这两类管理风格没有对错之分,究竟哪种方式更适合完全取决于团队的状况。其实这里我更想说一下关于放权式的管理风格,对于一个制度刚刚建立,流程还没有跑顺畅,团队残缺,骨干员工业务能力不及格的团队,采用放权式管理是错误的。你必须事无巨细,从第一线的业务细节抓起,手把手的带员工,教会他们怎么正确的做事情,怎样达到你的要求,手把手的培养业务骨干,搭建团队核心架构。

 

 

 

这些年我看到过太多的案例,管理层自己从不真正深入业务,也缺乏对业务的深刻理解,没有找到问题的本质原因。总是寄希望于招人来解决问题,结果换了一茬又一茬人,问题永远解决不了,而且从来不深刻反思自己是否亲自尝试解决业务问题。很多时候架构反应出来的问题,其实是组织、流程的问题。总之,作为管理层,如果自己没有深入一线去发现问题,自己动手去解决问题的决心和勇气的话,那这个团队很难有新的突破和成功。

 

 

 

 

 

 

 

 

团队文化

 

 

在我刚参加工作的前几年,就听过一些关于团队文化和企业文化的一些概念,并没有特别深刻的印象。尤其我读了《基业长青》这本书后,让我感受到对于一个企业而言,决定短期的是技巧,决定中期的是战略,决定长期的是文化。企业文化对一家公司来说真的很重要,同样团队文化对于一个团队来说也很重要,我在带团队之初也曾经忽视了团队文化的冲击。

 

 

 

在带领这个团队之初,我私下找一些团队同学做1on1沟通,我发现这里面的问题还是比较严重的,很多人为了避免故障遭受惩罚,不敢去重构优化代码,把自己封闭到一个很小的圈子,也没有过多的追求和理想,以前也没有末位淘汰机制,大家觉得可以继续吃大锅饭。当时部门都是工作多年的老人,老的风气和习惯已经形成了很顽固的不良文化,工作情绪受到很大的影响。

 

 

 

老的不良的文化包括:

 

 

 

做事情没有积极性;

 

永远不承认自己的错误,永远找借口推卸责任,永远都是别人的问题;

 

不求有功但求无过;责任心差,对待工作自我要求低;

 

对工作安排喜欢讨价还价;

 

 

 

在一个不好的文化氛围下,优秀的员工会被排挤,团队没有向心力,也很难留住好的人才,员工流失率会非常高。我认为衡量一个团队文化氛围是否有吸引力,有一个很重要的指标,新员工的流失率:如果一个团队氛围非常好,新员工入职以后往往能够快速融入进来,流失率很低;如果团队氛围差,新员工入职以后比较茫然难以融入,往往会很快离职,流失率非常高,实际上留不住新员工远远比留不住老员工更可怕。

 

 

 

接下来我希望给团队树立的文化是:

 

 

 

坦诚,公开,透明;

 

平等相处,消除等级感;

 

工作气氛轻松,团队关系和谐;

 

敢于担当,主动承担责任;

 

成就他人,乐于分享。

 

 

 

关于团队文化这个话题其实很泛,可以单独写一篇文章出来的。这里我主要基于团队文化以上几点,谈一下我的一些个人的看法。

 

 

 

坦诚的力量

 

 

首先,我觉得坦诚无论对于一个 TL 还是团队成员来说,坦诚也是一种价值观,对于一个团队的发展来说是非常重要的。作为一个 TL,带领一支团队,我觉得最重要的是 TL 本人必须做到坦诚的态度,只有对团队坦诚,才能和团队之间形成信任,只有和团队形成了信任,才能成为一支默契的团队。

 

 

 

通用电气 CEO 杰克·韦尔奇说过:什么是信任?当一个领导真诚、坦率、言出必行的时候,信任就出现了,事情就是这么简单。为什么坦诚精神能行得通?很简单,因为坦诚有化繁为简的力量!

 

 

 

坦诚的性格是管理者最基本的要求,只有管理者坦诚,才能获得团队的信任,作秀式的演讲和奖励并不能够真正获得团队的心,还是需要在工作中脚踏实地一点一滴去做好最平凡普通的事情。坦诚能够让你直面自身的缺陷,有针对性地改变自己,解决团队的问题,造就一个互相信任的团队氛围。

 

 

 

我见过一个比较典型的案例,日常工作中主管对于下属不够坦诚,下属与主管的平时一些工作沟通中,下属做的不够好的地方,主管不及时进行沟通与辅导,结果最后KPI 考核被打了低绩效。换位思考一下,这个被打低绩效的人是我,我也会不服气,有问题你为啥不提前告诉我,让我提前去改正。对待下属要有勇气,敢于指出他们的问题,对于表现不好的员工要敢于批评和管理,例如为什么解雇你。这些谈话和冲突往往让人感到不舒服,我也承认每次谈低绩效是硬着头皮的,但是你必须有这样的勇气,坦诚不仅仅要对那些表现良好的人,还要对那些表现糟糕的人。

 

 

 

苹果创始人乔布斯是一个对自己、对别人坦诚得可怕的人,坦诚的残酷,直面事情最真实的一面。的确坦诚的态度在很多时候会让别人感觉不舒服,乔布斯粗暴的坦诚态度也备受争议,但我觉得,如果你是一个结果导向的人,还是应该尽量坚持坦诚的态度,否则最终的结果可能远远偏离你的目标。

 

 

 

 

 

 

 

允许你的下属challenge你

 

 

其次,我再聊一下关于平等相处,消除等级感,这点我觉得最重要的让大家感受到你的亲和力,不是一个高高在上的领导。比如很多时候团队一些技术方案的决策不是你一个人来决定,有时候还是要善于倾听一下团队成员的意见,要允许团队成员challenge你。

 

 

 

其实,国内外要求下属服从的企业文化很普遍,这不一定是坏事,特别是公司如果有想法的人太多,想法又无法统一起来,公司的整体战略呈现精神分裂状态,那基本上就离死不远了。所以管理层统一公司战略,一线员工强调使命必达。

 

 

 

国内的外企格外强调下属的服从性,把这一点作为员工的基本职业素养来培训,常用来讲解的故事就是《把信送给加西亚》,强调上司安排一项工作以后,下属不允许谈任何条件,不允许challenge上司,必须无条件服从,克服一切困难也要完成工作任务,以解领导之忧。这种执行力让上司感觉很舒服,而且公司管理实施难度也比较低。

 

 

 

多数管理者都喜欢比较听话的下属,认为顺从的下属更好用。心态上高人一等,不会放低心态倾听下属的意见,即使自己错了也不会承认错误,一方面害怕自己的权威被挑战,另外害怕向下属认错,觉得抹不开面子。我不是圣人,作为TL曾经也犯过一些错误,我也曾私下里和个别同学道过谦。放开心态,不需要过多的太在意别人的看法,这些我觉得都是无所谓的小事。

 

 

 

从我个人自身的一些经历来看,其实一味地要求下属服从是有害的,要适当允许你的下属challenge你。

 

 

 

如果一味地要求下属服从,不能进行任何反驳,长时间下来会导致团队的人缺乏思考,只是一味的按照 TL 的想法去执行,当下属内心并不认可工作本身,仅仅出于职业性完成工作,成绩最多是合格,很难达到卓越。同时会导致下属缺乏工作积极性主动性,容易养成下属逃避责任的习惯。

 

 

 

相反我觉得作为 TL 一定要鼓励下属积极主动地思考,让下属能够自己设定成长目标,对工作拥有归属感和责任感;尽量给予下属更自由的空间,不要设置过多形式主义的约束;要允许下属去challenge你,参与你的决策,甚至质疑你的决策。用这种方式增加下属对工作的归属感,工作责任心更强,更积极主动,能够自我驱动。

 

 

 

当你的决策错误的时候,下属可以帮你纠错,集体的智慧毕竟高于个人,俗话说“三个臭皮匠赛过诸葛亮”。

 

 

 

 

 

 

 

owner 意识

 

 

“Owner 意识”主要体现在两个层面:一是认真负责的态度,二是积极主动的精神。认真负责是工作的底线,积极主动是“Owner 意识”更高一级的要求。

 

 

 

自私确实是人的天性,不是自己的东西,很难谈什么责任感,更不用说主动性了。因此,团队管理就是要努力地培养大家的责任感,主人翁意识,想做到这一点,就需要增强团队成员的参与感,让他们知晓并理解所做事情的价值、来龙去脉,不断地强化使命感。

 

 

 

例如可以将系统、业务范围等根据团队成员的兴趣点、以往项目经历等多种因素划分给指定人负责,并明确赏罚机制。要清晰地传达一种思想,那就是:这块东西就是你的,干好了评优、升职、加薪等都会优先考虑;干不好,出事情了,你要负责,我也会负责。如果有一天你看到团队成员像呵护自己的孩子一样,去对待自己的工作,那么你的目的已经达到了,他已经完全具备 owner 意识了。

 

 

 

建立学习型的组织

 

 

最后一点我要谈的是建立学习型的组织,团队成员要尽可能地分享自己的知识和想法,大家互相学习,也通过分享能够总结自己学习过程中零散的知识点。如何建立人才梯队的,其实就是要建立学习型组织,让大家积极参与学习与分享。具体做法KPI里设置一项技术分享与团队贡献,团队内部轮流进行技术分享,一方面让大家去学习、研究一些前沿技术,尤其是团队可能会用到的一些技术储备,如果他真的能把这个技术给大家讲明白的话,那他就是真的掌握了,同时也让其他人开始了解并学习这项技术,同时还能够锻炼其演讲与口才。

 

 

 

鼓励团队成员敢于去分享,乐于去分享,开放心态成就他人。把技术培训和分享坚持下去,形成这样一种学习型的文化以后,你就会发现整个研发团队的技术能力的提升速度是非常惊人的,并且不会再占用太多额外的时间。当你再招一个资历较浅的新员工时,他也在能在这种环境中快速提升,通常半年左右时间就能达到非常好的水平。

 

 

 

当然,一开始的团队可能没有这样的意识,就需要你作为管理者强行去推动,把要求列入KPI,很认真地考核他,慢慢地,团队就会形成这样的氛围和文化。当然建立这种学习型的组织,也可以建立一些读书分享会,把读的一些书籍感受分享给大家,另外一点团队的wiki知识库一定要建立起来,让团队同学把一些日常的技术方案、项目总结、故障总结通过文档的形势积累起来。

 

 

 

沟通与辅导

 

 

根据美国普林斯顿大学的调查报告,在所有对工作产生影响的因素中,沟通占的比例高达 75%。而我们工作中出现的80%问题都是由沟通不当造成的,可见沟通的重要性。多数时候,我们只想着表达自己的观点,只关注自己想说什么,我们会尽量使用漂亮的PPT、华美的语言、一堆的数据、甚至引章据典,而不关心别人听懂没有,没有思考别人是否想听,别人是否听得懂。

 

 

 

沟通在我们的工作中无处不在,你会发现尤其在技术这个圈子里,能够进行高效沟通的人占比会更少一些。沟通按照沟通对象类型通常分为向下沟通(同下属沟通)、横向沟通(跨团队沟通)、向上沟通(同老板沟通),接下来只讨论如何同下属进行沟通,最为有效沟通方式:一对一沟通。

 

 

 

一对一沟通,又被称作一对一会议、One-on-one 等,是互联网公司常用的沟通方式。一对一沟通虽然被广泛使用,但是涉及的文章却很少,这里我给大家推荐本书《格鲁夫给经理人的第一课》、《创业维艰 : 如何完成比难更难的事》,这两本书有更多关于一对一沟通介绍。格鲁夫是 Intel 公司的总裁,成功带领 Intel 公司完成了从半导体存储器到微处理器的转型,也是我非常欣赏的一位CEO。《创业维艰》的作者本·霍洛维茨是硅谷的顶级VC,投资了Facebook、Twitter等公司。

 

 

 

在《格鲁夫给经理人的第一课》一书中,格鲁夫对「一对一沟通」的介绍如下:

 

 

 

在英特尔,一对一会议通常是由经理人召集他的部属召开的,这也是维系双方从属关系最主要的方法。一对一会议主要的目的在于互通信息以及彼此学习。经过对特定事项的讨论,上司可以将其技能以及经验传授给下属,并同时建议他切入问题的方式;而下属也能对工作中碰到的问题进行汇报。

 

 

 

在我看来,技术研发同学多数比较内向,不轻易向别人表达自己内心的一些想法。一对一沟通的意义是可以使得信息从下而上地传递,同时可以把一些疑问、想法、意见、问题、规划等等和管理者做沟通,从而获得在其它渠道不易获得的信息,保证透明。

 

 

 

 

 

 

 

1on1沟通聊什么

 

 

在《创业维艰:如何完成比难更难的事》这本书中专门拿出了一节提到了一对一沟通(1on1),具体聊那些内容给了一些建议,作为TL我通常会与团队的人聊以下话题:

 

 

 

你有没有认为自己的价值和能力被低估了吗?为什么?

 

你觉得在工作中能学到东西吗?你最近学到了什么?你还希望在哪些领域进行学习?

 

近期这段时间,对自己有哪些满意、不满意的地方?

 

目前工作中,有哪些困惑?希望我如何去帮助你?

 

对团队和我的一些期待和建议。

 

在公司战略和目标方面,你最不清楚的是什么?

 

 

 

以上这些内容,除了在一对一沟通中交流之外,很难找到别的渠道来有效解决。通过这些1on1的沟通,真的可以得到很多反馈信息,甚至得到的一些信息令我感到吃惊,原来还有这些细节问题我没有做好。一对一沟通构造了一个渠道,这个渠道自下而上,使得以上这些内容都能够被倾听,从而被解决。

 

 

 

1on1沟通的一些注意点

 

 

 

★ 找个私密的环境

 

 

找个空会议室或者别人听不到谈话的角落,不要在工位或嘈杂的环境中进行,因为私密的环境才能降低沟通中某些话被他人听到的心理压力,才能更轻松和真实的表达自己。

 

 

 

★ 最好提前告知1on1的团队成员

 

 

一般需要提前1周把1on1沟通的话题、具体时间通知到团队成员,这样的好处是团队成员可以提前准备下聊的内容,因为临时性的沟通很容易出现因为人类记忆力的问题,导致一些想聊的问题在当时没想到。

 

 

 

★ 定期进行

 

 

在《创业维艰》一书中,本·霍洛维茨认为一对一沟通需要保证至少一个月一次。而格鲁夫认为,需要根据部属对工作的熟悉度,而进行不同程度的掌控。

 

 

 

另外,格鲁夫还认为,事情变化的速度也是影响一对一沟通频率的因素。作为技术研发部门,我通常会1-2月进行一次1on1沟通。

 

 

 

★ 用心倾听并行动

 

 

沟通要有效,用心倾听、保持真诚是必要的前提,否则员工不可能将心中的问题提出来。

 

 

 

保持真诚需要不敷衍任何团队同学提出的问题,不管这个问题有多尖锐。如果你也不知道如何解决这个问题,不妨和团队同学一起讨论讨论,看看大家能不能一起寻找可行的办法。切忌不要讲空话和套话,一旦团队同学发现这是一个无效的沟通渠道之后,「自下而上」的通道就被关闭了。

 

 

 

★ 适当引导

 

 

并不是每一个员工都懂得一对一沟通的重要性,也不是每一个员工都能主动倾述问题,寻求帮助。很多程序员的性格都是比较内向的,有一些甚至不善于表达自己。

 

 

 

所以,虽然员工是一对一沟通的「主角」,但是上司也是需要进行适当的引导。对于上司已经发现的员工工作中的困难,可以适当的主动提出来,以便于更好地讨论,这也会让员工感到很体贴。

 

 

 

 

 

 

 

招聘与解雇

 

 

对于一个团队来说,人才是最核心、关键的。招聘和解雇尤其对于一个新上任的技术TL,都是一个很大的挑战,接下来我们重点讨论这两个话题。

 

 

 

招聘

 

 

招聘很多时候取决于公司在什么发展时期,需要招聘什么样的人。在初创时期,本不太可能招聘到清一色的专家人才,这个时候活下来比啥都重要,态度和味道是重点看的。在高速发展之后,可能需要引进能带来新思路的一些人才,这取决于业务、技术、组织三者的对齐。那么这个时候,就是既要高技能,又要好的做事态度、习惯。

 

 

 

在搭建技术团队招聘前,要先明确所搭团队的类型,一般来说有三种不同类型的技术团队,即项目驱动型、业务驱动型和技术驱动型,不同类型的技术团队在招聘时也有很大的不同,比如技术驱动型团队你可能需要一个在中间件、语言功底非常深厚、有大局观的人,业务驱动型的团队可能需要有业务sense,并且具备良好技术和业务架构能力的人。

 

 

 

在招聘这条路上,我也走过弯路,一开始我对候选人的背景、语言功底、架构能力以及运维、数据库方面比较关注,希望能招到全栈的技术人才,后来发现我忽视了一个很重要的点沟通与协作能力、态度。后来导致新人来到团队后,虽然技术牛B,但喜欢闭门造车,不喜欢和别人沟通,团队协作能力不够好,整体产出和效率不高。所以在招聘新人的过程中,不能够只盯着候选人有什么经验,会什么框架等技术面,也需要着重考量他们的综合素质,一个领导力好的候选人,能够非常快速地融入团队,也能够非常快的学习一些知识。

 

 

 

★ 招聘步骤:

 

 

 

1.根据搭建团队的目标,做好招聘计划

 

 

 

根据团队自身的定位,招聘合适的人才。有几点需要TL特别关注的,作为TL要对候选人的成长负责,切忌因人设岗、因单独项目而招人,比如前端团队招聘一些后端开发,工程团队招聘算法,这样以来可能会导致候选人进来后很难融入到团队,没有存在感,长时间下来会导致新人离职。

 

 

 

2.确定招聘需求(定岗定责):列出每个岗位的职责、需要具备的技能及其他要求。

 

 

 

招聘需求归根结底是需要什么样的人,与据整体业务和组织发展匹配。

 

 

 

3.合理利用人才招聘渠道

 

 

 

从我自身的经历来看,人才招聘渠道多数通过互联网招聘渠道以及朋友推荐更可靠一些,对于高级别的人才可以采用猎头定向挖人。

 

 

 

 

 

 

 

★ 人才筛选:

 

 

 

作为技术面试官,对于人才的筛选也是非常重要关键的一个环节,要根据自己团队的目标来选取合适的人才,设定完成的时间期限,将面试的重点放在专业技能、管理能力、价值观(公司认同)等方面,一般要求如下:

 

 

 

和岗位需要的专业技能高度匹配:专业技术技能面试过关,定岗定责;

 

沟通力强:理解公司的业务,知晓管理层,了解公司的发展方向;

 

责任心:凡事有交代,件件有着落,事事有回音;

 

靠谱并自带正能量:不抱怨,主动解决问题,懂得纪律的重要性,一诺千金;

 

价值观认同:认同公司,有目标有理想、有激情有冲劲;

 

背景调查:非常有用的一个办法,可以大幅度降低选人风险,不用怕麻烦,这个工作的付出永远都是值得的。

 

 

 

另外,我想说的对于技术面试官需要有一定甄别人才的能力,同时有意识地提高这方面的能力,我提供以下几点建议给技术面试官:

 

 

 

如果对候选人有些犹豫和纠结,请你放弃这个候选人,你最担心的问题往往很大概率上会发生。

 

明确我们招聘的候选人标准,比如后端JAVA研发:JAVA基础和分布式领域知识技能考察是必须的,少问记忆性问题和太理论性问题,更多地从候选人的一些实践经历中,提取出对这个候选人的更有价值的判断。

 

一面非常重要,要保证客观、公平,后面的交叉和终面往往参考前面的评价反馈,我们今天不仅是为我们的团队选拔人才,更是为公司选拔人才,还是要高标准的要求。从心理学角度讲,必须要交叉面试,而且交叉面试官的给出的反馈往往是比较客观、中肯的,而且要以交叉面试官的评价为主。

 

面试官切忌拿自己擅长的东西去考察候选人,需要认真的看候选人的简历,从候选人的经历中去考察这个人的综合能力。

一个团队的健康发展,最重要的是核心技术人,所以招聘工作必须谨慎,一旦有人加入就等于在上了一艘船,其中的纠结、痛苦、欢喜都要一起面对。招募一个不合适人员的成本不仅仅是薪资那么简单。所以请一定要放过那些经验不错、资质不错但是很犹豫匹配度、落地融入堪忧的面试者,其结局大部分都是彼此痛苦。

作为技术TL最成功的是招到比你更优秀的人,你不需要担心自己会不会被取代,一是成就个人和成就团队,作为TL应该抱着如何成就团队的发展思路,不能让自己成为天花板,本身技术就不应该是你最擅长的事情!二是兼容并蓄,发展多样性。刘邦善用汉初三杰,单项能力不如韩信、张良。TL不要已自己的长短来衡量招聘的人,而是看团队技能视图的缺口和发展项。

解雇

解雇员工通常更多的是针对触犯公司文化、原则红线,或者持续无法跟上公司节奏的员工进行的处理。在阿里也有这么一句土话:“如果你没有开除或解雇过一位员工,算不上真正合格的管理者”,大多数技术管理者性格比较随和,不喜欢开除员工。

但是出现触犯红线的员工或者跟不上节奏的员工,尤其是不认可团队价值观的同学,会把一些负面情绪、行为影响到团队其他同学,因此需要杀伐决断,当机立断采用合适的方法让员工离开。当然,如果只是能力跟不上的员工,你也可以推荐给其他公司适合的岗位,让和自己一起奋战过的兄弟有一个好归宿,也会让在职的员工会感觉温暖。整体上“慈不掌兵”,在开人这件事情上,高级管理者不要过于犹豫,为了一两个人最后影响整体团队的士气反而得不偿失。

多数互联网公司对于技术人员都有相应的KPI考核,对于达不到预期的人员会进行淘汰。解雇尤其对于新上任的技术主管还是有一定挑战的,我相信人的本性还是善良的,作为技术TL不想让团队成员面对这一难题,包括我自己在内。

一家公司在成长,组织肯定要升级,人员的新老交替也是正常的。如果团队成员的表现达不到预期,不通过 KPI 考核机制告诉他,也许他不会意识到自身的一些问题,他永远不会成长起来,相对短期这些经济回报而言,个人的成长更为关键重要。在阿里有这么一句土话“不经历3.25的人生,不是完美的阿里之旅”,当你处于发展的低谷时,经历一次末位考核结果也许能够让他彻底清醒,认识到自己的不足,彻底激发自己的潜能,能够触底反弹。

心理学上有个著名的邓宁-克鲁格效应,又称达克效应。大意是,人很容易对自我产生认知偏差,最简单来说,就是会过于高估自己。达克效应的曲线图:

 

 

 

 

 

 

 

上面的图片上反映出,大部分人其实都处于愚昧之巅。人能够成长为智者和大师要先从愚昧之巅,掉到绝望之谷,然后辛苦攀爬,积累知识和经验,成为智者和大师。有担当的管理者的一个重要责任,就是把下属从愚昧之巅推向绝望之谷,至于能否爬上开悟之坡,看个人造化。

一个合格的技术TL必须要给团队成员塑造一个绝望山谷,同时还要让他看到一个开悟之坡,这样员工会不断突破自我。作为一个有担当的管理者,我们不应该是一个老好人的角色,也要有冷酷无情的一面,阿里也有一句土话“心要仁慈,刀要快”,当团队中出现一些达不到团队要求的人,管理者应该主动去拉他一把,如果多次尝试,最终达不到预期,应该请他离开。因为到了中途,再被残酷淘汰,无论对组织,还是对个人,损失都更大。

转自:阿里技术微信公众号

发表在 管理 | 在阿里做了五年技术主管,我有话想说已关闭评论

mybatis动态sql

用来循环容器的标签forEach,查看例子

foreach元素的属性主要有item,index,collection,open,separator,close。

  • item:集合中元素迭代时的别名,
  • index:集合中元素迭代时的索引
  • open:常用语where语句中,表示以什么开始,比如以'(‘开始
  • separator:表示在每次进行迭代时的分隔符,
  • close 常用语where语句中,表示以什么结束,

在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:

  • 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list .
  • 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array .
  • 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key.

针对最后一条,我们来看一下官方说法:

注意 你可以将一个 List 实例或者数组作为参数对象传给 MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以“list”作为键,而数组实例的键将是“array”。

所以,不管是多参数还是单参数的list,array类型,都可以封装为map进行传递。如果传递的是一个List,则mybatis会封装为一个list为key,list值为object的map,如果是array,则封装成一个array为key,array的值为object的map,如果自己封装呢,则colloection里放的是自己封装的map里的key值

  1. //mapper中我们要为这个方法传递的是一个容器,将容器中的元素一个一个的
  2. //拼接到xml的方法中就要使用这个forEach这个标签了
  3. public List<Entity> queryById(List<String> userids);
  4. //对应的xml中如下
  5.   <select id="queryById" resultMap="BaseReslutMap" >
  6.       select * FROM entity
  7.       where id in 
  8.       <foreach collection="userids" item="userid" index="index" open="(" separator="," close=")">
  9.               #{userid}
  10.       </foreach>
  11.   </select>

concat模糊查询

  1. //比如说我们想要进行条件查询,但是几个条件不是每次都要使用,那么我们就可以
  2. //通过判断是否拼接到sql中
  3.   <select id="queryById" resultMap="BascResultMap" parameterType="entity">
  4.     SELECT *  from entity
  5.     <where>
  6.         <if test="name!=null">
  7.             name like concat('%',concat(#{name},'%'))
  8.         </if>
  9.     </where>
  10.   </select>

choose (when, otherwise)标签

choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。

例如下面例子,同样把所有可以限制的条件都写上,方面使用。choose会从上到下选择一个when标签的test为true的sql执行。安全考虑,我们使用where将choose包起来,放置关键字多于错误。

  1. <!--  choose(判断参数) - 按顺序将实体类 User 第一个不为空的属性作为:where条件 -->  
  2. <select id="getUserList_choose" resultMap="resultMap_user" parameterType="com.yiibai.pojo.User">  
  3.     SELECT *  
  4.       FROM User u   
  5.     <where>  
  6.         <choose>  
  7.             <when test="username !=null ">  
  8.                 u.username LIKE CONCAT(CONCAT('%', #{username, jdbcType=VARCHAR}),'%')  
  9.             </when >  
  10.             <when test="sex != null and sex != '' ">  
  11.                 AND u.sex = #{sex, jdbcType=INTEGER}  
  12.             </when >  
  13.             <when test="birthday != null ">  
  14.                 AND u.birthday = #{birthday, jdbcType=DATE}  
  15.             </when >  
  16.             <otherwise>  
  17.             </otherwise>  
  18.         </choose>  
  19.     </where>    
  20. </select>

selectKey 标签

在insert语句中,在Oracle经常使用序列、在MySQL中使用函数来自动生成插入表的主键,而且需要方法能返回这个生成主键。使用myBatis的selectKey标签可以实现这个效果。   下面例子,使用mysql数据库自定义函数nextval(‘student’),用来生成一个key,并把他设置到传入的实体类中的studentId属性上。所以在执行完此方法后,边可以通过这个实体类获取生成的key。

  1. <!-- 插入学生 自动主键-->  
  2. <insert id="createStudentAutoKey" parameterType="liming.student.manager.data.model.StudentEntity" keyProperty="studentId">  
  3.     <selectKey keyProperty="studentId" resultType="String" order="BEFORE">  
  4.         select nextval('student')  
  5.     </selectKey>  
  6.     INSERT INTO STUDENT_TBL(STUDENT_ID,  
  7.                             STUDENT_NAME,  
  8.                             STUDENT_SEX,  
  9.                             STUDENT_BIRTHDAY,  
  10.                             STUDENT_PHOTO,  
  11.                             CLASS_ID,  
  12.                             PLACE_ID)  
  13.     VALUES (#{studentId},  
  14.             #{studentName},  
  15.             #{studentSex},  
  16.             #{studentBirthday},  
  17.             #{studentPhoto, javaType=byte[], jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler},  
  18.             #{classId},  
  19.             #{placeId})  
  20. </insert>

调用接口方法,和获取自动生成key

  1. StudentEntity entity = new StudentEntity();  
  2. entity.setStudentName("黎明你好");  
  3. entity.setStudentSex(1);  
  4. entity.setStudentBirthday(DateUtil.parse("1985-05-28"));  
  5. entity.setClassId("20000001");  
  6. entity.setPlaceId("70000001");  
  7. this.dynamicSqlMapper.createStudentAutoKey(entity);  
  8. System.out.println("新增学生ID: " + entity.getStudentId());

if标签

if标签可用在许多类型的sql语句中,我们以查询为例。首先看一个很普通的查询:

  1. <!-- 查询学生list,like姓名 -->  
  2. <select id="getStudentListLikeName" parameterType="StudentEntity" resultMap="studentResultMap">  
  3.     SELECT * from STUDENT_TBL ST   
  4. WHERE ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}),'%')  
  5. </select>

但是此时如果studentName为null,此语句很可能报错或查询结果为空。此时我们使用if动态sql语句先进行判断,如果值为null或等于空字符串,我们就不进行此条件的判断,增加灵活性。

参数为实体类StudentEntity。将实体类中所有的属性均进行判断,如果不为空则执行判断条件。

  1. <!-- 2 if(判断参数) - 将实体类不为空的属性作为where条件 -->  
  2. <select id="getStudentList_if" resultMap="resultMap_studentEntity" parameterType="liming.student.manager.data.model.StudentEntity">  
  3.     SELECT ST.STUDENT_ID,  
  4.            ST.STUDENT_NAME,  
  5.            ST.STUDENT_SEX,  
  6.            ST.STUDENT_BIRTHDAY,  
  7.            ST.STUDENT_PHOTO,  
  8.            ST.CLASS_ID,  
  9.            ST.PLACE_ID  
  10.       FROM STUDENT_TBL ST   
  11.      WHERE  
  12.     <if test="studentName !=null ">  
  13.         ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName, jdbcType=VARCHAR}),'%')  
  14.     </if>  
  15.     <if test="studentSex != null and studentSex != '' ">  
  16.         AND ST.STUDENT_SEX = #{studentSex, jdbcType=INTEGER}  
  17.     </if>  
  18.     <if test="studentBirthday != null ">  
  19.         AND ST.STUDENT_BIRTHDAY = #{studentBirthday, jdbcType=DATE}  
  20.     </if>  
  21.     <if test="classId != null and classId!= '' ">  
  22.         AND ST.CLASS_ID = #{classId, jdbcType=VARCHAR}  
  23.     </if>  
  24.     <if test="classEntity != null and classEntity.classId !=null and classEntity.classId !=' ' ">  
  25.         AND ST.CLASS_ID = #{classEntity.classId, jdbcType=VARCHAR}  
  26.     </if>  
  27.     <if test="placeId != null and placeId != '' ">  
  28.         AND ST.PLACE_ID = #{placeId, jdbcType=VARCHAR}  
  29.     </if>  
  30.     <if test="placeEntity != null and placeEntity.placeId != null and placeEntity.placeId != '' ">  
  31.         AND ST.PLACE_ID = #{placeEntity.placeId, jdbcType=VARCHAR}  
  32.     </if>  
  33.     <if test="studentId != null and studentId != '' ">  
  34.         AND ST.STUDENT_ID = #{studentId, jdbcType=VARCHAR}  
  35.     </if>   
  36. </select>

使用时比较灵活, new一个这样的实体类,我们需要限制那个条件,只需要附上相应的值就会where这个条件,相反不去赋值就可以不在where中判断。

  1. public void select_test_2_1() {  
  2.     StudentEntity entity = new StudentEntity();  
  3.     entity.setStudentName("");  
  4.     entity.setStudentSex(1);  
  5.     entity.setStudentBirthday(DateUtil.parse("1985-05-28"));  
  6.     entity.setClassId("20000001");  
  7.     //entity.setPlaceId("70000001");  
  8.     List<StudentEntity> list = this.dynamicSqlMapper.getStudentList_if(entity);  
  9.     for (StudentEntity e : list) {  
  10.         System.out.println(e.toString());  
  11.     }  
  12. }

if + where 的条件判断

当where中的条件使用的if标签较多时,这样的组合可能会导致错误。我们以在3.1中的查询语句为例子,当java代码按如下方法调用时:

  1. @Test  
  2. public void select_test_2_1() {  
  3.     StudentEntity entity = new StudentEntity();  
  4.     entity.setStudentName(null);  
  5.     entity.setStudentSex(1);  
  6.     List<StudentEntity> list = this.dynamicSqlMapper.getStudentList_if(entity);  
  7.     for (StudentEntity e : list) {  
  8.         System.out.println(e.toString());  
  9.     }  
  10. }

如果上面例子,参数studentName为null,将不会进行STUDENT_NAME列的判断,则会直接导“WHERE AND”关键字多余的错误SQL。  这时我们可以使用where动态语句来解决。这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。  上面例子修改为:

  1. <!-- 3 select - where/if(判断参数) - 将实体类不为空的属性作为where条件 -->  
  2. <select id="getStudentList_whereIf" resultMap="resultMap_studentEntity" parameterType="liming.student.manager.data.model.StudentEntity">  
  3.     SELECT ST.STUDENT_ID,  
  4.            ST.STUDENT_NAME,  
  5.            ST.STUDENT_SEX,  
  6.            ST.STUDENT_BIRTHDAY,  
  7.            ST.STUDENT_PHOTO,  
  8.            ST.CLASS_ID,  
  9.            ST.PLACE_ID  
  10.       FROM STUDENT_TBL ST   
  11.     <where>  
  12.         <if test="studentName !=null ">  
  13.             ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName, jdbcType=VARCHAR}),'%')  
  14.         </if>  
  15.         <if test="studentSex != null and studentSex != '' ">  
  16.             AND ST.STUDENT_SEX = #{studentSex, jdbcType=INTEGER}  
  17.         </if>  
  18.         <if test="studentBirthday != null ">  
  19.             AND ST.STUDENT_BIRTHDAY = #{studentBirthday, jdbcType=DATE}  
  20.         </if>  
  21.         <if test="classId != null and classId!= '' ">  
  22.             AND ST.CLASS_ID = #{classId, jdbcType=VARCHAR}  
  23.         </if>  
  24.         <if test="classEntity != null and classEntity.classId !=null and classEntity.classId !=' ' ">  
  25.             AND ST.CLASS_ID = #{classEntity.classId, jdbcType=VARCHAR}  
  26.         </if>  
  27.         <if test="placeId != null and placeId != '' ">  
  28.             AND ST.PLACE_ID = #{placeId, jdbcType=VARCHAR}  
  29.         </if>  
  30.         <if test="placeEntity != null and placeEntity.placeId != null and placeEntity.placeId != '' ">  
  31.             AND ST.PLACE_ID = #{placeEntity.placeId, jdbcType=VARCHAR}  
  32.         </if>  
  33.         <if test="studentId != null and studentId != '' ">  
  34.             AND ST.STUDENT_ID = #{studentId, jdbcType=VARCHAR}  
  35.         </if>  
  36.     </where>    
  37. </select>

if + set实现修改语句

当update语句中没有使用if标签时,如果有一个参数为null,都会导致错误。  当在update语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置SET 关键字,和剔除追加到条件末尾的任何不相关的逗号。使用if+set标签修改后,如果某项为null则不进行更新,而是保持数据库原值。如下示例:

  1. <!-- 4 if/set(判断参数) - 将实体类不为空的属性更新 -->  
  2. <update id="updateStudent_if_set" parameterType="liming.student.manager.data.model.StudentEntity">  
  3.     UPDATE STUDENT_TBL  
  4.     <set>  
  5.         <if test="studentName != null and studentName != '' ">  
  6.             STUDENT_TBL.STUDENT_NAME = #{studentName},  
  7.         </if>  
  8.         <if test="studentSex != null and studentSex != '' ">  
  9.             STUDENT_TBL.STUDENT_SEX = #{studentSex},  
  10.         </if>  
  11.         <if test="studentBirthday != null ">  
  12.             STUDENT_TBL.STUDENT_BIRTHDAY = #{studentBirthday},  
  13.         </if>  
  14.         <if test="studentPhoto != null ">  
  15.             STUDENT_TBL.STUDENT_PHOTO = #{studentPhoto, javaType=byte[], jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler},  
  16.         </if>  
  17.         <if test="classId != '' ">  
  18.             STUDENT_TBL.CLASS_ID = #{classId}  
  19.         </if>  
  20.         <if test="placeId != '' ">  
  21.             STUDENT_TBL.PLACE_ID = #{placeId}  
  22.         </if>  
  23.     </set>  
  24.     WHERE STUDENT_TBL.STUDENT_ID = #{studentId};      
  25. </update>

if + trim代替where/set标签

trim是更灵活的去处多余关键字的标签,他可以实践where和set的效果。

trim代替where

  1. <!-- 5.1if/trim代替where(判断参数) -将实体类不为空的属性作为where条件-->  
  2. <select id="getStudentList_if_trim" resultMap="resultMap_studentEntity">  
  3.     SELECT ST.STUDENT_ID,  
  4.            ST.STUDENT_NAME,  
  5.            ST.STUDENT_SEX,  
  6.            ST.STUDENT_BIRTHDAY,  
  7.            ST.STUDENT_PHOTO,  
  8.            ST.CLASS_ID,  
  9.            ST.PLACE_ID  
  10.       FROM STUDENT_TBL ST   
  11.     <trim prefix="WHERE" prefixOverrides="AND|OR">  
  12.         <if test="studentName !=null ">  
  13.             ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName, jdbcType=VARCHAR}),'%')  
  14.         </if>  
  15.         <if test="studentSex != null and studentSex != '' ">  
  16.             AND ST.STUDENT_SEX = #{studentSex, jdbcType=INTEGER}  
  17.         </if>  
  18.         <if test="studentBirthday != null ">  
  19.             AND ST.STUDENT_BIRTHDAY = #{studentBirthday, jdbcType=DATE}  
  20.         </if>  
  21.         <if test="classId != null and classId!= '' ">  
  22.             AND ST.CLASS_ID = #{classId, jdbcType=VARCHAR}  
  23.         </if>  
  24.         <if test="classEntity != null and classEntity.classId !=null and classEntity.classId !=' ' ">  
  25.             AND ST.CLASS_ID = #{classEntity.classId, jdbcType=VARCHAR}  
  26.         </if>  
  27.         <if test="placeId != null and placeId != '' ">  
  28.             AND ST.PLACE_ID = #{placeId, jdbcType=VARCHAR}  
  29.         </if>  
  30.         <if test="placeEntity != null and placeEntity.placeId != null and placeEntity.placeId != '' ">  
  31.             AND ST.PLACE_ID = #{placeEntity.placeId, jdbcType=VARCHAR}  
  32.         </if>  
  33.         <if test="studentId != null and studentId != '' ">  
  34.             AND ST.STUDENT_ID = #{studentId, jdbcType=VARCHAR}  
  35.         </if>  
  36.     </trim>     
  37. </select>

trim代替set

  1. <!-- 5.2 if/trim代替set(判断参数) - 将实体类不为空的属性更新 -->  
  2. <update id="updateStudent_if_trim" parameterType="liming.student.manager.data.model.StudentEntity">  
  3.     UPDATE STUDENT_TBL  
  4.     <trim prefix="SET" suffixOverrides=",">  
  5.         <if test="studentName != null and studentName != '' ">  
  6.             STUDENT_TBL.STUDENT_NAME = #{studentName},  
  7.         </if>  
  8.         <if test="studentSex != null and studentSex != '' ">  
  9.             STUDENT_TBL.STUDENT_SEX = #{studentSex},  
  10.         </if>  
  11.         <if test="studentBirthday != null ">  
  12.             STUDENT_TBL.STUDENT_BIRTHDAY = #{studentBirthday},  
  13.         </if>  
  14.         <if test="studentPhoto != null ">  
  15.             STUDENT_TBL.STUDENT_PHOTO = #{studentPhoto, javaType=byte[], jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler},  
  16.         </if>  
  17.         <if test="classId != '' ">  
  18.             STUDENT_TBL.CLASS_ID = #{classId},  
  19.         </if>  
  20.         <if test="placeId != '' ">  
  21.             STUDENT_TBL.PLACE_ID = #{placeId}  
  22.         </if>  
  23.     </trim>  
  24.     WHERE STUDENT_TBL.STUDENT_ID = #{studentId}  
  25. </update>

foreach

对于动态SQL 非常必须的,主是要迭代一个集合,通常是用于IN 条件。List 实例将使用“list”做为键,数组实例以“array” 做为键。

foreach元素是非常强大的,它允许你指定一个集合,声明集合项和索引变量,它们可以用在元素体内。它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符。这个元素是很智能的,它不会偶然地附加多余的分隔符。

注意:你可以传递一个List实例或者数组作为参数对象传给MyBatis。当你这么做的时候,MyBatis会自动将它包装在一个Map中,用名称在作为键。List实例将会以“list”作为键,而数组实例将会以“array”作为键。

这个部分是对关于XML配置文件和XML映射文件的而讨论的。下一部分将详细讨论Java API,所以你可以得到你已经创建的最有效的映射。

参数为array示例的写法

接口的方法声明:

  1. public List<StudentEntity> getStudentListByClassIds_foreach_array(String[] classIds);

动态SQL语句:

  1. <!— 7.1 foreach(循环array参数) - 作为where中in的条件 -->  
  2. <select id="getStudentListByClassIds_foreach_array" resultMap="resultMap_studentEntity">  
  3.     SELECT ST.STUDENT_ID,  
  4.            ST.STUDENT_NAME,  
  5.            ST.STUDENT_SEX,  
  6.            ST.STUDENT_BIRTHDAY,  
  7.            ST.STUDENT_PHOTO,  
  8.            ST.CLASS_ID,  
  9.            ST.PLACE_ID  
  10.       FROM STUDENT_TBL ST  
  11.       WHERE ST.CLASS_ID IN   
  12.      <foreach collection="array" item="classIds"  open="(" separator="," close=")">  
  13.         #{classIds}  
  14.      </foreach>  
  15. </select>

测试代码,查询学生中,在20000001、20000002这两个班级的学生:

  1. @Test  
  2. public void test7_foreach() {  
  3.     String[] classIds = { "20000001", "20000002" };  
  4.     List<StudentEntity> list = this.dynamicSqlMapper.getStudentListByClassIds_foreach_array(classIds);  
  5.     for (StudentEntity e : list) {  
  6.         System.out.println(e.toString());  
  7.     }  
  8. }

2参数为list示例的写法  接口的方法声明:

  1. public List<StudentEntity> getStudentListByClassIds_foreach_list(List<String> classIdList);

动态SQL语句:

  1. <!-- 7.2 foreach(循环List<String>参数) - 作为where中in的条件 -->  
  2. <select id="getStudentListByClassIds_foreach_list" resultMap="resultMap_studentEntity">  
  3.     SELECT ST.STUDENT_ID,  
  4.            ST.STUDENT_NAME,  
  5.            ST.STUDENT_SEX,  
  6.            ST.STUDENT_BIRTHDAY,  
  7.            ST.STUDENT_PHOTO,  
  8.            ST.CLASS_ID,  
  9.            ST.PLACE_ID  
  10.       FROM STUDENT_TBL ST  
  11.       WHERE ST.CLASS_ID IN   
  12.      <foreach collection="list" item="classIdList"  open="(" separator="," close=")">  
  13.         #{classIdList}  
  14.      </foreach>  
  15. </select>

测试代码,查询学生中,在20000001、20000002这两个班级的学生:

  1. @Test  
  2. public void test7_2_foreach() {  
  3.     ArrayList<String> classIdList = new ArrayList<String>();  
  4.     classIdList.add("20000001");  
  5.     classIdList.add("20000002");  
  6.     List<StudentEntity> list = this.dynamicSqlMapper.getStudentListByClassIds_foreach_list(classIdList);  
  7.     for (StudentEntity e : list) {  
  8.         System.out.println(e.toString());  
  9.          }

sql片段标签:通过该标签可定义能复用的sql语句片段,在执行sql语句标签中直接引用即可。这样既可以提高编码效率,还能有效简化代码,提高可读性

需要配置的属性:id=”” >>>表示需要改sql语句片段的唯一标识

引用:通过标签引用,refid=”” 中的值指向需要引用的中的id=“”属性

  1. <!--定义sql片段-->  
  2. <sql id="orderAndItem">  
  3.     o.order_id,o.cid,o.address,o.create_date,o.orderitem_id,i.orderitem_id,i.product_id,i.count  
  4.   </sql>  
  5.  <select id="findOrderAndItemsByOid" parameterType="java.lang.String" resultMap="BaseResultMap">  
  6.     select  
  7. <!--引用sql片段-->  
  8.     <include refid="orderAndItem" />  
  9.     from ordertable o  
  10.     join orderitem i on o.orderitem_id = i.orderitem_id  
  11.     where o.order_id = #{orderId}  
  12.   </select>
END

发表在 java, mysql | mybatis动态sql已关闭评论

程序员又背锅了——波音737 MAX坠毁并停飞事件

事件

3月10日,埃塞俄比亚航空一架波音737-8飞机发生坠机空难,这是继去年10月29日印尼狮航空难事故之后,波音737-8飞机发生的第2起空难。为确保中国民航飞行安全,中国民航局要求国内运输航空公司于2019年3月11日18时前暂停波音737-8飞机的商业运行,并表示将持续跟进波音737-8坠毁事故调查的情况。

 

起因(待验证)

波音改变了飞机的空气动力学后,737MAX飞机变得不稳定,特别是有抬头的倾向。这个问题是由波音公司修改737MAX飞机的空气动力学引起的,这种飞机在物理上无法解决。因此,波音选择修补飞行控制软件,增加一个程序来监控飞机的抬头。一旦发现飞机有抬头趋势,立即自动调节降低头。但这个“安全补丁”已成为737MAX飞机的最大缺陷。

因为设计师编写程序代码,它高于飞行员的权限,这意味着一旦系统监视飞机的头部,它将自动控制飞机的潜水,飞行员不可能进行干预。

发表在 行业新闻 | 程序员又背锅了——波音737 MAX坠毁并停飞事件已关闭评论

CentOS 复制、删除、移动、压缩、解压等常用命令

压缩、解压

tar -cvf fille.tar file(可以多个文件空格隔开)-c: 建立压缩档案;-v: 显示所有过程;-f: 使用档案名字,是必须的,是最后一个参数)
tar -xvf file.tar 解包到当前目录
tar -xvf file.tar -C dir 把文件解压到指定目录中
zip 压缩后文件名 源文件
zip -r 压缩后目录名 原目录
unzip file.zip -d dir 解压到指定目录
gunzip file1.gz 解压 file1.gz
gzip file1 压缩 file1
gzip -9 file 最大程度压缩文件

 

文件、目录

rm -f file 强制删除文件,不提示
rm -r dir 递归删除其文件和文件夹
rm -rf dir 强制删除文件夹及其内容,不提示
mv dir/file dir 将文件或者文件夹移动到指定目录
mv -t dir file 将文件或者文件夹移动到指定目录
mkdir dir dir2 创建两个文件夹
mkdir -p /tmp/dir 创建多级目录
cp file file1 将文件file复制一份file1
cp -a file/dir dir 将文件或者文件夹复制到指定目录
cd .. 返回上一级目录
cd ../.. 返回上两级目录
cd / 返回根目录
ls 列举出当前目录中所有文件
ls -a 列举出当前目录中所有文件,包括隐藏文件
ls -l 显示文件的详细信息
ls -lrt 按时时间排序显示文件
pwd 显示当前路径

 

网络相关

ip add 显示当前ip地址
ifdown eth0 禁用 ‘eth0’ 网络设备
ifup eth0 启用 ‘eth0’ 网络设备

 

系统相关

su 用户名 切换用户登录
shutdown -h now 关机
shutdown -r now 重启
reboot 重启

发表在 linux | CentOS 复制、删除、移动、压缩、解压等常用命令已关闭评论

重启虚拟机后,再次重启nginx会报错

前几天本站突然无法访问,经检查是nginx的问题:

原因:

重启虚拟机后,再次重启nginx会报错: open() “/var/run/nginx/nginx.pid” failed (2: No such file or directory)

解决方法:

(1)进入 cd /usr/local/nginx/conf/ 目录,编辑配置文件nginx.conf ;

(2)在配置文件中有个注释的地方: #pid        logs/nginx.pid;

(3)将注释放开,并修改为:pid    /usr/local/nginx/logs/nginx.pid;

(4)在 /usr/local/nginx 目录下创建 logs 目录:mkdir /usr/local/nginx/logs

(5)启动nginx服务:/usr/local/nginx/sbin/nginx

发表在 nginx | 重启虚拟机后,再次重启nginx会报错已关闭评论

信息革命的本质(原创)

软件工程师和建筑工程师的本质区别是授人与鱼和授人与鱼,即创造工具和创造建筑。

信息革命是人类工具的革命。

信息革命的核心是计算机的使用

互联网是计算机的感官和触手的延伸。

人工智能是计算机的灵魂。

发表在 理念 | 信息革命的本质(原创)已关闭评论

我的程序里 《我的歌声里》程序员版

我的程序里

 

没有一点点防备

也没有一丝顾虑

突然错误出现

在我的日志里带给我惊喜

身不自已可是你偏又这样

在我不知不觉中悄悄的 消失

从我的堆栈里没有音讯

剩下了报警短信

你 存在我某一个模块里

我的梦里,我的心里,我的程序里

你 存在我深哪一个模块里

我的梦里 我的心里 我的程序里

还记得我们曾经肩并肩一起排查

某个线上问题尽管一开始下起来没法下手

但我们没有放弃自己的借口

一个断点

一个结构

一行一行注释的背后

好像是一场战斗 不能回头

你存在我某一行代码里

我的梦里 我的心里 我的程序里

你存在我哪一行代码里?

我的梦里 我的心里 我的程序里

世界之大

为何要当码农

是年少抽风?

是为了不同你

存在程序员脑海里

我的梦里 我的心里 我的程序里

你存在我每天的生活里

我的梦里我的心里我的程序里

发表在 未分类 | 我的程序里 《我的歌声里》程序员版已关闭评论

elasticsearch 6.5.4集群 安装在centos 6.5 一主两从

系统环境

3台阿里云centos 6.5 8G内存

elasticsearch部署一主两从

 

查看centos版本

lsb_release -a

CentOS release 6.5 (Final)

 

java安装(3台分别安装)

yum list installed | grep java

yum -y list java*

yum install java-1.8.0-openjdk.x86_64

 

elasticsearch安装(3台分别安装)

cd /data/eshome

mkdir es

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.4.zip

unzip elasticsearch-6.5.4.zip

mv elasticsearch-6.5.4 es

cd es

 

关闭防火墙(centos6.5)(3台分别执行)

service iptables stop

chkconfig iptables off

service iptables status

#因阿里云服务器有防火墙设置,这里关闭系统自带防火墙

 

创建elasticsearch用户(3台分别执行)

es 规定 root 用户不能启动 es,所以需要创建一个用户来启动 es

# 创建用户名为 es 的用户

useradd es

# 设置 es 用户的密码

passwd es

#pass:123456

# 将 /data/eshome/es 的拥有者设置为 es

chown -R es:es /data/eshome/es

 

配置阿里云服务器(ECS)内网hosts(3台分别配置)

vim /etc/hosts:

#elasticsearch

111.111.111.111 es-node-1

222.222.222.222 es-node-2

333.333.333.333 es-node-3

#以上ip为虚拟ip,实际是阿里内网IP,内网互通速度更快流量免费,不作为外网访问地址,需要这3台主机hosts文件设置,

 

编辑elasticsearch配置文件(3台分别配置)

node-1服务器 vim config/elasticsearch.yml

cluster.name: searchname

node.name: node-1

network.host: 0.0.0.0

network.publish_host: es-node-1

#默认访问端口

#http.port: 9200

#集群主机互相访问地址

discovery.zen.ping.unicast.hosts: [“node-2”, “node-3”]

#防止脑裂

discovery.zen.minimum_master_nodes: 2

#锁定物理内存

bootstrap.memory_lock: true

#阿里云服务器报错解决

bootstrap.system_call_filter: false

#以下head插件用(可选)

http.cors.enabled: true

http.cors.allow-origin: “*”

node-2服务器 vim config/elasticsearch.yml

cluster.name: searchname

node.name: node-2

network.host: 0.0.0.0

network.publish_host: es-node-2

#默认访问端口

#http.port: 9200

#集群主机互相访问地址

discovery.zen.ping.unicast.hosts: [“node-1”, “node-3”]

#防止脑裂

discovery.zen.minimum_master_nodes: 2

#锁定物理内存

bootstrap.memory_lock: true

#阿里云服务器报错解决

bootstrap.system_call_filter: false

#以下head插件用(可选)

http.cors.enabled: true

http.cors.allow-origin: “*”

node-3服务器 vim config/elasticsearch.yml

cluster.name: searchname

node.name: node-3

network.host: 0.0.0.0

network.publish_host: es-node-3

#默认访问端口

#http.port: 9200

#集群主机互相访问地址

discovery.zen.ping.unicast.hosts: [“node-1”, “node-2”]

#防止脑裂

discovery.zen.minimum_master_nodes: 2

#锁定物理内存

bootstrap.memory_lock: true

#阿里云服务器报错解决

bootstrap.system_call_filter: false

#以下head插件用(可选)

http.cors.enabled: true

http.cors.allow-origin: “*”

 

安装elasticsearch-head插件(可以安装在其中一台、也可以安装在其他服务器)

git clone git://github.com/mobz/elasticsearch-head.git

cd elasticsearch-head

npm install

npm run start

open http://localhost:9100/

 

启动elasticsearch(3台分别启动)

切换到 es 用户,启动 es

su es

前台启动:./bin/elasticsearch

后台启动:./bin/elasticsearch -d

关闭后台启动:

ps -ef | grep elasticsearch

kill -9  [进程号]

 

Elasticsearch启动常见问题(可以启动之前先配置好)

问题1:

max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

解决方法:

#切换到root用户修改 vim /etc/security/limits.conf # 在最后面追加下面内容 es hard nofile 65536 es soft nofile 65536

修改后重新登录 es 用户,使用如下命令查看是否修改成功

ulimit -Hn 65536

 

问题2:

max number of threads [1024] for user [es] is too low, increase to at least [4096]

解决方法:vi /etc/security/limits.d/90-nproc.conf ,修改配置如下:

*          soft    nproc     1024

增加:

#es用户名

es         soft    nproc     4096

 

问题3:

ERROR: [3] bootstrap checks failed

max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536]

修改:vim /etc/security/limits.conf

soft nofile 65536

 

问题4:

max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

解决方法: 提高vm.max_map_count 的大小

# 切换到root用户 vim /etc/sysctl.conf # 在最后面追加下面内容 vm.max_map_count=262144 # 使用 sysctl -p 查看修改后的结果 sysctl -p

 

问题5:

memory locking requested for elasticsearch process but memory is not locked

原因:锁定内存失败

解决方法:

需要修改

vim /etc/security/limits.conf

es soft memlock unlimited

es hard memlock unlimited

修改:vim /etc/sysctl.conf

vm.swappiness=0

 

以上修改之后重启机器

 

问题6:

Likely root cause: java.nio.file.AccessDeniedException: /data/eshome/es/config/elasticsearch.keystore

设置用户权限

chown -R es:es /data/eshome/es

参考:ElasticSearch官网

发表在 linux, 大数据 | elasticsearch 6.5.4集群 安装在centos 6.5 一主两从已关闭评论

转《精益创业》——读书笔记

作者:埃里克.莱斯

创业像是开车,而不是发射火箭。

发射火箭是什么概念?就是大家要有足够的钱,要有足够的空间,足够的场地,足够的人,选一个好日子,然后准备发射火箭了,升到半空中,嘭的一声爆掉了,好多创业都是这样失败的。就是做好了充分的准备,一切东西万事俱备,推出的那一刻发现不管用,然后完蛋。

创业像开车,开车是什么意思呢?就是一路你从北京开到上海,路过了多少个红绿灯,拐了多少次弯,踩了多少次刹车,简直是难以计数,你是在不断地调整过程当中,才最终到达了你的目的地。

所以,如果我们很多人如果把创业视作是靠事先谋划,拿到足够的资金就一定能够一次做起来的一件事的话,这只是发射火箭的过程。

精益创业的核心是什么?

任何一个企业在创办之初,你的业务能否成功,一定是取决于你的两个假设,要把它验证了。第一个叫做价值假设,就是你所提供的东西对于消费者到底有没有价值,大家会不会为此而买单。第二个假设叫做增长假设,就是说你这个业务有价值大家会用,但是能不能传播得开,能不能有很多人用。所以价值假设和增长假设将是我们在这本书里面经常提到的两个概念。如果你对你的项目在这两点上还不确定的话,请一定要好好听书里的方法。

一:愿景—就是你到底要做什么。

精益创业这个词本身来自于日本丰田的精益生产,精益生产的特点就是小批量,然后经常性的停车,如果发现生产过程中有任何问题全部都停车,然后来检查到底是怎么回事。很多人觉得这样会造成浪费,但是事实证明,这样做造成的浪费要比永远不停然后边干变修改造成的损失要小得多。

哪些企业需要用到精益创业?

首先,如果你不让你的员工意识到他所做的每一份不确定性的工作都是创业的话,你的企业就没有创业精神。硅谷的秘诀是什么?那就是所有人都有着创始人心态。

何为新创企业?就是指在极端不确定的情况下开发新产品,或者新服务的都叫做创业行为,包括小 中 大型的企业。

把用户找来面谈,为什么不用你的产品,是一个非常快捷走出困境的方法。

用户的需求和他们原本设计的初衷是完全不同的,这是产品成败致命的一关。

很多创始人喜欢推迟统计数据,就是过早地拿出统计数据会太丢脸,所以很多人都喜欢说等过一段,等做出好看一点的成绩,再来披露这个数据,但是这样做欺骗的却是创始人团队自己。

在做任何一个实验的时候,最重要的事要解决这么几个问题。

1 顾客认同你正在解决的问题就是他们所面临的问题吗?很多的创业项目都是建立在创始人一厢情愿,单相思的基础之上,就是他觉得顾客应该都有这个需求,然后就去玩命的作,结果最后发现根本没有人在乎这个事情。

技术有时也会是一个陷阱,当我们有着特别独特的技术的时候,容易陷进去走不出来。

2 如果有解决问题的方法,顾客会不会为之买单?这就需要创业者去不断地验证。

3 他们会向我们购买吗?

4 我们能够开发出解决问题的方法,你自己能不能做到到?

这四个问题是我们在进行精益创业之前一定要解决的问题。

二 核心-开发 测量 认知

开发产品,测量数据,认知概念,完成信念飞跃。

要完成信念飞跃,除了类比和反证之外,还有一招就是现地现物,就是有任何问题解决不了,一定到现场去看。在现场去看,跟客户聊天,甚至趴到车底下去找一找到底问题出现在哪儿,这时候才能真正解决认知飞跃的问题。

还有就是要与客户交谈。

要小心分析瘫痪症,这个症状的产生就是没有精益创业的思想,无论怎么样先拿一个东西去试一下。MVP 是最小化可行产品的缩写。

GROUPON的创始人一开始非常的窘,他们的起步是从发帖开始的,然后用PDF去展现产品,而这些需要的投资几乎是0,所以精益创业是真正能够帮你从零起步的一个东西。

所以大家要学会做MVP:

第一种做最简单的MVP,像网站跟帖,PDF 宣传等。

第二种叫做视频化的MVP,一边讲解,一边演示,一边假装,然后是这么回事,想要的话先上GROUPON上团购去。

第三种叫贵宾式MVP

第四种叫人工替代系统,在你没有能力去编程的时候,最好的方法是让人来做。

当我们不知道顾客是谁的时候,我们就不知道什么叫做质量。

放心大胆的去尝试,就是放弃与认知无关的一切功能和努力。

即便是想的再明白也要去验证,因为消费者永远会给你意外的惊喜。

衡量三部曲:

1 确认基准线,包括转化率,注册率,适用率,顾客生命周期,顾客价值等等。

这些数据一开始在推出来的时候要把它记录下来,要有一个用数据说话的好习惯。可以做单一MVP,也可以做多个MVP,有一个原则就是一定要从你最冒险的那个假设开始试起。一定从最难得那个假设开始,把你最认为自己可能做不到的东西先测试。

2 调整自己的引擎

每天五美元提升产品质量,每天五美元可以从谷歌上买来100个点击,再小的企业都可以做广告,把人拉过来,这就是调整引擎的过程。然后进行同期群的分析,把受众直接分成两堆,一堆有功能,一堆没有功能,然后不断地监测他们的行为,进而不断的优化认知。

优化过程中要注意虚荣性指标,比如说下载量排名,口碑排名等等这些不挣钱的东西,不是生死攸关的一些数据。所以大家一定要小心虚荣性指标,多次的进行对比测试。

看板,是一个非常好的工具,大家有兴趣的话可以看下书。

丰田拉绳:任何一个工人发现生产上有纰漏,就会拉一个绳,然后整个生产线停工,好多人讲这太浪费了,事实上这样做减少了特别多的浪费,因为如果没有人拉绳,错误会传到下一个环节,再传下去,然后整个一批的货物全完蛋,所以宁肯停下来检查修正,再走,都好过不断地让它去运转。

一个项目整个放在一起做要比分散开做要快的多。所以要让程序员,我们的产品经理,甚至我们的市场人员组成一个TEAM.这些人要共同为这个事情负责,而不是流水线,做完了给你,你做完给他。因为大批量生产会产生一个大批量死亡螺旋,就是说整个事情效果不好,怪谁?所以这就是大批量和小批量的区别。

转型意味着失败,但实际上每一个公司都要定期开会,叫转型还是坚持会。转型一点不丢脸。季琦做了如家,做了汉庭,做了好多这样的公司都很成功,但是他说没有一个公司是按照他们的计划书做的,所有的公司都变来变去,最后变成现在这个样子,所以转型一点不丢人。

只要你想生存,就能够找到转型的方法。

三:加速

企业的本质是什么?——创造客户,然后留住他们。

增长的四个方向:

1 口碑相传

2 衍生效应-病毒式的传播

3 资金与广告

4 客户的重复购买

加速的三个工具:

1 粘着式增长—统计流失率和新增的数量,如果是正数,增长速度可以保证。

2 付费式增长—例如打广告

3 病毒式增长—依靠人与人之间的传递,是正常使用产品的必然结果。顾客并非有意充当布道者,不需要到处为产品说好话,只要顾客使用产品就自然带动了增长,病毒式传播就无刻不在。

事实上大量的企业只是用以上其中一种。

五个为什么工具。 用五个为什么的方法能够帮我们找到很多症结,就是问题出现在哪里了。但是不要把它衍变成五大罪状。

精益创业核心的目的是避免浪费,因为地球的资源是有限的。

未来不是靠人的胜利,而是靠体系的胜利。

创业能够成功的人,不是有三头六臂,也不是智商比别人高很多,而核心是他们摸索出了或者恰好碰上了一些正确的方法和思路。

这本书对我也是启发很大,如果有一天自己也加入创业的队伍,我知道我应该提前准备些什么,而不是像以往想的那样要先有钱,而其实没钱也可以创业。希望想要创业的朋友可以提前读这本书,少走些弯路。

发表在 产品 | 转《精益创业》——读书笔记已关闭评论