精华内容
下载资源
问答
  • 因此,函数保极限值,两者符号一致,极限值保函数值时,符号上面要多加一等号,大家可以通过指数函数这个例子,方便记住这结论。 还有保号性的证明问题: 意思是在x0的某个去心邻域内有保号性,只需要证明有...

    在这里插入图片描述在这里插入图片描述

    说起来,等号加进去是有道理的,比如说常见的指数函数y=e^x,函数值是大于0的,但是当取极限时,比如x趋于负无穷时,该函数的极限值等于0。因此,函数保极限值,两者符号一致,极限值保函数值时,符号上面要多加一个等号,大家可以通过指数函数这个例子,方便记住这个结论。

    还有保号性的证明问题
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    意思是在x0的某个去心邻域内有保号性,只需要证明有这么一个邻域存在就行,

    ε越大,这个邻域越大;ε越小,这个邻域越小,仅此而已.

    展开全文
  • 项目管理的几个过程

    千次阅读 2017-02-05 21:46:05
    项目管理的几个过程 http://www.unjs.com/ziliaoku/gl/97773.html http://www.unjs.com/ziliaoku/gl/97773.html [项目管理的几个过程]  一.商务谈判  1.作人的姿态  作人似乎跟商务谈判...

    项目管理的几个过程

    http://www.unjs.com/ziliaoku/gl/97773.html

    http://www.unjs.com/ziliaoku/gl/97773.html


    [项目管理的几个过程]

        一.商务谈判

        1.作人的姿态

        作人似乎跟商务谈判不太有关系,很多技术人员相信PM需要的是本事,是如何做好一个项目,而不是会搞好关系弄的四平八稳的人,

    项目管理的几个过程

    。随着PM在 中国 的悄悄兴起,越来越多的PM开始在老总的授意下参与商务谈判,和 销售 们一起打单子,这就比较实在的需要PM们去揣摩客户的心理。揣摩客户心理需要有多方面的知识,需要深度和广度,然而,最重要的仍然是作人。如何放下架子,降低作人的姿态,对从技术人员转型的PM们来说,是至关重要的。 降低作人的姿态需要从多个方面去 实施 ,最主要应该记住:人不可貌相,更不可以地位衡量。很多 公司 为了保持公司形象,会统一叫员工打扮的好看一点,看起来象个白领的样子。然而,老板多半是没有约束的。中国改革开放才 二十年 ,很多有钱的老板实业家文化层次都不高,往往是当 大学 生们只会把 屁股 坐在板凳上肆意挥霍 父母 辛苦积攒的财富时,他们已经在各地奔波,积累丰富的 商业 经验 并对金钱, 人生 社会 的本质有了充分的 认识 ,形成了自己稳定的思维框架。这些人,很多都是穿着旧旧的衣服,戴着破破的手表, 说话 的时候经常会带上三字经,钻进 上海 的人堆里,搞不好你会把他当成民工。因为到他们所处的社会地位,已经不需要任何华丽的外表来衬托自己的身份,他们有的是底气。对PM来说,这是个非常危险的挑战。虽然说项目在初期有意向时会对对方的人事和关键 人物 有一定的了解,然而大项目里能说的上话的人太多了。上海人最瞧不起的就是土气,很多人谈项目的时候看到民工或很俗气的 表现 不免会皱皱眉头,往往在皱眉头的时候就失去了项目,也就是失去了 市场 和金钱。PM必须作到能与每一个层次的人交谈,尤其是看起来比自己层次要低的群体,哪怕是公司里扫地的阿姨。只有作到谦虚谨慎,不摆架子,尊重别人,才会得到别人的尊重,才有机会赢得项目。鼻子比眼睛高的人只会把自己的鼻子撞扁。

        2.丰富的知识面

        光尊重别人还不足以赢得项目,准确的说是赢得对方关键人物的信赖。PM一般用不着陪客户喝酒吃饭,那是销售们的事情,但是PM和客户讨论问题可能是最多的。讨论问题的时候就是机会,如何投其所好,是一大关键。金钱与美女依然是常规的敲门砖,然而这种傻瓜也知道的办法人人都会去做。老板的关系也只是一个方面,如今的大老板,哪个没有关系?同等条件下PM凭什么去胜过别人一筹? 我一个朋友(PM)打一个单子时,发现对方对什么都不太感兴趣,费了很大力气也找不到突破口。对方这个人非常顺利,金钱地位美女样样不缺。他了好多天和对方交谈,以自己的博学逐渐取得了对方的信任。后来他隐约发现对方对数学和天文学的发展史有所涉猎,如获至宝,回家花一个通宵的时间在网络上搜索相关资料。第二天他根本不谈项目的事情,只跟对方大谈特谈哥白尼,布鲁诺,伽利略这些人的生平,整整吹了一天。对方点头如捣蒜泥,态度和热情都来个一百八十度转弯,隔天他就拿到了单子。这是个经典的战例,谁能事先想到哥白尼会来帮助IT的人赚钱?这个PM靠的就是博学和由博学引申出的敏锐的感觉抓住了机会,让客户产生共鸣。客户感觉他层次也很高,而且和自己有共通之处,信任度大大增强,把项目交给他放心。如今这种例子在商务谈判中已经屡见不鲜了。对PM来说,并不要求在各个方面都很精通,那是不可能的事情,只要PM对一些流行的话题和天文地理历史各方面的知识有个大概的了解,在需要的时候能尽快的掌握,才有机会创造机遇和把握机遇。

        3.强大的沟通能力

        胸中有万千墨却不知如何表达其实是比较少见的,但并非绝对没有。每个人的人生轨迹都有所不同,思维受环境的影响也各有差异。包括象我们目前这个班级里的一些未来的MSE们,一定有比较内向或者不太表达自己观点的人,这些人比较被动,往往很难承担起谈判的重任。从今天开始,这类人就必须重新学习如何说话,如何大声的争论。沟通,并不仅仅是大声说话,而是在表达自己观点的同时发现问题并综合整理加以解决。除此之外,沟通的能力与社会经验息息相关,与PM的见识联系紧密。在日常生活中,PM就要多留心,多思考,当别人想到某个层次的时候要争取比别人考虑的更深。当然,也有一些不够踏实的朋友把沟通和吹牛当成了完全的一回事情,在和客户交流的时候口若悬河的说一些不着边际的话。这种人,碰到不懂,不太认真或者好奇心强的客户是有一定市场的;而有水平,负责任的客户往往会觉得这种人不可靠,一般不会把单子交给他。PM需要把握好这个度,吹是肯定要吹的,只是吹牛的时候一定要有基础的去吹,对从来没涉及过的领域或者根本不懂的东西轻易不要发表意见,挑选自己熟悉的方向合理的进行发挥,适当的留上一两手,给对方高深莫测的感觉,效果最好。

        4.优秀的售前团队

        这个团队一般是由总经理发起并组建的,通常不指定PMP,对团队的成员如SALES,PM,SA,ENGINEER们的团队合作提出了比较高的要求。一般公司在接下一个单子进行到一定程度的时候,PM往往会尴尬的发现协议上销售代表们对客户的一些承诺是几乎做不到或者根本做不到的事情。这种情况非常多,销售的任务是拿下单子,我听到的销售们说的最多的就是"没问题"或者"NO PROBLEM",但是当我听到客户的要求和销售的回答时我总是心惊肉跳,很不自然。销售是非常辛苦的,为了建立客户关系,尤其是空白的市场是很不容易的,往往为了一个单子会牺牲非常多。在这种情况下,和销售进行协调自然而然的又落到了PM的头上。在销售和客户做承诺之前,PM要主动的跟销售交流,提供粗略的总体设计框架和技术难关以及能考虑出的工作量,而不是等出了问题再被动和销售在老板面前互相推委责任。在组建团队的时候,PM要根据团队里每个人的素质和任务进行因人置宜的信息传递。优秀的售前团队合作是接单的重要保障。 在商务谈判的实际操作中,存在着各式各样的问题,PM的职责和要求绝非以上几点所能描述详尽。根据环境,政策,人文,关系等各方面的不同情况,PM的不同成长经历,每个PM最终都会建立自己对商务谈判的看法和经验。但是有一点可以肯定,这是PM成为PM的第一道关,也是最重要的一关。接不到单子,PM将失去存在的意义。与销售有所不同,PM在该阶段的任务除了接单,还要尽可能的搜集客户关键人物的资料并与对方各个阶层的负责人建立良好的客户关系,以便在项目实施时充分调动资源。

        二.启动阶段

        1.项目的一些基本概念

        项目三要素有多种版本,各不相同。实际操作中多分为范围,成本与进度,其中最重要的莫过于范围。我们把项目最终生成并提交给用户的产品和文档统称为递交件。谈判的时候一定要确立递交件的标准和要求,也就是范围。尽管商战的时候不可避免的客户会不断提高标准和要求,而承诺的款项却不会有一分钱的增加。但是这个标准对每个公司来说都有一个底线,一旦超过了这个底线,那项目就肯定是亏的。除非是为了二期有利可图或者是为了搞好关系,否则范围超过底线的时候情愿不做,再厉害的PM在这种情况下也是无能为力。建立范围需要的就是PM的多年的实战经验,在大大小小的项目中用血泪换来的一些体会。在这个时候,很能体现PM与技术人员的区别。成本就是客户答应付的款项,与我们的投入成本并不是一回事情。进度就不用多描述了。

        项目如何成功?也有一些关键的因素。个人的理解也不尽相同,通常包括以下几个方面:界定工作目标及工作任务;老板或高层的支持;优秀的PM和开发团队;充足的资源;良好的沟通;对客户的积极反应以及适当的监控和反馈。这里要注意的就是资源和高层的支持。一个上规模的公司总是同时会有很多项目,可是再大规模的公司资源也不足以保证每个项目都能组建最合适的开发队伍或拥有最好的环境。这时候各个团队或者部门之间不可避免的会发生资源争夺战,摩擦再所难免。这时候对PM的作人再次提出挑战。除了高层对PM项目的重视程度,如果PM平时在公司与同事相处的好往往能使很多别人看起来很棘手的问题迎刃而解。相反,一个不会作人的PM由于人缘差,即使高层强压别的部门或团队配合,别人也会能拖就拖,延缓项目的进度和质量。有时候,这种内耗对项目和PM来说是毁灭性的。对客户的积极反应也比较关键。一般来说PM已经被项目里大大小小的事情搞的筋疲力尽,要PM去主动要求客户配合是很吃力的事情。然而,这个时候,越是困难,越是觉得累,越是要去主动。客户往往也不是特别的积极,主动与客户联系沟通和测试能及早发现问题。从风险控制的角度来说,问题发现的越早,风险越小,损失也就越小。积极的态度可以带动客户的积极性,在项目完工的时候,客户对你的感激往往是难以用语言描述的,这对以后接单或者做二期三期会打下良好的基础。因为在和别的新客户谈判的时候,新客户自然会找你的老客户了解情况,这时老客户随意的一句话顶的上你很费心的十句。

        项目具有商业行为的几个重要特征,有消费源,有参与者,有成功关键因素,有财务目标,有风险。

        2.启动阶段的主要任务

        根据PMI的解释,接单之后项目自然转入启动阶段。启动阶段PM的主要任务是率领总体架构设计师和系统分析员收集尽可能详细的数据,确立尽可能详细的需求,进一步确立详细的项目范围,预估资源,确立其他方案并获得进入下一阶段的批准。在这个阶段,随着需求分析的深入,PM也开始在公司内部进行人员挑选和资源争夺,着手组建自己的项目团队。项目即将进入计划阶段。

        在收集完数据之后,PM要和客户开始明确项目的大小,成本,规格,期限等重要特征并将其写入合同文本,同时准备内部的包括预算,衡量标准等文档,建立项目的评估标准。接下来就是需求分析。由于专业的原因,我们这里仅讨论软件工程项目的需求分析(以下简称需求分析)。 需求分析的主要参与人员有PM,总体架构设计师,系统分析员,熟悉业务流程的客户。PM统领的团队这时候还不是真正的开发团队,我们叫做前期团队。随着需求分析的逐步深入,新的团队成员不断加入,启动阶段结束的时候正式的团队将建立。对一个已经启动的项目来说,需求分析直接决定了项目的成功与失败。最初的需求体现在客户的工作说明书或招标文件及附件上。这种需求一般比较含糊,无法体现客户真正的需求。前期团队要根据自己的经验和客户沟通并引导客户进入正轨。有时候客户会很不讲道理或者思僵化,就要求按照他的思维去定一些明显错误的需求。这个时候团队成员要耐心和客户举事实,谈经验,讲道理,用图形或模型等直观的方式将需求描述出来,比如常见的数据流图等。所以说,争论再所难免,客户有时候会吹胡子瞪眼睛拍桌子甚至会说"这个东西不要你们做了"之类的话。PM此时除了要亲身参与需求分析综合整理文档之外,还要处理好团队成员与客户的关系,确保关系不会恶化到无法收拾的地步。只要PM尽力约束团队中的成员,这个度还是很容易控制的。

        对快速开发和叠代开发来说,需求和实现往往是同步进行,开发速度快是一大优势。对有相同或类似模式的小项目来说采用快速开发或叠代开发是很合算的做法,时下流行的极限编程就是针对这方面建立的思维模式。然而,大中型项目中有太多不一样的需求和模块。如果不是因为项目有差异,那么市场上就只有产品而没有项目了。所以,大中型项目的需求要认真仔细的去做。我们要讨论一个问题,究竟应该在需求分析和总体设计上花费多少时间?我们熟悉的瀑布开发模式基本上分需求分析,总体设计,软件开发,测试等几个阶段,然而究竟应该在前两个阶段上花多少时间却没有定论。实际项目操作的例子表明,分析设计的时间越长,需求设计做的越详细,测试的时间就越短,返工率越低,风险也越小,成本越容易得到控制。

        而需求分析和总体设计没有做好就急忙上马进行开发的项目在项目初期进展顺利的时候问题不大,到了项目后期和测试阶段一些潜伏期比较长但是破坏作用比较大的问题就会凸显出来,造成返工,延长测试时间。所以与其把问题堆积到紧张的项目后期,不如把时间多花点到需求分析和总体设计上。基础夯实了,金字塔就容易造了。 在日本公司打工的程序员们可能都知道,小日本的软件规范非常厉害,他们花在需求分析和总体设计上的时间通常在40%到50%左右,远远超过国内软件项目的实施,效果也要强的多。他们总体设计的规范甚至详尽到某个过程该如何判断,确立什么样的条件,换言之就是把什么时候该如何写(if...else)语句都帮程序员定好了。在这样的软件规范下,程序员更象是装配流水线上的工人,对一个模块或技术熟悉到一定程序就变成了完全的重复性劳动。所以在日本和欧美经常会有程序员是低级工作一说,很多人不明就里,对国内程序员也照搬,对国内的程序员来说是很不公平的。在国内,只会照抄别人代码,一点都不懂创新,凡事依靠别人,快下班就盯着表看的程序员是不少,这种人一般很难有什么前途。但是,优秀的不断进取的程序员也很多。由于国内没有象CMM这样的软件规范或者很少,所以这类优秀的程序员不少都是干着系统分析员甚至PM的活,拿着程序员的工资。这类程序员虽然在起步时会吃很多亏,而且是主动找亏吃,然而几年之后与前一种程序员的社会地位会出现明显的分化。当上进的程序员们作为PM进行商务谈判的时候,前者还在各个公司里频繁跳槽,跳来跳去都不满意。有些扯开了,回到我们的话题。

        日本的软件规范与CMM有惊人的相似,其中至少有35%以上都是几乎一模一样的。最近经济不景气,东京倒闭了160家软件公司,这个数字是今年6月份的,还在不断增加。这些公司纷纷抢滩上海,招收技术人员。如果去这样的公司应聘就要考虑清楚了,进去可以学到他们的规范和质量控制,可是要想从程序员成为系统分析员或PM,比登天还难,

    管理资料

    项目管理的几个过程 》( http://www.unjs.com )。往往一个程序员进去干了好几年,对自己的那一块熟的不得了,而对隔壁同事所做的东西一窍不通。拒传华为正在尝试CMM4(华为印度研究所已经通过CMM4),对在华为工作的程序员们来说可谓福祸难料。当然,已经作到PM或QA或者 热爱 CODING的朋友例外。 需求分析本身也存在着时间分配的问题。第一遍需求分析花的时间会最长,分析员们在客户的各个部门之间几乎把腿都跑断,把口水说干,就是为了确立一个初期的需求模型。所有的文档将会提交给PM进行复审并签字,不合格的打回重做。反馈表随之将提交给客户,第二遍第三遍等等等等接踵而来,与客户反复讨论和磋商,反复提交文档和表格,目的只有一个,明确需求。当PM最终合并了所有文档并确立需求之后,最终生成的需求文档将提交给客户的各部门负责人签字。这些文档将作为合同的附件添加,以便在将来项目变更或者碰到重大问题时和客户扯皮的重要依据。

        需要说明的是,客户并非都是蛮不讲理,但是说实话,颇有无奈,国内目前的项目大多数客户为了不让自己的钱白花,经常变着法子提需求。在启动阶段明确需求并签字,无论最终情况如何,一份详尽的书面文档可以解决很多口头承诺或概念模糊的文档带来的许多问题。 详尽的需求分析有一个额外的好处就是对一些双方都很陌生或者从来无人尝试的领域将是一个决定是否进行项目的判断标准。有时候,这种大项目在签单时双方都没有绝对把握保证可以出成果,一旦在需求分析阶段发现难以逾越的技术难关,就会放弃项目。典型的例子就是NMD洲际导弹防御系统。上世纪八十年代初美国搞星球大战计划,拖跨了苏联。大家对那段历史有些含糊,很多人认为苏联人上了美国的当。其实并不完全如此,苏联人的情报机构无孔不入,并非那么容易上当受骗。实际上当时美国国防部已经开始着手NMD系统软件的需求分析,前后耗资数亿美圆,耗时两年,仅仅是做需求分析,终于发现存在着在当时技术上无法达到的高度,随后项目被放弃。

        3.项目启动

        项目启动要确定项目计划,与客户一起实施第一次项目审核,确认并对一些产品和服务向下包厂商下订单。这个时候的PM会忽然发现有开不完的会,一天开三到四个会议是很正常的事情。这些会议有与客户的会议,与下包厂商的,有团队的,有公司高层的。团队的会议主要是建立正式的团队,提供团队成员的角色和职责,提供绩效管理方法,向成员提供项目范围和目标。与客户的一个主要会议将是项目启动会议。在这个会议上PM会与客户确立正式的交流渠道,项目综合描述,让项目参与人员相互了解,建立以PM为核心的管理制度。还有一些零零碎碎的东西甚至包括办公场地的大小,电话多少部,所有人的联系方式等等都要在会议上确立,并做会议记录。这都是些非常琐碎的事情,听起来婆婆***,却是非常必要,缺一不可。大概就是所谓三军未动,粮草先行吧。

        这时候,作为公司高层,应该向全公司发表申明,正式给PM发布项目经理任命书和项目授权书。这个动作虽然在别人看来有些形式主义,但是对提高PM本人的士气和责任感是有很大助力的。

        三.计划阶段

        1.定义结构分工结构图(WBS)

        启动阶段结束后,项目进入计划阶段,也就正式进入实施。这里概念可能有些不太对头,其实是翻译的缘故,反正大家明白意思就行,不用拘泥于字面。WBS是一组要提交的项目元素,用来组织定义项目的总体范围,具体包括从工作内容,资源,成本角度考虑项目范围;建立一套系统所需要的分层工作结构;把项目分解成易于管理的几个细目,这概念有些模糊,其实跟资源管理器里分目录是一回事情。可以说,WBS是计划阶段的核心。WBS会详细的分到递交件,包括给自己人用的项目使用的过程文件,给客户用的模块和说明文档,完成每个细目的标准以及如何把这些细目的责任分配到具体的个人。WBS有缩进式和树状式,我这里也没办法画图,大家参考一些项目管理的书籍,里面有详细介绍。我整个文章只挑我觉得需要注意的地方,如非必要,对技术细节或者工具使用不做详细介绍。WBS的细目并不需要分解到同一水平,最下面的细目叫做工作包,分包的依据是个人的责任和可信度,也就是说到每个人头上的任务是否能落实,是否有把握完成;还有就是准备对项目进行控制的程度,程度越深,WBS树也就越深。由于WBS是实用性的东西,根据个人理解也不一样,所以一个项目可能会有几个正确的WBS,看PM的需要和最适合当前团队状态的进行选择

        WBS的定义还是很麻烦的。

        PM要召开团队进行讨论,向成员提供与项目相关的所有详细资料,并把WBS树分解到二层三层。然后要花上一段时间让成员进行头脑风暴式(BRAINING STORM)思考,制订工作产出和相应人员的职责,记录每一个工作包的完成标准。在头脑风暴式思考时,会有很激烈的争论,PM要协调关系,调节气氛,从自己能考虑到的各个角度旁推侧敲,提示成员的思维角度和方向并加以总结。尽管很麻烦,制订WBS仍然是非常值得的。如同需求分析一样,WBS准备的越充分,编码的进度越快。

        2.风险管理

        既然是商业行为,那么项目的风险必然存在,相信阅读这个帖子的朋友不少人都经历过或大或小的风险。有些风险很容易解决,有些风险则大大损害利益。不论什么样的风险,能避免尽量避免,所以有必要对风险进行管理。由于风险的不可预知性,风险管理难度很大,概念也很难讲清楚,只能从一些可行的角度去分析,进行管理。

        首先要识别风险。这是个难度很高的活。PM要先召开风险识别会议,这个会议面向公司,高层,跨部门的有经验的人都将参加。然后审核由项目小组生成的风险清单并与重要成员进行风险沟通,检查一些重要的风险源如WBS,成本(时间)预估,人员计划,采购管理等等。最后就要用到PM本身在以前类似项目中得到的经验教训。

        识别之后要进行分析。我们可以进行粗略的量化分析(精确分析是不可能的事情)。有经验的人可以一起参加讨论,把提交出来的风险进行分类。首先按发生的可能性分,一般分成高,中,低三个级别,虽然很勉强,但是好歹也有个量化了;然后按耗去的成本分,也是高,中,低三级。我们可以把这两种类别的三个级别进行组合,碰到可能性也高,成本也高的风险就定位为不能接受。碰到这种风险只好让客户修改需求或者增加风险预留成本,否则一旦亏起来不是亏一点点,有可能赔的很厉害。高和中,中和中的搭配都是属于高风险,中和低,低和低搭配属于低,高和低搭配属于中。

        针对出现的可能性,需要采取一些手段降低风险。到目前为止也没有一个定论说有绝对好的方式,只能尽其所能的避免。有几种方法可以考虑,第一种是将风险纳入项目管理计划并指定负责人,由外部人员定期检查项目风险,一旦风险发生,执行风险管理计划;第二种是保险,这种属于风险转嫁;第三种方式有点奸,不过最保险,就是把客户拖下水,让他们一起参与风险管理,呵呵,到时候就好说话了:) 风险管理作为项目计划之后,PM需要更新WBS,修改日程计划和更新风险管理计划。 风险预留通常是成本的8%。

        3.预估

        预估是从量化的角度对项目进行评估,主要包括工作量,任务期限,人力,设备,材料,成本等,要注意预估不是财务策略或报价。 预估其实并不是一次性工作,在整个项目过程中,预估始终需要。预估似乎没什么特别需要提的地方,每个PM接到项目的时候自然会有预估,在项目发生变更或进入下一阶段时也会预估。预估的作用主要还是让PM作到心中有个底,安排计划时不至于毫无头绪。

        4.进度计划 进度计划就是一个模块或功能要写多长时间,PM安排个日期,设立里程碑,叫程序员们不能偷懒。进度计划是从WBS提取过来的。对PM来说,合理的安排进度计划对项目控制和激励团队士气有着很大的作用。对程序员来说,进度计划毫无疑问是噩梦。 显示进度计划一般有先后顺序图,甘特图和里程碑图表。上回邵卫老师讲课,推荐的工具是m$的PROJECT,这个工具我还不会用,因为没时间去摸索。我的头倒是用的很溜了,近一个月来他就用这个PROJECT画了一个又一个的里程碑图,不停的折磨我和同事的神经。我们一般都是一边开发一边做UNIT TEST,效果上来看,因为有强大的时间压力,效率上比之前确实要提高不少,可是我们也只能结结巴巴的赶完进度。由于TEAM里人少,我们都是一个人做几个人的活。我每天早晨六点多出门,经过将近两小时颠簸,八点多点已经坐在位子上,中午吃15分钟的饭,干到晚上八点下班,到家吃完饭往往已经11点了。一个多月我从来没吃过早饭,没有睡过六个小时以上的懒觉。虽然强大的压力使我们能在短时间内掌握尽可能多的技能,开发更多的模块,但是对我们的情绪也是有很大的影响。所以说,项目里程碑是一把双刃剑,合理安排才能既促进效率也不至于打击士气。团队成员士气的逐级衰落会给项目后期的开发带来难以估计的影响,进度将会大大延缓。关于PM和团队的问题我们后面会讲到,这里我先祥林嫂一把,然后跳过。 里程碑图表的特征是任务,成员和时间,任务和成员用文字标志,时间用数字描述并辅助以图线跨度,象阶梯一样非常形象,一目了然。管理起来非常方便,完了的打个钩就可以了。

        网络逻辑图是表示任务和逻辑关系的示意图,可以用先后次序表示,也可以用关键路径表示。其实把各个活动划分为1,2,3,4等阶段,每个阶段包括小活动1.1,1.2,2.1,2.2,2.3,2.4,3.1,3.2,3.3,4.1,4.2等,日程计划也分四种,一般只提到从前向后和从后向前两种。从前向后的概念就是某项活动必须相同或晚于直接指向这项活动的的所有活动的最早结束时间的最晚时间。有些绕口,我们打个比方:2阶段指向3阶段,那么2阶段里的4个子阶段也都指向3。假设2.1结束时间为1月12日,2.2结束时间为1月22日,2.3结束时间为1月15日,2.4结束时间为1月20日,那么,2阶段中最晚的结束时间是2.2的1月22日,所以在3阶段中的3个子阶段3.1,3.2,3.3的最早开始时间都不能早于1月22日。至于从后向前的例子大家自己去推吧,我就不举了,刚才几个123打的我累死了:) 项目经常需要调整进度。在不改变项目范围的情况下,调整进度有几种方法:利用快速跟踪手段来改变任务间的关系;将串行的任务改成并行;改变工作方法(可能改变WBS);改变日期限制,使关键路径上的任务开始或结束的更早。

        虽然方法多样化,在我看来只有一条,就是拼命的压榨程序员的劳动力。如何压榨,还是个技巧。如前面所分析的,需求分析恨不得多分点时间给它,压需求是不太可能;测试阶段后期接近完工,罗里巴唆的事情一大堆,忙都忙不完,那时候PM一门心思提前/按时完工,好收钱,压那段时间似乎也不太可能。说来残酷,最能压的还是CODING,编码阶段往往是压缩重点,总之大家埋头苦干就是了,大项目压缩的时候程序员吃喝拉撒都在公司是很正常的事情,相信不少人都有很深的体会,这里伤心事情也就不提了。只是我总结一下,让未来的PM们有压榨后来人的依据,呵呵。测试前期也可以适当的压一压,那时候人刚完工,都比较懒散。国内一般企业规模都不大,没有专门的质量控制部门,所以质量保证和测试往往就是程序员或PM本身。其实质量保证和测试人员的人数和素质都应该要高于程序员。在日本和CMM实施的公司里,编码压缩是很容易实现的事情,因为那些程序员真的是技能熟练的装配工人,压起来方便的很。他们这样培养人的目的或许就是为了压缩吧?!

        四.控制和执行阶段

        1.软件开发 实在没什么好说的,也是大家最不愿意谈的,平时在公司里谈的已经够多的了,还要在这里受我唠叨。需要提醒的依然是团队合作精神和完善的文档管理制度。

        SOURCESAFE这些工具有时候还是有必要使用的。经常看到有人说天才程序员不写注释什么的。我相信有这种天才程序员,因为我碰到过几个。

        我爱人公司里也有一个,他们的一套产品核心代码就是这个人写的,4年过去了,周边代码换了好几茬,核心算法始终没换过,可惜这小子跟了李洪痔,如今已经不知所踪了。但是他的代码似乎也要有点注释的,没有注释过段时间再天才的程序员也不能保证他是最有记忆力的。而且,对一个项目的编码来说,靠一两个人打天下如今是不可能了。别人的公司都是团队,两人智慧胜一人,这头还在靠一个天才支撑门面,实际上市场可就别人抢了去,那时候再天才也没用了。

        编码的时候讲究技术公开,程序员不要藏着掖着,对大家没好处,PM要想办法调动大家创新思维的积极性,营造良好的技术讨论氛围,碰到技术难关的时候就容易攻破了。 有个问题需要单独对还没有PM觉悟的程序员说,其实是在调研的时候就定了的,就是使用什么样的开发工具。没有经验的程序员往往会拿着C++或者JAVA的资格证书或者拥有一两个开发工具的一些经验而得意洋洋。其实老板和PM根本不看重这个,他们关心的是使用什么样的工具能尽快的达到目的。管你什么C++,DELPHI,PB还是JAVA,只要能做的出来,VFP都可以用。我举这个例子并非说不看中工具,而是提醒想转型为PM的程序员,第一要把工具当作工具,而不要被工具套进去,钻研一些一辈子都用不上的技术;第二要掌握的并非是单独的一个工具,而是流行的程序设计的思想,以及在最短时间内掌握一门陌生工具的能力。只有建立了这样的思维,才有可能转为PM,否则一辈子都是技术工人,最多就是个技术总监。

        2.变更 对任何项目,变更无可避免,无从逃避,只能去积极应对,这个应对应该是从需求分析就开始了。对一个需求分析做的很好的项目来说,基准文件定义的范围越详细清晰,用户跟PM扯皮的幌子就越少。而需求没做好,基准文件里的范围含糊不清,被客户抓住空子搞你一下是非常头疼的事情,往往要付出无谓的牺牲,有时候甚至非常火大。 需求做的好,文档清晰又有客户签字,那么后期客户提出的变更就超出了合同的范围,需要另外收费。这个时候千万不能手软,并非要刻意赚取客户的钱财,而是不能养成客户经常变更的习惯,否则后患无穷,维护的成本会让PM吃不消。

        在客户提出变更请求时,要建立变更申请登记表和变更申请表,并让客户签字。当然,有时候一些不是非常关键的模块PM也不至于一点不讲情面,该卖面子的时候还是要卖,尤其是当着对方领导的面,千万要卖面子,但是也别卖的太干脆,不要让他们得到的太容易。 需求做的不好,客户抓住漏洞或者非常不讲道理,麻烦就大了。有时候争论会很厉害,到非常白热化的地步,PM与客户代表几乎沟通不了。PM在客户关系和短期利益两方面难以取舍,一般都是向客户妥协,最终形成恶性循环。这种情况非常难办。一般这种情况都是到了项目后期,做重大的更改几乎是不可能的事情,如果白做就要亏钱。而这个时候如果PM跟对方高层的人关系搞的定,可以透过对方高层把事情压住。然而由于已经到后期,客户代表不会轻易更换,对方这次没有改成,必然心怀不满,下回在别的模块依然会找麻烦或者在谈二期的时候动动手脚,都是很让PM伤脑筋的事情,这方面目前还没有什么好的解决方法,所以尽可能的做好需求比什么都重要。相对需求来说,什么WBS,风险管理,计划进度都是扯淡,需求做好了,一帆风顺。还有一种办法就是装可怜,要装的非常的象,在对方的领导面前装,而且不能让人看出是装的样子,要让你自己都觉得你自己是真的可怜,那么就算这次客户硬是要求改了,下回他也必然不好意思再叫你改。其实人心都是肉长的,如果可能的话,我还是不赞同使用一些手段的,但是有时候客户非常难以在短时间打动而工期又将接近,这种情况下就要靠PM耍一些手段了。各人有各人的方式,八仙过海,各显神通吧。 PM在变更管理中需要做的是分析变更请求,评估变更可能带来的风险和修改基准文件。

        3.质量控制 大公司有质量管理部门(QA),QA的成员基本上都是由非常有经验的PM转型过来的老狐狸,是老总接班人的有力争夺者。一个QA会管理多个项目,有时候甚至会亲身参与。PM和QA有些象和老鼠,不停的通过报表传递一些心照不宣的假数字。QA对PM的工作最终是有评定的:A级表示总体在控制下;B级表示当前在控制下;C级表示有显著问题;D级表示有重大问题。如果PM得了个D,那可不太妙,不但世界级的QA会每个月要收报告,地区QA会一个星期找来面谈一次,训一顿。得到A的PM是很逍遥的,基本上不会有人来过问。在没有QA的公司,质量控制只能由经过授权的团队成员进行,效果就要差的多了。 质量管理贯穿整个项目周期,详细的可以参见CMM。

        4.成本管理 PM经常通过控制进度和预估来控制成本。PM必须经常问自己,项目已经到了什么阶段?完成了多少?花费了多少?完成时成本是多少?挣值法的术语不少,象BCWS,BCWP,ACWP,但是关系比较简单,大家参阅一下相关资料,这里不再羸述。总之,PM要管理好成本,注意节约,但并非是拼命剥削程序员,该花的还是要花。

        五.结束阶段

        1.项目结束 项目结束时,PM要将最终系统方案提交给用户,完成项目所有的提交件,收集项目全部信息并结束项目,完成或终止合约,签署项目结束的相关文件。 项目结束意味着可以收钱了。PM辛苦了那么多,终于可以高兴一下了,收到最后一笔款项,意味着递交件的移交和团队的解散,项目也转入维护阶段。不过收钱未必代表着赚钱,要看项目是否按时完工。一般来说,提前完工的项目很少,但是能赚大钱;按时完工的赚小钱;延期的要赔钱。一个人首次承担PM,如果没有人带,多半会失败。失败没什么,所有的PM(注意是所有,不是几乎所有)都失败过,然而失败会成为教训和经验,推动你继续前行。在美国,每年至少有40%的项目无法实施被搁浅。只有在项目中和生活中不断磨练,培养自身素质和作人的基本准则,才能成为赚大钱的PM。

        2.项目完工会议 项目结束时,依然要开会,不过少多了。一般跟客户要开一个,主要是确定所有的提交件都已经被接收,对突出的个体进行表扬,对外宣传成功案例,标志并记录项目的正式结束。这时候开会很轻松,目的也很明确,做完了大家好聚好散,或者以后有机会再合作。 团队要解散,内部会议肯定是要开的。也没什么好废话的,该表扬就表扬,该发多少奖金就发多少奖金,毕竟大家都累死累活的干了那么长时间。项目结束请客户出去泡温泉时PM们千万别忘记了辛苦为你工作的 程序员和工程师们,当然,如果他们不愿意看到你的脸那么你就折现发到他们的存折上去,正好让他们回家好好休息休息。这样下一个项目需要他们的时候他们才会为你卖命。说出来奖金发出去似乎你损失了,其实你赚大了。

        3.客户满意度 形势也好,历史记录也好,尽管项目结束的时候客户满意度PM心中已经有底了,但是还是有必要向客户各个层次的项目代表发一张信息反馈表,收回的信息将反应客户的满意度。其实真正体现客户满意度的地方有很多。第一就是付钱付的快,如果客户不满意,一分钱藏在他牙缝里你也很难抠出来;第二就是二期,二期非常非常重要,因为这里已经是属于一种无销售成本的项目,又有一期的经验,可以说二期的钱是最好赚的。这中间的感觉,只有经历过的人才明白。


      〔 项目管理的几个过程 〕随文赠言:【受惠的人,必须把那恩惠常藏心底,但是施恩的人则不可记住它。——西塞罗】
    展开全文
  • 上次分享的基础题:53道常见NodeJS基础面试题(附答案),这次来点难的,看看你能答对几个?1、Node模块机制1.1 请介绍一下node里的模块是什么Node中,每个文件模块都是一个对...

    上次分享的基础题:53道常见NodeJS基础面试题(附答案),这次来点难的,看看你能答对几个?

    1、Node模块机制

    1.1 请介绍一下node里的模块是什么

    Node中,每个文件模块都是一个对象,它的定义如下:

    function Module(id, parent) {  this.id = id;  this.exports = {};  this.parent = parent;  this.filename = null;  this.loaded = false;  this.children = [];}
    module.exports = Module;
    var module = new Module(filename, parent);
    

    所有的模块都是 Module 的实例。可以看到,当前模块(module.js)也是 Module 的一个实例。

    1.2 请介绍一下require的模块加载机制

    这道题基本上就可以了解到面试者对Node模块机制的了解程度 基本上面试提到

    1)、先计算模块路径

    2)、如果模块在缓存里面,取出缓存

    3)、加载模块

    4)、的输出模块的exports属性即可

    // require 其实内部调用 Module._load 方法Module._load = function(request, parent, isMain) {  //  计算绝对路径  var filename = Module._resolveFilename(request, parent);
      //  第一步:如果有缓存,取出缓存  var cachedModule = Module._cache[filename];  if (cachedModule) {    return cachedModule.exports;
      // 第二步:是否为内置模块  if (NativeModule.exists(filename)) {    return NativeModule.require(filename);  }    /********************************这里注意了**************************/  // 第三步:生成模块实例,存入缓存  // 这里的Module就是我们上面的1.1定义的Module  var module = new Module(filename, parent);  Module._cache[filename] = module;
      /********************************这里注意了**************************/  // 第四步:加载模块  // 下面的module.load实际上是Module原型上有一个方法叫Module.prototype.load  try {    module.load(filename);    hadException = false;  } finally {    if (hadException) {      delete Module._cache[filename];    }  }
      // 第五步:输出模块的exports属性  return module.exports;};
    

    接着上一题继续发问

    1.3 加载模块时,为什么每个模块都有__dirname,__filename属性呢,new Module的时候我们看到1.1部分没有这两个属性的,那么这两个属性是从哪里来的

    // 上面(1.2部分)的第四步module.load(filename)// 这一步,module模块相当于被包装了,包装形式如下// 加载js模块,相当于下面的代码(加载node模块和json模块逻辑不一样)(function (exports, require, module, __filename, __dirname) {  // 模块源码  // 假如模块代码如下  var math = require('math');  exports.area = function(radius){      return Math.PI * radius * radius  }});
    

    也就是说,每个module里面都会传入__filename, __dirname参数,这两个参数并不是module本身就有的,是外界传入的

    1.4 我们知道node导出模块有两种方式,一种是exports.xxx=xxx和Module.exports={}有什么区别吗

    exports其实就是module.exports

    其实1.3问题的代码已经说明问题了,接着我引用廖雪峰大神的讲解,希望能讲的更清楚。

    module.exports vs exports很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:
    方法一:对module.exports赋值:
    // hello.js
    function hello() {    console.log('Hello, world!');}
    function greet(name) {    console.log('Hello, ' + name + '!');}
    module.exports = {    hello: hello,    greet: greet};方法二:直接使用exports:
    // hello.js
    function hello() {    console.log('Hello, world!');}
    function greet(name) {    console.log('Hello, ' + name + '!');}
    function hello() {    console.log('Hello, world!');}
    exports.hello = hello;exports.greet = greet;但是你不可以直接对exports赋值:
    // 代码可以执行,但是模块并没有输出任何变量:exports = {    hello: hello,    greet: greet};如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:
    首先,Node会把整个待加载的hello.js文件放入一个包装函数load中执行。在执行这个load()函数前,Node准备好了module变量:
    var module = {    id: 'hello',    exports: {}};load()函数最终返回module.exports:
    var load = function (exports, module) {    // hello.js的文件内容    ...    // load函数返回:    return module.exports;};
    var exportes = load(module.exports, module);也就是说,默认情况下,Node准备的exports变量和module.exports变量实际上是同一个变量,并且初始化为空对象{},于是,我们可以写:
    exports.foo = function () { return 'foo'; };exports.bar = function () { return 'bar'; };也可以写:
    module.exports.foo = function () { return 'foo'; };module.exports.bar = function () { return 'bar'; };换句话说,Node默认给你准备了一个空对象{},这样你可以直接往里面加东西。
    但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports赋值:
    module.exports = function () { return 'foo'; };给exports赋值是无效的,因为赋值后,module.exports仍然是空对象{}。
    结论如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值;
    如果要输出一个函数或数组,必须直接对module.exports对象赋值。
    所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况:
    module.exports = {    foo: function () { return 'foo'; }};或者:
    module.exports = function () { return 'foo'; };最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。
    

    2、Node的异步I/O

    本章的答题思路大多借鉴于朴灵大神的《深入浅出的NodeJS》

    2.1 请介绍一下Node事件循环的流程

    在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们成为Tick。

    每个Tick的过程就是查看是否有事件待处理。如果有就取出事件及其相关的回调函数。然后进入下一个循环,如果不再有事件处理,就退出进程。

    2.2 在每个tick的过程中,如何判断是否有事件需要处理呢?

    每个事件循环中有一个或者多个观察者,而判断是否有事件需要处理的过程就是向这些观察者询问是否有要处理的事件。

    在Node中,事件主要来源于网络请求、文件的I/O等,这些事件对应的观察者有文件I/O观察者,网络I/O的观察者。

    事件循环是一个典型的生产者/消费者模型。异步I/O,网络请求等则是事件的生产者,源源不断为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。

    在windows下,这个循环基于IOCP创建,在*nix下则基于多线程创建。

    2.3 请描述一下整个异步I/O的流程

    3、V8的垃圾回收机制

    3.1 如何查看V8的内存使用情况

    使用process.memoryUsage(),返回如下

    {  rss: 4935680,  heapTotal: 1826816,  heapUsed: 650472,  external: 49879}
    

    heapTotal 和 heapUsed 代表V8的内存使用情况。external代表V8管理的,绑定到JavaScript的C++对象的内存使用情况。rss, 驻留集大小, 是给这个进程分配了多少物理内存(占总分配内存的一部分) 这些物理内存中包含堆,栈,和代码段。

    3.2 V8的内存限制是多少,为什么V8这样设计

    64位系统下是1.4GB, 32位系统下是0.7GB。因为1.5GB的垃圾回收堆内存,V8需要花费50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。这是垃圾回收中引起JavaScript线程暂停执行的事件,在这样的花销下,应用的性能和影响力都会直线下降。

    3.3 V8的内存分代和回收算法请简单讲一讲

    在V8中,主要将内存分为新生代和老生代两代。新生代中的对象存活时间较短的对象,老生代中的对象存活时间较长,或常驻内存的对象。

    3.3.1 新生代

    新生代中的对象主要通过Scavenge算法进行垃圾回收。这是一种采用复制的方式实现的垃圾回收算法。它将堆内存一份为二,每一部分空间成为semispace。

    在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间。

    当开始垃圾回收的时候,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间发生角色对换。

    应为新生代中对象的生命周期比较短,就比较适合这个算法。

    当一个对象经过多次复制依然存活,它将会被认为是生命周期较长的对象。这种新生代中生命周期较长的对象随后会被移到老生代中。

    3.3.2 老生代

    老生代主要采取的是标记清除的垃圾回收算法。与Scavenge复制活着的对象不同,标记清除算法在标记阶段遍历堆中的所有对象,并标记活着的对象,只清理死亡对象。

    活对象在新生代中只占叫小部分,死对象在老生代中只占较小部分,这是为什么采用标记清除算法的原因。

    3.3.3 标记清楚算法的问题

    主要问题是每一次进行标记清除回收后,内存空间会出现不连续的状态

    这种内存碎片会对后续内存分配造成问题,很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。

    为了解决碎片问题,标记整理被提出来。就是在对象被标记死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。

    3.3.4 哪些情况会造成V8无法立即回收内存

    闭包和全局变量

    3.3.5 请谈一下内存泄漏是什么,以及常见内存泄漏的原因,和排查的方法

    什么是内存泄漏

    内存泄漏(Memory Leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。

    如果内存泄漏的位置比较关键,那么随着处理的进行可能持有越来越多的无用内存,这些无用的内存变多会引起服务器响应速度变慢。

    严重的情况下导致内存达到某个极限(可能是进程的上限,如 v8 的上限;也可能是系统可提供的内存上限)会使得应用程序崩溃。常见内存泄漏的原因 内存泄漏的几种情况:

    一、全局变量

    a = 10;  //未声明对象。global.b = 11;  //全局变量引用 这种比较简单的原因,全局变量直接挂在 root 对象上,不会被清除掉。
    

    二、闭包

    function out() {      const bigData = new Buffer(100);      inner = function () {              }  }
    

    闭包会引用到父级函数中的变量,如果闭包未释放,就会导致内存泄漏。上面例子是 inner 直接挂在了 root 上,那么每次执行 out 函数所产生的 bigData 都不会释放,从而导致内存泄漏。

    需要注意的是,这里举得例子只是简单的将引用挂在全局对象上,实际的业务情况可能是挂在某个可以从 root 追溯到的对象上导致的。

    三、事件监听

    Node.js 的事件监听也可能出现的内存泄漏。例如对同一个事件重复监听,忘记移除(removeListener),将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现,所以事件重复监听可能收到如下警告:

    emitter.setMaxListeners() to increase limit 
    

    例如,Node.js 中 Agent 的 keepAlive 为 true 时,可能造成的内存泄漏。当 Agent keepAlive 为 true 的时候,将会复用之前使用过的 socket,如果在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将导致事件重复监听从而产生内存泄漏。

    原理上与前一个添加事件监听的时候忘了清除是一样的。在使用 Node.js 的 http 模块时,不通过 keepAlive 复用是没有问题的,复用了以后就会可能产生内存泄漏。所以,你需要了解添加事件监听的对象的生命周期,并注意自行移除。

    排查方法

    想要定位内存泄漏,通常会有两种情况:

    对于只要正常使用就可以重现的内存泄漏,这是很简单的情况只要在测试环境模拟就可以排查了。

    对于偶然的内存泄漏,一般会与特殊的输入有关系。想稳定重现这种输入是很耗时的过程。如果不能通过代码的日志定位到这个特殊的输入,那么推荐去生产环境打印内存快照了。

    需要注意的是,打印内存快照是很耗 CPU 的操作,可能会对线上业务造成影响。快照工具推荐使用 heapdump 用来保存内存快照,使用 devtool 来查看内存快照。

    使用 heapdump 保存内存快照时,只会有 Node.js 环境中的对象,不会受到干扰(如果使用 node-inspector 的话,快照中会有前端的变量干扰)。

    PS:安装 heapdump 在某些 Node.js 版本上可能出错,建议使用 npm install heapdump -target=Node.js 版本来安装。

    4、Buffer模块

    4.1 新建Buffer会占用V8分配的内存吗

    不会,Buffer属于堆外内存,不是V8分配的。

    4.2 Buffer.alloc和Buffer.allocUnsafe的区别

    Buffer.allocUnsafe创建的 Buffer 实例的底层内存是未初始化的。新创建的 Buffer 的内容是未知的,可能包含敏感数据。使用 Buffer.alloc() 可以创建以零初始化的 Buffer 实例。

    4.3 Buffer的内存分配机制

    为了高效的使用申请来的内存,Node采用了slab分配机制。slab是一种动态的内存管理机制。Node以8kb为界限来来区分Buffer为大对象还是小对象,如果是小于8kb就是小Buffer,大于8kb就是大Buffer。

    例如第一次分配一个1024字节的Buffer,Buffer.alloc(1024),那么这次分配就会用到一个slab,接着如果继续Buffer.alloc(1024),那么上一次用的slab的空间还没有用完,因为总共是8kb,1024+1024 = 2048个字节,没有8kb,所以就继续用这个slab给Buffer分配空间。

    如果超过8kb,那么直接用C++底层地宫的SlowBuffer来给Buffer对象提供空间。

    4.4 Buffer乱码问题

    例如一个份文件test.md里的内容如下:

    床前明月光,疑是地上霜,举头望明月,低头思故乡
    

    我们这样读取就会出现乱码:

    var rs = require('fs').createReadStream('test.md', {highWaterMark: 11});// 床前明???光,疑???地上霜,举头???明月,???头思故乡
    

    一般情况下,只需要设置rs.setEncoding('utf8')即可解决乱码问题

    5、webSocket

    5.1 webSocket与传统的http有什么优势

    客户端与服务器只需要一个TCP连接,比http长轮询使用更少的连接

    webSocket服务端可以推送数据到客户端

    更轻量的协议头,减少数据传输量

    5.2 webSocket协议升级时什么,能简述一下吗?

    首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:

    GET ws://localhost:3000/ws/chat HTTP/1.1Host: localhostUpgrade: websocketConnection: UpgradeOrigin: http://localhost:3000Sec-WebSocket-Key: client-random-stringSec-WebSocket-Version: 13
    

    该请求和普通的HTTP请求有几点不同:

    GET请求的地址不是类似/path/,而是以ws://开头的地址;

    请求头Upgrade: websocket和Connection: Upgrade表示这个连接将要被转换为WebSocket连接;

    Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;

    Sec-WebSocket-Version指定了WebSocket的协议版本。

    随后,服务器如果接受该请求,就会返回如下响应:

    HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: server-random-string
    

    该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

    6、https

    6.1 https用哪些端口进行通信,这些端口分别有什么用

    443端口用来验证服务器端和客户端的身份,比如验证证书的合法性

    80端口用来传输数据(在验证身份合法的情况下,用来数据传输)

    6.2 身份验证过程中会涉及到密钥, 对称加密,非对称加密,摘要的概念,请解释一下

    密钥:密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥,分别应用在对称加密和非对称加密上。

    对称加密:对称加密又叫做私钥加密,即信息的发送方和接收方使用同一个密钥去加密和解密数据。

    对称加密的特点是算法公开、加密和解密速度快,适合于对大数据量进行加密,常见的对称加密算法有DES、3DES、TDEA、Blowfish、RC5和IDEA。

    非对称加密:非对称加密也叫做公钥加密。非对称加密与对称加密相比,其安全性更好。

    对称加密的通信双方使用相同的密钥,如果一方的密钥遭泄露,那么整个通信就会被破解。

    而非对称加密使用一对密钥,即公钥和私钥,且二者成对出现。私钥被自己保存,不能对外泄露。公钥指的是公共的密钥,任何人都可以获得该密钥。用公钥或私钥中的任何一个进行加密,用另一个进行解密。

    摘要:摘要算法又称哈希/散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。算法不可逆。

    6.3 为什么需要CA机构对证书签名

    如果不签名会存在中间人攻击的风险,签名之后保证了证书里的信息,比如公钥、服务器信息、企业信息等不被篡改,能够验证客户端和服务器端的“合法性”。

    6.4 https验证身份也就是TSL/SSL身份验证的过程

    简要图解如下:

    7、进程通信

    7.1 请简述一下node的多进程架构

    面对node单线程对多核CPU使用不足的情况,Node提供了child_process模块,来实现进程的复制,node的多进程架构是主从模式,如下所示:

    var fork = require('child_process').fork;var cpus = require('os').cpus();for(var i = 0; i < cpus.length; i++){    fork('./worker.js');}
    

    在linux中,我们通过ps aux | grep worker.js查看进程

    这就是著名的主从模式,Master-Worker

    7.2 请问创建子进程的方法有哪些,简单说一下它们的区别

    创建子进程的方法大致有:

    spawn():启动一个子进程来执行命令

    exec(): 启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况

    execFlie(): 启动一个子进程来执行可执行文件

    fork(): 与spawn()类似,不同电在于它创建Node子进程需要执行js文件

    spawn()与exec()、execFile()不同的是,后两者创建时可以指定timeout属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死

    exec()与execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。

    7.3 请问你知道spawn在创建子进程的时候,第三个参数有一个stdio选项吗,这个选项的作用是什么,默认的值是什么。

    选项用于配置在父进程和子进程之间建立的管道。

    默认情况下,子进程的 stdin、 stdout 和 stderr 会被重定向到 ChildProcess 对象上相应的 subprocess.stdin、subprocess.stdout 和 subprocess.stderr 流。

    这相当于将 options.stdio 设置为 ['pipe', 'pipe', 'pipe']。

    7.4 请问实现一个node子进程被杀死,然后自动重启代码的思路

    在创建子进程的时候就让子进程监听exit事件,如果被杀死就重新fork一下

    var createWorker = function(){    var worker = fork(__dirname + 'worker.js')    worker.on('exit', function(){        console.log('Worker' + worker.pid + 'exited');        // 如果退出就创建新的worker        createWorker()    })}
    

    7.5 在7.4的基础上,实现限量重启,比如我最多让其在1分钟内重启5次,超过了就报警给运维

    思路大概是在创建worker的时候,就判断创建的这个worker是否在1分钟内重启次数超过5次

    所以每一次创建worker的时候都要记录这个worker 创建时间,放入一个数组队列里面,每次创建worker都去取队列里前5条记录

    如果这5条记录的时间间隔小于1分钟,就说明到了报警的时候了

    7.6 如何实现进程间的状态共享,或者数据共享

    我自己没用过Kafka这类消息队列工具,问了java,可以用类似工具来实现进程间通信,更好的方法欢迎留言。

    8、中间件

    8.1 如果使用过koa、egg这两个Node框架,请简述其中的中间件原理,最好用代码表示一下。

    上面是在网上找的一个示意图,就是说中间件执行就像洋葱一样,最早use的中间件,就放在最外层。处理顺序从左到右,左边接收一个request,右边输出返回response

    一般的中间件都会执行两次,调用next之前为第一次,调用next时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行next函数时,就将依次恢复上游中间件的行为,让上游中间件执行next之后的代码

    例如下面这段代码

    const Koa = require('koa')const app = new Koa()app.use((ctx, next) => {    console.log(1)    next()    console.log(3)})app.use((ctx) => {    console.log(2)})app.listen(3001)执行结果是1=>2=>3
    

    koa中间件实现源码大致思路如下:

    // 注意其中的compose函数,这个函数是实现中间件洋葱模型的关键// 场景模拟// 异步 promise 模拟const delay = async () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      resolve();    }, 2000);  });}// 中间间模拟const fn1 = async (ctx, next) => {  console.log(1);  await next();  console.log(2);}const fn2 = async (ctx, next) => {  console.log(3);  await delay();  await next();  console.log(4);}const fn3 = async (ctx, next) => {  console.log(5);}
    const middlewares = [fn1, fn2, fn3];
    // compose 实现洋葱模型const compose = (middlewares, ctx) => {  const dispatch = (i) => {    let fn = middlewares[i];    if(!fn){ return Promise.resolve() }    return Promise.resolve(fn(ctx, () => {      return dispatch(i+1);    }));  }  return dispatch(0);}
    compose(middlewares, 1);
    

    9、其它

    现在在重新过一遍node 12版本的主要API,有很多新发现,比如说

    fs.watch这个模块,事件的回调函数有一个参数是触发的事件名称,但是呢,无论我增删改,都是触发rename事件(如果更改是update事件,删除delete事件,重命名是rename事件,这样语义明晰该多好)。

    后来网上找到一个node-watch模块,此模块增删改都有对应的事件, 并且还高效的支持递归watch 文件。

    util模块有个promisify方法,可以让一个遵循异常优先的回调风格的函数,即 (err, value) => ... 回调函数是最后一个参数,返回一个返回值是一个 promise 版本的函数。

    const util = require('util');const fs = require('fs');
    const stat = util.promisify(fs.stat);stat('.').then((stats) => {  // 处理 `stats`。}).catch((error) => {  // 处理错误。});
    

    9.1 杂想

    crypto模块,可以考察基础的加密学知识,比如摘要算法有哪些(md5, sha1, sha256,加盐的md5,sha256等等),接着可以问如何用md5自己模拟一个加盐的md5算法, 接着可以问加密算法(crypto.createCiphe)中的aes,eds算法的区别,分组加密模式有哪些(比如ECB,CBC,为什么ECB不推荐),node里的分组加密模式是哪种(CMM),这些加密算法里的填充和向量是什么意思,接着可以问数字签名和https的流程(为什么需要CA,为什么要对称加密来加密公钥等等)

    tcp/ip,可以问很多基础问题,比如链路层通过什么协议根据IP地址获取物理地址(arp),网关是什么,ip里的ICMP协议有什么用,tcp的三次握手,四次分手的过程是什么,tcp如何控制重发,网络堵塞TCP会怎么办等等,udp和tcp的区别,udp里的广播和组播是什么,组播在node里通过什么模块实现。

    os,操作系统相关基础,io的流程是什么(从硬盘里读取数据到内核的内存中,然后内核的内存将数据传入到调用io的应用程序的进程内存中),冯诺依曼体系是什么,进程和线程的区别等等(我最近在看马哥linux教程,因为自己不是科班出身,听了很多基础的计算机知识,受益匪浅,建议去bilibili看)

    linux相关操作知识(node涉及到后台,虽然是做中台,不涉及数据库,但是基本的linux操作还是要会的)

    node性能监控(自己也正在学习中)

    测试,因为用的egg框架,有很完善的学习单元测试的文档,省略这部分

    数据库可以问一些比如事务的等级有哪些,mysql默认的事务等级是什么,会产生什么问题,然后考一些mysql查询的笔试题。。。和常用优化技巧,node的mysql的orm工具使用过没有。。。

    作者 | 孟祥_成都
    来源 | https://juejin.cn/post/6844903951742025736

    推荐阅读

    30道常见React基础面试题(附答案)

    END

    关注下方「前端开发博客」,回复 “电子书”

    领取27本电子书

    ❤️ 看完两件事

    如果你觉得这篇内容对你挺有启发,我想邀请你帮我两个小忙:

    1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

    2. 关注公众号「前端开发博客」,每周重点攻克一个前端面试重难点,

    3. 公众号后台回复「电子书」即可免费获取 27本 精选的前端电子书!

    点个在看支持我吧,转发就更好了

    展开全文
  • 几个简单数学分布

    千次阅读 2016-08-09 22:43:21
    其中概率密度函数f(x)并代表概率,只是代表当前x点的概率密度,类似于速度代表位移一样,我们把所有可能发生事件概率相加应该为1(上图面积): ∫+∞−∞f(x)dx=1 其中f(x)>=0,也可以计算下雨量在...

    1. 概率密度函数


    假如我们要预测明天的下雨量,x表示下雨的量,f(x)就表示为概率密度,我们随便画一个概率密度,他们的关系如下:

    其中概率密度函数f(x)并不代表概率,只是代表当前x点的概率密度,类似于速度不代表位移一样,我们把所有可能发生事件概率相加应该为1(上图面积):

    +f(x)dx=1

    其中f(x)>=0,也可以计算下雨量在某个范围内的概率:

    P(a<x<b)=abf(x)dx=1

    积分后的概率即成为概率分布。

    2. 二项分布


    抛硬币是典型的二线分布,假设我们抛了5次硬币,设定P(x)表示有x次硬币正面朝上的话,我们可以得到一个类似如下的概率分布:

    其中x为正面朝上的次数,离散变量和连续变量的差别可以看下面的泊松分布。

    3. 泊松分布


    泊松分布是二项分布的极限情况。

    假设我们现在要估计某个路口一小时经过k辆车的概率,第一步我们需要先大量的观察一段时间,获得一个一小时内通过汽车数量的期望λ

    然后我们把一小时分为60分钟,同时假设每一分钟要么经过一辆车,要么没有车,那么按照二项分布的式子:

    P(k)=Ck60(λ60)k(1λ60)60k

    也就是说,期望除以60分钟(把一小时分成60份)获得每一分钟有一辆车经过的概率。

    但是很明显我们不能确保每分钟真的只过一辆,为了更加精确,我们可以把一小时继续分为3600秒或72000个半秒,也就是说分的越多份,越精确。如果我们这么一直分下去,我们就获得了泊松分布,也就是二项分布的极限情况。

    如果引入极限和e,泊松分布可以表达为(参考这里):

    P(X=k)=eλλkk!

    泊松分布的概率密度和累计概率图像如下: 

    4. 正态分布


    跟泊松分布一样,正态分布其实也是在大量观察现实世界的接触上总结推理出来的,它的概率密度函数为:

    f(x)=12π−−√σe(xμ)22σ2

    图像类似:

    其中μ为观察到的数据的均值,是期望的一种估计方式,类似上面泊松分布估计用的期望,在图上表示为中心点的位置。

    σ是样本的标准差,在图上可以表现为向中央的紧缩程度。

    正态分布的特点是大自然中很多事件都符合它的描述,比如20岁男子的身高、同一个学校里学生的成绩分布等等。

    正态分布还有一个有趣的特点是:

    1. 横轴区间(μ-σ,μ+σ)内的面积(即概率)为68.268949%
    2. 横轴区间(μ-1.96σ,μ+1.96σ)内的面积为95.449974%
    3. 横轴区间(μ-2.58σ,μ+2.58σ)内的面积为99.730020%

    正态分布可以通过调整其两个参数能够拟合很多自然界的情况,也可以和其他分布在某些情况下互相转换。

    5. Gamma分布


    正态分布的特点是左右对称,这个世界也有很多不符合这种分布的情况,比如某个事件的热度,可能会先迅速上升,然后缓慢降低热度,还有发射火箭的速度等等。

    Gamma分布的概率密度函数为:

    其中α为形状参数,表示分布的形状,β为尺度参数,表示左右两边的对称情况,数值越大越对称,无限大时区域正态分布。

    下图中k=α,θ=β:

    数据的期望可以表示为:E(X)=α/βD(X)=β/(α2)

    从物理意义上说,Gamma分布表示第α件事情发生时所需等待的时间. b表示某事件发生需要的时间

    Gamma(a,b)表示第 α 件事情发生时所需等待的时间


    让我们先通过一个例子,了解什么是"泊松分布"。

    已知某家小杂货店,平均每周售出2个水果罐头。请问该店水果罐头的最佳库存量是多少?

    假定不存在季节因素,可以近似认为,这个问题满足以下三个条件:

    (1)顾客购买水果罐头是小概率事件。

    (2)购买水果罐头的顾客是独立的,不会互相影响。

    (3)顾客购买水果罐头的概率是稳定的。

    在统计学上,只要某类事件满足上面三个条件,它就服从"泊松分布"。

    泊松分布的公式如下:

    P(X=k)=\frac{e^{-\lambda}\lambda^k}{k!}

    各个参数的含义:

      P:每周销售k个罐头的概率。

      X:水果罐头的销售变量。

      k:X的取值(0,1,2,3...)。

      λ:每周水果罐头的平均销售量,是一个常数,本题为2。

    根据公式,计算得到每周销量的分布:

    从上表可见,如果存货4个罐头,95%的概率不会缺货(平均每19周发生一次);如果存货5个罐头,98%的概率不会缺货(平均59周发生一次)。

    http://www.ruanyifeng.com/blog/2013/01/poisson_distribution.html

    http://sobuhu.com/math/2013/06/17/distributions.html


    需要注意的是概率密度函数和概率的关系

    密度这个概念的理解的确不那么简单
    事实上,对于连续型随机变量,例如X服从正态分布,
    那么X在每一点的概率都是0,但是X在一个区间内的概率却不是0
    这不难理解,就像一根质量分布不均匀的钢笔,钢笔在每个点上的质量都是0,
    但是钢笔在一个小块儿内的质量却不是0
    
    对于这种随机变量X怎么研究呢?很简单!
    设dx是一个非常小的正数,因为任何函数在一个很小的区间上都可以近似看成线性的,那
    么X处于(x,x+dx)内的概率一定可以近似表示成f(x)dx的形式
    这里的f(x)就叫做X的概率密度
    
    不止是概率密度,物理上的各种密度的原理都是这样的
    
    你可能会问,为什么X在每一点的概率都是0,但是X在一个区间内的概率却不是0?
    这是因为概率论的公理体系只能保证可列个概率为0的事件的并还是概率为0的
    然而一个区间包含不可列个点!因此尽管这些点的概率都是0,
    它们的并,也就是这个区间的概率却可以不是0
    对于离散型的概率密度函数,取一个x值,获得的就是取x时的概率

    对于连续性的,必须是要取一个区间的,概率才有意义


    3. 伯努利、二项分布、多项分布

    伯努利分布就是对单次抛硬币的建模,X~Bernoulli(p)的PDF为 f(x)=px(1p)1x ,随机变量X只能取{0, 1}。对于所有的pdf,都要归一化!而这里对于伯努利分布,已经天然归一化了,因此归一化参数就是1。

    很多次抛硬币的建模就是二项分布了。注意二项分布有两个参数,n和p,要考虑抛的次数。

    二项分布的取值X一般是出现正面的次数,其PDF为:


    Cxn 就是二项分布pdf的归一化参数。如果是beta分布,把 Cxn 换成beta函数分之一即可,这样可以从整数情况推广为实数情况。所以beta分布是二项分布的实数推广!

    多项分布则更进一层,抛硬币时X只能有两种取值,当X有多种取值时,就应该用多项分布建模。

    这时参数p变成了一个向量 p⃗ =(p1,,pk) 表示每一个取值被选中的概率,那么X~Multinomial(n,p)的PDF为:


    伯努利分布最简单,就是抛一次硬币

    二项式分布是抛多次硬币,出现n次正面的概率,其概率密度函数图,就是直方图


    多项式分布就是抛一个多面体,每个面朝上的概率为pi, 所以p1+p2+...+pk=1, 抛一次的结果是(x11,x12,x13,...,x1k)注意只有一个x1i=1,其他都为0

    那么抛n次多面体,就是多项式分布,其中xi表示i面朝上一共出现了xi次


    展开全文
  • 通信原理中的几个重要概念

    千次阅读 2016-07-08 11:43:09
    根据概率论的极限中心定理,大量相互均匀的、独立的微小随机变量的总和趋于高斯分布。对于平稳的高斯过程,由于其平稳性,他的数学期望和方差都是与时间无关的常数 如果噪声n(t)的功率谱密度在的整个频率范围内都是...
  • 本节课我们来介绍概率统计当中的极限思维,我们首先从一大家都非常熟悉的场景切入来展开我们本节内容的讨论。 比如说,我们想获得本省151515岁男生的平均身高,这时你会怎么做?显然你不会也可能真的去统计全省...
  • 能够砸伤人则需要水滴具有的动能,即公式(1/2)mv^2,而水滴的质量是一定的,需要达到很高的速度时才能突破人体的承受极限而致人受伤。但是,当水滴具有足够大的速度时,根据v=9.8t,可以知道已经经过了比较长的...
  • 摘要 IBM Rational Unified Process®(或简称 RUP®)是一完善的软件开发过程框架,它具有若干种即装即用的实例。源自 RUP 的过程范围很广,从满足短周期的小型项目需要的轻量级 RUP,到满足大型的、可能是分布式...
  • 5G的7大用途,你知道几个

    千次阅读 2019-12-05 11:31:31
    阿里妹导读:5G时代悄悄来临,甚至成为街头巷尾都在讨论的话题。相信你一定有过一些疑问:什么是5G?仅仅只是网速更快吗?...记得在研究生的一门课程《信息论》中严格推导过这公式,这定理的公式指明了与通信速...
  • 无穷小的乘积仍是无穷小,而两无穷小之商却有如下种情况: 例如:当时,、、都是无穷小,但是 ,, 两无穷小之比的极限的各种不同情况, 反映出不同无穷小趋向于零时,在“快慢”上是有区别的。 由...
  • 尤肖虎等:基于AI的5G技术:4大研究方向、4个典型范例   本文作者 尤肖虎:东南大学教授,移动通信国家重点实验室主任 张 川:东南大学信息科学与工程学院副教授 谈晓思:移动通信国家重点实验室(东南大学...
  • 最重要的 10 几个 J2EE 最佳实践

    千次阅读 2004-07-20 01:55:00
    发信人: was (银杏叶◇The Day of Days), 信区: J2EE标 题: 最重要的 10 几个 J2EE 最佳实践 (IBM dW)发信站: BBS 水木清华站 (Tue Jul 13 20:52:45 2004), 站内IBM WebSphere 开发者技术...
  • 3,缩略图仅仅host在几个机器上 4,持有一些小东西所遇到的问题:  -OS级别的大量的硬盘查找和inode和页面缓存问题  -单目录文件限制,特别是Ext3,后来移到多分层的结构。内核2.6的最近改进可能让 Ext3允许大目录...
  • 如果要输出一键值对象{},可以利用exports这存在的空对象{},并继续在上面添加新的键值; 如果要输出一函数或数组,必须直接对module.exports对象赋值。 所以我们可以得出结论:直接对module....
  • 新托业2020一周极限自救攻略

    千次阅读 2020-09-30 23:45:02
    如果你需要备考托业考试, 如果你只有一周七天的时间, 如果你需要快速提升200分到785, 如果你考不过就毕了业, 最重要的,如果你还想放弃你自己, 这里是我的经验,希望能帮到
  • 写一生成斐波那契数列的程序,初学计算机迟早会写那么一次,至少看过别人的代码一次。 一、小鸟层次 int fibonacci_a(int n) { if( n== 0 || n==1 ) return n; else return fibonacci_a(n-1) + ...
  • 第一节:图片质量与ISO 第二节:快门 第三节:光圈  第四节:测光,曝光与曝光补偿 第五节:焦距和焦距转换系数 第六节:景深与光圈优先 ...这ISO是国际标准组织的缩写,International Standards
  • 判别模式与生成模型基础知识 举例:要确定一瓜是好瓜还是坏瓜,用判别模型的方法是从历史数据中学习到模型,然后通过提取这瓜的特征来预测出这只瓜是好瓜的概率,是坏瓜的概率。 举例:利用生成模型是根据好瓜的...
  • 存储:存储的几个主要指标是IOPS、带宽与响应时间。IOPS:指的是系统在单位时间内能处理的最大IO频度,一般是指单位时间内能完成的随机小IO个数。带宽(throuput):有的时候也较吞吐量,指的是单位时间按内最大的IO...
  • 架构设计 例子和实践

    千次阅读 2012-01-20 09:45:00
    但我写这些文档以来基本上是每写一次就把目录结构给改一次,应该说这是因为自己对这些文档的理解开始加深,慢慢的越来越明白这些文档的作用和其中需要阐述的东西,觉得这三份文档主要阐述了一系统的设计和实现过程...
  • 极限编程(Xtreme Program)的测试理念是,在开发程序的之前或者同时就要编写出相应的测试程序,而不是象通常那样等到测试阶段再来编写测试程序。 当程序完成时,它的测试程序也应该已经完成。从软件工程的普遍规律来...
  • IBM Software Services for WebSphereKeys Botzum, 高级顾问/IT 专业人员, IBM Software Services for WebSphereRuth Willenborg, 高级经理, IBM WebSphere Performance2004 年 7 月 本文列出了十几个最重要的 ...
  • 裴礼文《数学分析中的典型问题与方法》 第1章 一元函数极限 第3天61~90 4.O.Stolz公式 数列的情况。 ∞比∞型。Stolz公式 0/0型Stolz公式。(用的少) 例1.4.1∞比∞型 例1.4.2组合数应用stolz公式的例子...
  • 从中心极限定理的模拟到正态分布 谢益辉 关键词:R 语言; SAS; Shapiro 检验; 中心极限定理; 动画; 密度曲线; 数学假设条件; 样本均值; 样本量; 正态分布; 泰勒展开; 直方图; 统计模拟; 钟形曲线; ...
  • 目标集成方法将几个弱学习器的预测与给定的学习算法结合起来,以提高单个学习器的通用性和鲁棒性(健壮性) 1)Bagging:训练多个学习器取平均 在原始训练集的随机子集上建立几个黑箱学习器的实例,然后将它们的...
  • 极限编程与敏捷开发

    千次阅读 2006-05-18 14:37:00
    n 经常性地交付可以工作的软件,交付的间隔可以从几个星期到几个月,交付的时间间隔越短越好。  n 在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。  n 围绕被激励起来的个体来...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,078
精华内容 4,431
关键字:

典型的几个极限不存在例子