21

今天,由于一个比较低级的失误,影响到了系统的可用性。
(话说回来,又有哪次事故不是由于低级失误造成的呢?)

简单来说,起因是一个server有改动,需要部署到N台机器上。
改动后的server会读取一个之前已经存在,但是没有用到的配置文件。
配置文件里面有一项,应该是本机的IP。
由于某种原因(就是低级失误的根源), 部分机器上的IP配置是相同的,都设成了其中的一台。
导致的结果就是那台不幸的机器由于负载过重,请求处理不过来。
而且由于这是一个比较关键的server,于是乎,系统的可用性被波及了。

但凡涉及到可用性(或者稳定性、用户体验之类用户可以感知的问题),总是比较容易引起关注的。
虽然不是事故的直接责任人,不过整个过程中暴露出来的问题,还是值得思考和总结一下。
追究责任或许有警示的作用,但正如代码都会有bug,人也总有犯错的时候。
如何在流程和制度上避免同样问题的再次出现,更值得去分析和研究吧。

有两个为什么需要解答:
1 为什么定位问题消耗了过长的时间?
2 为什么会部署了错误的配置文件?

对于第一个问题,有工具的原因、有个人的原因。
主要是因为出现状况的早期没有深入分析, 错过了尽早查出根源的机会,导致耗时较多。
对于这类问题,感觉唯一解是强化个人的责任心 。

而第二个问题,则是想讨论的重点。
虽然强调流程和制度,可能会被人诟病过于僵化。
但对于一个希望长期运营的产品来说,流程就像一个人的起居饮食一样,
虽然每天的重复会非常沉闷,但只要一天没有,就可能引起各种各样的缭乱。
其实流程对程序员来说也并不是完全的无聊和乏味,它可以搞定所有重复性的体力劳动,
省出来的时间,足够写不少有意思的程序了。而这还没考虑到由此避免的潜在的差错时间。

据个人感觉,流程上的问题有两个:
1 配置文件设置本机IP不是由部署脚本完成的,就算有部署脚本,也是人工写好再执行,并不是由部署系统根据占位符生成
2 上线后,开发人员会监控程序的运行,但通常不会检查每台机器上的配置文件是否正确,尤其是在机器很多的情况下

所以我的建议无非也是两个:
1 对于那些和机器有绑定关系的配置文件,由开发人员在运维平台上录入配置模板,部署时由脚本自动生成合适的配置文件。 如果再能够通过cron脚本,定期检查配置文件的合法性,则更加完美
2 开发人员在上线后,除了监控程序的运行状态,还要检查配置文件是否符合预期

其实只要第一条做到了,第二条就有蛇足之嫌。但又有谁能保证部署脚本没有bug呢?

Tagged with:
24

铁甲将军夜渡关,
朝臣待漏五更寒。
山寺日高僧未起,
算来名利不如闲。

Tagged with:
01

Tables map to classes, rows to objects, and columns to object attributes.

Tagged with:
14

三年前,我刚进腾讯,是在Qzone的商城组,职位是后台开发。
顾名思义,商城是卖东西的地方,涉及到的业务有装扮、购买、扣费等逻辑。
当时Team里没有专门的前端工程师,前端的工作都由后台工程师兼任。
从技术的角度看,就是从前端的JS、HTML、CSS,到中间的CGI,都后台的服务器,都有涉及。
其实这是一个挺好的职位,对增加技术的广度很有好处。

刚到Qzone的时候,我的导师Welkin,就对我说:
”现在的代码很烂,但也就这样了,到有人忍受不了的时候,自然会重写的“,而且说了不止一次。
他当时在做套装,可能改得比较苦闷吧。

当时我刚进去没几天,不太理解他的意思,而且也有点不以为然,心想:
”只要自己严谨一点,总不至于写出很烂的代码吧?“
后来我才知道,原来写代码和打篮球一样,也是团体运动,一个人是赢不了球的。

做完一个相对独立的小活动,赚了一百万之后(很夸张),开始真正接触商场的项目。
第一步当然是看已有的代码,当时只是觉得不太容易看懂,也并不算很烂。
到了后来,当我有一次要改购物车的代码时,才体会到Welkin当时的痛苦。

基本上是过程式的JS,存在不少全局变量,嵌套关系过于复杂。
以至于单纯看代码已经很难了解实际的运行流程,必须要用调试器来单步跟踪了。
显然,Welkin还能忍受这种代码,因为他做套装的时候,并没有重写这些代码。
必须承认,当时我也容忍了这些代码,只是修改了部分,以求完成功能。
当然我可以给自己借口:当时业务逻辑和前端技术都不太熟悉,想改也无从下手。
但是总之,我容忍了这种代码的存在。

再后来要对大头贴做改版,再次碰到了这种无从下手的代码。
从代码的风格可以看出,明显经过三个人以上的修改。
这次我把流程整理了一下,但也没有重写。
或者说,一定程度上,我又容忍了。

08年10月,我来到了邮箱,早期的主要工作是对阅读空间的CGI做重构。
经过几轮的重构之后,程序的流程变得比较让人满意,在这个基础上做新功能的速度也有了提升。
但是经过一段时间的开发之后,现在代码里的坏味慢慢开始散发出来。

所以我的结论是,代码也符合热力学定律,自然就处于熵增的状态,必然会慢慢变得腐烂。
一个团队的代码质量,并不决定于你能写出多好的代码,而是决定于你能容忍多烂的代码。

18

还是写点东西吧,不管有什么用,起码没有坏处。
– 写在最前面

在互联网这个快速变化的行业,要跟上每天都可能改变的需求,传统的瀑布开发模式显然不合适。
于是有“敏捷软件开发”这样的新开发模式被应用到互联网的日常开发当中,而这也是在腾讯内常用的模式。

根据《敏捷软件开发宣言》,四个最重要的价值观是:

  • 人和交互重于过程和工具。
  • 可以工作的软件重于求全责备的文档。
  • 客户协作重于合同谈判。
  • 随时应对变化重于循规蹈矩。

由以上的价值观所强调的部分,其实可以归纳出两个要点:人本身,以及人之间直接、快速、有效的沟通

从人的角度考虑,最起码的要求是,需要整个开发过程中的每个角色都可以在其位,谋其事。
至于角色的划分,可能不同公司有不同的方法。在Web方面,根据我的理解,大致可以分为以下几种:

产品经理:

基本上是各个角色中和人打交道成份最高的一个,所以对沟通能力的要求会比较高;另外良好的策划能力也是必须的
负责产品的整体方向,收集用户需求(包括来自用户和老大的),协调各种资源⋯⋯
常用的工具是Word、PPT、各式各样的沟通工具(IM、Email、SMS、BBS、广播⋯⋯)

项目经理:

跟踪项目的开发进度,保证版本的准时交付
负责规划产品的整体开发,划分需求,分配任务⋯⋯,
常用的工具可是各种各样的协作平台(例如TAPD)

UI设计师:

最具艺术家气息的角色
打交道的对象是布局、色彩、字体等等UI相关的元素
用户对网站的第一印象主要源自于UI设计师对产品的设计和感觉
常用的工具是PhotoShop

页面设计师:

将UI设计师的设计图切分为浏览器可以解析的页面
打交道的对象是HTML、CSS
最麻烦之处在于要兼容页面在各种浏览器上的表现,尤以个性独特著称的IE为甚
常用的工具是Editplus、Notepad++等编辑器(我没见过有人用Dream Weaver或者FrontPage),IE Dev Tool、Firebug等调试工具

交互工程师:

处理页面上的动态效果,浏览器和HTTP Server之间的通信,以及浏览器上的业务逻辑
打交道的对象主要是JavaScript(VBScript极其少见)
最麻烦之处在于要兼容脚本在各种浏览器上的表现,尤是以个性独特著称的IE为甚
常用的工具是Aptana等IDE;调试会用Visual Studio脚本调试器,Firebug等,当然也不会禁用alert,虽然比printf更不优雅

前端工程师:

串联浏览器和后台服务间的数据传输,产品逻辑的主要控制者
打交道的对象是PHP、JSP、ASP、CGI等动态程序
这里的前端主要是指后台服务的前端,充当了表现和数据间的桥梁。
对应到MVC模型,属于Controll部分。相应的,前面提到的三个角色属于View
常用的工具是Eclipse、Visual Studio、Source Insight、TextMate等IDE(可能SI和TM不算IDE,因为本身没有编译功能,不过可以定制)

后台工程师:

实现后台服务,为网站的数据存储提供支持,是系统性能、可靠性的主要保障者
打交道对象是OS、API、网络、文件系统、MySQL等
MVC模型里的Model
常用的工具是VIM、Emacs

UI设计师、页面设计师、交互工程师、前端工程师、后台工程师之间的关系,有点类似于网络模型中的各个层次:使用下层提供的服务,并为上层提供服务。
用户可以直接感受到部分,大部分都是UI设计师、页面设计师和交互工程师完成的。
因为关乎用户体验,所以他们的工作需要处理非常多的细节效果,这许多细节的叠加,就构成了用户对网站的感觉,决定了用户是否会喜欢上我们的产品。
而前端工程师和后台工程师的工作很多是用户不能直接看到的,有点类似于海面下的冰山。虽然不能直接看到,但系统的速度和稳定性都依赖于两者的工作。

而产品经理和项目经理基本上和上面的五个角色都会打交道。

在实际的工程中,并不一定每个角色都有一个或多个人担任,往往是一个人身兼数职。例如,在项目的起步阶段,1经理、1设计师和1工程师就可以构架出基本的功能和服务。

看了上面这么多文字,如果让你想办法提升一个团队的产能,你会有什么建议?
在我看来,不外乎两个途径:提升个人能力,降低沟通成本。

先说提升个人能力。这和每个人的基本素质、学习欲望、工作态度等自身因素有关,也受公司的氛围、培训以及行业的状况等外部因素的影响。这些都不是朝夕之间可以改变的,所以如果希望短期看到成效,还是看看沟通方面可以做些什么。(当然,如果有充足的资金,人也不是问题)

关于降低沟通成本,我觉得有几个方法,效果从好到差分别是:

上策:将上述的各个角色都放到一个真实的Team里

这在小团队或者创业团队里比较容易实现,但是当人员日益增加,小组按照职能划分之后,就很难维持了。

中策:成立一个虚拟团队,并让他们坐在一起

如果不能保证组织架构上的整体性,其他保证名义上和空间上的吧。但如果人员来自不同的部门,这一条也很难实现。

下策:定期会议

其实很多无谓会议都是低效的根源,但如果没办法做到以上的两点,唯有牺牲一些个人工作的效率,来提升沟通的效率了。

上面的种种方法,终极目的都是“让每个人自始至终都参与其中”,有了归属感之后,自然就不会把实现产品仅仅当成是自己的工作了,其实这也是QQ邮箱能够做到敏捷的秘诀吧。

Tagged with:
01

原以为在Putty里设置几个宏就行,
结果一个Hardcode的输入框和几个Disable的复选框之外,找不到其他有用的东西。

答案还是在Google里:)
需要在Server上设置一个环境变量,
现在的写法是:


PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}\007"';

不单Putty,SecureCRT之类的终端应该也适用吧。
可以加到~/.profile,每次登陆自动生效。

Tagged with:
十二 30

第一章

     Xcode需要运行在基于Intel的Leopard之后的Mac OS之上
     Cocoa Touch使用Objective-C 2.0
     编写iPhone应用程序的特别之处
          只有一个正在运行的程序
          只有一个窗口
          访问受限     沙盒
          有限的响应时间     按下Home后5秒内没完成清理,将被强制退出
          有限的屏幕大小     480*320
          有限的系统资源     内存不足时,应用程序会收到通知。如果不能及时释放内存,可能被强制退出
          缺少Cocoa工具     不支持垃圾收集
          新属性     定位、摄像、图片库和加速计
     本书内容
          2 Interface Builder基础
          3 基础交互
          4 基本控件
          5 方向感应
          6 多视图
          7 工具栏
          8 TableView
          9 分层列表
          10 应用程序设置
          11 数据管理     SQLite
          12 绘图     Quartz和OpenGL ES
          13 多点触摸
          14 Core Location
          15 加速计
          16 摄像头和图片库
          17 I18n

第二章
     Project的目录结构,仅为逻辑结构
          Classes     存放大部分代码
          Other Sources     其他源代码
               .pch     预编译的头文件 precompiled header
               main.m     main函数,通常不需修改
          Resources     非代码文件,图标、图像、音频、视频文本
               .xib     Interface Builder用到的信息
               Info.plist     应用程序相关属性列表
               MainWindow.xib     主Interface Builder文件
          Frameworks     框架和库
          Products     编译生成的应用程序
     Interface Builder
          支持.nib(旧)和.xib(新)两种格式,统称nib或nib文件
     nib文件的构成
          File’s Owner     所有nib文件的第一个图标,表示加载nib文件的对象,控制器类是与之同名的nib文件的Owner
          First Responder     用户当前正在与之交互的对象,随用户与界面的交互而变化
          其他图标表示将在nib文件加载时创建的对象实例
     图标
          png     57*57
          放到Resources文件夹中
          在Info.plist中设置
     仿真器
          删除~/Library/Application Support/iPhone Simulator以清空主屏幕图标

第三章
     MVC模型
          M     Objective-C类
          V     Interface Builder
          C     NSObject,更多的是通用控制器类,如UIViewController
     控制器
          控制器类试用一种特殊的实例变量(输出口,outlet)来引用nib中的对象
          输出口可以看成是指向nib中的对象的指针
          可以通过绑定操作方法,设置nib文件的界面对象触发控制类中的特殊方法
     输出口
          IBOutlet     一个没有内容的宏,唯一作用是告诉Interface Builder此实例变量将被连接到nib中的对象
     操作
          IBAction     告诉Interface Builder,此方法是一个操作,且可以被某控件触发
          - (IBAction)doSomething:(id) sender;
     Outlet与Action的声明
          @property (retain, nonatomic) IBOutlet UILabel *statusText;
          retain 对象在内存中驻留
          nonatomic 无须支持多线程?
          myVar = [someObj foo] 等价于 myVar = someObj.foo
          someObj.foo = myVar 等价于 [someObj setFoo:myVar]
     Outlet与Action的实现
          @synthesize statusText
          [statusText release] in dealloc(),照应retain
     应用程序委托
          每个iPhone应用程序有且仅有一个UIApplication实例,负责应用程序的运行循环以及处理各种应用程序级功能
     连接输出口
          Control+拖动,从File‘s Owner到相应的控件对象
     指定操作
          Touch Up Inside
          拖动,从Event到File‘s Owner
          

Tagged with:
十二 27

1. 头文件依然是.h
2. .cpp变成了.m
3. #import代替了#include,#import保证只会引用一次,相当于.h里的#ifndef #define #endif模式
4. 用@interface … @end来声明类,取代class ClassName{}
5. 用@implementation … @end来实现类
6. Data Members放在@interface ClassName : Parent{ … }中,默认权限为@protected,在ObjC里称为Instance Variables
7. Member Functions放在@interface ClassName: Parent{} … @end中,在ObjC里称为Instance Methods
8. Instance Methods的声明方式为:scope (returnType) methodName: (parameter1Type) parameter1Name;
9. scope分instance和class两种,分别用-和+表示
10. 调用method的方式是[object method],相当于object->method()
11. 带参数调用method的方式是[object method: parameter]
12. 没有Object,只有Pointer to Object
13. 通常的构造方式: Object * obj = [[Object alloc] init]
14. 析构的方式:[obj release]15.多参数method的声明方式: scope (returnType) methodName: (parameter1Type) parameter1Name label1Name: (parameter2Type) parameter2Name … ;
16. labelName不是必须的
17. 这种特别的语法源自SmallTalk
18. private: [list of vars] protected: [list of vars] public: [list of vars] 改成了@private, @protected, @public
19. Class Variable用static的方法实现⋯⋯
20. +(void) initialize 会在构造的时候被调用
21. ObjC通常用@符号表示语言的衍生部分
22. ObjC用id来表示范型对象的指针
23. 支持动态类型识别
24. Categories机制可用于不继承已有class的前提下加入新功能
25. Posing机制允许Child取代Parent
26. Protocol相当于pure virtual class
27. ObjC由两种内存管理的方法,1) retain and release,2) retain and release/autorelease
28. Foundation相当于STL,NSArray对应vector,NSDictionary对应map
29. ObjC不支持Namespace
30. 不支持重载

Tagged with:
十二 05

用了JQuery,实在很方便。
但是很奇怪,评论小数字的click只有在展开了之后才会响应,路过的JS高手帮忙解释一下啊:)
已经上传到userscript.org,id63440,希望可以尽快修正上面提到的问题。

// ==UserScript==
// @name           QQMail Broadcast
// @namespace      QMBC
// @description    UI & Functional Improvement for Broadcast
// @include        http://*.mail.qq.com/cgi-bin/reader_article_list*
// ==/UserScript==
 
// Add jQuery
var GM_JQ = document.createElement('script');
GM_JQ.src = 'http://jquery.com/src/jquery-latest.js';
GM_JQ.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(GM_JQ);
 
// Check if jQuery's loaded
function GM_wait()
{
    if(typeof unsafeWindow.jQuery == 'undefined') { window.setTimeout(GM_wait,100); }
    else { $ = unsafeWindow.jQuery; letsJQuery(); }
}
GM_wait();
 
function letsJQuery()
{
    $(document).ready(function () {
		var _div = $("<div style='float: right;'></div>");
 
		var _readMode = $("<a>Read Mode</a>");
		_readMode.bind("click", ReadMode);
 
		_div.append(_readMode);
 
		$("<span>|</span>").css("margin", "10px").appendTo(_div);
 
		var _commentMode = $("<a>Comment Mode</a>");
		_commentMode.bind("click", CommentMode);
 
		_div.append(_commentMode);
 
		$("#articlecontent").prepend(_div);
    });
}
 
function ReadMode()
{
	$("div.update a[id^=artTitle_]").click();
}
 
function CommentMode()
{
	var links = $("div.postInfo a[id^=artCommentListLink]:visible");
	links.click();
}
Tagged with:
24

1. Hidden iframe

将iframe的src指向一个url,server收到请求后,Keep-Alive。
数据直接以script的方式下发到Browser,Browser收到数据后直接执行。
只要不超时,链接会一直保留。

优:感觉这是真正的长连接,对stream也有完整的支持。
劣:Browser状态栏会一直处于“连接中”,ESC会导致链接断开,会有跨域问题

2. Script Tag

用JS创建一个script对象,将该对象的src指向一个url,Keep-Alive。
在一定时间内(超时前),如果sever有数据下发,则用script的方式发送到Browser。
Browser收到数据后直接执行,此时需要重建script对象,建立另一个链接。

优:没有Hidden iframe的缺点,也比较轻量
劣:不是真正的长链接,每收到一个新的下发数据,都需要重新建立链接

3 AJAX

用JS创建一个XHR对象,将该对象的src指向一个url,Keep-Alive。
在一定时间内(超时前),如果sever有数据下发,则通过已建立的链接发送到Browser。
Browser收到数据后直接执行,此时需要重建XHR对象,建立另一个链接。

优:没有Hidden iframe的缺点
劣:存在跨域问题,不是真正的长链接,每收到一个新的下发数据,都需要重新建立链接

2 和 3也可以叫做Long Polling

除了这三种方法,还可以用Flash,由Flash和Server通信,页面用过JS和Flash通信。
这可以实现真正的下发,甚至不需要维护长链接。
但也可能存在被防火墙屏蔽的问题。

三种方法都用php模拟了一下:

Hidden Iframe

<html>
<head>
<script>
function callback(data)
{
	document.getElementById("t").value = data + "\n" + document.getElementById("t").value;
}
 
callback("abc");
</script>
</head>
<body>
hello
<textarea id="t"></textarea>
<iframe src="/hiddeniframe.php" width="0" height="0"></iframe>
</body>
</html>
<?php
	ob_end_flush();
	echo "<script>";
	echo "domain=serverpush";
	echo "</script>";
	for( ; ; )
	{
		$t = time();
		echo "<script>";
		echo "parent.callback($t);";
		echo "</script>";
		flush();
		sleep(1);
	}
?>

Script Tag

<html>
<head>
<script>
function callback(data)
{
	document.getElementById("t").value = data + "\n" + document.getElementById("t").value;
}
 
function connect()
{
    var _script = document.createElement("script");
    _script.setAttribute("type", "text/javascript");
    _script.setAttribute("src", "script.php");
    document.getElementsByTagName("head")[0].appendChild(_script);	
}
 
connect();
</script>
</head>
<body>
<textarea id="t"></textarea>
</body>
</html>
<?php
	sleep(10);
	$r = time();
	echo "callback($r);";
	echo "setTimeout(connect, 1000)";
?>

AJAX

<html>
<head>
<script>
function callback(data)
{
	document.getElementById("t").value = data + "\n" + document.getElementById("t").value;
}
 
function connect()
{
	var ajax = new XMLHttpRequest;
	ajax.open("POST", "ajax.php");
	ajax.onreadystatechange = function()
	{       
		if (ajax.readyState == 4)
		{       
			if (ajax.status == 200) 
			{       
				callback(ajax.responseText);	
				setTimeout(connect, 1000);
			}       
		}       
	}
	ajax.send();
}
 
connect();
 
</script>
</head>
<body>
<textarea id="t"></textarea>
</body>
</html>
<?php
	sleep(10);
	echo time();
?>
Tagged with:
preload preload preload