I has to work overtime again today

大家都知道:软件项目的加班可能真是有百害而无一利,但总还有些时候,有些人有侥幸心理,或是现实情况实在无法让步,比如项目必须在某个日期上线; 在这些情况下,应该跟团队沟通些什么?Quora资深软件工程师Edmond Lau在自己的一篇博客中,给出了自己的看法。这篇博客名为《为了满足项目上线日期要求,我该怎么告诉我的团队工作更多时间?》

文章一开始,Edmond指出:

在试图告诉团队要加班之前,一定要确保一点:为了保证上线日期,你有一个切实可行的计划。否则,从长远来看的最佳策略是:根据团队当前开发能力和效率,重新定义要上线的功能;或者将截止日期重新调整为更现实的时间。

接下来,Edmond列举了自己的亲身经验。他曾参与过两个持续多月大型项目,而且团队成员都是高手,专注投入在这两个项目上。为了在某个日期之前上线,工程经理要求团队加班工作,每周60到70个小时。可是项目最后还是没有按时完成。Edmond这样回顾项目的后果:

这样的方式让一些团队成员筋疲力尽,有些人后来离开了,其他人用了很久才恢复过来。这两个项目的情况都无法判断:加班是否真能加快进度,加班的决策在当时也许很合理,但是长远来看,两只团队都深受其害。我们从这两个项目中学到惨痛的教训:不管你多么希望项目在某个日期前完成,都对它能否做到这一点毫无帮助。不要将积极思考与现实的乐观主义混为一谈。

Edmond列举了几个加班无助于项目尽快交付的原因:

  • 工作更多时间,每小时的效率将会降低。

如果你的团队已经习惯了每周40小时工作的生活节奏,而且一年来都是如此,那么很可能他们加班的效率会低于平常,甚至可能产生负面影响。疲累和睡眠减少会伤害认知功能,而且降低工作质量。

他还列举了150年以来的研究文献,证明这一点,而且有些文献甚至说明:如果加班过多,

有问题的工作累计带来的负面影响,会导致项目完成日期延迟,甚至比同样的团队按照每周40小时的工作节奏完成得还要晚。

  • 很可能你现在落后的时间进度比你以为的还要多。

在工程中,做出准确的项目估算非常难以做到。进度已经晚了,说明上个月的工作就估算得不够。所以,可能过去的工作估算少了,更有可能的是:整个项目都估算少了,包括剩下几个月的工作。

我们在项目开始时估算得要比项目结束时准确,因为开始时的工作重点放在可以深入理解的工作之上;到结束时,团队常常低估整合测试用去的时间> ,而且每个没有预料到的问题都会让进度延后一周甚至更多。这些效应叠加起来会产生更多延迟。

他引用了《人月神话》里面的话:

特别是没有为系统测试留出足够时间,这会带来灾难性后果。因为延迟是在项目日程结束时来临的,除非交付日期快到了,没人会意识到进度上的问题。

  • 额外的工作时间会让团队精疲力竭。
    Edmond引用了《人件》中的一个症状:“undertime”,也就是说:跟随加班而来的,常常是员工为了要补偿生活方面的损失,而在工作中耗费时间去做与工作无关之事。

我们的经验指出:额外工作时间的积极影响被大大夸大了,而其负面影响却从未有人考量。负面影响可能十分严重:错误、倦怠、失误不断增多、补偿用的“undertime”等等。

  • 额外的工作时间会伤害团队士气。
    在Edmond看来,加班之前,可能团队每个人都有自己的方式来完成每周40小时的工作,而且还能保证团队的凝聚力。一旦要加班,可能某些人就要多完成一些不能多加班的人要做的事情,这会影响彼此之间的关系,以前开心的团队,现在可能就不开心了。

  • 管理向截止日期的冲刺,需要耗费更多管理开销。

为了管理额外工作而举行更多站立会议和其他会议,这很常见,因为你希望团队要彼此沟通,保证每个人都在做正确的事情。然而,这种额外的沟通开销常常不会放在工作估算中。

  • 向截止日期的冲刺,会刺激产生更多技术债务。
    Edmond认为:为了赶进度而加班,几乎不可避免地会令得团队找捷径。而且在面临更多项目压力的情况下,这些技术债务很可能越积越多,将来一定要付出更多成本偿还。

不过,总有些现实情况不可避免,真到那时候,Edmond建议在沟通时要强调一下因素。

  • 要理解为什么进度落了这么远,找出根本原因,还要与团队沟通。

没赶上进度,是因为人们偷懒,还是因为项目要比想象的复杂,要占用更多时间?如果不能理解根本原因,也就不能有信心同样的问题不会在未来几个月出现。

  • 向团队说明更可行的项目计划和时间进度,说明为什么要加班才能真正赶上上线日期,还要说明怎么做。

仅仅告诉团队他们落后了还不够,如果不能得出更详细、更明确的计划达成目标,这就是一个警告信号,说明你比你以为的情况更落后。

  • 确保团队每个人都能理解、认同你的新进度安排。

如果关键成员不相信你的时间表可行,那就得好好想想:你可能无法在新的指定日期前完成你想完成的工作。要是无法做到所有人都认可,那么可能只有某些人认同加班,除了团队中会有不公平的感觉之外,可能你还是无法达成最后完成日期。

  • 重点放在项目、团队或是组织的整体工作目标上,说明为什么按时上线如此重要。

如果你无法把团队融合在一起,这就是另一个警讯:说明不是所有人都像你这么有动力去加班。

最后,Edmond指出:

如果,在接下来冲刺的两个月时间里,你发现实际进度比修正后的还要延迟,那就准备放弃这个冲刺吧。接受现实,知道你可能是在马拉松的半程发起了冲刺,而终点线比你想的要远得多。不再可能让团队更努力去解决问题了。减少损失,不妨想想怎么制订一个应急计划应对后续问题。

错过截止日期很糟糕,但要是错过了截止日期,而且让团队精疲力尽,而且没有应急计划,这就更糟糕了。

译文出处: InfoQ

前言

程序员的编程技能随着经验的积累,会逐步提高。我认为编程能力可以分为一些层次。
下面通过两个维度展开编程能力层次模型的讨论。
一个维度是编程技能层次,另一个维度是领域知识层次。

编程技能层次

编程技能层次,指的程序员设计和编写程序的能力。这是程序员的根本。

0段—非程序员:

初学编程者,遇到问题,完全是懵懵懂懂,不知道该怎么编程解决问题。也就是说,还是门外汉,还不能称之为“程序员”。计算机在他面前还是一个神秘的黑匣子。

1段—基础程序员:

学习过一段时间编程后,接到任务,可以编写程序完成任务。
编写出来的代码,正常情况下是能够工作的,但在实际运行中,碰到一些特殊条件就会出现各类BUG。也就是说,具备了开发Demo软件的能力,但开发的软件真正交付给客户使用,恐怕会被客户骂死。
程序员程序是写好了,但到底为什么它有时能正常工作,有时又不行,程序员自己也不知道。
运行中遇到了bug,或者需求改变,需要修改代码或者添加代码,很快程序就变得结构混乱,代码膨胀,bug丛生。很快,就连最初的开发者自己也不愿意接手维护这个程序了。

2段—数据结构:

经过一段时间的编程实践后,程序员会认识到“数据结构+算法=程序”这一古训的含义。他们会使用算法来解决问题。进而,他们会认识到,算法本质上是依附于数据结构的,好的数据结构一旦设计出来,那么好的算法也会应运而生。
设计错误的数据结构,不可能生长出好的算法。
记得某一位外国先贤曾经说过:“给我看你的数据结构!”

3段—面向对象:

再之后,程序员就会领略面向对象程序设计的强大威力。大多数现代编程语言都是支持面向对象的。但并不是说,你使用面向对象编程语言编程,你用上了类,甚至继承了类,你就是在写面向对象的代码了。
我曾经见过很多用Java,Python,Ruby写的面向过程的代码。
只有你掌握了接口,掌握了多态,掌握了类和类,对象和对象之间的关系,你才真正掌握了面向对象编程技术。
就算你用的是传统的不支持面向对象的编程语言,只要你心中有“对象”,你依然可以开发出面向对象的程序。
如,我用C语言编程的时候,会有意识的使用面向对象的技巧来编写和设计程序。用struct来模拟类,把同一类概念的函数放在一起模拟类。如果你怀疑用C语言是否能编写出面向对象的代码,你可以看一下Linux内核,它是用C语言编写的,但你也可以看到它的源代码字里行间散发出的浓浓的“对象”的味道。

真正掌握面向对象编程技术并不容易。

在我的技术生涯中,有两个坎让我最感头疼。

  1. 一个坎是Dos向Windows开发的变迁过程中,框架的概念,很长一段时间我都理解不了。Dos时代,都是对函数库的调用,你的程序主动调用函数。Windows时代,则换成了框架。就算是你的main程序,其实也是被框架调用的。UI线程会从操作系统获取消息,然后发送给你的程序来处理。Java程序员熟悉的Spring框架,也是这样一个反向调用的框架。
    现在因为“框架”这个术语显得很高大上,因此很多“类库”/“函数库”都自称为“框架”。在我看来这都是名称的滥用。
    “类库”/“函数库”就是我写的代码调用它们。
    “框架”就是我注册回调函数到框架,框架来调用我写的函数。

  2. 另一个坎就是面向对象。很长一段时间我都不知道应该怎么设计类和类之间的关系,不能很好的设计出类层次结构来。
    我记得当时看到一本外国大牛的书,他讲了一个很简单、很实用的面向对象设计技巧:“叙述问题。然后把其中的名词找出来,用来构建类。把其中的动词找出来,用来构建类的方法”。虽然这个技巧挺管用的,但也太草根了点,没有理论依据,也不严谨。如果问题叙述的不好,那么获得的类系统就会是有问题的。

掌握面向对象思想的途径应该有很多种,我是从关系数据库中获得了灵感来理解和掌握面向对象设计思想的。
在我看来,关系数据库的表,其实就是一个类,每一行记录就是一个类的实例,也就是对象。表之间的关系,就是类之间的关系。O-Rmapping技术(如Hibernate),用于从面向对象代码到数据库表之间的映射,这也说明了类和表确实是逻辑上等价的。
既然数据库设计和类设计是等价的,那么要设计面向对象系统,只需要使用关系数据库的设计技巧即可。
关系数据库表结构设计是很简单的:

  1. 识别表和表之间的关系,也就是类和类之间的关系。是一对一,一对多,多对一,还是多对多。这就是类之间的关系。
  2. 识别表的字段。一个对象当然有无数多的属性(如,人:身高,体重,性别,年龄,姓名,身份证号,驾驶证号,银行卡号,护照号,港澳通行证号,工号,病史,婚史etc),我们写程序需要记录的只是我们关心的属性。这些关心的属性,就是表的字段,也就是类的属性。“弱水三千,我取一瓢饮”!

4段—设计模式:

曾经在网上看到这样一句话:“没有十万行代码量,就不要跟我谈什么设计模式”。深以为然。
记得第一次看Gof的设计模式那本书的时候,发现虽然以前并不知道设计模式,但在实际编程过程中,其实还是自觉使用了一些设计模式。设计模式是编程的客观规律,不是谁发明的,而是一些早期的资深程序员首先发现的。
不用设计模式,你也可以写出满足需求的程序来。但是,一旦后续需求变化,那么你的程序没有足够的柔韧性,将难以为继。而真实的程序,交付客户后,一定会有进一步的需求反馈。而后续版本的开发,也一定会增加需求。这是程序员无法回避的现实。

写UI程序,不论是Web,Desktop,Mobile,Game,一定要使用MVC设计模式。否则你的程序面对后续变化的UI需求,将无以为继。

设计模式,最重要的思想就是解耦,通过接口来解耦。这样,如果将来需求变化,那么只需要提供一个新的实现类即可。
主要的设计模式,其实都是面向对象的。因此,可以认为设计模式是面向对象的高级阶段。只有掌握了设计模式,才能认为是真正彻底掌握了面向对象设计技巧。

我学习一门新语言时(包括非面向对象语言,如函数式编程语言),总是会在了解了其语法后,看一下各类设计模式在这门语言中是如何实现的。这也是学习编程语言的一个窍门。

5段–语言专家:

经过一段时间的编程实践,程序员对某一种常用的编程语言已经相当精通了。有些人还成了“语言律师”,擅长向其他程序员讲解语言的用法和各种坑。
这一阶段的程序员,常常是自己所用语言的忠实信徒,常在社区和论坛上和其他语言的使用者争论哪一种语言是最好的编程语言。他们认为自己所用的语言是世界上最好的编程语言,没有之一。他们认为,自己所用的编程语言适用于所有场景。他们眼中,只有锤子,因此会把所有任务都当成是钉子。

6段–多语言专家:

这一个阶段的程序员,因为工作关系,或者纯粹是因为对技术的兴趣,已经学习和掌握了好几种编程语言。已经领略了不同编程语言不同的设计思路,对每种语言的长处和短处有了更多的了解。
他们现在认为,编程语言并不是最重要的,编程语言不过是基本功而已。
他们现在会根据不同的任务需求,或者不同的资源来选择不同的编程语言来解决问题,不再会因为没有使用某一种喜爱的编程语言开发而埋怨。

编程语言有很多种流派和思想,有一些编程语言同时支持多种编程范式。

静态类型编程范式

采用静态类型编程范式的编程语言,其变量需要明确指定类型。代表语言:C,C++,Pascal,Objective-C,Java,C#,VB.NET,Swif,Golang

这样做的好处是:

  1. 编译器可以在编译时就能找出类型错误。
  2. 编译器编译时知道类型信息,就可以提高性能。

这种范式认为,程序员肯定知道变量的类型,你丫要是不知道变量的类型,那你就别混了!编译时,程序会报错。
Swift和Go语言都是静态类型编程语言,但它们都不需要明确指定类型,而是可以通过推断由编译器自动确定其类型。

动态类型编程范式

采用静态类型编程范式的编程语言,其变量不需要明确指定类型。任意变量,可以指向任意类型的对象。代表语言:Python,Ruby,JavaScript

动态类型的哲学可以用鸭子类型(英语:ducktyping)这个概念来概括。JamesWhitcombRiley提出的鸭子测试可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

这种范式认为,程序员肯定知道变量的类型和它支持的方法和属性,你丫要是不知道变量的类型,那你就别混了!运行时程序会崩溃!程序崩溃怨谁?怨你自己呗,你不是合格的程序员!

动态类型的好处是:
不需要明确定义接口和抽象类型。只要一个类型支持需要的方法和属性,那么就OK。程序会相当灵活和简单。C++,Java,C#视之为命脉的接口/基类,在动态语言这里都视如无物!

缺点是:

  1. 如果类型不对,编译器也无法找到错误,而是运行时程序崩溃。
  2. 因为编译器不知道变量的类型,因此无法优化性能。

面向对象编程范式

面向对象编程范式,从上世纪70年代末开始兴起。它支持类和类的实例作为封装代码的模块。代表语言:Smalltalk,C++,Objective-C,Java,C#,VB.NET,Swift,Go,Python,Ruby,ActionScritp,OCaml.

早期编程语言都是面向过程的。就是顺序,条件,循环,构成一个个函数。随着代码规模的增大,人们发现有必要对代码进行模块化。一个概念对应的代码放在一个文件中,这样便于并发开发和进行代码管理。

人们还发现了“程序=数据结构+算法”的规律。因此,一个概念对应的数据结构和函数应该放在一个文件中。这就是类的概念。

面向对象编程范式,确实极大地提高了生产效率,因此得到了广泛的应用,因此在语言层面支持面向对象编程范式的语言是极多的。

C语言尽管在语言层面上并不支持面向对象编程范式,但现代的C语言开发都会应用面向对象的模块化思想,把同一类的数据结构和函数放在一个文件中,采用类似的命名方式。

毕竟C语言没有在语言层面上支持面向对象,因此就有很多程序员想给C语言添加面向对象支持。其中的代表是C++和Objective-C。

C++是一种新的语言,但大部分语言元素是和C兼容的。

Objective-C是完全兼容的C的。Objective-C是给C添加了薄薄的一层语法糖以支持接口(就是其他语言的类)和协议(就是其他语言的接口)。甚至,Objective-C一开始的实现,就是一个C语言的预编译器。Objective-C坦白讲,除了添加的语法不太符合C流外,实际上其面向对象系统设计是相当精妙的。乔布斯早年慧眼识珠,把Objective-C收人囊中,因为封闭于Apple/NextStep系统内,因此少有人知。随着iOs系统的普及,Objective-C近几年才名满天下。

函数式编程范式

函数式编程范式,是一些数学家发明的编程语言,他们认为程序就是数学函数嘛。代表语言:Lisp,Erlang,JavaScript,OCaml,Prog

有很多大牛极力鼓吹过函数式编程语言,认为其极具革命性。但我认为他们过高估计了函数式编程范式的威力,我并不认为函数式编程范式相对于面向对象编程范式有何高明之处。

函数式编程语言,核心就是函数,它们没有Class类的概念。但它的函数又不是传统面向过程语言的函数,它的函数支持“闭包”的概念。

在我看来,函数式编程语言的函数,也就是“闭包”,说白了,其实就是“类”。编程语言发展到今天,就是需要模块化,就是需要把“数据结构”和“算法”结合起来。不论何种语言,不把它们结合起来的编程方式,都是没有出路的。

面向对象编程语言,用类把“数据结构”和“算法”结合起来。类的核心是“数据结构”,也就是其“属性”,而不是“算法”,其“函数”。在类中,是函数依附于属性。

而函数式编程语言,用闭包把“数据结构”和“算法”结合起来。是函数能够抓取外部的字段。是“属性”依附于“函数”。

“类”本质上和“闭包”是等价的。现在很多面向对象编程语言都加上了对闭包的支持。观察其代码,我们可以发现,它们实际上都是用“类”来实现“闭包”的。

“类”和“闭包”谁更易用?明显是“类”。

而“闭包”更简洁一些,因此“闭包”在面向对象编程语言中常用来替换匿名类。只有一个函数的类,写成一个类太麻烦,不如写成闭包,更加简洁。

吐槽一下OCaml语言,其前身Caml语言本身是一种挺好的函数式语言,硬生生添加了一套完整的面向对象机制,同时支持面向对象和函数式编程范式,很容易像C++一样脑裂的。

也有很多面向对象语言控看着JavaScript嫌烦,总是想把面向对象支持添加到JavaScript上。ActionScript就是其中一种尝试。我用过,真的是和Java没多少区别了。

再吐槽一下ExtJS。当初选型Web前端开发框架时比较了ExtJS和JQuery。

ExtJS明显是Java高手开发的,硬生生用JavaScript模拟Swing的设计思想,搞了一套UI库。

JQuery开发者明显是领悟了JavaScript的函数式编程范式,依据JavaScript的动态函数式编程语言的特点打造了一套UI库,立刻秒杀ExtJS。

由ExtJS和JQuery的故事,我们可以看到多语言编程能力是多么的重要。ExtJS的作者精通并喜爱Java,因此他把手术刀JavaScript当做锤子Java使,一通乱敲,费力不讨好。

函数式编程语言,还有尾递归等一些小技巧。尾递归可以不用栈,防止递归调用时栈溢出。

模板编程范式

模板编程,就是把类型作为参数,一套函数可以支持任意多种类型。代表语言:C++

模板编程的需求,是在C++开发容器库的时候发明的。因为容器需要保存任意类型的对象,因此就有了泛型的需求。

C++的模板编程,是在编译时,根据源码中的使用情况,创建对应类型的代码。除了C++这种方式,Java,C#也有类似的机制,叫做“泛型”,但它们的实现方式和C++的模板很不同。它们的编译器不会生成新的代码,而是使用强制类型转换的方式实现。

在没有模板/泛型的编程语言中,怎样在容器中存放对象呢?存取公共基类类型(Java,C#)的对象,或者void*指针(C)即可,取出时自己强制类型转换为实际类型。动态类型语言,不关心类型,更是无所谓了,随便什么对象直接往容器里扔进去,取出来直接用即可。

一些C++高手又在模板的基础上搞出了“模板元编程”。因为模板编程,就是C++的编译器搞定的嘛,模板元编程就是让编译器运算,编译完结果也就算出来了。我不知道除了研究和炫技,这玩意有啥用?

小结

一门语言是否值得学习,我认为有几个标准:

  1. 是否要用,要用就得学,这么没有疑问的。毕竟我们都要吃饭的嘛。
  2. 其语言特性是否给你耳目一新的感觉。如果是,那就值回票价了。如Go语言废掉了异常,改用返回多值。我深以为然。我其实已经主动不用异常好多年了。因为,我觉得既然C不支持异常也活得很好,为什么需要异常呢?出错了,返回错误码。无法挽回的错误,直接Abort程序就可以嘛!而且,异常实际上是违反面向过程编程原则的。一个函数应该只有一个入口一个出口。抛出异常就多了出口了。
  3. 是否擅长某一个领域。如果你手里只有一把锤子,那么你就只能把所有任务都当做钉子猛锤一通。但如果工具箱里有多种工具,那面对不同的任务就得心应手多了。

7段—架构设计

还需要掌握架构设计的能力,才能设计出优秀的软件。架构设计有一些技巧:

1、分层

一个软件通常分为:

表现层–UI部分
接口层–后台服务的通讯接口部分
服务层–实际服务部分
存储层—持久化存储部分,存储到文件或者数据库。

分层的软件,可以解耦各个模块,支持并行开发,易于修改,易于提升性能。

2、SOA

模块之间通过网络通讯互相连接,松耦合。每一个模块可以独立部署,可以增加部署实例从而提高性能。每一个模块可以使用不同的语言和平台开发,可以重用之前开发的服务。SOA,常用协议有WebService,REST,JSON-RPC等。

3、性能瓶颈

1). 化同步为异步。
用内存队列(Redis),工作流引擎(JBpm)等实现。内存队列容易丢失数据,但是速度快。工作流引擎会把请求保存到数据库中。
通过化同步请求为异步请求,基本上99.99%的性能问题都可以解决。

2). 用单机并行硬件处理。
如,使用GPU,FPGA等硬件来处理,提高性能。

3). 用集群计算机来处理。
如,Hadoop集群,用多台计算机来并行处理数据。
自己的软件栈中,也可以把一个模块部署多份,并行处理。

4). 用cache来满足请求。常用的内容加热cache后,大量的用户请求都只是内存读取数据而已,性能会得到很大的提升。
cache是上帝算法,记得好像它的性能只比最佳性能低一些,就好像你是上帝,能够预见未来一样。现在X86CPU遇到了主频限制,CPU提升性能的主要途径就是增加高速Cache了。

4、大系统小做

遇到大型系统不要慌,把它切分成多个模块,用多个小程序,通过SOA协作来解决。这秉承了Unix的设计思想。Unix上开发了大量单一目的的小程序,它主张用户通过管道来让多个小程序协作,解决用户的需求。当然,管道方式通讯限制太多,不够灵活。因此,现在我们可以通过URI,通过SOA的方式来让多个程序协作。Andorid和iOS上的应用程序,现在都是通过URI实现协作的。这也算是Unix设计思想的现代发展吧?!

5、Sharding切片

现在有一个潮流,就是去IOE。I-IBM大型机,O-Oracle数据库,E-EMC存储。之前,大型系统常用IOE去架构,在大型机上部署一个Oracle数据库,Oracle数据库用EMC存储保存数据。IOE是当今最强的计算机,数据库和存储。但他们面对海量系统也有抗不住的一天。

Oracle数据库是Shareeverything的,它可以在一个计算机集群(服务器节点不能超过16个)上运行。计算机集群都共用一个存储。

去IOE运动,标志着ShareEverything模式的破产。必须使用ShareNothing,系统才能无限扩展。

用MySQL数据库就可以应付任意规模的数据了。前提是,你会Sharding分片。把大系统切分成若干个小系统,切分到若干台廉价服务器和存储上。更Modern一些,就是切分到大量虚拟机上。

如,铁道部的12306网站。我们知道火车票都是从属于某一列列车的。那么我们把每一个列车作为一个单元来切分,就可以把12306网站切分成几千个模块。一台虚拟机可以承载若干个模块。当某些列车成为性能瓶颈之后,就可以把它们迁移到独立的虚拟机上。即使最终有部分列出服务不可用,系统也不会完全不可用。

12306网站,只有一个全局的部分,就是用户登录。这个可以交给第三方负责。如可以让用户用微信,微博,qq等账户登录。

也可以自己实现用户登录服务。还是用切片的方式用多台Redis服务器提供服务。Redis服务器存储每一个登录用户的sessionId和userId,角色,权限等信息。sessionId是随机生成的,可选择其部分bit用于标识它在哪一个Redis服务器上。用户登录后,把sessionId发给客户。用户每次请求时把sessionId发回给服务器。服务器把sessionId发给Redis服务器查询得到其用户信息,对用户请求进行处理。如果在redis服务器上找不到sessionId,则让用户去登录。即使所有注册用户同时登陆,也不需要太多的内存。而且,可以在session内存过多时,删除最早登陆的用户的session,强制他再次登陆。同时活跃的用户数不会太多。

领域知识层次

前面的所有层次,都是关注编程本身的技能,说白了,就是基本功,本身并不能产生太大的价值。但有太多的程序员浪费太多的时间在那些筑基的层次上。

有些程序员特别喜欢钻研编程语言,每有一种新的编程语言出来或者旧语言被热炒,就会投入精力进去研究。我就是其中之一,浪费了很多精力在编程语言上,在奇技淫巧上。

我觉得C++语言是一个特别大的坑。刚开始是作为面向对象的C被开发的。后来发现了模板编程,就大力鼓吹模板编程和进一步的模板元编程。最近又推出了C++11,C++14等新标准,进一步添加了很多新东西,函数式编程,类型推断等。C++过分复杂,太多的坑消耗了大量程序员的大量精力。我使用C++时,只使用面向对象部分和模板部分,其他过于精深的特性都不使用。

计算机科学是一个面相当广泛的学科,有很多领域知识需要和值得我们深入研究,我们才能写出有价值的程序来。软件必须要和行业结合起来,要落地才有价值。仅仅研究编程技巧,不懂领域知识是写不出有价值的程序的。

计算机科学领域有很多,列举一些如下:

  • 存储—-块设备,文件系统,集群文件系统,分布式文件系统,光纤SCSI,iSCSI,RAID等。

  • 网络—-以太网,光纤网,蜂窝网络,WIFI,VLAN等。

  • 计算机体系结构,主要就是CPU指令集。x86,ARM等。

  • USB协议。需要知道URB包。

  • PCI协议,PCI-E协议。现代计算机的外设都是PCI协议和PCI-E协议的。显卡现在全是通过 PCI-E协议连接到计算机上的。相对来说减少了很多需要学习的知识。搞虚拟化就需要深入掌握PCI协议。

  • 图像处理–图像压缩,视频实时编码等。

  • 3D游戏

  • 关系数据库

  • NoSQL数据库

  • 操作系统

  • 分布式操作系统

  • 编译原理

  • 机器学习–现在大数据要用哦!

了解这些领域知识,也包括了解该领域现有的商用硬件、商用软件和开源软件。很多时候,你要完成的工作,已经有现成的工具了。你只要使用现成的工具就可以完成任务,不需要进行开发。有时候,只需要组合现有的工具,写一些脚本就可以完成任务。

如,我一次要实现一个双向同步任务。找到了一个优秀的开源软件Unison,编写一下配置文件就圆满地完成了任务。不需要编写任何代码。

还有一次,要做高可用,用Python调用了几个开源软件就轻松实现了。

编写安装程序,定制操作系统,知道了操作系统的领域知识,写几行脚本就可以轻松搞定。

不具备领域知识的人,就可能不得不进行大量无谓的开发,甚至开发很久之后才发现,这根本就是一条死路。

另外,扎实的领域知识,可以大大提高编程调试、查错的能力。知道编译器和编程语言运行时工作原理,就能快速根据编译错误和警告信息修改代码。

知道操作系统底层运行机制,就能快速找到运行时错误的问题根源。如,有一次我编写一个windows升级服务程序。它是一个windows服务,需要执行dos脚本,这个脚本会替换掉这个windows服务本身。发现有时脚本执行无效,查了一晚上,发现当windows服务安装后,第一次启动就执行脚本时就会有权限问题,log都正确,但实际执行这个脚本没有任何效果。但一旦windows服务程序启动一次之后就ok。这必然是windows操作系统底层安全机制的问题,因为我对Windows内核了解不多,因此花了很长时间才发现这个问题,并对造成这个问题的根源并不清楚。

0段—领域知识菜鸟

对领域知识没有多少认知,通过搜索引擎找到一些该领域的软件和硬件的介绍性文章,按照文章指示配置和使用软件。勉强能够使用现有软硬件。

1段—领域知识行家

了解领域内常用硬件,深入掌握领域内常用软件的配置和使用技巧。能够使用现有软硬件熟练搭建解决方案,能够解决实际工作中遇到的种种问题。

2段—领域知识专家

当你不仅仅掌握了该领域的软件和工具,知道怎么用,还知道其原理,“知其然,也知其所以然”,就是该领域的知识专家了。

你知道网络协议的原理,你才能在网络出现问题时知道是哪里可能出现了问题。是mac冲突,ip冲突,还是网络环路?

你知道存储的原理,你才能知道为什么这种存储方式不适合虚拟化,那种存储方式适合虚拟化,另一种方式适合资料备份。

你知道PCI协议,你才能知道你怎样才能虚拟化一个硬件设备。

你知道网卡硬件协议,你才能模拟出一个虚拟机能正常使用的虚拟网卡。

你知道视频编码格式和原理,才能知道什么视频格式占用带宽最少,什么视频格式占用CPU最少。

你了解IntelVT/Amd V指令集,才能知道虚拟化是怎样实现的。

你明白工作流其实就是状态机,在遇到复杂工作流程时,你才能知道怎样设计满足要求的工作流引擎。

3段—科学家

你是领域知识专家,但你的知识都是来自于书本,来自于其他人的。

如果你满足于当领域知识专家,你只能拾人牙慧,永远别想超越。别人的研究成果,未必愿意告诉你。当别人告诉你的时候,它可能已经发现了更新的理论,并且新一代产品可能马上就要发布了。

科学家是探索未知,勇于创新的人,是推动人类社会进步的人。

传说,思科的一位高管曾经半开玩笑地说过:“如果思科停止了新技术的研发,华为就会找不着方向”。这是在嘲笑华为只是处在领域知识专家的水平,只能山寨无法超越。我不知道华为的实际情况,但希望现在的华为已经走到了领跑者的位置。

欧文·雅各布斯发现了CDMA码分多址的原理,并发现它在通讯上大有可为,组建了高通公司。高通公司主要以专利授权费为生,它雇佣了大量科学家在通讯领域展开研究。有人说高通是专利流氓。这些人不明白知识的价值。在他们眼里,Windows的合理价格就应该是5元钱,一张光盘的价格。iPhone就应该是1000多元裸机的价格。高通是专利流氓,那你也流氓一个CDMA,LTE出来给我看看!

X86芯片在设计上没有考虑虚拟化。因此会有所谓的“虚拟化漏洞”出现。就是说,一些CPU特权指令执行时,在虚拟机环境下不会抛出异常,因此就无法切换到Host。这样,X86芯片上就无法运行虚拟机。

VmWare公司是由美国的几位科学家在1998年创建的。他们发现可以使用二进制翻译的技术,在X86计算机上运行虚拟机。

Xen虚拟化软件也是几位科学家发明的。他们发现只要修改虚拟机操作系统和Host操作系统的内核,在需要执行“虚拟化漏洞”指令时直接调用Host的功能,就可以实现虚拟化,而且大大提高了虚拟机的运行性能。

后来,Intel为自己的芯片添加了IntelVT指令集,Amd为自己的芯片添加了AmdV指令集,弥补了“虚拟化漏洞”。于是就有了KVM虚拟机软件,它直接用CPU硬件指令实现虚拟化。

KVM在执行CPU指令时,是直接在物理CPU上运行的,因此效率极高。但是,虚拟机运行虚拟外设时,就必须用软件模拟,因此虚拟机的IO访问速度很慢。

IBM科学家RustyRussell,借鉴了Xen的研发经验,创建了VirtIO技术。就是在虚拟机中编写一套PCI虚拟设备和驱动,这套虚拟PCI设备有一块虚拟设备内存。这个虚拟设备内存Host是可以访问的,虚拟机通过VirtIO驱动程序也可以访问。也就是一块内存在虚拟机和Host中共享,这就解决了虚拟机的IO性能问题。

再讲一个搜索引擎的故事:

很久以前,我要给一个程序添加搜索功能。刚开始使用sql查询实现,发现实在太慢了。后来找了开源的Lucene项目。它使用反向索引技术,通过在文件中创建反向索引,大大提高了搜索速度。

Google的两位创始人发现了html中link的秘密,他们发现可以通过html页面的link关系来为每一个html页面设置权重。也就是PageRank算法。于是,Google的自动搜索引擎击败了Yahoo人工分类的搜索引擎。

OK,利用反向索引技术和PageRank,以及一个简单的html爬虫机器人,我们就可以创建一个搜索引擎了。但是,互联网很大,每天产生大量新网页,要为整个互联网建立反向索引是很困难的。

若干年后Google又公开了三篇论文:Googlefs,Mapreduce,Bigtable。于是Lucene项目的开发者根据Google的Mapreduce论文开发了Hadoop项目。MapReduce就是使用大量计算机存储数据并计算,最后汇总结果。使用Hadoop+反向索引+PageRank,就可以创建搜索引擎了。Yahoo,Baidu等公司纷纷基于Hadoop开发了自己的搜索引擎。

但是,其他公司的搜索引擎效果还是没法和Google相比。这一点我们程序员最清楚。像我,就总是翻墙出去,只为了Google一下。

Google黑板报上发表了吴军博士的一些文章,其中介绍了很多机器学习方面的知识。从文中可以知道,Google其实使用机器学习来分析搜集到的页面。Google明显不会把这个公式公开出来。即使有一天Google真的公开了这个公式,那么可以想见Google肯定又研发出了更加犀利的秘籍,山寨货的搜索引擎效果还是比不上Google的。

山寨是通向创新的必由之路。在成为领域的领头羊和领导者之前,必然要经过学习,模仿的阶段。但要成为行业的老大,成为Champion,必须勇于弯道超车,勇敢地走上创新之路,成为真正的科学家,真正的大牛!

总结

编程能力可分为两个维度:一个是编程技能水平,另一个是领域知识水平。

有些程序员可能把精力都花在提升编程技能上了,领域知识知之甚少,这其实在日常工作中也是极其有害的。有些需求可能早已经有了现成、开源免费的解决方案,或者只需要组合几个现有软件就可以快速搞定,而他们却不得不自己花大量时间去开发。另外,缺少领域知识,在程序出现非预期状况时,很难快速定位到问题的根源,很难解决bug。

来源:良少的博客(@虚拟化良少)
链接:http://blog.csdn.net/shendl/article/details/43835421

前记

今天在使用lumen自带的类Log的时候,发现每次记录一个Log,总会在storage/logs/lumen.log文件末尾追加一条记录,那么问题来了,时间久了,lumen.log 这个文件就会非常大,而且也不利于管理,于是打算写一个自己的log类,在各位网友博客小伙伴的帮助下,终于完成了,记录一下写Log的历程,希望能帮助一些后来打算改造Log类的小伙伴吧。

Lumen

开始前,先让我们了解下lumen:

Lumen 是一个由 Laravel 元件搭建而成的微框架, 由 Laravel 官方维护. Lumen 为速度而生, 是当前最快的 PHP 框架之一, 甚至比类似的微框架 Silex 速度还要快.

Lumen 比其他微框架的优点是, 构建在 Laravel 之上, 使其具备 Laravel 强大的功能, 如 路由, 依赖注入, Eloquent ORM, 数据库迁移管理, 队列和计划任务等.

Laravel 本来就是一个功能齐全, 速度飞快的框架, 但是 Lumen 因为去除了很多 Laravel 的配置和可自定义的选项, 速度越加飞快, 毫秒必争.

飞快的速度, 再加上 Laravel 非常方便的功能, 使用 Lumen 开发应用会是非常愉悦的体验.

Lumen 专为微服务或者 API 设计, 举个例子, 如果你的应用里面有部分业务逻辑的请求频率比较高, 就可以单独把这部分业务逻辑拿出来, 使用 Lumen 来构建一个小 App.

因为 Lumen 是对 Laravel 优化了框架的加载机制, 所以 Lumen 对资源的要求少很多.

当然, 你可以使用 队列系统 与你的主 Laravel 应用进行交互. Laravel 和 Lumen 从一开始就是设计成能一起很好的工作, 并且, 配合使用, 允许你构架一个强大的, 以微服务为驱动的应用程序.

Lumen 同时也非常适用于构建 API 接口, 此类型的应用通常情况下不需要具备 全栈框架 的所有功能, 如 HTTP 会话管理, Cookies, 和模版系统.

历程

简单的了解了Lumen,那么接下来就来介绍Log的封装过程吧,在介绍前,各位看官如果对lumen的加载机制不清楚,不妨看下这篇文章:Laravel 架构中的 Container/ServiceProvider/Facade

了解系统的Log类

Lumen文档 错误和日志 介绍了如何使用Lumen的错误和日志类,而且该日志记录器提供了RFC 5424中定义的七种日志级别:alert, critical, error,warning, notice, info 和 debug。我们看到以下代码

1
use Log;

既然Log类可以直接应用,说明Lumen在初始化的时候就定义了 Log 的 namespace 或者 起了 Log的 aliases, 首先找namespace,没有找到定义Log 的 namespace的地方,那肯定是起了 Log 的别名,找了些资料,终于发现Log的加载流程:

  1. 根目录下bootstrap\app.php文件有一行代码:
    1
    $app->withFacades();

这一行是调用 vendor\laravel\lumen-framework\src\Application->withFacades() 方法,会加载Lumen的一些Facades,下面列了 application的几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* Create a new Lumen application instance.
*
* @param string|null $basePath
* @return void
*/

public function __construct($basePath = null)
{

date_default_timezone_set(env('APP_TIMEZONE', 'Asia/Chongqing'));

$this->basePath = $basePath;
$this->bootstrapContainer();
$this->registerErrorHandling();
}

/**
* Bootstrap the application container.
*
* @return void
*/

protected function bootstrapContainer()
{

static::setInstance($this);

$this->instance('app', $this);
$this->instance('path', $this->path());

$this->registerContainerAliases();
}
/**
* Register the facades for the application.
*
* @return void
*/

public function withFacades()
{

Facade::setFacadeApplication($this);

if (! static::$aliasesRegistered) {
static::$aliasesRegistered = true;

class_alias('Illuminate\Support\Facades\App', 'App');
class_alias('Illuminate\Support\Facades\Auth', 'Auth');
class_alias('Illuminate\Support\Facades\Bus', 'Bus');
class_alias('Illuminate\Support\Facades\DB', 'DB');
class_alias('Illuminate\Support\Facades\Cache', 'Cache');
class_alias('Illuminate\Support\Facades\Cookie', 'Cookie');
class_alias('Illuminate\Support\Facades\Crypt', 'Crypt');
class_alias('Illuminate\Support\Facades\Event', 'Event');
class_alias('Illuminate\Support\Facades\Hash', 'Hash');
class_alias('Illuminate\Support\Facades\Log', 'Log');
class_alias('Illuminate\Support\Facades\Mail', 'Mail');
class_alias('Illuminate\Support\Facades\Queue', 'Queue');
class_alias('Illuminate\Support\Facades\Request', 'Request');
class_alias('Illuminate\Support\Facades\Schema', 'Schema');
class_alias('Illuminate\Support\Facades\Session', 'Session');
class_alias('Illuminate\Support\Facades\Storage', 'Storage');
class_alias('Illuminate\Support\Facades\Validator', 'Validator');
}
}

/**
* Register container bindings for the application.
*
* @return void
*/

protected function registerLogBindings()
{

$this->singleton('Psr\Log\LoggerInterface', function () {
return new Logger('lumen', [$this->getMonologHandler()]);
});
}

/**
* Get the Monolog handler for the application.
*
* @return \Monolog\Handler\AbstractHandler
*/

protected function getMonologHandler()
{

return (new StreamHandler(storage_path('logs/lumen.log'), Logger::DEBUG))
->setFormatter(new LineFormatter(null, null, true, true));
}

/**
* Register the core container aliases.
*
* @return void
*/

protected function registerContainerAliases()
{

$this->aliases = [
'Illuminate\Contracts\Foundation\Application' => 'app',
'Illuminate\Contracts\Auth\Guard' => 'auth.driver',
'Illuminate\Contracts\Auth\PasswordBroker' => 'auth.password',
'Illuminate\Contracts\Cache\Factory' => 'cache',
'Illuminate\Contracts\Cache\Repository' => 'cache.store',
'Illuminate\Contracts\Config\Repository' => 'config',
'Illuminate\Container\Container' => 'app',
'Illuminate\Contracts\Container\Container' => 'app',
'Illuminate\Contracts\Cookie\Factory' => 'cookie',
'Illuminate\Contracts\Cookie\QueueingFactory' => 'cookie',
'Illuminate\Contracts\Encryption\Encrypter' => 'encrypter',
'Illuminate\Contracts\Events\Dispatcher' => 'events',
'Illuminate\Contracts\Filesystem\Factory' => 'filesystem',
'Illuminate\Contracts\Hashing\Hasher' => 'hash',
'log' => 'Psr\Log\LoggerInterface',
'Illuminate\Contracts\Mail\Mailer' => 'mailer',
'Illuminate\Contracts\Queue\Factory' => 'queue',
'Illuminate\Contracts\Queue\Queue' => 'queue.connection',
'Illuminate\Redis\Database' => 'redis',
'Illuminate\Contracts\Redis\Database' => 'redis',
'request' => 'Illuminate\Http\Request',
'Illuminate\Session\SessionManager' => 'session',
'Illuminate\Contracts\View\Factory' => 'view',
];
}

/**
* The available container bindings and their respective load methods.
*
* @var array
*/

public $availableBindings = [
'auth' => 'registerAuthBindings',
'auth.driver' => 'registerAuthBindings',
'Illuminate\Contracts\Auth\Guard' => 'registerAuthBindings',
'auth.password' => 'registerAuthBindings',
'Illuminate\Contracts\Auth\PasswordBroker' => 'registerAuthBindings',
'Illuminate\Contracts\Broadcasting\Broadcaster' => 'registerBroadcastingBindings',
'Illuminate\Contracts\Bus\Dispatcher' => 'registerBusBindings',
'cache' => 'registerCacheBindings',
'Illuminate\Contracts\Cache\Factory' => 'registerCacheBindings',
'Illuminate\Contracts\Cache\Repository' => 'registerCacheBindings',
'config' => 'registerConfigBindings',
'composer' => 'registerComposerBindings',
'cookie' => 'registerCookieBindings',
'Illuminate\Contracts\Cookie\Factory' => 'registerCookieBindings',
'Illuminate\Contracts\Cookie\QueueingFactory' => 'registerCookieBindings',
'db' => 'registerDatabaseBindings',
'Illuminate\Database\Eloquent\Factory' => 'registerDatabaseBindings',
'encrypter' => 'registerEncrypterBindings',
'Illuminate\Contracts\Encryption\Encrypter' => 'registerEncrypterBindings',
'events' => 'registerEventBindings',
'Illuminate\Contracts\Events\Dispatcher' => 'registerEventBindings',
'Illuminate\Contracts\Debug\ExceptionHandler' => 'registerErrorBindings',
'files' => 'registerFilesBindings',
'filesystem' => 'registerFilesBindings',
'Illuminate\Contracts\Filesystem\Factory' => 'registerFilesBindings',
'hash' => 'registerHashBindings',
'Illuminate\Contracts\Hashing\Hasher' => 'registerHashBindings',
'log' => 'registerLogBindings',
'Psr\Log\LoggerInterface' => 'registerLogBindings',
'mailer' => 'registerMailBindings',
'Illuminate\Contracts\Mail\Mailer' => 'registerMailBindings',
'queue' => 'registerQueueBindings',
'queue.connection' => 'registerQueueBindings',
'Illuminate\Contracts\Queue\Factory' => 'registerQueueBindings',
'Illuminate\Contracts\Queue\Queue' => 'registerQueueBindings',
'redis' => 'registerRedisBindings',
'request' => 'registerRequestBindings',
'Illuminate\Http\Request' => 'registerRequestBindings',
'session' => 'registerSessionBindings',
'session.store' => 'registerSessionBindings',
'Illuminate\Session\SessionManager' => 'registerSessionBindings',
'translator' => 'registerTranslationBindings',
'url' => 'registerUrlGeneratorBindings',
'validator' => 'registerValidatorBindings',
'view' => 'registerViewBindings',
'Illuminate\Contracts\View\Factory' => 'registerViewBindings',
];
  1. 通过看源码,我们发现有这么一个变量 $availableBindings, log最终是 Monolog\Logger类。如果看官不明白,可以参照上文中的Facade的原理链接,了解一下lumen的机制。

集成自己的Log类

上文我们明白了Log既然是加载了 Monolog\Logger 类,那我们可以集成一个自己的Log类,最终代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
* default log directory is storage/api/
* default log name format is YYYY-mm-dd.log
*/


namespace App\Sphenginx;

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;

/**
* user defined Log class, with StreamHandler and LineFormatter
*
* @author Sphenginx
*/

class Log
{


//define static log instance.
protected static $_log_instance;

/**
* 获取log实例
*
* @return obj
* @author Sphenginx
**/

public static function getLogInstance()
{

if (static::$_log_instance === null) {
static::$_log_instance = new Logger('Sphenginx');
}
return static::$_log_instance;
}

/**
* Handle dynamic, static calls to the object.
*
* @param string $method 可用方法: debug|info|notice|warning|error|critical|alert|emergency 可调用的方法详见 Monolog\Logger 类
* @param array $args 调用参数
* @return mixed
* @author Sphenginx
*/

public static function __callStatic($method, $args)
{

$instance = static::getLogInstance();

//组织参数信息
$message = $args[0];
$context = isset($args[1]) ? $args[1] : [];
$path = isset($args[2]) ? $args[2] : 'api/';

//设置日志处理手柄,默认为写入文件(还有mail、console、db、redis等方式,详见Monolog\handler 目录)
$handler = new StreamHandler(storage_path($path) . date('Y-m-d').'.log');

//设置输出格式LineFormatter(Monolog\Formatter\LineFormatter), ignore context and extra
$handler->setFormatter(new LineFormatter(null, null, true, true));

$instance->pushHandler($handler);
$instance->$method($message, $context);
}

}

说明下,__callStatic 方法是php5.3+以来 非常方便的一个魔术方法。

More Info

architect

攻城狮,美其名曰架构师。相信每家公司对架构师的定义和要求都不一样,今天我仅以我的角度,聊一聊我眼里的架构狮。

几年前,我对架构师的印象,停留在:画漂亮的设计图,研究高性能,技术攻关一把手。反正就是各种技术高手的标签都可以贴上去。最近两三年,和自己做的事情、职责多少有一点点关系,思路发生了一些变化,总结下来,我觉得架构师的关注点应该是:

  • 架构团队
  • 架构技术
  • 架构流程规范
  • 架构产品
  • 架构客户

架构团队

没有团队,就没有架构师。

架构师需要关注并不断优化团队的结构。

一个20几个人的开发团队,架构师作为团队的主角,需要能最大化团队的效率:包括把什么样的人放到什么样的位置和哪些人做哪方面的对接;根据团队成员的特点如何做技术配比和责任分工,如何组织好团队协助自己完成技术攻关、技术研究和选型。

架构师还需要时刻关注团队的成长路线。

对每个开发组的lead,帮助他们在最短的时间内达到他们的下一个技术制高点,同时帮助他们筛选出组里的后备军,在合适的时间,给后备军独立攻关亦或带小团队成长的机会。

架构技术

做为架构师的看家本领,架构师需要做到的当然是技术过硬、视野够广。至少在某一个领域有一技之长,同时不失对当前流行技术的了解、深入把控。主要体现在:

技术选型

选择合适的技术,本身就是仁者见仁、智者见智。但是,对架构师的挑战,在于你是否是在任何项目、任何产品上都坚持只用自己熟悉的技术。接触过很多“架构师”,坚持的认为熟悉的就是最棒的、不管黑猫白猫抓到耗子就是好猫。这是我坚决反对的观点。既然是架构师,技术上必然有一技之长,要么编程牛逼可以从应用层写到内核层,要么对数据有很强的分析能力,要么对分布式高性能有很多的研究和经验,要么是客户端架构师对iOS/Android 有深入的研究,但是,不管是哪一个领域的高手,都不应该也不能够丢失对新技术的嗅觉敏感度。好的架构师,一定是在“老的技术”的保障下,逐步的研究、采用、推广当前流行的新技术,然后在合适的时间,完成新老技术方案的升级。同时,还需要考虑产品的特点、团队的结构以及自己对选用技术的把控能力。

平台选型

我把平台选型拎出来,是因为最近两三年云的快速的发展和变革。做为“有追求的架构师”,你是坚持创造一切,还是拥抱云服务?我是一个云服务的坚决拥护者,道理很简单,让合适的团队做合适的事情,同时,对于中小规模的互联网产品线,利用远比创造成本更低。所以,我们自己的产品,能托管于云端的,坚决不自己构建。目前我们在用的云服务:阿里云托管所有的主机,阿里云RDS托管数据,阿里云OSS数据备份,阿里云ODPS大数据计算,七牛云文件存储和文件CDN,云片发SMS短信,Send Cloud发送邮件通知。

持续交付

再牛的架构师,如果不能带领团队把产品交付出来,即使拥有再牛逼的技术、技术选型和平台选型再高大上,也于事无补。架构师需要平衡好架构设计、产品的交付周期、技术方案的难易程度。

架构师失业论

今天和团队聊天的时候,提到了“架构师失业论”。我一直认为,当今技术和平台的变革,不出几年,架构师们都可以统统失业了。公司付给架构狮们高额的薪水,可以完全足够用来支付给云平台。未来,云将主导一切,托管一切。你看看AWS的Lambda,你看看现在活起来的Mesos,已经可以让开发者忘记内存、CPU、磁盘的存在了,随着访问量自动无限扩容。你说,还要你架构师有何用呢?

架构规范

规范在很大程度上绝对了开发团队的开发效率、开发团队和产品团队的沟通效率。在我看来,规范包括但不限于:

开发规范

包括代码的目录结构规范,编码规范,SCM管理规范,包管理和工具规范等等。

发布规范

又包括代码版本管理规范,开发环境、测试环境、Staging环境以及生产环境要求,上线发布的流程等等。

沟通规范

包括团队沟通的工具、形式,代码review的方式方法等。

我们目前在使用Git,同时,遵循 git flow,代码review走merge request,CI集成gitlab CI & docker runner/Jenkins。合理利用工具,可以是开发效率事半功倍。

架构产品

产品和架构师有什么关系?架构师除了对产品有100%的了解,还需要了解竞争对手的产品、功能,以及技术能力。同时,架构师做为产品团队和开发团队的沟通枢纽,需要协调好团队沟通、合作中出现的问题。当然第一的需要避免的发生的就是 “开发工程师拿刀砍了产品经理”!…

写在最后

也有人问我,架构师有什么优秀的品质。

第一,品质是人特有的特性,所以,好的品质并不需要区分架构师和非架构师。所以,那些你知道的优秀品质,对架构师统统适用。

第二,我怎么知道,我又不是架构师……

转自大房说眼里的架构师.

Markdown概述

宗旨

Markdown 是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber)。它允许人们 使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档

可读性,无论如何,都是最重要的。一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令所构成。Markdown 语法受到一些既有 text-to-HTML 格式的影响,包括 SetextatxTextilereStructuredTextGrutatextEtText,而最大灵感来源其实是纯文本电子邮件的格式。

总之, Markdown 的语法全由一些符号所组成,这些符号经过精挑细选,其作用一目了然。比如:在文字两旁加上星号,看起来就像强调。Markdown 的列表看起来,嗯,就是列表。Markdown 的区块引用看起来就真的像是引用一段文字,就像你曾在电子邮件中见过的那样。

兼容HTML

Markdown 语法的目标是:成为一种适用于网络的书写语言。

Markdown 不是想要取代 HTML,甚至也没有要和它相近,它的语法种类很少,只对应 HTML 标记的一小部分。Markdown 的构想不是要使得 HTML 文档更容易书写。在我看来, HTML 已经很容易写了。Markdown 的理念是,能让文档更容易读、写和随意改。HTML 是一种发布的格式,Markdown 是一种书写的格式。就这样,Markdown 的格式语法只涵盖纯文本可以涵盖的范围。

不在 Markdown 涵盖范围之内的标签,都可以直接在文档里面用 HTML 撰写。不需要额外标注这是 HTML 或是 Markdown;只要直接加标签就可以了。

要制约的只有一些 HTML 区块元素――比如 <div><table><pre><p> 等标签,必须在前后加上空行与其它内容区隔开,还要求它们的开始标签与结尾标签不能用制表符或空格来缩进。Markdown 的生成器有足够智能,不会在 HTML 区块标签外加上不必要的 <p> 标签。

例子如下,在 Markdown 文件里加上一段 HTML 表格:

1
2
3
4
5
6
7
8
9
这是一个普通段落。

<table>
<tr>
<td>Foo</td>
</tr>
</table>

这是另一个普通段落。

请注意,在 HTML 区块标签间的 Markdown 格式语法将不会被处理。比如,你在 HTML 区块内使用 Markdown 样式的强调会没有效果。

HTML 的区段(行内)标签如 <span><cite><del> 可以在 Markdown 的段落、列表或是标题里随意使用。依照个人习惯,甚至可以不用 Markdown 格式,而直接采用 HTML 标签来格式化。举例说明:如果比较喜欢 HTML 的 <a><img> 标签,可以直接使用这些标签,而不用 Markdown 提供的链接或是图像标签语法。

和处在 HTML 区块标签间不同,Markdown 语法在 HTML 区段标签间是有效的。

Markdown基本语法

代码区域

如果你只想高亮语句中的某个函数名或关键字,可以使用 `function_name()` 实现

通常编辑器根据代码片段适配合适的高亮方法,但你也可以用 ``` 包裹一段代码,并指定一种语言

支持的语言:actionscript, apache, bash, clojure, cmake, coffeescript, cpp, cs, css, d, delphi, django, erlang, go, haskell, html, http, ini, java, javascript, json, lisp, lua, matlab, nginx, objectivec, perl, php, python, r, ruby, scala, smalltalk, sql, tex, vbscript, xml


	``` javascript
	$(document).ready(function () {
	    alert('hello world');
	});
	```
	

谢谢hexo漩涡的帮助!
详见hexo tag plugins

也可以使用 4 空格缩进,再贴上代码,实现相同的的效果

    def g(x):
        yield from range(x, 0, -1)
    yield from range(x)

标题

Markdown提供了两种方式(Setext和Atx)来显示标题。
例:

1
2
3
4
5
6
7
8
9
10
Setext方式
标题1
=================

标题2
-----------------


Atx方式
# 标题1
## 标题2
###### 标题6

换行

在文字的末尾使用两个或两个以上的空格来表示换行。

引用

行首使用[大于号+空格]表示引用段落,内部可以嵌套多个引用。

语法:

1
2
3
4
> 这是一个引用,
> 这里木有换行,
> 在这里换行了。
> > 内部嵌套

效果:

这是一个引用,
这里木有换行,
在这里换行了。

内部嵌套

列表

  • 无序列表使用 *、 + 或 - 后面加上空格来表示。

语法:

1
2
3
4
5
6
7
8
9
10
11
* Item 1
* Item 2
* Item 3

+ Item 1
+ Item 2
+ Item 3

- Item 1
- Item 2
- Item 3

效果:

  • Item 1
  • Item 2
  • Item 3
  • Item 1
  • Item 2
  • Item 3
  • Item 1
  • Item 2
  • Item 3

  • 有序列表使用数字加英文句号加空格表示。
    语法:

1
2
3
1. 列表前使用 [数字+空格]
2. 我们会自动帮你添加数字
7. 不用担心数字不对,显示的时候我们会自动把这行的 7 纠正为 3

效果:

  1. 列表前使用 [数字+空格]
  2. 我们会自动帮你添加数字
  3. 不用担心数字不对,显示的时候我们会自动把这行的 7 纠正为 3

强调

Markdown使用 * 或 _ 表示强调。

语法:

1
2
3
4
5
单星号 = *斜体*
单下划线 = _斜体_
双星号 = **加粗**
双下划线 = __加粗__
删除线 = ~~加粗~~

效果:
单星号 = 斜体
单下划线 = 斜体
双星号 = 加粗
双下划线 = 加粗
删除线 = 加粗

表格

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
| Tables        | Are           | Cool  |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |



dog | bird | cat
----|------|----
foo | foo | foo
bar | bar | bar
baz | baz | baz

效果:

Tables Are Cool
col 3 is right-aligned $1600
col 2 is centered $12
zebra stripes are neat $1

dog bird cat
foo foo foo
bar bar bar
baz baz baz

链接

Markdown支持两种风格的链接:InlineReference

Inline :以中括号标记显示的链接文本,后面紧跟用小括号包围的链接。如果链接有title属性,则在链接中使用空格加”title属性”。
Reference :一般应用于多个不同位置使用相同链接。通常分为两个部分,调用部分为链接文本;定义部分可以出现在文本中的其他位置,格式为 ref: http://some/link/address (可选的标题)。
注:ref中不区分大小写。
语法:

1
2
3
这是一个Inline[示例](http://sphenginx.github.io "可选的title")。
这是一个Reference[示例][ref]。
[ref]:http://sphenginx.github.io (可选的title)

效果:
这是一个Inline示例
这是一个Reference示例

图片

图片的使用方法基本上和链接类似,只是在中括号前加叹号。

语法:

1
2
![图片名称](http://图片网址)
![GitHub头像](https://avatars1.githubusercontent.com/u/1829395?v=3&s=460)

效果:

GitHub头像

注:Markdown不能设置图片大小,如果必须设置则应使用HTML标记 <img>

1
<img src="https://avatars1.githubusercontent.com/u/1829395?v=3&s=460" width="400" height="100">

效果如下:

分隔线

在一行中使用三个或三个以上的*、-或_可以添加分隔线,其中可以有空白,但是不能有其他字符。

语法:

1
***

效果:


转义字符

Markdown中的转义字符为\,可以转义的有:

1
2
3
4
5
6
7
8
9
10
11
12
\\ 反斜杠
\` 反引号
\* 星号
\_ 下划线
\{\} 大括号
\[\] 中括号
\(\) 小括号
\# 井号
\+ 加号
\- 减号
\. 英文句号
\! 感叹号

颜色与字体

markdown 不支持颜色和字体,所以如果想添加颜色或字体,只能使用 html 标签来实现这些需求了。

语法:

1
<font color="red">我是红色的</font>

效果:

我是红色的

高级技巧

制作一份待办事宜 Todo 列表

语法:

1
2
3
4
5
- [ ] 支持以 PDF 格式导出文稿
- [ ] 改进 Cmd 渲染算法,使用局部渲染技术提高渲染效率
- [x] 新增 Todo 列表功能
- [x] 修复 LaTex 公式渲染问题
- [x] 新增 LaTex 公式编号功能

效果:

  • [ ] 支持以 PDF 格式导出文稿
  • [ ] 改进 Cmd 渲染算法,使用局部渲染技术提高渲染效率
  • [x] 新增 Todo 列表功能
  • [x] 修复 LaTex 公式渲染问题
  • [x] 新增 LaTex 公式编号功能

高效绘制流程图|序列图

不知道为啥Git pages 不支持这种写法,可以copy在最新的Markdown语法解析器里面试试,或者点击 Markdown在线编辑器 尝试

1
2
3
4
5
6
7
8
st=>start: Start
op=>operation: Your Operation
cond=>condition: Yes or No?
e=>end

st->op->cond
cond(yes)->e
cond(no)->op

行内 HTML 元素

目前只支持部分段内 HTML 元素效果,包括 <kdb> <b> <i> <em> <sup> <sub> <br> ,如

  • 键位显示
    1
    使用 <kbd>Ctrl<kbd>+<kbd>Alt<kbd>+<kbd>Del<kbd> 重启电脑

效果:
使用 Ctrl+Alt+Del 重启电脑

  • 代码块

    1
    使用 <pre></pre> 元素同样可以形成代码块
  • 粗斜体

    1
    <b> Markdown 在此处同样适用,如 *加粗* </b>

扩展

支持 jsfiddle、gist、runjs、优酷视频,直接填写 url,在其之后会自动添加预览点击会展开相关内容。

1
2
3
4
http://{url_of_the_fiddle}/embedded/[{tabs}/[{style}]]/
https://gist.github.com/{gist_id}
http://runjs.cn/detail/{id}
http://v.youku.com/v_show/id_{video_id}.html

公式

当你需要在编辑器中插入数学公式时,可以使用两个美元符 $$ 包裹 TeX 或 LaTeX 格式的数学公式来实现。提交后,问答和文章页会根据需要加载 Mathjax 对数学公式进行渲染。如:

1
2
3
4
5
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a}. $$

$$
x \href{why-equal.html}{=} y^2 + 1
$$

效果如下:
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a}. $$

$$
x \href{why-equal.html}{=} y^2 + 1
$$

Markdown编辑器

Win平台

Linux平台

Mac平台

结语

以上几种格式是比较常用的格式,所以我们针对这些语法做了比较详细的说明。除这些之外,Markdown 还有其他语法,如想了解和学习更多,可以参考这篇『Markdown 语法说明』。:smile: (It seems the hexo does not support emoji)……

强烈建议您现在就立马用 Markdown 写一篇文章吧,体会一下 Markdown 的优雅之处!

说明

最近公司要用lumen框架开发应用APP的接口,用到了PHP新版本,一些新特性必须要了解,且有些可以在开发时就使用,如果不使用,那么何必升级PHP版本呢,显得有些得不偿失了!
所以整理了一下 一些特性,有可能不全,待添加。

PHP5.3 new feature

支持命名空间 (Namespace)

毫无疑问,命名空间是PHP5.3所带来的最重要的新特性。在PHP5.3中,则只需要指定不同的命名空间即可,命名空间的分隔符为反斜杆\。
Demo:
在select.php 中定义命名空间

1
2
3
<?php    
namespace Zend\Db\Table;
class Select {}

这样即使其它命名空间下存在名为Select的类,程序在调用时也不会产生冲突。代码的可读性也有所增加。
调用方法
call.php

1
2
3
4
5
<?php    
//namespace Zend\Db;
include('select.php');
$s = new Zend\Db\Table\Select();
$s->test();

支持延迟静态绑定(Late Static Binding)

在PHP5中,我们可以在类中通过self关键字或者CLASS来判断或调用当前类。但有一个问题,如果我们是在子类中调用,得到的结果将是父类。因为在继承父类的时候,静态成员就已经被绑定了。 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php    
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();

以上代码输出的结果是:
A

这和我们的预期不同,我们原来想得到子类的相应结果。
PHP 5.3.0中增加了一个static关键字来引用当前类,即实现了延迟静态绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php    
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 这里实现了延迟的静态绑定
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}

B::test();

以上代码输出的结果是:
B

支持goto语句

多数计算机程序设计语言中都支持无条件转向语句goto,当程序执行到goto语句时,即转向由goto语句中的标号指出的程序位置继续执行。尽管goto语句有可能会导致程序流程不清晰,可读性减弱,但在某些情况下具有其独特的方便之处,例如中断深度嵌套的循环和 if 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php    
goto a;
echo 'Foo';
a:
echo 'Bar';
for($i=0,$j=50; $i<100; $i++) {
while($j--) {
if($j==17) goto end;
}
}
echo "i = $i";
end:
echo 'j hit 17';

支持闭包、Lambda/Anonymous函数

闭包(Closure)函数和Lambda函数的概念来自于函数编程领域。例如JavaScript 是支持闭包和 lambda 函数的最常见语言之一。
在PHP中,我们也可以通过create_function()在代码运行时创建函数。但有一个问题:创建的函数仅在运行时才被编译,而不与其它代码同时被编译成执行码,因此我们无法使用类似APC这样的执行码缓存来提高代码执行效率。
在PHP5.3中,我们可以使用Lambda/匿名函数来定义一些临时使用(即用即弃型)的函数,以作为array_map()/array_walk()等函数的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php    
echo preg_replace_callback('~-([a-z])~', function ($match) {
return strtoupper($match[1]);
}, 'hello-world');
// 输出 helloWorld
$greet = function($name)
{

printf("Hello %s\r\n", $name);
};
$greet('World');
$greet('PHP');
//...在某个类中
$callback = function ($quantity, $product) use ($tax, &$total) {
$pricePerItem = constant(__CLASS__ . "::PRICE_" . strtoupper($product));
$total += ($pricePerItem * $quantity) * ($tax + 1.0);
};
array_walk($products, $callback);

新增两个魔术方法__callStatic()__invoke()

PHP中原本有一个魔术方法__call(),当代码调用对象的某个不存在的方法时该魔术方法会被自动调用。新增的__callStatic()方法则只用于静态类方法。当尝试调用类中不存在的静态方法时,__callStatic()魔术方法将被自动调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php    
class MethodTest {
public function __call($name, $arguments) {
// 参数 $name 大小写敏感
echo "调用对象方法 '$name' "
. implode(' -- ', $arguments). "\n";
}
/** PHP 5.3.0 以上版本中本类方法有效 */
public static function __callStatic($name, $arguments) {
// 参数 $name 大小写敏感
echo "调用静态方法 '$name' "
. implode(' -- ', $arguments). "\n";
}
}

$obj = new MethodTest;
$obj->runTest('通过对象调用');
MethodTest::runTest('静态调用'); // As of PHP 5.3.0

以上代码执行后输出如下:

1
2
调用对象方法'runTest' –- 通过对象调用
调用静态方法'runTest' –- 静态调用

以函数形式来调用对象时,__invoke()方法将被自动调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php    
class MethodTest {
public function __call($name, $arguments) {
// 参数 $name 大小写敏感
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}

/** PHP 5.3.0 以上版本中本类方法有效 */
public static function __callStatic($name, $arguments) {
// 参数 $name 大小写敏感
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context'); // As of PHP 5.3.0

三元运算符增加了一个快捷书写方式

原本格式为是(expr1) ? (expr2) : (expr3)
如果expr1结果为True,则返回expr2的结果。
PHP5.3新增一种书写方式,可以省略中间部分,书写为expr1 ?: expr3
如果expr1结果为True,则返回expr1的结果

1
2
3
4
//原格式  
$expr = $expr1 ? $expr1 :$expr2
//新格式
$expr = $expr1 ? : $expr2

PHP5.3中其它值得注意的改变

1.1.1.修复了大量bug
1.1.2. PHP性能提高
1.1.3. php.ini中可使用变量
1.1.4. mysqlnd进入核心扩展 理论上说该扩展访问mysql速度会较之前的MySQL 和 MySQLi 扩展快(参见http://dev.mysql.com/downloads/connector/php-mysqlnd/)
1.1.5. ext/phar、ext/intl、ext/fileinfo、ext/sqlite3和ext/enchant等扩展默认随PHP绑定发布。其中Phar可用于打包PHP程序,类似于Java中的jar机制。
1.1.6. ereg 正则表达式函数 不再默认可用,请使用速度更快的PCRE 正则表达式函数

弃用功能

PHP 5.3.0 新增了两个错误等级: E_DEPRECATED 和 E_USER_DEPRECATED. 错误等级 E_DEPRECATED 被用来说明一个函数或者功能已经被弃用. E_USER_DEPRECATED 等级目的在于表明用户代码中的弃用功能, 类似于 E_USER_ERROR 和 E_USER_WARNING 等级.
下面是被弃用的 INI 指令列表. 使用下面任何指令都将导致 E_DEPRECATED 错误.

1
2
3
4
5
6
7
define_syslog_variables
register_globals
register_long_arrays
safe_mode
magic_quotes_gpc
magic_quotes_runtime
magic_quotes_sybase

弃用 INI 文件中以 ‘#’ 开头的注释.
弃用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
call_user_method() (使用 call_user_func() 替代)
call_user_method_array() (使用 call_user_func_array() 替代)
define_syslog_variables()
dl()
ereg() (使用 preg_match() 替代)
ereg_replace() (使用 preg_replace() 替代)
eregi() (使用 preg_match() 配合 'i' 修正符替代)
eregi_replace() (使用 preg_replace() 配合 'i' 修正符替代)
set_magic_quotes_runtime() 以及它的别名函数 magic_quotes_runtime()
session_register() (使用 $_SESSION 超全部变量替代)
session_unregister() (使用 $_SESSION 超全部变量替代)
session_is_registered() (使用 $_SESSION 超全部变量替代)
set_socket_blocking() (使用 stream_set_blocking() 替代)
split() (使用 preg_split() 替代)
spliti() (使用 preg_split() 配合 'i' 修正符替代)
sql_regcase()
mysql_db_query() (使用 mysql_select_db() 和 mysql_query() 替代)
mysql_escape_string() (使用 mysql_real_escape_string() 替代)

废弃以字符串传递区域设置名称. 使用 LC_* 系列常量替代.
mktime() 的 is_dst 参数. 使用新的时区处理函数替代.

更多新特性,参加官网:http://php.net/manual/en/migration53.new-features.php

PHP 5.4 new feature

Traits

Traits提供了一种灵活的代码重用机制,即不像interface一样只能定义方法但不能实现,又不能像class一样只能单继承。至于在实践中怎样使用,还需要深入思考。
魔术常量为__TRAIT__
官网的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
trait SayWorld {  
public function sayHello() {
parent::sayHello();
echo "World!\n";
echo 'ID:' . $this->id . "\n";
}
}

class Base {
public function sayHello() {
echo 'Hello ';
}
}

class MyHelloWorld extends Base {
private $id;

public function __construct() {
$this->id = 123456;
}

use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();

/*will output:
Hello World!
ID:123456
*/

Short array syntax 数组简短语法

1
2
3
4
5
$arr = [1,'james', 'james@fwso.cn'];  
$array = [
  "foo" => "bar",
  "bar" => "foo"
  ];

Array dereferencing 数组值

1
2
3
4
5
6
7
8
function myfunc() {  
return array(1,'james', 'james@fwso.cn');
}
#以前我们需要这样:
$arr = myfunc();
echo $arr[1];
#在PHP5.4中这样就行了:
echo myfunc()[1];

本例要注意一个要点 http://www.laruence.com/2011/12/19/2409.html

实例化类

1
2
3
4
5
6
class test{  
function show(){
return 'test';
}
}
echo (new test())->show();

支持 Class::{expr}() 语法

1
2
3
foreach ([new Human("Gonzalo"), new Human("Peter")] as $human) {  
echo $human->{'hello'}();
}

Callable typehint

1
2
3
4
5
6
7
8
9
10
11
12
function foo(callable $callback) {  
}

foo("false"); //错误,因为false不是callable类型
foo("printf"); //正确
foo(function(){}); //正确

class A {
  static function show() {
}
}
foo(array("A", "show")); //正确

函数类型提示的增强

由于php是弱类型的语言,因此在php 5.0后,引入了函数类型提示的功能,其含义为对于传入函数中的参数都进行类型检查,举个例子,有如下的类:

1
2
3
4
5
6
7
8
9
10
class bar {  
function foo(bar $foo) {
}
}
//其中函数foo中的参数规定了传入的参数必须为bar类的实例,否则系统会判断出错。同样对于数组来说,也可以进行判断,比如:
function foo(array $foo) {
}

foo(array(1, 2, 3)); // 正确,因为传入的是数组
foo(123); // 不正确,传入的不是数组

PHP 5.4.0 性能大幅提升, 修复超过100个bug.

废除了register_globals, magic_quotes以及安全模式。
另外值得一提的是多字节支持已经默认启用了,
default_charset从ISO-8859-1已经变为UTF-8.
默认发送“Content-Type: text/html; charset=utf-8”,
你再也不需要在HTML里写meta tag,也无需为UTF-8兼容而传送额外的header了。

删除的特性
最后,我们集中整理了几年来标记为已弃用的多个特性。这些特性包括 allow_call_time_pass_reference、define_syslog_variables、highlight.bg、register_globals、register_long_arrays、magic_quotes、safe_mode、zend.ze1_compatibility_mode、session.bug_compat42、session.bug_compat_warn 以及 y2k_compliance。
除了这些特性之外,magic_quotes 可能是最大的危险。在早期版本中,未考虑因 magic_quotes 出错导致的后果,简单编写且未采取任何举措使自身免受 SQL 注入攻击的应用程序都通过 magic_quotes 来保护。如果在升级到 PHP 5.4 时未验证已采取正确的 SQLi 保护措施,则可能导致安全漏洞。

其他改动和特性
有一种新的“可调用的”类型提示,用于某方法采用回调作为参数的情况。
htmlspecialchars()htmlentities() 现在可更好地支持亚洲字符,如果未在 php.ini 文件中显式设置 PHP default_charset,这两个函数默认使用 UTF-8 而不是 ISO-8859-1。
<?=(精简回显语法)现在始终可用,无论 short_tags ini 设置的值为何。这应该使模板化系统创建者感到满意。
会话 ID 现在默认通过 /dev/urandom(或等效文件)中的熵生成,而不是与早期版本一样成为必须显式启用的一个选项。
mysqlnd 这一捆绑的 MySQL 原生驱动程序库现在默认用于与 MySQL 通信的各种扩展,除非在编译时通过 ./configure 被显式覆盖。
可能还有 100 个小的改动和特性。从 PHP 5.3 升级到 5.4 应该极为顺畅,但请阅读迁移指南加以确保。如果您从早期版本升级,执行的操作可能稍多一些。请查看以前的迁移指南再开始升级。

更多新特性,参照官网:http://php.net/manual/en/migration54.new-features.php

PHP5.5 new feature

放弃对Windows XP和2003 的支持

弃用e修饰符

e修饰符是指示preg_replace函数用来评估替换字符串作为PHP代码,而不只是仅仅做一个简单的字符串替换。不出所料,这种行为会源源不断的出现安全问题。这就是为什么在PHP5.5 中使用这个修饰符将抛出一个弃用警告。作为替代,你应该使用preg_replace_callback函数。你可以从RFC找到更多关于这个变化相应的信息。

新增函数和类

boolval()
PHP已经实现了strval、intval和floatval的函数。为了达到一致性将添加boolval函数。它完全可以作为一个布尔值计算,也可以作为一个回调函数。
hash_pbkdf2()
PBKDF2全称“Password-Based Key Derivation Function 2”,正如它的名字一样,是一种从密码派生出加密密钥的算法。这就需要加密算法,也可以用于对密码哈希。更广泛的说明和用法示例
array_column()

1
2
3
4
5
6
7
//从数据库获取一列,但返回是数组。  
$userNames = [];
foreach ($users as $user) {
$userNames[] = $user['name'];
}
//以前获取数组某列值,现在如下
$userNames = array_column($users, 'name');

intl 扩展
将有许多改进 intl的扩展。例如,将会有新的IntlCalendar,IntlGregorianCalendar,IntlTimeZone,IntlBreakIterator,IntlRuleBasedBreakIterator,IntlCodePointBreakIterator类。之前,我竟然不知道有这么多关于intl扩展,如果你想知道更多,我建议你去最新公告里找 Calendar和 BreakIterator。

一个简单的密码散列API

1
2
3
4
5
6
7
8
9
$password = "foo";    
// creating the hash
$hash = password_hash($password, PASSWORD_BCRYPT);
// verifying a password
if (password_verify($password, $hash)) {
// password correct!
} else {
// password wrong!
}

新的语言特性和增强功能。

常量引用
“常量引用”意味着数组可以直接操作字符串和数组字面值。举两个例子:

1
2
3
4
5
6
7
8
9
function randomHexString($length) {    
$str = '';
for ($i = 0; $i < $length; ++$i) {
$str .= "0123456789abcdef"[mt_rand(0, 15)]; // direct dereference of string
}
}
function randomBool() {
return [false, true][mt_rand(0, 1)]; // direct dereference of array
}

我不认为在实践中会使用此功能,但它使语言更加一致。请参阅 RFC。

调用empty()函数(和其他表达式)一起工作

目前,empty()语言构造只能用在变量,而不能在其他表达式。
在特定的代码像empty($this->getFriends())将会抛出一个错误。作为PHP5.5 这将成为有效的代码

获取完整类别名称

PHP5.3 中引入命名空间的别名类和命名空间短版本的功能。虽然这并不适用于字符串类名称

1
2
3
4
use Some\Deeply\Nested\Namespace\FooBar;    
// does not work, because this will try to use the global `FooBar` class
$reflection = new ReflectionClass('FooBar');
echo FooBar::class;

为了解决这个问题采用新的FooBar::class语法,它返回类的完整类别名称, 这里::class 等价于get_class()

参数跳跃

如果你有一个函数接受多个可选的参数,目前没有办法只改变最后一个参数,而让其他所有参数为默认值。
RFC上的例子,如果你有一个函数如下:

1
function create_query($where, $order_by, $join_type='', $execute = false, $report_errors = true) { ... }

那么有没有办法设置$report_errors=false,而其他两个为默认值。为了解决这个跳跃参数的问题而提出:

1
create_query("deleted=0", "name", default, default, false);

我个人不是特别喜欢这个提议。在我的眼睛里,代码需要这个功能,只是设计不当。函数不应该有12个可选参数。

Getter 和 Setter

如果你从不喜欢写这些getXYZ()和setXYZ($value)方法,那么这应该是你最受欢迎的改变。提议添加一个新的语法来定义一个属性的设置/读取:

1
2
3
4
5
6
7
8
9
10
11
12
<?php  
class TimePeriod {
public $seconds;
public $hours {
get { return $this->seconds / 3600; }
set { $this->seconds = $value * 3600; }
}
}
$timePeriod = new TimePeriod;
$timePeriod->hours = 10;
var_dump($timePeriod->seconds); // int(36000)
var_dump($timePeriod->hours); // int(10)

生成器 yield

目前,自定义迭代器很少使用,因为它们的实现,需要大量的样板代码。生成器解决这个问题,并提供了一种简单的样板代码来创建迭代器。
例如,你可以定义一个范围函数作为迭代器:

1
2
3
4
5
6
7
8
9
<?php  
function *xrange($start, $end, $step = 1) {
for ($i = $start; $i < $end; $i += $step) {
yield $i;
}
}
foreach (xrange(10, 20) as $i) {
// ...
}

上述xrange函数具有与内建函数相同的行为,但有一点区别:不是返回一个数组的所有值,而是返回一个迭代器动态生成的值。

foreach 支持list()

对于“数组的数组”进行迭代,之前需要使用两个foreach,现在只需要使用foreach + list了,但是这个数组的数组中的每个数组的个数需要一样。看文档的例子一看就明白了。

1
2
3
4
5
6
7
$array = [  
[1, 2],
[3, 4],
];
foreach ($array as list($a, $b)) {
echo "A: $a; B: $b\n";
}

更多新特性,参照官网:http://php.net/manual/en/migration55.new-features.php

PHP5.6 new feature

命名空间 use 操作符开始支持函数和常量的导入

1
2
3
4
5
6
7
8
9
10
11
namespace Name\Space {  
const FOO = 42;
function f() { echo __FUNCTION__."\n"; }
}
namespace {
use const Name\Space\FOO;
use function Name\Space\f;

echo FOO."\n";
f();
}

输出

1
2
42  
Name\Space\f

使用**操作符计算乘方

phpdbg

PHP自带了一个交互式调试器phpdbg,它是一个SAPI模块,更多信息参考 phpdbg文档 。

php://input 可以被复用

php://input 开始支持多次打开和读取,这给处理POST数据的模块的内存占用带来了极大的改善。

大文件上传支持

可以上传超过2G的大文件。

更多新特性,参照官网:http://php.net/manual/en/migration56.new-features.php

现状

现在,像达内、华清远见、国嵌、北大青鸟、传播智客等等 IT 培训机构很多,为尚未毕业的大学生、毕业了一时找不到工作的大学生、工作后想转行的再就业者提供了一个掌握新技能的机会,通过三个月或半年或更久的培训, 你就可以掌握某一种技能,比如 Android 开发、Java Web 开发、iOS 开发、嵌入式 Linux 开发,然后,没然后了吗……

Sorry,然后还是有的,只不过,“然后”并不是像培训机构的销售代表频频给你通电话时宣传的那样光明罢了。有的机构会推荐你就业直到你彻底失望他们推荐的单位,有的机构会放你出去闯荡江湖四处碰壁……形式不一,但,一段痛苦的旅程从此开始了,这倒是真的。

很多单位歧视培训机构毕业的学员,你所在的单位是这样吗?或者你从 IT 培训机构毕业后,找工作时被鄙视了吗?

为了弄明白为什么 IT 培训机构出来的程序员在找工作时经常遭遇不平等对待,我们需要弄明白“教育”和“培训”的差别。

教育和培训

大致上讲,我们所说的“教育”,指的是掌握一般性的原理与技巧的过程;而我们所说的“培训”,不过是学会某种特定技能的过程。

上面是温伯格在《程序开发心理学》中说到的,符合大多数人对“教育”和“培训”的理解。

你可以通过培训机构学会理发、做饭、修汽车、写代码、做蛋糕,这没什么稀奇的,当我们接受培训时,就是为了某项技能而去的,不是吗?我去蓝翔,难道不是为了开挖掘机吗?

通常我们认为培训机构(学校)是学习某种不太复杂的谋生技能的摇篮。这种技能还有一个特点,就是相对稳定、变化不是特别频繁,能够在相当长一段时间内保持基本的稳定性。所以,一旦你获得了这项技能,就可以靠它吃饭吃上一阵子,一年半载,三年五年,十年八年,都有可能。

而教育,我们通常认为是一项基础性的工作,重知识,重原理,周期长,见效慢,与社会脱节,有时还能把人变傻(注意我不是讽刺现行教育制度,也不是诋 毁我们伟大的大学教育)。虽然如此,很多 IT 公司的基础部门还是被受过正规大学教育的朋友们占据了重要岗位。因为大家普遍认为,虽然学校教育严重脱离社会现实,但名牌大学的学生的智力水平、学习能 力,平均来看还是高于未能考上大学的中学生,更适合于从事某种对智力、学习能力有些特别要求的技术岗位。

没错,程序员正是这样的岗位。程序员的平均智力水平和学习能力高于大部分的其它行业从业者。

然而,教育和培训的差别,并不是关键。关键是,出于某种原因,面试官或公司主管对出身“培训机构”的人有偏见。为了说明这一点,我们先要看看程序员需要的特殊能力。

成为一个合格的程序员,需要以下“特殊”能力:

  • 自知之明

  • 自我学习

  • 努力

看起来没什么出奇之处,也许你会觉得一个程序员最重要的能力不是上面三项,没关系,知否知否,应是绿肥红瘦,随便怎么看,横看成岭侧成峰,一千个观众就有一千个哈姆雷特,嗯,开启口水模式……这里省略 500 字……

大家公认程序员从事的是烧脑性工作,行业发展日新月异,各种新语言新技术新框架新概念层出不穷,需要程序员时刻保持归零的学习心态,持续不断地保持学习维持竞争能力和价值。所以,我也是从这个角度出发,选择了前面提到的三点,实际上这三点指向的是“[学习之道]”

自知之明

我们要了解自己拥有什么、缺乏什么,然后才能开始学习。

通过不断地总结、回顾自己做过的事情,我们就可以慢慢了解自己的能力边界。哪些事情做好了,好在哪里,为什么好在那里而不是别处,是由你自身的哪种 行为、才干、能力决定的?哪些事情做得不好,坏在哪里,为什么坏在那个点而不是其它的点,改善你自身的哪种行为或能力可以改变事情的走向,还是说你没什么 能改变的那就是你的局限?

当你了解了自己,就能发现自己应该做什么,就能决定自己的学习方向,而不是盲目地把自己交付给别人(学校、老师、家长、培训机构),因为,没有什么人真的可以为你负责,能为你负责的,只有你自己。

当然,自知是最难的事,也不是一朝一夕的事,需要不断的自省和内视才可以做到。

自我学习

知名的教育专家林格有两本非常著名的书,《教育是没有用的》和《学习是不需要教的》。林格有一个非常核心的观点:学习能力是人与生俱来的能力,是人 之天赋,是不需要教的,但这种能力会随着年龄的增长和家庭教育、学校教育的误导而萎缩或消失,所以,教育的方向就是营造一个环境,让人自己发现自己的学习 能力。

六祖慧能一朝顿悟传承衣钵,佛性自在每人心中。只不过,世人多如神秀,认为“身是菩提树,心如明镜台,时时勤拂拭,勿使惹尘埃”,一定要借助外力和 各种各样的清规戒律,方可保持灵台清明,最终获得某种修行。这种扭曲了教育本质的说法、做法大行其道,导致中学教育(大学相对自由)重知识、重规矩、重技 能而轻发现、轻唤醒、轻因材施教、轻有教无类,最终扼杀了学生的自我学习能力。所以,很多人不是天生不会学习,而是在成长过程中在家庭、学校、社会的各种 外力撕扯中慢慢丧失了自我学习能力。更甚之,多数人不自知这种能力的丧失。

然而,程序员尤其需要自我学习能力。

学校会教你操作系统原理,会教你计算机组成原理,会教你算法,会教你C语言,会教你 Java,各种知识都会教你。然而你到工作中,能不能用你学到的知识解决问题,实在是个未知数。

培训机构会教你怎么写 Java 代码,怎么安装某个 IDE,怎么完成一个个人博客或购物车之类的小项目。然而,你是在框好的架子下被动地按照老师的要求“完成”了这些事。到了工作中,面对巨大的未知和陌生的项目,你能不能举一反三灵活运用,充满了未知。

一个程序员能不能自己学会一门技术、能不能自己解决一个问题特别重要

因为,很少有主管会手把手的教会你编程和设计的实际技巧,也很少有主管会大发慈悲把你送去研习班学习工作需要的技能,在一个现实的环境里,一切都要 靠你自己。假如你自己不能独立习得某项必须的技术,真没有人能帮得上你。假如你自己不能独立解决问题,真没有人能始终拉扯着你。这也正合梁漱溟说的话: “任何一个人的学问成就,都是出于自学。学校教育不过给学生一个开端,使他更容易自学而已。青年于此,不可不勉。”

很多人轻视培训机构,是认为培训机构的老师,多数脱产,没有丰富的一线工程实践,是业余选手,而这些“业余选手”却要通过短短的一期培训来为社会培 养“专业选手”,这基本是一个笑话。即便我们能举出不少从培训机构出来的优秀选手,那也只能说明,这个选手本身具有很好的学习能力。

如果一个人意识到了自己还具备自我学习能力,那他完全没必要去培训机构浪费动辄六七千一两万的学费——他完全可以自己学到必须的知识和技能,假如他真有兴趣的话。

而要检验你是否还有自我学习能力,先不要去培训机构,自己找本讲编程的书、找台电脑、连上网,花一两个月时间就能搞明白你是否适合做一个程序员。一旦你通过了这种自我学习实验,那时再挑一个培训机构系统地学习某条技术栈不迟。

言而总之,你能不能成为合格的或优秀的程序员,取决于自我学习能力,而不是参加过专业培训。所以,很多公司在招募程序员时,不太愿意考虑培训机构毕 业的学员,因为在面试官的心里,觉得如果你有能力,自己就可以学会,完全没必要去培训机构,你接受了培训,他反倒认为你可能缺乏自我学习能力(以及对技术 的兴趣),担心你不能胜任将来的工作,他太了解了,你学的那点东西根本不够用,还有很多新东西等着你学,所以,他不愿意考虑你。

而对于知名大学的毕业生,虽然可能和你一样是一张白纸,但别人会以为,能进得了大学当得了学霸,起码学习能力没问题的概率高一些。

努力

有一句话是这么说的,“以大多数人的努力程度之低,根本还轮不到拼天赋”。其实,努力也是一种天赋。为什么有的人明知努力可以改变生活,可他还是不 努力呢?因为,臣妾做不到啊!为什么做不到,因为他缺乏“勤奋”、“努力”之类的天赋和才干。真的,勤奋、努力的人,多数是生就的,少数是被后妈(生活) 逼的。

程序员白天要上班,晚上偶尔还要加个班,自由时间少,而新技术很多,什么时候去学呢?你以为实际的项目一定可以让你锻炼新技术吗?要知道,大部分的 项目在技术选型时,会考虑技术的成熟度和团队的技术储备,很少有冒险采用大家都不熟悉的技术的,不可控因素太多,风险太大,项目失败的概率很高。那这样的 话,你什么时间丰富自己呢?

八小时之内是现在,八小时之外是将来。你可以用的,就是你的业余时间了。你看,人家都在打游戏、看电视、挎着女伴的胳膊去速8,你还要苦哈哈的学习,如果你没有“努力”这种天赋,是很难做到的。

所以,很多面试官在面对培训机构毕业的人选时,也会有诸如“如果你有自我学习能力并且努力,其实没必要上什么培训班,完全可以自己搞定”之类的想 法,而你上了培训班,是不是反过来证明你不够努力咧……然后,他又会想,是不是因为你没自知之明不知道自己要干啥才被忽悠到培训机构去交学费了呢……也 许,他还会想,是不是因为你对技术其实不感兴趣只是想谋个事儿干呢……

额,你看,面无表情之下,其实各种想法如同暗流漩涡,澎湃不息……所以,最后,你可能就只好“回去等消息”了……

不知道说了这么多,你是否明白了个中原因——面试官会觉得培训出来的学员,可能对技术没有那么浓厚的兴趣、缺乏足够强的自我学习能力也不能很好的自 律和努力。但在我的观念里,其实应该这么看待 IT 培训机构的学员:他只是找了一个类似学校的地方系统学了一些东西,和别人并无什么不同,如果他对技术有兴趣,有自我学习能力,一样可以做一个优秀的程序员。

原文转自:http://developer.51cto.com/art/201511/498403.htm