[转载]Asp.net MVC2.0系列文章-显示列表和详细页面操作

mikel阅读(1120)

[转载]Asp.net MVC2.0系列文章-显示列表和详细页面操作 – 灵动生活 – 博客园.

上一篇文章,我们简单地完成了新闻的添加操作(ASP.NET MVC2.0系列文章添加操作)此篇文章,我们使用ASP.NET MVC2.0实现新闻清单的展示和新闻详细页面。

创建View视图IndexNewsDetails

创建新闻首页,用来显示新闻列表。

Views/News目录下,单击右键,选择Add->View, 修改相关配置如下图所示

在生成的HTML代码中,进行相关 展示方面的修改。主要代码如下:

<% foreach (var item in Model) { %>

<tr>

<td>

<%: Html.ActionLink(“Edit”, “NewsEdit”, new { id=item.Id }) %> |

<%: Html.ActionLink(“Details”, “NewsDetails”, new { id=item.Id })%> |

<%: Html.ActionLink(“Delete”, “Delete”, new { /* id=item.PrimaryKey */ })%>

</td>

<td>

<%: item.Title %>

</td>

<td>

<%: String.Format(“{0:g}”, item.CreateTime) %>

</td>

<td>

<%: item.Content %>

</td>

</tr>

<% } %>

使用Foreach循环遍历新 闻List中的记录。

<%: Html.ActionLink(“Details”, “NewsDetails”, new { id=item.Id })%> 此连接URL会 寻找当前Controller下的NewsDetails Action方法,以新闻编号Id为参数进行传值。

同样的方法创建新闻详细页面视图NewsDetails.asp

生成的核心代码如下:

<asp:Content ID=”Content2″ ContentPlaceHolderID=”MainContent” runat=”server”>

<h2>详细内容</h2>

<fieldset>

<legend>新闻</legend>

<div class=”display-label”>标题</div>

<div class=”display-field”><%: Model.Title %></div>

<div class=”display-label”>创建时间</div>

<div class=”display-field”><%: String.Format(“{0:g}”, Model.CreateTime) %></div>

<div class=”display-label”>新闻内容</div>

<div class=”display-field”><%: Model.Content %></div>

</fieldset>

<p>

<%: Html.ActionLink(“Edit”, “NewsEdit”, new { id=Model.Id }) %> |

<%: Html.ActionLink(“Back to List”, “Index”) %>

</p>

</asp:Content>

<%: Html.ActionLink(“Edit”, “NewsEdit”, new { id=Model.Id }) %> | 此连接会跳转到新闻编辑页面,同样以新闻编号Id传值。

修改Controller文件

Controllers/News文件下

修改Action Name=Index的方法,以使Index.aspx页面初始化数据,此处未读读取数据库,而是伪造了一些数据,且放到静态变量中:

public static List<THelperMVC.Models.News.NewsModel> newsList;

Index Action 代码如下:

public ActionResult Index()

{

newsList= new List<THelperMVC.Models.News.NewsModel>();

for (int i = 0; i < 10; i++)

{

THelperMVC.Models.News.NewsModel news=new THelperMVC.Models.News.NewsModel();

news.Id = i;

news.Title = “Title” + i.ToString();

news.CreateTime = System.DateTime.Now;

news.Content = “Content ??¨²¨Y” + i.ToString();

newsList.Add(news);

}

return View(newsList);

}

使用For循环生成10条新闻记录。

修改NewsDetails.Aspx所对应的Action方法,如下

// GET: /News/Details/5

public ActionResult NewsDetails(int id)

{

THelperMVC.Models.News.NewsModel news=newsList[id];

return View(news);

}

根据URL传过来的参数(即新闻编号Id,从全局静态变量中寻找NewsModel实体,从而初始化新闻详细页面。

最后修改母版页中的,News连接,如下图所示:

此时,点击首页的News超链接,会寻找NewsController文件夹下的Index方法,从而初始化Views/News/Index.aspx页面。

程序运行效果

按下Ctrl+F5运行程序, 如下图所示:

点击上图中的【News】超链接,跳转到 新闻列表页面,如下图所示:

点击Details超链接,会 跳转到相应记录的详细页面,如下图所示:

版权

作者:灵动生活

出处:http://www.cnblogs.com/ywqu

如果你认为 此文章有用,请点击底端的【推荐】让其他人也了解此文章

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]第一次用ASP.NET MVC2 做网站

mikel阅读(1059)

[转载]第一次用ASP.NET MVC2 做网站 – Software Designer – 博客园.

大概有5年没有做网站了,最近帮朋友做一个小网站,正好体验一下ASP.NET MVC2。

花了两个周末整整4天时间编写了整个网站的网页框架和后台代码,美工设计和产品内容的填充是别人做的。
接下来说一下这次做网站的几点心得:
1)5年前对于小网站来说只要IE6中排版没问题就行了,现在则不同了,要考虑IE8、Firefox、Chrome等。
第一个问题就是在IE6/IE7里CSS的text-align属性不仅对文字起作用,对盒子也起作用。
因此如果用text-align做右对齐或居中对齐在IE8里排版就会乱掉。解决方法是右对齐设置float属性,
水平居中则没有直接的方法,可以通过设置margin-left:auto和margin-right:auto来实现。
第二个问题是盒子的宽度和高度是否包括padding,是否包括margin,对于CSS2的模型很不习惯。
2)绝对定位,指定position:absolute后并不直接相对于所在的容器定位,需要父对象有定位设置。
3)IE6/IE7不支持displayed:inline-block,支持该属性的呢block之间的间距不一样。
4)使用P元素时,段落与容器的间距IE8与Firefox的显示不一致,后来改用span和相对定位。
5)刚开始有很大一部分时间花在写CSS调整排版,由此感慨啊WPF的Panel和Style是多么的好用,LaTex的盒子排版模型又是多么 的先进。
W3C真应该考虑一下简单性、一致性、正交性、完备性之类的问题。
以上说的都是CSS,接下来进入正题ASP.NET MVC2。
1)看着VS自动生成的代码,经过简单的学习就上手了。关键是HtmlHelper,UrlHelper,ViewData的使用。
2)MVC最大好处一是最终生成的页面不像以前的ASP.NET页面那么臃肿了,浏览器下载和加载会快一些。
二是URL直接对应服务器方法的调用,而且返回的结果比较灵活。
3)整体来看,使用MVC后需要书写的代码量减少了,对于一些典型问题 有封装好的解决方法。
4)使用MVC后对JavaScript的依赖提高了,很多功能适合用JavaScript来实现。结合Ajax用户体验变好了。
5)支持ASP.NET 4.0的网站空间目前比较难找而且价格高,这倒是个问题。
下面是几个技巧:
1)表单验证失败后,直接返回View,已填写的内容就会清空,可以这样做:
ViewData.ModelState.AddModelError(“FormValidator”, message);
foreach (string field in Request.Form.Keys)
{
ViewData.Add(field, Request.Form[field].Trim());
}
然后Form中用HtmlHelper创建的Input控件会自动从ViewData中获取value。
2)输出纯文本:return Content(message, “text/plain”, Encoding.UTF8); 或者
return new ContentResult { Content = message, ContentType = “text/plain”, ContentEncoding = Encoding.UTF8 };
显示PDF文件:
return File(“~/Content/developersguide.pdf”, “application/pdf”, “developersguide.pdf”);
3)HTML的select元素默认第一个选项是选中的,需要javascript执行oSelect.selectedIndex = -1;变成不选中状态。

[转载]策略模式-3

mikel阅读(905)

[转载]策略模式-3 – 云飞龙行 – 博客园.

3  模式讲解

3.1  认识策略模式

(1)策略模式的功能
策略模式的功能是把具体的算法实现,从具体的业务处理里面独立出来,把它们实现成为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换。
策略模式的重心不是如何来实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活、具有更好的维护性和扩展性。
(2)策略模式和if-else语句
看了前面的示例,很多朋友会发现,每个策略算法具体实现的功能,就是原来在if-else结构中的具体实现。
没错,其实多个if-elseif语 句表达的就是一个平等的功能结构,你要么执行if,要不你就执行else,或者是elseif,这个时候,if块里面的实现和else块里面的实现从运行 地位上来讲就是平等的。
而策略模式就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下文来与具体的策略类进行交互。
因此多个if-else语句可以考虑使 用策略模式。
(3)算法的平等性
策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正是因为这个平等性,才能实现算法之间可以相互替 换。
所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。
所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现
(4)谁来选择具体的策略算法
在策略模式中,可以在两个地方来进行具体策略的选择。
一个是在客户端,在使用上下文的时候,由客户端来选择具体的策略算法,然后把这个策略算法设置给上下文。前面的示例就是这种情况。
还有一个是客户端不管,由上下文来选择具体的策略算法,这个在后面讲容错恢复的时候给大家演示一下。
(5)Strategy的实现方式
在前面的示例中,Strategy都是使用的接口来定义的,这也是常见的实现方式。但是如果多个算法具有公共功能的话,可以把Strategy实现成为抽 象类,然后把多个算法的公共功能实现到Strategy里面。
(6)运行时策略的唯一性
运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态的在不同的策略实现中切换,但是同时只能使用一个。

(7)增加新的策略
在前面的示例里面,体会到了策略模式中切换算法的方便,但是增加一个新的算法会怎样呢?比如现在要实现如下的功能:对于公司的“战略合作客户”,统一8 折。
其实很简单,策略模式可以让你很灵活的扩展新的算法。具体的做法是:先写一个策略算法类来实现新的要求,然后在客户端使用的时候指定使用新的策略算法类就 可以了。
还是通过示例来说明。先添加一个实现要求的策略类,示例代码如下:

/**

* 具体算法实现,为战略合作客户客户计算应报的价格

*/

public class CooperateCustomerStrategy implements Strategy{

public double calcPrice(double goodsPrice) {

System.out.println(“对于战略合作客户,统一8折”);

return goodsPrice*0.8;

}

}

然后在客户端指定使用策略的时候指定新的策略算法实现,示例如下:

public class Client2 {

public static void main(String[] args) {

//1:选择并创建需要使用的策略对象

Strategy strategy = new CooperateCustomerStrategy ();

//2:创建上下文

Price ctx = new Price(strategy);

//3:计算报价

double quote = ctx.quote(1000);

System.out.println(“向客户报价:”+quote);

}

}

除了加粗部分变动外,客户端没有其他的变化。

运行客户端,测试看看,好好体会一下。
除了客户端发生变化外,已有的上下文、策略接口定义和策略的已有实现,都不需要做任何的修改,可见能很方便的扩展新的策略算法。
(8)策略模式调用顺序示意图
策略模式的调用顺序,有两种常见的情况,一种如同前面的示例,具体如下:

a:先是客户端来选择并创建具体的策略对象

b:然后客户端创建上下文

c:接下来客户端就可以调用上下文的方法来执行功能了,在调用的时候,从客户端传入算法需要的参数

d:上下文接到客户的调用请求,会把这个请求转发给它持有的Strategy

这种情况的调用顺序示意图如图3所示:

图3  策略模式调用顺序示意图一

策略模式调用还有一种情况,就是把Context当做参数来传递给Strategy,这种方式的调用顺序图,在讲具体的Context和 Strategy的关系时再给出。

3.2  容错恢复机制

容错恢复机制是应用程序开发中非常常见的功能。那么什么是容错恢复呢?简单点说就是:程序运行的时候,正常情况下应该按照某种方式来做,如果按照某种方式 来做发生错误的话,系统并不会崩溃,也不会就此不能继续向下运行了,而是有容忍出错的能力,不但能容忍程序运行出现错误,还提供出现错误后的备用方案,也 就是恢复机制,来代替正常执行的功能,使程序继续向下运行。
举个实际点的例子吧,比如在一个系统中,所有对系统的操作都要有日志记录,而且这个日志还需要有管理界面,这种情况下通常会把日志记录在数据库里面,方便 后续的管理,但是在记录日志到数据库的时候,可能会发生错误,比如暂时连不上数据库了,那就先记录在文件里面,然后在合适的时候把文件中的记录再转录到数 据库中。
对于这样的功能的设计,就可以采用策略模式,把日志记录到数据库和日志记录到文件当作两种记录日志的策略,然后在运行期间根据需要进行动态的切换。
在这个例子的实现中,要示范由上下文来选择具体的策略算法,前面的例子都是由客户端选择好具体的算法,然后设置到上下文中。
下面还是通过代码来示例一下。
(1)先定义日志策略接口,很简单,就是一个记录日志的方法,示例代码如下:

/**

* 日志记录策略的接口

*/

public interface LogStrategy {

/**

* 记录日志

* @param msg 需记录的日志信息

*/

public void log(String msg);

}

(2)实现日志策略接口,先实现默认的数据库实现,假设如果日志的长度超过长度就出错,制造错误的是一个最常见的运行期错误,示例代码如 下:

/**

* 把日志记录到数据库

*/

public class DbLog implements LogStrategy{

public void log(String msg) {

//制造错误

if(msg!=null && msg.trim().length()>5){

int a = 5/0;

}

System.out.println(“现在把 ‘”+msg+”‘ 记录到数据库中”);

}

}

接下来实现记录日志到文件中去,示例代码如下:

/**

* 把日志记录到文件

*/

public class FileLog implements LogStrategy{

public void log(String msg) {

System.out.println(“现在把 ‘”+msg+”‘ 记录到文件中”);

}

}

(3)接下来定义使用这些策略的上下文,注意这次是在上下文里面实现具体策略算法的选择,所以不需要客户端来指定具体的策略算法了,示例代码如下:

(4)看看现在的客户端,没有了选择具体实现策略算法的工作,变得非常简单,故意多调用一次,可以看出不同的效果,示例代码如下:

(5)小结一下,通过上面的示例,会看到策略模式的一种简单应用,也顺便了解一下基本的容错恢复机制的设计和实现。在实际的应用中,需要设计容错 恢复的系统一般要求都比较高,应用也会比较复杂,但是基本的思路是差不多的。

未完待续……

[转载]我们能提高工作效率

mikel阅读(1424)

[转载]我们能提高工作效率 – Vincent.Q – 博客园.

作为软件公司,客户需求每天都会出现,帮助客户实现需求也是必须的,否则就要喝西北风了.如何处理这些需求,如果快速简便的处理这些需求,成为每个 软件公司所要面对的事情,如果你的客户需求响应时间多于你的竞争对手,那客户在下一次选购产品时,我们可能就会因此失去竞争力.

客户的 需求也是五花八门的,本人目前只接触过ERP和HIS软件,因此可以归纳为管理类软件,即和Db数据库打交道.总体可归纳为两种,即:功能需求和业务需 求.分别举个例子,功能需求是指将datagrid所展示出来的数据,导出至excel文件或者将excel文件导入至Db中.业务需求是指增加一张检验 单或检查单.

这里只讨论业务需求的处理,因为功能需求相比业务需求来说,它的范围是有限的.比如框架只需满足这些功能就可以了,如果有 额外需求,可以通过功能的叠加处理,因此,它是容易掌控的,只要有一套经过几年积累的框架即可.

业务需求我们无法掌控,因为我们不知道 客户现在在想什么,以后怎么想?可能有人会说,那是因为我们对客户的行业需求不清晰.是的,没错,相当正确.可是,目前中国的IT公司大部分都是在摸着石 头过河,这里面有政策,市场,行业规则等很多因素控制,总之,不是我们所能控制的,所以客户提出的业务需求,我们只有及时的解决和回复,才能在目前的市场 中站稳脚根.

业务需求相对Db来说,就是增删改查的功能,如果再细化的话,其实是可以归纳为很多功能需求的集合.举个刚才数据导出的例 子:现在客户要求客户ID字段值也要求导出!

接下来,我们的流程可能是这样的

1. 找到调数据导出功能的页面,然后找到该功能代码

2. 在代码中加入客户ID栏目值

3. 将代码所属pbl生成pbd或者dll文件,再提交至质保部

4. 质保部对该需求测试,测试成功再给客户发升级包

以 上,该需求已处理完成.

我们现在采用另外一种处理方式来看看可不可以节省时间

1. datagrid控件的展示数据功能,我们对此进行拆分.,它所需要的SQL语句和栏目属性是不同的,其他功能完全一致,比如数据需要导出和导入,数据检 索等.

2. 我们将不同的部分,即容易变化的部分封装起来.其实我们在编码过程中,要创建一个类,也就是这个原 因,将容易变化的东西封装起来.

如图-1所示:将datagrid控件所需的数据源单独存放起来.

clip_image002

图-1

如图-2所示,将 datagrid控件所需栏目格式单独保存起来.同时,也可以配置哪些栏目需要导出或导出,是不是下拉菜单等.

clip_image004

图-2

3. 经过上述步骤的处理,我们回头再看看那个导出需求如何处理

a) 首先,在图1所示的SQL数据源中加入此字段

b) 其次,在图2所示的栏目属性中加入此栏目信息

经过以下步骤,整个需求解决过程结束.我们看看省略了那些步骤:

a) 修改代码的过程

b) 交由质保部测试的过程

c) 发客户升级包的过程.因此,修改配置文件只需实施员参与即可.

下面,我们再谈页面的功能,看看有什么能够优化的?页面如果根据功能划分的话,可分为以下3个页面

l frmSheet祖先页面

l frmSheetBill,单据页面,维护单条数据

l frmSheetList,列表页面,展示批量数据

也有很多其他功能不同的页面,不过,都可以在此基础上扩展.我们以列表页面为例,如 图-3所示

clip_image006

图-3

1. 最上面是工具栏,属于功能区

我们通过图-4所示的方式加载工具栏即可.它可以将一个页面所需的功能全部通过字符串的方式存储起来,以后 若是对功能名称有所增加或删减,通过修改配置文件即可,如图-5所示.需要说明一点:如果功能的流程有所变化,还是要修改代码的.

clip_image007

图-4

clip_image009

图-5

2. 中间是检索区域,提供常用检索条件,如果是比较复杂的检索,弹出高级检索页面即可.

创建一个自定义检索控件,可以动态生成检索控件即 可.这里说下检索方式,前面提到的数据源sql语句,一般是select … from ….很少有where条件,那如何加载数据呢?如图-6所示即可

clip_image011

图-6

3. 最下面的datagrid控件,展示批量数据.

首先加载栏目属性,其他次拼接检索条件生成dictionary对象(可理解为 where条件),通过倒数第二行代码生成datatable,最后加载至控件即可.看这个方法,专属于这个控件的代码相当于,目前只是收集检索条件(也 是可以封装成控件的),其余部分均是公用的.

我们再看看工具栏的事件是如何处理的,如图-7所示

clip_image012

图-7

规范的命名,清晰的功能 名称,一看就知道该功能是做什么的?开发员不必再单独创建额外事件,这样方便问题查找.

我们再看看对方法集的封装,如图-8和图-9所 示

clip_image013

图-8

clip_image014

图-9

通过以上方法的封装, 比如有功能需要从xml文件读入数据,只须调此方法即可,不需要自己再单独写方法,不是很方便?而且也容易维护

我很期待中国什么时候能 出现一个非常完善的中间件(也可理解为某个行业软件的开发平台),来结束这种各自为战的局面.现在的IT企业,可能每个企业里都会有一套所谓的”框架”, 这样就重复造了轮子.我也想了很久为啥软件公司不用其他公司专门的框架,原因有如下几点:

1. 自己造的轮子,心里踏实,想什么时候改选,就什么时候改造

2. 体现公司本身的研发实力,本身就是开发软件的,还用其他的,太跌份了

3. 有的软件公司把自己的软件比作是自己的孩子,所以,大家都喜欢亲生的,不喜欢领养或别人生的,呵呵.

以上谈论,都是个 人歪谈,若引起某些人的共愤,还请见谅.希望我上面写的东西能给大家一些参考吧.

[转载]Discuz!NT负载均衡方案

mikel阅读(1120)

[转载]Discuz!NT负载均衡方案 – 代震军BLOG – 博客园.

在前面的几篇文章中,主要谈到了在Discuz!NT中的跨站缓存数据,数据库负载均衡。但如果要实现将产品分布式布置到若干机器,组成集群来共同支撑起 整个业务的话,还是有一定问题的(后面会有所介绍)。下面先介绍一下如何使用 Discuz!NT负载均衡方案搭建分布式应用。

Discuz!NT前端负载均衡可以是nginx,lvs,haproxy等,当然配置最简单的基于nginx实现的,下面是它的一些简介:

Nginx(“engine x”)是俄罗斯人编写的十分轻量级的HTTP服务器。它不但是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发,已经在该站点运行超过两年半了。Igor 将源代码以类BSD许可证的形式发布。
Google在线安全博客中统计nginx服务或代理了大约所有Internet虚拟主机的4%。而netcraft的统计显示,nginx服务的主机 在过去的一年里以四倍的速度增长。短短的几年里,它的排名已跃进第9。
Nginx专为性能优化而开发,性能是其最重要的考量,实现上非常注重效率 。它支持内核Poll模型,能经受高负载的考验,有报告表明能支持高达 50,000个并发连接数。
Nginx具有很高的稳定性。其它HTTP服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,也很可能会导致服务器物理内存耗尽频繁交换,失去响 应,只能重启服务器。例如当前Apache一旦上到200个以上进程, web响应速度就明显非常缓慢了。而Nginx采取了分阶段资源分配技术,使得它的CPU与内存占用率非常低。Nginx官方表示保持10,000个没有 活动的连接,它只占2.5M内存,所以类似DOS这样的攻击对nginx来说基本上是毫无用处的。就稳定性而言, nginx比
lighthttpd 更胜一筹。
Nginx 超越 Apache 的高性能和稳定性,使得国内使用 Nginx 作为 Web 服务器的网站也越来越多,其中包括新浪博客、新浪播客、网易新闻等门户网站频道,六间房、56.com等视频分享网站,水木社区等知名论坛,豆瓣、 YUPOO相册、海内SNS、迅雷在线等新兴Web 2.0网站。
下面这张图简要说明在我们产品中nginx的作用:

图中的ASP.NET就是我们布署的相应iis站点应用,相信做过负载均衡的朋友会发现,在大型网站架构中,IIS或其它应用服务器会有许多(节点),而 nginx会动态的按照相应权重给不同的节点上分配相应请求(有关nginx在window和linux下的配置可参见这篇文章

也就是下面这张图所说明的:

这里先抛开对静态文件缓存(通常使用squid,以后会进行介绍),图中web服务器(IIS)会有几个集群,这就需要将产品分布布署到若干机器上,这样 如果某台机器(节点)上的文件发生变化,就需要有一种同步机制来保证不同站点之间的文件一致且是最新的。在discuz!nt产品中,有一些目录下的文件 会频繁发生变化,比如:

1.在Discuz!NT的后台有模板生成机制,它会将前台的htm模板文件(位于discuz.web\templates目录下)翻译并生成为 “aspx”文件,而有关翻译转换这部分内容请参见这篇文章

2.前台discuz.web\config下的配置文件,该目录下文件存储的是整个论坛的相应配置信息,所有功能的开关都需要进行记录,非常重要,当管 理员在后台通过相关页面修改了这些配置文件后,需要第一时间将这些信息同步到其它分布节点上。
这的确是一个挑战,但好在已有相应的软件能帮助我们实现这个基础功能,就是 cwRsync,它最早是在linux下的一个同步工具,后来有了Windows版本,就是cwRsync,利用它同时再借助windows中的“任务计 划”来创建定时任务,就可以实现定时同步功能了,之间在windows2003上可以设置分钟级别的同步方式,如下:

而有关如何设置它,可以参考这篇文章

除了文件同步,还有附件的问题,比如用户在一个节点上发了主题并上传了相关附件,那就会造成只有该节点的目录下有相应附件(如图片等),而别的节点上没 有。虽然可以通过上面的同步机制来实现多个节点上同步附近,但这势必会造成存储空间和服务器性能上的降低,好在我们的产品中提供了远程附件功能,它允许通 过FTP方式将上传到指定节点上的附件上传到远程的ftp服务器上,同时修改数据库中的附件路径为FTP上远程附件的路径,有关这方面的内容,可以参见这篇文章

除了上面两个问题,还有nginx对ajax的支持还不够,因为要在不同的节点上均衡负载,所以从一个节点上获取的脚本可能会被nginx均衡到另外一个 节点上,从而产生ajax跨节点安全性的问题。这个问题在我们的产品中非常严重。众所周知,我们在3.0版本之后将原有的大量的功能全部改成了ajax方 式,比如发帖,回复,登录,前台版主管理操作等等。这个问题要想从根本上解决,只能寄希望于nginx的开发团队了。但后来经过测试发现,还存在变通的方 法,就是在nginx配置文件
(nginx.conf)中,可以设置下面这些信息:

代码

location / {
……
proxy_set_header   Host             $host;
proxy_set_header   X- Real-IP        $remote_addr;  ;#防止ajax安全请求问题
proxy_set_header   REMOTE- HOST        $remote_addr; ;#防止ajax安全请求问题

这样就可以欺骗IIS,让它以为当前被分配的ajax请求就是来自于本地,同时我们加上相应的端口绑定,可就以实现 ajax请求了,如下:

代码

……
upstream 10.0.2.136 {
server 10.0.2.136:8086;# 端口一样是为了防止ajax安全请求问题
server 10.0.2.137:8086;
}

server {
listen       8086;
server_name  10.0.2.136;
……

注意,必须是同一端口(如上面:8086端口)。这样就可以解决ajax安全性调用问题了,如果IP地址怕发生冲突或不够
用,可以为一台服务器 绑定多个IP地址,这样就可以解决某些站点必须使用80端口的问题了,详细设置可以下载这个文件

现在,我们可以大体梳理一下整个负载均衡方案,首先是数据库读写分离方式:

然后是分布式缓存方案:

当然方案还有一些因素目前没有过多分析,比较squid文件加速,比如下面这张经常用在Linux平台上的负载均衡架构图的右侧红框部分:

另外还有CDN等,这些都会 在后续章节中进行补充,敬请期待。

原文链接:http://www.cnblogs.com/daizhj/archive/2010/06/24/1667422.html

BLOG: http://daizhj.cnblogs.com/

作者:daizhj,代震军

[转载]执行性能测试—起步

mikel阅读(1039)

[转载]执行性能测试—起步 – Killmyday – 博客园.

程序越来越大,为了让程序更快地响应用户的输入,需要执行性能测试,最近在研究性 能测试,就从这篇最基础的文章开始起步吧。VS 2010自带了一个功能 强大的性能测试工具—Performance Wizard,在研究过程 中,我决定用通过分析最普通的排序操作的程序来开始我的学习过程。

程序的功能很简单,接受任何文本文件,排序,然后将结果输出到一个新的文件中。第 一个版本很简单,使用最好写的冒泡排序实现:

#region 常见的排序方法 适合小文件的排序

static void Main(string[] args)

{

if (args.Length != 2)

{

Console.WriteLine(“Usage: sort <file name> <output file name>”);

return;

}

string[] sortedLines;

using (StreamReader reader = new StreamReader(Path.GetFullPath(args[0])))

{

var text = reader.ReadToEnd();

sortedLines = Sort(text);

}

using (StreamWriter writer = new StreamWriter(Path.GetFullPath(args[1])))

{

foreach (var line in sortedLines)

writer.WriteLine(line);

}

}

#region 使用冒泡排序来测试性能

// V1: 使用冒泡排序来测试性能

private static string[] Sort(string text)

{

var lines = text.Split(new string[] { “\r\n” }, StringSplitOptions.None);

for (int i = 0; i < lines.Length; ++i)

{

for (int j = i + 1; j < lines.Length; ++j)

{

if (string.CompareOrdinal(lines[i], lines[j]) < 0)

{

var temp = lines[i];

lines[i] = lines[j];

lines[j] = temp;

}

}

}

return lines;

}

#endregion

使用一个大小为2.5 兆的文本文件作为输入数据,并且用下面的步骤执行性能测试:

1. 打开Visual Studio,并点击菜单栏里的“分析(Analyze)”。

2. 点击“启动性能测试向导(Launch Performance Wizard)”。

3. 在“性能测试向导(Performance Wizard)”页面上选择“CPU Sampling (recommended)”。

4. 在后续页面上,使用页面默认的选项做完设置。

耐心等待程序执行完以后,Visual studio会提供一个报表,在摘要(Summary)里面—下图,可以看到,程序使用了大约36秒才完成了所有的过程。

另外,在Hot Path表格里,也可以看出,String.CompareOrdinal函数所占用的时间是最多的,占整个程序执行的78.44%。而从另外一个统计表Functions,这个统计表显示了在程序执行完毕之前,函数触发样例的次数,注意,这个样例触发的次数和函数被调用 的次数没有对应关系,两个样例之间,函数可能被调用了多次,而且多个函数也可能被调用到—这个跟我们使用“CPU Sampling (recommended)”的策略有关,它的优点是快不影响被测程序的运行,缺点就是 不够精确。从Functions的统计表里面,可以看到String.CompareOrdinal被调用到的次数是其他函数的好几倍:

既然已经知道冒泡排序是整个程序的瓶颈了,那我们第二个版本就是将冒泡算法改成快 速排序,看看有没有改进:

#region 常见的排序方法 适合小文件的排序

static void Main(string[] args)

{

if (args.Length != 2)

{

Console.WriteLine(“Usage: sort <file name> <output file name>”);

return;

}

string[] sortedLines;

using (StreamReader reader = new StreamReader(Path.GetFullPath(args[0])))

{

var text = reader.ReadToEnd();

sortedLines = Sort(text);

}

using (StreamWriter writer = new StreamWriter(Path.GetFullPath(args[1])))

{

foreach (var line in sortedLines)

writer.WriteLine(line);

}

}

#region 使用快速排序来测试性能

// V2: 使用快速排序来测试性能

private static string[] Sort(string text)

{

var lines = text.Split(new string[] { “\r\n” }, StringSplitOptions.None);

QuickSort(lines, 0, lines.Length – 1);

return lines;

}

private static int Partition(string[] lines, int lower, int upper)

{

var pivot = lines[upper];

var newPivot = lower – 1;

for (int i = lower; i < upper; ++i)

{

if (string.CompareOrdinal(pivot, lines[i]) > 0)

{

var temp = lines[i];

newPivot = newPivot + 1;

lines[i] = lines[newPivot];

lines[newPivot] = temp;

}

}

++newPivot;

lines[upper] = lines[newPivot];

lines[newPivot] = pivot;

return newPivot;

}

private static void QuickSort(string[] lines, int lower, int upper)

{

if (lower < upper)

{

var pivot = Partition(lines, lower, upper);

QuickSort(lines, lower, pivot – 1);

QuickSort(lines, pivot + 1, upper);

}

}

#endregion

#endregion

这次的结果要比上次好很多,这次只用了3秒多就完成了所有的工作,而且在Hot Path里面,String.CompareOrdinal函数在程序执行时间的比重也下降了很多。

虽然我们从总的执行时间上来看,看到了很大的性能提高,但是知道那些函数的执行效 率提高了,会让你更直观的感觉到后续性能优化的目标。VS 2010支持 对比多个性能报告,并给出两个报告之间的差别,例如哪些函数的执行速度有提高之类的:

1. 点击菜单栏里的“分析(Analyze)”。

2. 点击“Compare Performance Report”,在弹出来的对话框里选择两个性能测试的报告。

上图里,我在“Column 里面选择了“Exclusive Samples”—因为对比百分比对我来说不够直观。从对比的结果来看,快速排序在各 个方面全面胜出—除了Partition函数,因为那是一个新函数。

接下来,为了查看快排对大文件排序的性能,我使用了一个700多兆的文本文件进行测试,这次—程序崩溃了,崩溃的原因是OutOfMemoryException……

[转载]策略模式-2

mikel阅读(988)

[转载]策略模式-2 – 云飞龙行 – 博客园.

2  解决方案

2.1  策略模式来解决

用来解决上述问题的一个合理的解决方案就是策略模式。那么什么是策略模式呢?

(1)策略模式定义
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

(2)应用策略模式来解决的思路
仔细分析上面的问题,先来把它抽象一下,各种计算报价的计算方式就好比是具体的算法,而使用这些计算方式来计算报价的程序,就相当于是使用算法的客户。
再分析上面的实现方式,为什么会造成那些问题,根本原因,就在于算法和使用算法的客户是耦合的,甚至是密不可分的,在上面实现中,具体的算法和使用算法的 客户是同一个类里面的不同方法。
现在要解决那些问题,按照策略模式的方式,应该先把所有的计算方式独立出来,每个计算方式做成一个单独的算法类,从而形成一系列的算法,并且为这一系列算 法定义一个公共的接口,这些算法实现是同一接口的不同实现,地位是平等的,可以相互替换。这样一来,要扩展新的算法就变成了增加一个新的算法实现类,要维 护某个算法,也只是修改某个具体的算法实现即可,不会对其它代码造成影响。也就是说这样就解决了可维护、可扩展的问题。
为了实现让算法能独立于使用它的客户,策略模式引入了一个上下文的对象,这个对象负责持有算法,但是不负责决定具体选用哪个算法,把选择算法的功能交给了 客户,由客户选择好具体的算法后,设置到上下文对象里面,让上下文对象持有客户选择的算法,当客户通知上下文对象执行功能的时候,上下文对象会去转调具体 的算法。这样一来,具体的算法和直接使用算法的客户是分离的。
具体的算法和使用它的客户分离过后,使得算法可独立于使用它的客户而变化,并且能够动态的切换需要使用的算法,只要客户端动态的选择使用不同的算法,然后 设置到上下文对象中去,实际调用的时候,就可以调用到不同的算法。

2.2  模式结构和说明

策略模式的结构示意图如图1所示:

图1  策略模式结构示意图

Strategy:
策略接口,用来约束一系列具体的策略算法。Context使用这个接口来调用具体的策略实现定义的算法。
ConcreteStrategy:
具体的策略实现,也就是具体的算法实现。
Context:
上下文,负责和具体的策略类交互,通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文 的方法。

2.3  策略模式示例代码

(1)首先来看策略,也就是定义算法的接口,示例代码如下:

/**

* 策略,定义算法的接口

*/

public interface Strategy {

/**

* 某个算法的接口,可以有传入参数,也可以有返回值

*/

public void algorithmInterface();

}

(2)该来看看具体的算法实现了,定义了三个,分别是ConcreteStrategyA、ConcreteStrategyB、 ConcreteStrategyC,示例非常简单,由于没有具体算法的实现,三者也就是名称不同,示例代码如下:

/**

* 实现具体的算法

*/

public class ConcreteStrategyA implements Strategy {

public void algorithmInterface() {

//具体的算法实现

}

}

/**

* 实现具体的算法

*/

public class ConcreteStrategyB implements Strategy {

public void algorithmInterface() {

//具体的算法实现

}

}

/**

* 实现具体的算法

*/

public class ConcreteStrategyC implements Strategy {

public void algorithmInterface() {

//具体的算法实现

}

}

(3)再来看看上下文的实现,示例代码如下:

/**

* 上下文对象,通常会持有一个具体的策略对象

*/

public class Context {

/**

* 持有一个具体的策略对象

*/

private Strategy strategy;

/**

* 构造方法,传入一个具体的策略对象

* @param aStrategy 具体的策略对象

*/

public Context(Strategy aStrategy) {

this.strategy = aStrategy;

}

/**

* 上下文对客户端提供的操作接口,可以有参数和返回值

*/

public void contextInterface() {

//通常会转调具体的策略对象进行算法运算

strategy.algorithmInterface();

}

}

2.4  使用策略模式重写示例

要使用策略模式来重写前面报价的示例,大致有如下改变:

  • 首先需要定义出算法的接口。
  • 然后把各种报价的计算方式单独出来,形成算法类。
  • 对于Price这个类,把它当做上下文,在计算报价的时候,不再需要判断,直接使用持有的具体算法进行运算即可。选择使用哪一个算法的功能挪出 去,放到外部使用的客户端去。

这个时候,程序的结构如图2所示:

图2  使用策略模式实现示例的结构示意图

(1)先看策略接口,示例代码如下:

/**

* 策略,定义计算报价算法的接口

*/

public interface Strategy {

/**

* 计算应报的价格

* @param goodsPrice 商品销售原价

* @return 计算出来的,应该给客户报的价格

*/

public double calcPrice(double goodsPrice);

}

(2)接下来看看具体的算法实现,不同的算法,实现也不一样,先看为新客户或者是普通客户计算应报的价格的实现,示例代码如下:

/**

* 具体算法实现,为新客户或者是普通客户计算应报的价格

*/

public class NormalCustomerStrategy implements Strategy{

public double calcPrice(double goodsPrice) {

System.out.println(“对于新客户或者是普通客户,没有折扣”);

return goodsPrice;

}

}

再看看为老客户计算应报的价格的实现,示例代码如下:

/**

* 具体算法实现,为老客户计算应报的价格

*/

public class OldCustomerStrategy implements Strategy{

public double calcPrice(double goodsPrice) {

System.out.println(“对于老客户,统一折扣5%”);

return goodsPrice*(1-0.05);

}

}

再看看为大客户计算应报的价格的实现,示例代码如下:

/**

* 具体算法实现,为大客户计算应报的价格

*/

public class LargeCustomerStrategy implements Strategy{

public double calcPrice(double goodsPrice) {

System.out.println(“对于大客户,统一折扣10%”);

return goodsPrice*(1-0.1);

}

}

(3)接下来看看上下文的实现,也就是原来的价格类,它的变化比较大,主要有:

  • 原来那些私有的,用来做不同计算的方法,已经去掉了,独立出去做成了算法类
  • 原来报价方法里面,对具体计算方式的判断,去掉了,让客户端来完成选择具体算法的功能
  • 新添加持有一个具体的算法实现,通过构造方法传入
  • 原来报价方法的实现,变化成了转调具体算法来实现

示例代码如下:

/**

* 价格管理,主要完成计算向客户所报价格的功能

*/

public class Price {

/**

* 持有一个具体的策略对象

*/

private Strategy strategy = null;

/**

* 构造方法,传入一个具体的策略对象

* @param aStrategy 具体的策略对象

*/

public Price(Strategy aStrategy){

this.strategy = aStrategy;

}

/**

* 报价,计算对客户的报价

* @param goodsPrice 商品销售原价

* @return 计算出来的,应该给客户报的价格

*/

public double quote(double goodsPrice){

return this.strategy.calcPrice(goodsPrice);

}

}

(4)写个客户端来测试运行一下,好加深体会,示例代码如下:

public class Client {

public static void main(String[] args) {

//1:选择并创建需要使用的策略对象

Strategy strategy = new LargeCustomerStrategy ();

//2:创建上下文

Price ctx = new Price(strategy);

//3:计算报价

double quote = ctx.quote(1000);

System.out.println(“向客户报价:”+quote);

}

}

运行一下,看看效果。
你可以修改使用不同的策略算法具体实现,现在用的是LargeCustomerStrategy,你可以尝试修改成其它两种实现,试试看,体会一下切换 算法的容易性。

未完 待续……

[转载]小心使用“#”

mikel阅读(1074)

[转载]小心使用“#” – ∠角络 – 博客园.

最近因为程序中要加日志模块,所以就想用切面方式(AOP)解决这个问题,然而遇到了头痛的问题,就是日志老写两次,也不知道哪里出错了,开始以为框架哪 里有问题,调试了很多次都找不到原因。后来索性写了一个测试页面,发现在没有任何html标记的页面居然只写了一次日志,所以排除了程序上的问题,开始找 页面的问题了。

页面一行行排查过来,最终发现了是一行“<a href=”#”><img src=”#” width=”100″ border=”0″ height=”40″ alt=”#”/></a>”出了问题,那么为什么这行会出现问题呢?

img标记的src属性规定插入的图像的url地址,也就是含路径的图像文件名。该属性值是和页面一起会向服务器有个请求,那么如果把src=”#”,页 面就会再次请求当前页,导致页面执行两次。

也不知道当初为啥会把src的属性设为#,可能是空链接用惯了,也没多想。在使用img中的src属性,包括css中的url(background- image等属性)请求如果设为#值的话,都会再试请求当前页面。所以在使用“#”值,切忌当心再当心,造成不必要的浪费。

[转载]策略模式-1

mikel阅读(1017)

[转载]策略模式-1 – 云飞龙行 – 博客园.

首先感谢众多朋友的支持、评论和鼓励,只有多多努力,写点好的博文来回报大家的好意!

接下来想写写另外一个虽然较简单,但是使用很频繁的模式——策略模式

策略模式(Strategy)

1  场景问题

1.1  报价管理

向客户报价,对于销售部门的人来讲,这是一个非常重大、非常复杂的问题,对不同的客户要报不同的价格,比如:

(1)对普通客户或者是新客户报的是全价

(2)对老客户报的价格,根据客户年限,给予一定的折扣

(3)对大客户报的价格,根据大客户的累计消费金额,给予一定的折扣

(4)还要考虑客户购买的数量和金额,比如:虽然是新用户,但是一次购买的数量非常大,或者是总金额非常高,也会有一定的折扣

(5)还有,报价人员的职务高低,也决定了他是否有权限对价格进行一定的浮动折扣

甚至在不同的阶段,对客户的报价也不同,一般情况是刚开始比较高,越接近成交阶段,报价越趋于合理。
总之,向客户报价是非常复杂的,因此在一些CRM(客户关系管理)的系统中,会有一个单独的报价管理模块,来处理复杂的报价功能。
为了演示的简洁性,假定现在需要实现一个简化的报价管理,实现如下的功能:
(1)对普通客户或者是新客户报全价
(2)对老客户报的价格,统一折扣5%
(3)对大客户报的价格,统一折扣10%
该怎么实现呢?

1.2  不用模式的解决方案

要实现对不同的人员报不同的价格的功能,无外乎就是判断起来麻烦点,也不多难,很快就有朋友能写出如下的实现代码,示例代码如下:

/**

* 价格管理,主要完成计算向客户所报价格的功能

*/

public class Price {

/**

* 报价,对不同类型的,计算不同的价格

* @param goodsPrice 商品销售原价

* @param customerType 客户类型

* @return 计算出来的,应该给客户报的价格

*/

public double quote(double goodsPrice,String customerType){

if(customerType.equals(“普通客户”)){

System.out.println(“对于新客户或者是普通客户,没有折 扣”);

return goodsPrice;

}else if(customerType.equals(“老客户”)){

System.out.println(“对于老客户,统一折扣5%”);

return goodsPrice*(1-0.05);

}else if(customerType.equals(“大客户”)){

System.out.println(“对于大客户,统一折扣10%”);

return goodsPrice*(1-0.1);

}

//其余人员都是报原价

return goodsPrice;

}

}

1.3  有何问题

上面的写法是很简单的,也很容易想,但是仔细想想,这样实现,问题可不小,比如:

(1)第一个问题:价格类包含了所有计算报价的算法,使得价格类,尤其是报价这个方法比较庞杂,难以维护。

有朋友可能会想,这很简单嘛,把这些算法从报价方法里面拿出去,形成独立的方法不就可以解决这个问题了吗?据此写出如下的实现代码,示例代码如下:

/**

* 价格管理,主要完成计算向客户所报价格的功能

*/

public class Price {

/**

* 报价,对不同类型的,计算不同的价格

* @param goodsPrice 商品销售原价

* @param customerType 客户类型

* @return 计算出来的,应该给客户报的价格

*/

public double quote(double goodsPrice,String customerType){

if(customerType.equals(“普通客户”)){

return this.calcPriceForNormal(goodsPrice);

}else if(customerType.equals(“老客户”)){

return this.calcPriceForOld(goodsPrice);

}else if(customerType.equals(“大客户”)){

return this.calcPriceForLarge(goodsPrice);

}

//其余人员都是报原价

return goodsPrice;

}

/**

* 为新客户或者是普通客户计算应报的价格

* @param goodsPrice 商品销售原价

* @return 计算出来的,应该给客户报的价格

*/

private double calcPriceForNormal(double goodsPrice){

System.out.println(“对于新客户或者是普通客户,没有折扣”);

return goodsPrice;

}

/**

* 为老客户计算应报的价格

* @param goodsPrice 商品销售原价

* @return 计算出来的,应该给客户报的价格

*/

private double calcPriceForOld(double goodsPrice){

System.out.println(“对于老客户,统一折扣5%”);

return goodsPrice*(1-0.05);

}

/**

* 为大客户计算应报的价格

* @param goodsPrice 商品销售原价

* @return 计算出来的,应该给客户报的价格

*/

private double calcPriceForLarge(double goodsPrice){

System.out.println(“对于大客户,统一折扣10%”);

return goodsPrice*(1-0.1);

}

}

这样看起来,比刚开始稍稍好点,计算报价的方法会稍稍简单一点,这样维护起来也稍好一些,某个算法发生了变化,直接修改相应的私有方法就可以了。扩展起来 也容易一点,比如要增加一个“战略合作客户”的类型,报价为直接8折,就只需要在价格类里面新增加一个私有的方法来计算新的价格,然后在计算报价的方法里 面新添一个else-if即可。看起来似乎很不错了。
真的很不错了吗?
再想想,问题还是存在,只不过从计算报价的方法挪动到价格类里面了,假如有100个或者更多这样的计算方式,这会让这个价格类非常庞大,难以维护。而且, 维护和扩展都需要去修改已有的代码,这是很不好的,违反了开-闭原则。

(2)第二个问题:经常会有这样的需要,在不同的时候,要使用不同的计算方式。

比如:在公司周年庆的时候,所有的客户额外增加3%的折扣;在换季促销的时候,普通客户是额外增加折扣2%,老客户是额外增加折扣3%,大客户是额外增加 折扣5%。这意味着计算报价的方式会经常被修改,或者被切换。
通常情况下应该是被切换,因为过了促销时间,又还回到正常的价格体系上来了。而现在的价格类中计算报价的方法,是固定调用各种计算方式,这使得切换调用不 同的计算方式很麻烦,每次都需要修改if-else里面的调用代码。
看到这里,可能有朋友会想,那么到底应该如 何实现,才能够让价格类中的计算报价的算法,能很容易的实现可维护、可扩展,又能动态的切换变化呢?

未完 待续……

[转载]活该如此-开发人员的心态问题

mikel阅读(975)

[转载]活该如此 – 外面的世界很精彩 – 博客园.

最近一直没发什么问,一来是因为在武汉出差比较忙,二来也觉得没什么好说的,查MSDN 可能比我任何一篇文章收获都要大,有好的题目再写吧。

发现好文一篇,转贴如下,与君共勉:

http://soulogic.com/blog/archives/405.html

几 年前在家门口的一家小饭馆吃饭的时候,我想明白了“为什么所有的服务员都这么糟糕”的问题:老板只愿意为一个服务员的职位出这些钱,而一个服务员好到远不 止这些钱的时候,她就不会去干服务员了。当然,引申的还有为什么老板只愿意出这些钱,因为你去吃饭给他带去的利润只值这些,等等。

佐证我这个观点的是去年跟河北某 IDC 托管主机的时候,那个客服很不错,办事很麻利很干净,也试着自己搭站点什么的,偶尔还会问我一些问题,我当时就奇怪这样的人怎么会甘心当客服,果不其然, 几个月之后他就辞职了,之后换过两三个客服,不用说,都很糟糕,我还投诉过其中的一个。

后 来看到《卧底经济学》的时候,还知道有人做过正统的归纳、论证,提出一个词叫“比较优势”,末了作者还自嘲自己之所以可以靠写经济专栏为生,是因为更牛逼 的人虽然可以写经济专栏写得比他更好,但是人家可以靠别的方式挣到更多的钱。

在充分的市 场竞争环境下,所有人都会坐到相对于自己来说最正确的位置,我还是喜欢更为简短且粗俗的描述:所有人活该如此。

举个例子,公司和员工个人的利益有很大分歧,如 lifesinger 所说

偶尔去蓝色 理想转转,感慨JS版为啥这么多人在重复造轮子?感慨之后又感慨不重复造轮子的话,又怎么能学会JS?对于老板来说,复用性节约money,对于程序员来 说,重复造轮子是学习的好途径。

而最近碰到些事情,让我第一 次认真琢磨一个以前从来不认为是问题的问题:为什么有人愿意屈就公司的需要而自毁前程。当然,答案也是上面说过的,活该如此。

具体事情没法多说,只是一直做网站的我,从来没细想过,对倭外包、或者对 discuz/uchome 做二次开发的人,会有这么这么多,其实他们才是所谓 IT 行业的主流,而自己实际是被边缘化了的。

在不同角度看这个问题是不一样的,首先事情本身到没到“毁前程”这种程度,以及对“屈 就”的定量问题,就像物理性质的“弹性”概念,玻璃也有弹性的,看你跟什么东西比了。

我 最大的“屈就”是在 05 年的时候写了几个月的 JavaScript,不过相信真正对 JS 很懂的人不会认为我这句话是冒犯。当时公司的那个客户端,本身界面要经常改动,还要跟网站本身有一些交互,所以我把这部分工作接下来了,软件的 GUI 部分只有一个 IE 窗口控件,除了常见的浮动窗口之类的效果,还要做个进度条效果,而且是很多进度条,总之我在给我自己定目标、找动力。但无论如何那都不是一个让人愉快的工 作,会在各种各样的地方出现所谓的“偶然复杂度”(不知道现学现卖的这个词是否恰当),比方说,当时蹩脚的杀毒软件会让我绘制的 DIV 菜单消失、某个版本的 msn activex 会导致页面出现离奇错误(尽管那页没用到 activex,但是禁用 msn activex 后就一切都好了)、某特定语言的 win 98 下的正则不能用两个 / 而必须用 new RegExp,等等等等,并且当时除了 prototype 刚刚在世界掀起波澜,还没有任何框架,我也只是在学习,也不可能马上用在产品上。等做得差不多了,也有一两个人可以勉强接替我这部分工作的人,我跟经理 说,我不想干这个了,假设接替的人胜任不了,界面的变动也只能暂时放放了。

我的理由是 基于直觉的:干这个有天花板。前几天还在一次面试的时候解释过这问题:互联网对高水平的 JS/CSS 的需求量太少了,如果全国需要 1000 个能解决 c10k 问题的人,可能相同等级的 JS 程序员也就需要 3 个,显然后者更有竞争难度。

但直到两天前,我在买阮一峰翻译的《软件随想录》时顺便买的一本《Joel 谈优秀软件开发方法》里, 重新看到了 Paul Graham 的《伟大的黑客》,我早就忘了作者和文章标题,也忘了是什么时候看到的了,但我记得其中每一段话的要素,下面这两段对我尤其重要,我一直把他作为判断工作 的准则之一:

所有这类讨厌的小问题都有一个共同特征,既您从中学不到任何东西。写一个编译器很有趣, 那是因为您可以从中学会编译原理。为充满 BUG 的软件编写接口程序您却什么也学不到,因为那些 BUG 是随机的。因此,优秀的黑客会尽量回避那些讨厌的小问题。这不是挑三拣四,更主要的是为了自保——长期面对那样的小问题会使人变得愚蠢。黑客们的做法其实 与模特们戒食乳酪的道理是一样的。

我不知道您能否培养出这些品质,但至少 可以不去抑制它们。如果您有希望成为一名伟大的黑客,那么我建议您最好做到:一方面,绝不在枯燥的项目上浪费生命(除非不这么做您和家人就会饿死);另一 方面,做事必须有始有终、滴水不漏。我所认识的所有伟大黑客似乎都是如此,也许他们根本就没想过还有其他什么选择。

纵使自己最终无法达到 Paul Graham 笔下的黑客标准,起码,你不遵守这些就肯定无法达到了。另外这还是我不沾酒的一个漂亮的理由:我觉得那玩意伤脑子,哪怕只损伤万分之一我也无法接受。半年 前基于同样的理由我连可乐都戒了。

回到最初的问题上,让我直白(可能会有尴尬)地把话说 出来,如果你觉得你干的活挺傻逼的,马上跟老板说辞职,然后再考虑找工作的事,除非你自己也觉得,你只配干这种傻逼活。这是对我的几个同事想说而没能说出 口的话。

最近常常在想,03 年急于找工作的时候,是由于幸运,还是性格使然,而没沦落到改模板/做外包的工人,在目睹了像蔡學鏞所说的“大多數的工程師並不是「做了五年的系統開發」,而是「做了一年的系統開發,然後重複五次」”的实 例后,还是有些后怕。