开发过程中,碰到诡异的样式,Webkit内核浏览器样式还不是标准的。

找到此方法,记录下:

@media screen and (-webkit-min-device-pixel-ratio:0) {
    .className{position: relative; top: 10px;}
}

  

posted @ 2012-05-25 15:04 盼逆邵年 阅读(8) 评论(0) 编辑

项目开发完,测试说IE6下只要上传图片就崩溃,一开始还以为JS哪里写的不对,找啊找,都找不到,我自己都崩溃了要。

记录下:

 

.qq-uploader{position: relative; width: 250px;}

 

width的属性必须要写哦,不然IE6就崩溃,真不知道怎么回事。。。唉

posted @ 2012-05-09 14:03 盼逆邵年 阅读(11) 评论(0) 编辑

1.下载JDK,安装

官网下载地址:http://java.sun.com/javase/downloads/index.jsp

下载后,安装,选择你想把JDK安装的目录:

比如:JDK安装目录:E:\java

其他博文有说还要装JRE,其实JDK安装好了,就不需要安装JRE了,本身就有了。

 

2.设置JDK环境变量

右击“计算机”,点击“属性”,点击弹出界面的左部分的“高级系统设置”,选择“高级”选项卡,点击下部的“环境变量”

 

新建2个变量,编辑1个变量,分别填入以下信息,如图所示:

 

新建:

变量名:JAVA_HOME

变量值:你的JDK安装目录\jdk1.7.0

 

变量名:CLASSPATH

变量值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%TOMCAT_HOME%\BIN

(注意最前面有个.号)

 

编辑:

变量名:Path

变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

 (将此处的红色字符串粘帖到变量值的最前面)

 

开始运行输入CMD,在命令行里分别输入java; javac; java -version
如:显示以下信息,说明你的Java环境变量配置成功

 

3.下载Tomcat,官网上下载的文件都是绿色免安装的。如:apache-tomcat-7.0.26-windows-x64.zip

下载地址:http://tomcat.apache.org/download-70.cgi

下载后解压缩:E:\apache-tomcat-7.0.26,随你喜欢解压哪里。

 

4.设置Tomcat环境变量

 

新建:

变量名:TOMCAT_HOME

变量值:你的TOMCAT所在目录  
如:E:\apache-tomcat-7.0.26

 

现在环境基本都搭建好了,可以试一试了。

启动tomcat,运行tomcat目录/bin/startup, (点击startup,好像一闪而过,或弹出个命令行窗口,不是很爽,文章后面会提到怎么处理)

然后在浏览器中,输入http://localhost:8080

如果页面显示如图,恭喜你环境搭建成功了!

 

 

我们有看到tomcat目录/bin文件夹里有个tomcat7w.exe,顾名思义就是tomcat以window方式显示控制台。第1次点击打开它时候,可能会提示:tomcat指定的服务未安装,此时我们可以这样解决它。

打开命令行提示符窗口=> 进入Tomcat安装目录==> 进入bin目录下==> 输入:service.bat install 即可,如图操作,tomcat目录按自己机子上的来哦



这样就可以让tomcat以window方式显示控制台。点击tomcat7w.exe,会显示如图:

 

 

点击start启动tomcat服务,如果startup type选择AUTOMATIC,就是随开机启动啦,这样就不需要每次都手动开启啦。

 

5:常用的tomcat配置设置小技巧

 

一、端口8080改为80,从而http://localhost:8080/web项目   可以直接写为:http://localhos/web项目

tomcat目录/bin/文件夹下:编辑server.xml文档,

将:<Connector port="8080" protocol="HTTP/1.1"  connectionTimeout="20000"  redirectPort="8443" />

改为:<Connector port="80" protocol="HTTP/1.1"  connectionTimeout="20000"  redirectPort="8443" />

 

二、配置WEB项目的虚拟目录

配置之前,web项目文件夹里必须新建个文件夹:WEB-INF,此文件夹里必须有个web.xml的文件。这些都可以直接在tomcat/webapps/ROOT/里复制过来,放到自己的WEB项目中即可。

如果不配置,则需将web项目的文件夹放在tomcat/webapps/下,访问路径为:http://localhost:8080/web项目文件夹名称

如果WEB项目放在其他盘符,则在tomcat目录/bin/文件夹下:编辑server.xml文档,

在文档的末尾处,添加:<Context path="/nala" docBase="e:\www\nala"></Context>,如图所示

其中,path为访问标识,docBase为实际项目的所在目录,这样指定后,重启tomcat,访问:http://localhost:8080/nala,也就成功了。

 

好了,所有的配置和说明都完成了,大家可以在win7下使用tomcat环境爽下吧!

posted @ 2012-03-07 13:18 盼逆邵年 阅读(2049) 评论(1) 编辑

Uploadify是JQuery的一个上传插件,实现的效果非常不错,带进度显示。官方提供的实例是php版本的,您可以点击下面的链接进行浏览或下载。

效果图:

  效果图

 

部署和代码介绍:  

JSP前台页面:

<script type="text/javascript">
$(document).ready(
function() {
$(
"#uploadify").uploadify( {//初始化函数

'uploader' :'uploadify.swf',//flash文件位置,注意路径
'script' :'servlet/Upload',//后台处理的请求
'cancelImg' :'images/cancel.png',//取消按钮图片
'folder' :'uploads',//您想将文件保存到的路径
'queueID' :'fileQueue',//与下面的上传文件列表id对应
'queueSizeLimit' :8,//上传文件的数量
'scriptData':{'a':'value1','b':'value2'},//向后台传的数据
'fileDesc' :'rar文件或zip文件',//上传文件类型说明
'fileExt' :'*.rar;*.zip', //控制可上传文件的扩展名,启用本项时需同时声明fileDesc
'method':'get',//如果向后台传输数据,必须是get
'sizeLimit':1000,//文件上传的大小限制,单位是字节
'auto' :false,//是否自动上传
'multi' :true,
'simUploadLimit' :2,//同时上传文件的数量
'buttonText' :'BROWSE',//浏览按钮图片
'onComplete': function(event, queueID, fileObj,serverData,data) {//当上传完成后的回调函数,ajax方式哦~~
alert(data.speed);
}
});
});

</script>



<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/jquery.uploadify.v2.1.0.min.js"></script>

<div id="fileQueue"></div>
<input type="file" name="uploadify" id="uploadify" />
<p>
<a href="javascript:jQuery('#uploadify').uploadifyUpload()">开始上传</a>&nbsp;
<a href="javascript:jQuery('#uploadify').uploadifyClearQueue()">取消所有上传</a>
</p>

java后台:

String savePath = this.getServletConfig().getServletContext().getRealPath("");

savePath = savePath + "/uploads/";

File f1 = new File(savePath);

System.out.println(savePath);

if (!f1.exists()) {

f1.mkdirs();

}

DiskFileItemFactory fac = new DiskFileItemFactory();

ServletFileUpload upload = new ServletFileUpload(fac);

upload.setHeaderEncoding("utf-8");

List fileList = null;

try {

fileList = upload.parseRequest(request);

} catch (FileUploadException ex) {

ex.printStackTrace();
return;

}

Iterator it = fileList.iterator();

while (it.hasNext()) {

FileItem item = (FileItem) it.next();

if (!item.isFormField()) {

name = item.getName();

long size = item.getSize();

String type = item.getContentType();

if (name == null || name.trim().equals("")) {

continue;

}

// 扩展名格式:

if (name.lastIndexOf(".") >= 0) {

extName = name.substring(name.lastIndexOf("."));

}

File file = null;

do {

// 生成文件名:

name = UUID.randomUUID().toString();

file = new File(savePath + name + extName);

} while (file.exists());

File saveFile = new File(savePath + name + extName);

try {

item.write(saveFile);

} catch (Exception e) {

e.printStackTrace();

}

}

}

response.getWriter().print(name + extName);

代码很好懂,不讲解了。



下面是官方英文文档的一些翻译。此文档是2.14版本的,现在出了3.0版本,属性和事件名称有改变,注意参考JS文件

Uploadify属性

属性名 类型 说明
auto boolean 添加到队列后自动上传
buttonImg string 浏览按钮的背景图片
buttonText string 浏览按钮的显示文字
cancelImg string 取消上传按钮的图片
checkScript string 服务端用来检查文件是否重名的脚本
displayData string 上传时显示的提示(percentage百分比/speed速度)
expressInstall string 安装swf的文件(expressInstall.swf)路径
fileDataName string 重定义的input的名称(后台)
fileDesc string 文件打开对话框中的文件类型描述
fileExt string 可允许上传的文件类型
folder string 文件存储的文件夹
height integer 浏览按钮的高度
hideButton boolean 是否隐藏浏览按钮
method string 表单提交方式(post/get)
multi boolean 是否允许上传多个文件
queueID string 上传队列的元素的ID
queueSizeLimit integer 上传队列大小
removeCompleted boolean 完成上传时是否自动删除
rollover boolean 当鼠标移上时产生特效
script string 上传表单提交的目标脚本
scriptAccess string swf的文件地址
scriptData JSON 提交给后台的附加信息
simUploadLimit integer 同时可上传的文件实例数
sizeLimit integer 每文件的最大大小
uploader string uploadify上传的swf文件路径
width integer 浏览按钮的宽度
wmode string flash文件的wmode模式

Uploadify事件

事件名 参数 说明
onAllComplete function(event,data) 当所有文件上传完毕时触发
onCancel function(event,ID,fileObj,data) 当某文件被取消上传时触发
onCheck function() 当开始上传时检查是否重名
onClearQueue function(event) 当执行uploadifyClearQueue()后执行
onComplete function(event, ID, fileObj, response, data) 当某文件上传完毕时触发
onError function(event,ID,fileObj,errorObj) 当上传时有错误返回时触发
onInit function() 当uploadify实例加载完毕时触发
onOpen function(event,ID,fileObj) 当某文件开始上传时触发
onProgress function(event,ID,fileObj,data) 当某文件上传进度改变时触发
onQueueFull function(event,queueSizeLimit) 当上传队列达到限制时触发
onSelect function(event,ID,fileObj) 每个文件被添加到上传队列时触发
onSelectOnce function(event,data) 一次文件被添加到上传队列时触发
onSWFReady function() 当flash加载完毕时触发

Uploadify方法

方法名 说明
.uploadify() 创建Uploadify的实例
.uploadifyCancel() 从上传队列中移除一个文件
.uploadifyClearQueue() 清空上传队列
.uploadifySettings() 修改Uploadify实例的属性
.uploadifyUpload() 触法文件上传
posted @ 2012-03-03 10:29 盼逆邵年 阅读(289) 评论(0) 编辑

在B/S模型的Web应用中,客户端常常需要保持和服务器的持续更新。这种对及时性要求比较高的应用比如:股票价格的查询,实时的商品价格,自动更新的twitter timeline以及基于浏览器的聊天系统(如GTalk)等等。由于近些年AJAX技术的兴起,也出现了多种实现方式。本文将对这几种方式进行说明,并用jQuery+tornado进行演示,需要说明的是,如果对tornado不了解也没有任何问题,由于tornado的代码非常清晰且易懂,选择tornado是因为其是一个非阻塞的(Non-blocking IO)异步框架(本文使用2.0版本)。

 

在开始之前,为了让大家有个清晰的认识,首先列出本文所要讲到的内容大概。本文将会分以下几部分:

  1. 普通的轮询(Polling)
  2. Comet:基于服务器长连接的“服务器推”技术。这其中又分为两种:
    1. 基于AJAX和基于IFrame的流(streaming)方式
    2. 基于AJAX的长轮询(long-polling)方式
  3. WebSocket

 

 

古老的轮询

轮询最简单也最容易实现,每隔一段时间向服务器发送查询,有更新再触发相关事件。对于前端,使用js的setInterval以AJAX或者JSONP的方式定期向服务器发送request。

var polling = function(){
$.post('/polling', function(data, textStatus){
$("p").append(data+"<br>");
});
};
interval = setInterval(polling, 1000);

 

后端我们只是象征性地随机生成一些数字,并且返回。在实际应用中可以访问cache或者从数据库中获取内容。

import random
import tornado.web

class PollingHandler(tornado.web.RequestHandler):
def post(self):
num = random.randint(1, 100)
self.write(str(num))

 

可以看到,采用polling的方式,效率是十分低下的,一方面,服务器端不是总有数据更新,所以每次问询不一定都有更新,效率低下;另一方面,当发起请求的客户端数量增加,服务器端的接受的请求数量会大量上升,无形中就增加了服务器的压力。


Comet:基于HTTP长连接的“服务器推”技术

看到 这个标题有的人可能就晕了,其实原理还是比较简单的。基于Comet的技术主要分为流(streaming)方式和长轮询(long-polling)方式。
 

首先看Comet这个单词,很多地方都会说到,它是“彗星”的意思,顾名思义,彗星有个长长的尾巴,以此来说明客户端发起的请求是长连的。即用户发起请求后就挂起,等待服务器返回数据,在此期间不会断开连接。流方式和长轮询方式的区别就是:对于流方式,客户端发起连接就不会断开连接,而是由服务器端进行控制。当服务器端有更新时,刷新数据,客户端进行更新;而对于长轮询,当服务器端有更新返回,客户端先断开连接,进行处理,然后重新发起连接。
 

会有同学问,为什么需要流(streaming)和长轮询(long-polling)两种方式呢?是因为:对于流方式,有诸多限制。如果使用AJAX方式,需要判断XMLHttpRequest 的 readystate,即readystate==3时(数据仍在传输),客户端可以读取数据,而不用关闭连接。问题也在这里,IE 在 readystate 为 3 时,不能读取服务器返回的数据,所以目前 IE 不支持基于 Streaming AJAX,而长轮询由于是普通的AJAX请求,所以没有浏览器兼容问题。另外,由于使用streaming方式,控制权在服务器端,并且在长连接期间,并没有客户端到服务器端的数据,所以不能根据客户端的数据进行即时的适应(比如检查cookie等等),而对于long polling方式,在每次断开连接之后可以进行判断。所以综合来说,long polling是现在比较主流的做法(如fb,Plurk)。

 

接下来,我们就来对流(streaming)和长轮询(long-polling)两种方式进行演示。

 

流(streaming)方式

从上图可以看出每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。

流方式首先一种常用的做法是使用AJAX的流方式(如先前所说,此方法主要判断readystate==3时的情况,所以不能适用于IE)。
 

服务器端代码像这样:

class StreamingHandler(tornado.web.RequestHandler):
'''使用asynchronus装饰器使得post方法变成无阻塞'''
@tornado.web.asynchronous
def post(self):
self.get_data(callback=self.on_finish)

def get_data(self, callback):
if self.request.connection.stream.closed():
return

num = random.randint(1, 100) #生成随机数
callback(num) #调用回调函数

def on_finish(self, data):
self.write("Server says: %d" % data)
self.flush()

tornado.ioloop.IOLoop.instance().add_timeout(
time.time()+3,
lambda: self.get_data(callback=self.on_finish)
)

对于服务器端,仍然是生成随机数字,由于要不断输出数据,于是在回调函数里延迟3秒,然后继续调用get_data方法。在这里要注意的是,不能使用time.sleep(),由于tornado是单线程的,使用sleep方法会block主线程。因此要调用IOLoop的add_timeout方法(参数0:执行时间戳,参数1:回调函数)。于是服务器端会生成一个随机数字,延迟3秒再生成随机数字,循环往复。
 

于是前端js就是:

try {
var request = new XMLHttpRequest();
} catch (e) {
alert("Browser doesn't support window.XMLHttpRequest");
}

var pos = 0;
request.onreadystatechange = function () {
if (request.readyState === 3) { //在 Interactive 模式处理
var data = request.responseText;
$("p").append(data.substring(pos)+"<br>");
pos = data.length;
}
};
request.open("POST", "/streaming", true);
request.send(null);


对于tornado来说,调用flush方法,会将先前write的所有数据都发送客户端,也就是response的数据处于累加的状态,所以在js脚本里,我们使用了pos变量作为cursor来存放每次flush数据结束位置。

 

另外一种常用方法是使用IFrame的streaming方式,这也是早先的常用做法。首先我们在页面里放置一个iframe,它的src设置为一个长连接的请求地址。Server端的代码基本一致,只是输出的格式改为HTML,用来输出一行行的Inline Javascript。由于输出就得到执行,因此就少了存储游标(pos)的过程。服务器端代码像这样:

class IframeStreamingHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
self.get_data(callback=self.on_finish)

def get_data(self, callback):
if self.request.connection.stream.closed():
return

num = random.randint(1, 100)
callback(num)

def on_finish(self, data):
self.write("<script>parent.add_content('Server says: %d<br />');</script>" % data)
# 输出的立刻执行,调用父窗口js函数add_content
self.flush()

tornado.ioloop.IOLoop.instance().add_timeout(
time.time()+3,
lambda: self.get_data(callback=self.on_finish)
)


在客户端我们只需定义add_content函数:

var add_content = function(str){
$("p").append(str);
};


由此可以看出,采用IFrame的streaming方式解决了浏览器兼容问题。但是由于传统的Web服务器每次连接都会占用一个连接线程,这样随着增加的客户端长连接到服务器时,线程池里的线程最终也就会用光。因此,Comet长连接只有对于非阻塞异步Web服务器才会产生作用。这也是为什么选择tornado的原因。

使用iframe方式一个问题就是浏览器会一直处于加载状态。


长轮询(long-polling)方式


长轮询是现在最为常用的方式,和流方式的区别就是服务器端在接到请求后挂起,有更新时返回连接即断掉,然后客户端再发起新的连接。于是Server端代码就简单好多,和上面的任务类似:

class LongPollingHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def post(self):
self.get_data(callback=self.on_finish)

def get_data(self, callback):
if self.request.connection.stream.closed():
return

num = random.randint(1, 100)
tornado.ioloop.IOLoop.instance().add_timeout(
time.time()+3,
lambda: callback(num)
) # 间隔3秒调用回调函数

def on_finish(self, data):
self.write("Server says: %d" % data)
self.finish() # 使用finish方法断开连接


Browser方面,我们封装成一个updater对象:

var updater = {
poll: function(){
$.ajax({url: "/longpolling",
type: "POST",
dataType: "text",
success: updater.onSuccess,
error: updater.onError});
},
onSuccess: function(data, dataStatus){
try{
$("p").append(data+"<br>");
}
catch(e){
updater.onError();
return;
}
interval = window.setTimeout(updater.poll, 0);
},
onError: function(){
console.log("Poll error;");
}
};


要启动长轮询只要调用

updater.poll();


可以看到,长轮询与普通的轮询相比更有效率(只有数据更新时才返回数据),减少不必要的带宽的浪费;同时,长轮询又改进了streaming方式对于browser端判断并更新不足的问题。

 

WebSocket:未来方向

以上不管是Comet的何种方式,其实都只是单向通信,直到WebSocket的出现,才是B/S之间真正的全双工通信。不过目前WebSocket协议仍在开发中,目前Chrome和Safri浏览器默认支持WebSocket,而FF4和Opera出于安全考虑,默认关闭了WebSocket,IE则不支持(包括9),目前WebSocket协议最新的为“76号草案”。有兴趣可以关注http://dev.w3.org/html5/websockets/
 

在每次WebSocket发起后,B/S端进行握手,然后就可以实现通信,和socket通信原理是一样的。目前,tornado2.0版本也是实现了websocket的“76号草案”。详细可以参阅文档。我们还是只是在通信打开之后发送一堆随机数字,仅演示之用。

import tornado.websocket

class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
for i in xrange(10):
num = random.randint(1, 100)
self.write_message(str(num))

def on_message(self, message):
logging.info("getting message %s", message)
self.write_message("You say:" + message)

客户端代码也很简单和直观:

var wsUpdater = {
socket: null,
start: function(){
if ("WebSocket" in window) {
wsUpdater.socket = new WebSocket("ws://localhost:8889/websocket");
}
else {
wsUpdater.socket = new MozWebSocket("ws://localhost:8889/websocket");
}
wsUpdater.socket.onmessage = function(event) {
$("p").append(event.data+"<br>");
};
}
};
wsUpdater.start();

 

总结:本文对Browser和Server端持续同步的方式进行了介绍,并进行了演示。在实际生产中,有一些框架。包括Java的Pushlet,NodeJS的socket.io,大家请自行查阅资料。

本文参考文章:

  1. Browser 與 Server 持續同步的作法介紹 (Polling, Comet, Long Polling, WebSocket) (可能要翻墙)
  2. Comet:基于 HTTP 长连接的“服务器推”技术 
posted @ 2012-02-10 20:42 盼逆邵年 阅读(2321) 评论(11) 编辑

HTML5的新玩法——语音搜索。可惜的是只有webkit核心的浏览器才能使用。用法很简单
只需要在input添加属性x-webkit-speech即可,例子如下:
<"input type="text" x-webkit-speech /">
这样你的输入框右边里就多了个「小话筒」,点击的时候就会提示

这时说出来识别后就可以了,我测试下来,中文英语的识别率还挺高的。

语音输入其他属性:

  • lang

    这玩意可以强制输入框里面的语音的语言种类,例如
    <"input type="text" x-webkit-speech lang="zh-CN"/">

  • 语音事件

    目前已知的只有onwebkitspeechchange,顾名思义,就是语音发生变化时触发的事件,一般可以作为提交
    <"input type="text" x-webkit-speech onwebkitspeechchange="$(this).cloest('form').submit()"/">
    这样说完以后就自动搜索了

  • x-webkit-grammar

    这个不是语音搜索用的属性,但是可以控制这个输入的语法,例如在做搜索框的话就可以用
    <"input type="text" x-webkit-speech x-webkit-grammar="builtin:search" /">
    使得语音输入的内容尽量靠近搜索内容,去除多余的字符,例如「的」

这个功能相当有趣,现在HTML5和CSS3的逐渐规范化,和浏览器支持的更新,相信以后人性化的WEB产品将更多。

posted @ 2012-02-10 14:02 盼逆邵年 阅读(48) 评论(0) 编辑

本文作者曾在Facebook从事4年多的开发工作,这一段宝贵的工作经历汇聚成为10条人生箴言,希望每一位开发者阅读本文后,都能够从中获益。


1.坚持你的远景,但要对细节灵活

作为一个领导者,你需要依赖你自己的远景(至少在你负责的业务领域内)而那些和你一起或为你工作的人将依赖你的远见。什么是远景?就是对最终状态的一种描述。是你需要你的团队着陆的地方。是生效之后的新生活。它是北极星,和方向。这里有一个例子,当我启动支付风险团队的时候,我们只有规则引擎。规则是人写的。一条规则只是一个拥有非常有限变量的简单逻辑,例如“如果注册日期少于30天并且支出大于100美元并且是首次支付并且用户来自印度尼西亚,则拒绝交易”

人类难以有效的处理10个以上的变量。我们需要更加的可测量化。我们想要使很多机器比人更擅长的事情自动化。从而我们树立了一个远景,将我们主要的规则置换为机器学习模型。这一远景驱使我们增加了一位机器学习领域的博士和另一位在加入facebook以前有类似实操经验的工程师。赌注巨大,但是未来需要这个。

但你需要对细节灵活,因为条条大路通罗马。你需要给你的团队以足够的余地空间(wiggle room),只要你的团队是朝着正确的方向以合适的速度前进。另一个故事:一度我对决策树的兴趣比回归要大。但是实验算法的工程师告诉我在选择算法的时候他们只有可以忽略的区别。我可以坚持己见(这确实是当时我真实的想法)但我信任他并让他放手去选合适的算法。同设计者合作的过程中也有趣事发生,他们对于字体,颜色,行距及其他都有着吹毛求疵的偏好。我通常让他们由着性子只要对最终结果有益就行。我们想要选择正确的战场开战,这样的战场必须关乎全局,而不是纠缠于局部战斗。

2.只和最好的人合作

牛人只想和牛人一起工作。他们聚在一起就更牛逼。一流的人通常无法容忍二流货色。什么决定了“最好的人”?我的理解是那些能够快速尽其所知,学其所不能,从而完成事情并远超期望。他们的本能是超越期望,甚至他们自己的期望。对他们来说,足够好本身从来都不够好。 只拥有最好的人在团队中有很多好处。

⑴这让你更加愿意托付。救我的经验来看,牛人不会信任不熟悉的人。在接受你的帮助之前,他们需要知道你同他们一样出色甚至比他们自己还强。否则,他们宁愿尽其所能的独立完成。但如果你业已得到证明,他们将会非常信任你并乐于托付给你。一个有着有效托付的牛逼团队几乎无所不能。

⑵他们相互为榜样,并且籍此提升工作表现。这是很好的关注压力。如果使用得当,可以形成团队的良性循环。

⑶牛人们相互挑战。我记得一位工程师主管曾就我们能否在规定时限之前完成网站翻译所需的代码修改来打赌,他将把头发染成蓝色。这样的挑战往往是的“枯燥”的工作变成了游戏。成为游戏的一部分将非常有趣。当然往往会有更严肃的挑战,而且牛人喜欢挑战(不管是挑战别人还是接受挑战)

⑷牛人们相互学到很多。如果不是因为这里有这么多可以学习的话我不会在facebook呆4年。对缺乏经验的人来说,这显得越发正确。我们雇佣非常聪明的毕业生,这些人被激发来证明他们的牛逼之处。他们不愿来到一个舒适并且没有挑战性生活的公司。他们想学很多来丰富他们的经验,完成不可完成的任务并提升他们的职业路径。他们想要证明“是的,他们可以”。他们想和牛人一起来实现这些。

  你不想要二流货色但如何远离他们?首先,慢点招人。在给offer的时候固执一点。我曾无数次的在招聘决策会议上发现那些有着光辉履历的人无法得到雇佣只是因为某个面试官觉得这人无法给他以深刻印象。在其他例子当中,那些获得一致通过的候选人仍然无法获得雇佣机会因为每个人都只是觉得他将将符合要求而已。在雇佣问题上,绝大多数情形下,你要风险规避。(顺便提一下我们也会雇用那些没有全票通过的候选人,只要有一两票强烈推荐﹣你将乐于在信任你员工的问题上冒险)其次,炒鱿鱼要快。让二流货色存在就像服用慢性毒药。一天一点,逐渐但终究你会为此挂掉。你要紧你所能的把二流货色踢到鱿鱼轨道上(在某些情况下,法律不让你这么做)我见过一些慢吞吞的鱿鱼,那对团队造成的负面作用可不是闹着玩的。

 3.树立高期望并且衡量之。

作为领导者,你需要设定高但现实的期望。足够高使得你的团队不会感到无聊。现实使得他们不至于油尽灯枯。你要给他们创造一段经验使得旅程结束回顾时,他们会说:妈的,我都没想到我居然做了这个。这个屌爆了。在Facebook,象其他硅谷高技术公司一样,期望同回报相结合,因此树立明确的期望本身就至关重要。 并且你需要找到一个不容争辩的途径来衡量期望。我花了大量时间来和团队一起描绘我们在下季度里最重要的3-5个目标。目标被分别委派给单个或一组分工不同的攻城狮,或者被他们抢走。在这一情况下,我们不仅有一个由3到5个衡量指标组成的名单,使得我们可以籍此快速地说出来我们在干嘛,同时也知道每个具体目标后面是谁。团队的成功同个体表现息息相关。例如,我当年团队最大的贡献是在一年时间里,通过每季度不同的指标,最终降低了信用卡支付争议率75%。

有一点要强调的是﹣你还是要现实。在你只有10%的市场份额的时候却幻想10几倍的收入增长无疑不现实。史蒂夫乔老爷非常善于推动他的团队超越潜能但同时也榨干他们(尽管如此他们是这样为他们所做的而自豪)99.9%的领导不是乔老爷,当然他们也不需要是。但你仍然可以通过驱动一个可持续性的团队来取得成功同认识到他们的极限并无矛盾。 

4、重视数据而不盲从数据

决定产品方向时, 要的是想象力, 激情和胆量, 而不是数据. 数据能让你的团队沿着正确的方向前进而不出轨,也有助于产品从“一开始是什么样”到“最后应该是什么样”的逐渐优化成型. 但数据不能帮你决定方向. 举个例子,当我们在人工智能(机器学习)上压上我们团队所有的资源的时候, 我们忐忑不安. 但是我们坚信一点,现有的基于人工规则引擎的防欺诈系统会很快成为死胡同, 因为它太死板而且不易规模化以处理大数据。所以,就像在电影指环王中Frodo明知通向Mordor的道路很黑很冷很危险, 但那是一条他必须要选择去走的路;我们选择了在机器学习上压上所有的宝。失败, 整个团队会很难看; 但我们决定走艰难但我们认为是正确的路.这种思路同样应用在如何设计用于用户报告(外部工具)和案例审查(内部工具)的工具来应对潜在的欺骗行为。我们最后决定的方向是"进行自动处理"和"建立反馈机制"。直接抛给人工来处理总是很容易被选的一条路,因为只要建立一个人多人傻的客户支持团队即可. Lame!我们希望通过自动处理来解决大部分的欺诈案例,而把精力则放在那些确实需要单独处理的特殊案例上,同时把从业务支持团队(即客户支持部门)的处理意见自动采集并集成到下一轮的机器学习中去。由此,我们的机器判断会越加精确和聪明且与时俱进. 

但你不能忽视数据。没有数据的支撑而一味靠直觉走黑路, 很容易走岔道,甚至大错特错。有一段时间我们认为爬行工具(通过分析关联的cookie,信用卡)可能可以找到很多欺诈的同伙。通过实验结果却发现,这种预期是否成立很大程度上取决于当前流行的欺诈行为的特点. 比如,当失窃或贩卖信用卡的案例非常普遍的时候,关联分析是一种有效的方法。但如主要情况是帐户被黑或小宝们冒用妈妈的信用卡去网游消费时,关联分析就作用不大。直觉在现实前面碰了一脸的灰。不过幸运的是我们很快意识到这点且把这个项目叫停了, 所以没有浪费太多的资源。 

另外,顺带提一下A/B测试。A/B测试并不会告诉你去做什么产品,但它可以帮你确定实现产品时的哪个细微版本更能揪住用户大爷们的心.

5、避免无谓的时间浪费

刚进Facebook做工程师的时候,我非常享受那种日夜泡在码海中的感觉。后来慢慢的承担的项目责任越来越大越来越多,写代码的时间越来越少(但绝大多数时候仍占大头).有时候更多的是把时间花在决定产品的方向和设计上。很多事情是和产品经理设计人员一起搞的.但在Facebook攻城狮们有很大的发言权甚至有些时候是拍板的权力。Facebook希望攻城狮们有王者风范.Facebook希望攻城狮能决定自己要做什么应该做什么, 而不是总是"被决定"做什么(一种流行的说法是,write your ownjob description). 因此,我花了大量的时间在思考这些问题 -哪些功能需要添加,哪些功能需要删掉,需要开始或停掉哪些测试,我们正在流血流汗的是不是现在最最最重要的问题,我们是该花时间优化用户交互流程呢, 还是减少出错率, 还是让系统更快, 等等。这些问题很伤脑筋, 答案经常不确定,比一个劲码到手抽筋要难. 但这些问题很重要, 甚至可能决定了你熬的日日夜夜究竟有没有必要.建议所有的攻城狮思考思考代码之外的这些问题, 团队领导者就更有必要了. 当然,攻城狮的大多数时间还是应该花在代码上. 

那究竟哪些时间不应该被浪费呢?

很多, 但我只举两个我认为最重要的例子。

邮件。不是所有邮件都发而平等。有些邮件纯粹打酱油的. 有些邮件是不需要马上处理的.我尝试使用过滤规则来踢掉打酱油的邮件, 突出需要马上处理的重要邮件。对此,分享两点。

1) 建立一个适合你的邮件过滤系统.我会对重要和紧急的邮件做即刻回复,而暂缓处理那些可以等到晚上再回复的邮件(尤其是发自我自己的团队,产品经理,兄弟连和顶头的不顶头的上司们的邮件)。但是,我要确保在我挣扎的爬到床上之前,把这些邮件全部处理掉,读的读, 回的回。对于那些仅供参考的邮件,过滤系统会将其塞到某个固定的角落,我隔三差五去瞅瞅。此类邮件诸如某酒鬼询问NapaValley哪个酒窖比较正点等等. 这些邮件通常比较有趣, 挖的坑很大很深所以也很耗时间, 我通常不跳或者不马上跳。

2) 广而告之你的个人邮件处理策略. 我让我身边的战友们知道我是如何处理邮件的,并把这个政策放到我所有的邮件末端。如是说 - "正在尝试个人邮件处理策略-为了戒掉Email瘾,我将强迫自己每隔三小时或以上查看一次Email,急事请电话/短信/IM我" 这么做更多的是让别人明白不要指望马上得到回应.其实我查email比每3小时要频繁, 但至少不用马上逼得去回每个email了, 我可以憋着悠着点. 因为如果真的很急,我的iPhone应该已经响过了. 而且, 批量处理真的效率要高很多. 不骗你. 

会议。开会太容易变成一群人互相在扯对方的蛋. 浪费时间而且开完后发现没有结论且很蛋疼.但开会对于teamwork很多时候是必要的. 如何主持会议是门学问, 这里不细谈. 不过,你不可能也不需要参加每个邀请你的会议。当你认为你参加某会议于己于人都无太多价值的时候, 建议你考虑不去。如果想要有礼貌一点,那就写个email问问主持人你是否可以缺席.通常当你想过这个问题决定发这样的邮件时,答案通常都会是yes。有些时候我也会很可耻的让我的产品经理替我去开会。当然,我会鼓励他也争取不要去。Onlymake the meetings you really have to. 同样,我要求我自己的团队在组织和参加会议的时候要慎重,也经常问他们想想看自己花在会议上的时间是不是多了。一个做法是把可能的会议都整合在一起。有一个例子。早些时候,我们会经常收到来自支持团队的比较随意的会面请求。这让攻城狮的一天被会议分割得支离破碎.写代码的都知道没有3-4个小时的连续时间是不容易高潮的. 而且这种会议通常效率很低.于是,我们改变了做法,每周安排固定的答疑时间(office hour)和支持团队嗑想法然后follow up。当然,紧急的问题另当别论应当马上处理. 

有一个被经常忽略的原则 -有意识地去思考哪些事情不应该做并且马上不做。例如,哪些是无谓的争论可以避免介入(比如韩寒和方舟子的 -个人意见),哪些功能可以放弃,哪些关系不应该发展, 哪些人应该开掉,等等。我经常问自己一个很简单的问题,我现在正在做的是否对我的目标很重要。如果你清楚自己正在做的和自己想要的,答案会明了。Go forit.

6、增进亲密感是减少紧张关系的有效方式

工程师和支持团队之间有着纠结的合作竞争关系(注意,合作在前)。互联网技术公司中很多人(尤其是聪明人)总是期望工程师对所有问题给出一个让人会心一笑的解决方案。但现实是,不是每一个问题都可以或者应该在技术框架下解决。对于一些具体的问题,客户支持和运营部门会有一些非常深刻独到的见解. 工程师未必行. 毕竟很多见解需要不同的专业知识, 依靠实地经验。没错,工程师可以在代码中自动log大量的原始数据,但从原始数据中提炼可靠的insight却并不总能如愿. 就像大炼钢年代扔进去的是铁,出来的是铁疙瘩, 而非期望的钢. 和很多其他公司的客户或支持部门不同, 我们的支持部门招募了质量相当好的员工(很多来自美国名校 -在我直接接触的反欺诈支持组20来人中就有3名斯坦福校友)。因此,当两群都很聪明的人观点相左时,该听谁的呢? 紧张关系再所难免。

不同的工程师团队也存在着合作竞争关系。反垃圾邮件、安全和反欺诈(我的团队)这几个团队之间存在密切的工作协作关系。这些团队也都尽可能地相互学习,分享经验和技术。但是,有时候各团队独立处理类似但不同的一些问题时,都试图向对方推销自己的解决方案和理念。

如何让合作竞争保持在一种健康有序的状态? 我觉得关键是促进人与人之间的亲密感。把人搞近了, 事情就容易了.我花大量时间用在建立和其他团队的关系上面。例如两周一次或者一月一次和其他团队老大们的1对1碰头会。越相关的团队, 头碰得越频繁.我自己或者我的团队成员会有选择性的经常参加一些其他团队的会议 (我们称之为Friends &Familymeeting)。当为一个共同的大项目工作时,我曾安排不同的部门成员(工程师、支持、数据分析、金融财务)坐到一起进行项目冲刺。这是拉近相互之间距离的非常有效的一个做法,尤其对于减少扯皮的机会. 因为互相之间经常会请或被请喝咖啡. 我也会经常和一些人约定吃工作午餐, 经常聊的是家常,增的是感情。有的时候一次长距离的散步也更能让人畅所欲言。而这样的紧密关系,在我们面对一个极具挑战性的项目的关键时刻,会帮助大家紧紧的抱团闯关.

7.习惯委托, 但不要盲目, 请谨慎

分配任务委托别人的重要性比较容易理解. 因为你不是超人, 不能端茶倒水什么都做吃喝拉撒什么都管.有些时候, 你往往还不是最适合的人选. 当团队一大事情一多, 你一定要学会委托别人来负责合适的任务. 对有些领导者而言,委托别人一个重要的目标可能不是很放心, 觉都睡不好; 但我非常习惯委托别人, 有时候可能太习惯了.这是我一位前老板给我指出来的一个问题. 有一次我给一位组员分配了一个既有技术难度又有协调挑战的难题. 进程比较缓慢.但我给了他太多的时间空间来折腾, 而事实上他在某些方面需要一些加强, 有些方面需要我更多的主动的帮助. 我老板指出来,如果我要让别人随便折腾的话, 前提是我需要有足够的信心.  我需要有事实来逐渐证明我的决定是正确的.需要谨慎委托. 因为如果项目失败, 对他而言, 最终负责的人还是我, 不是别人. 所以我不能以别人不行来给失败的委托埋单. 

如果你有一个重要的任务需要委托给别人, 你要么

1) 已经对此人非常了解. 知道他战斗力非凡可以搞定; 或者相信他可以迅速学习提高打鸡血搞定;

要么

2) 需要在一开始手把手教他, 时不时问他, 直到你对他有足够的信心.

具体我是这么做的. 项目开始时, 我让被委托人给我一个整体计划以及几天内可以完成的任务.一开始经常会面跟进, 然后确定后几天的任务. 根据每次完成状况来估计他能不能"高快狠"地完成最终的目标.信心逐渐建成后可以减少关于该项目的细节讨论. 此时的委托可以放得更开. 但有一点要注意, 如果跟的太紧的话,可能让人觉得你对他不放心, 他也会做得畏首畏尾, 这可能比盲目的委托还更差. 所以在委托和谨慎之间,有一个微妙平衡.

我觉得在这一点上我还要加强. 这里也和大家提个醒.

8.意见反馈应该一个持续性的, 而不是一年一次或两次的活动

一年一度或两度的意见反馈在硅谷公司是非常常见的. 它的目的不是设置起来给员工难堪, 让他们互相责难的.它的目的是希望员工对自己对他人有更全面的认识, 以助进步. 意见反馈有自我反馈和同事反馈两部分. 自我反馈是自己评定自己,完成了哪些目标, 错失了哪些目标, 哪些方面做好了, 哪些方面还待进步. 但由于是自己踢球兼裁判, 难免有偏颇. 同事反馈,就像一枚镜子, 让你看到180度之外的自己. 在Facebook, 360度的正式意见反馈是一年两次, 并且和薪酬挂钩. 但近年来,意见反馈和薪酬评定逐渐分开. 比如我做的一件事就是季度性的意见反馈, 时间和正式评定错开. 在那几天中,我请求所有相关组的同事在自愿的前提下给我写写关于我直属组员的意见反馈, 短短几句都行. 我会收集, 综合,最后在1-1碰头会时反馈给我的组员. 

如果需要等半年才来收集意见的话, 很多相关故事早以忘得一干二净. 故事越久远, 记忆越模糊,意见越空洞, 说了等于没说. 而且, 意见反馈和薪酬绑在一起, 正常人(即使是牛人)都会很自然的把心眼更多的放到薪酬上,而不是意见本身.

除了季度性的轻型意见反馈, 日常的意见反馈如果有的话应当立马传递. 趁热打铁效果更好.

如何有效传递整理好的意见也很重要. 有句话是说"it's not what you say thatmatters, it's how you say it". 我没那么极端, 我觉得如何传递意见也同样重要.有两种方式我都试过, 不确定哪种更有效. 这里都谈一谈. 一种是以问为主逐渐深入促其思考, 比如"how did you thinkabout the meeting you hosted yesterday";另外一种是赤裸裸的直入主题, "hey, let's talk about the meetingyou held yesterday", 然后开始谈我自己的感觉. 不管哪种方式, 一定要给对方一个解释自己行为的机会;永远假设并告诉他我相信他的意愿是好的. 为了避免陷入"你昨天做了xxx" "没有, 我做的是yyy""我觉你是做了xxx"的死循环式的争论, 我首先争取和他们在"我们感知的即是事实"这一点上达成共识. 基于这点前提,我们把讨论的重点放在如何做能改变别人的感受最后让事情能顺利完成, 毕竟大多数重要的事都有很多人一同协作完成.当他们认识到自己想要改进某个方面的时候, 如何改是一个相对容易很多的问题 - 聪明人一向能够找出改进的办法,我所做的就是配合他们做头脑风暴. 最终谈话的目的是产生一个下次如何能做的更好的具体方案.

关于有效传递意见反馈, 另有4点提一下.

1) 意见反馈不见得都是负面的. 它可以是别人的一个长处. 你很欣赏. 你希望他这方面坚持做, 做得更多.比如一句"hey, I really love your weekly summary email with the keymetrics at the top. Please keep them coming"可能产生很好的激励效果.

2) 意见反馈必须摆事实和讲道理. 如果你只是告诉别人他很烂, 但不说什么时候浪过了以及为什么,除了给他添点火气之外无他用. 所以我在相关人员包括自己写意见反馈的时候要求提供实例. 比如一句 "I think he couldmake meetings transparent and shorter by having an agenda, like theweekly data review meeting on last Friday"比"his meeting is toolong"更有血有肉有效. 

3) 意见反馈必须是可操作的. 让人无从下手的意见意义不大.如果在提意见的同时提出一个方案以供参考就有意义的多. 但注意, 绝不能是命令的方式 (那是中青宝…). 比如前面的例子"I thinkhe could make meetings transparent and shorter by having an agendasent ahead of time…"就很容易操作.

4) (个人偏好) 在最近的两个评价周期中, 我给15个左右的同事(一半不直属我)写过意见反馈.我把我写的直接分享给他们. 出于这种想法, 在我下笔时就少了很多冲动. 因为他们会读, 所以我无法做到背后捅刀. 因为他们要读,所以我需要写得有意义, 容易理解, 并且加上很多例子. 并且, 我欢迎他们和我直接讨论. 如此一来,他们也明白我写这些反馈的一片苦心是为了他们进步.

9.你可以比你想象的做得刚好

这不是说说而已. 我自己就有一个亲身的例子. 我们曾经认为把一个高得离谱的欺诈率降到所允许的范围内会很难.的确很难. 但想想看我们最终牛逼了一把, 把它降到了比允许上限的一半还要低. 感觉很爽.很长一段时间内整个团队士气高昂信心爆棚做事像开了外挂.

牛人们总是不断的超越自己. 给他们一个离谱的目标, 配以应有的工具, 适当的帮助,足够的信心还有一定的时间, 他们会让你大吃一惊, 也会让自己大吃一惊. 这一点, 乔帮主是行家, 屡试不爽.

但做到这一点有一个前提 - 不能害怕犯错. 如果犯错是被要严惩的失败是不允许的话,牛人们只能在框框中被圈养, 没有办法实现突破. 有一句话我经常挂在嘴上"ask for forgiveness, not forpermission". 在Facebook, 大胆行事犯错是容易被原谅的. 

但反过来, 有一点要小心, 就像第7点所说的 - 你不能随便把一个离谱的目标交给一个人,然后期待他来给你惊喜. 盲目带来的可能是惊吓. 你需要真正的牛人, 至少是潜在牛人. 而作为一个领导者, 你的一个任务是帮助他们,鼓励他们, 来引爆自己的潜力点. Facebook不缺此类待引爆的牛人. 

10.不要过多设计或者过早优化

有些工程师有一股出于本能的冲动想把自己的程序规模化, 甚至在这些程序还没看到大规模使用的曙光之前.我在Facebook开始的时候, 也是冲动型工程师一杯. 但经历过几次失败的产品之后, 我牢记了这个教训. 不要过多设计或者过早优化.把核心功能设计的简单精炼. 只有在看到产品有被大规模使用的趋势后, 才来增加功能或增加规模量.有一个我做的产品使用的上限是200万月用户(当时Facebook整个月用户群是4000万左右),但我的实现已经做了很多额外的功来满足更多的用户. 做的时候感觉很爽(感觉自己很牛, 感觉再多人用产品也不会崩溃),之后感觉很惨.

但这一点不一定能适用于架构上的工作.比如Friendster这个网站的失败就是其基础架构的性能长期无法应对急速增长的用户以致网站很慢甚至崩溃. 在用户增长高潮来临之前, 你应该已经在架构上做了足够多的前戏.否则搞不好就要像Friendster收摊子散伙. 但同时也要意识到, 你所看到的用户访问模式, 你的网站功能,在你只有10万用户的时候, 可能和你有1亿用户的时候会很不一样. 所有太多太早太频繁的架构上的大动作可能会适得其反. 这一点上,你要小心判断. 

在Facebook的4年半很好玩. 我学到的感受到的远多于以上的十项. 但希望这个分享能对朋友们有点帮助.同时祝所有的朋友在自己现在扮演的角色上都有好运.

此外, 如果你是一位创业者, 并且认同以上的某些观点, 我很欢迎你通过一个共同的朋友来介绍自己.我会很乐意给出我的建议, 不管是在产品上技术上还是管理上. 如果机会合适的话, 参与早期的天使投资. 投资不多,但你可能收获一个有点经验的免费顾问 :)
posted @ 2012-02-10 10:23 盼逆邵年 阅读(176) 评论(0) 编辑
摘要: 如何确定一个js是否加载完全或者页面中的所有js加载完全,具体办法如下:function loadScript(url , callback){ var script = document.createElement("script"); script.type="text/javascript"; if(script.readyState){ script.onreadystatechange = function(){ if(script.readyState=="loaded"||sc...阅读全文
posted @ 2012-02-09 20:47 盼逆邵年 阅读(146) 评论(0) 编辑
摘要: 现在我们做网站不仅要在功能上满足用户的需求,更要提高用户体验。比如在上传文件的时候,我们是否能让用户一次性选择多个文件上传,是否能让用户在漫长等待中实时得到上传的进度,处理好这些文件上传的细节,那用户体验自然就提高了。今天我就来分享9个比较实用的jquery多文件上传插件,这些多文件上传插件能很好的提高用户体验。下面就一起来看看吧,希望这些jquery多文件上传插件能给你带来帮助。1. Plupload2. SWFUpload jQuery Plugin3. AJAX File Upload4. Uploadify5. JqUploader6. jqUploader Demonstration阅读全文
posted @ 2012-02-09 18:24 盼逆邵年 阅读(95) 评论(0) 编辑
摘要: #FFFFFF#FFFFF0#FFFFE0#FFFF00#FFFAFA#FFFAF0#FFFACD#FFF8DC#FFF68F#FFF5EE#FFF0F5#FFEFDB#FFEFD5#FFEC8B#FFEBCD#FFE7BA#FFE4E1#FFE4C4#FFE4B5#FFE1FF#FFDEAD#FFDAB9#FFD700#FFD39B#FFC1C1#FFC125#FFC0CB#FFBBFF#FFB90F#FFB6C1#FFB5C5#FFAEB9#FFA54F#FFA500#FFA07A#FF8C69#FF8C00#FF83FA#FF82AB#FF8247#FF7F50#FF7F24#FF7F0阅读全文
posted @ 2012-02-09 18:10 盼逆邵年 阅读(39) 评论(0) 编辑